blob: 4e535368ac476a2e49525e90575016276ae2db65 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "asix-88772b.h"
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/binding.h>
#include <ddk/protocol/ethernet.h>
#include <ddk/protocol/usb.h>
#include <usb/usb.h>
#include <usb/usb-request.h>
#include <zircon/assert.h>
#include <zircon/listnode.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <unistd.h>
#define READ_REQ_COUNT 8
#define WRITE_REQ_COUNT 4
#define INTR_REQ_COUNT 4
#define USB_BUF_IN_SIZE 16384
#define USB_BUF_OUT_SIZE 2048
#define INTR_REQ_SIZE 8
#define ETH_HEADER_SIZE 4
#define ETH_MTU 1500
#define ETHMAC_MAX_TRANSMIT_DELAY 100
#define ETHMAC_MAX_RECV_DELAY 100
#define ETHMAC_TRANSMIT_DELAY 10
#define ETHMAC_RECV_DELAY 10
#define ETHMAC_INITIAL_TRANSMIT_DELAY 0
#define ETHMAC_INITIAL_RECV_DELAY 0
typedef struct {
zx_device_t* device;
zx_device_t* usb_device;
usb_protocol_t usb;
uint8_t phy_id;
uint8_t mac_addr[6];
uint8_t status[INTR_REQ_SIZE];
bool online;
bool dead;
uint8_t bulk_in_addr;
uint8_t bulk_out_addr;
// pool of free USB requests
list_node_t free_read_reqs;
list_node_t free_write_reqs;
list_node_t free_intr_reqs;
// List of netbufs that haven't been copied into a USB transaction yet. Should only contain
// entries if free_write_reqs is empty.
list_node_t pending_netbufs;
uint64_t rx_endpoint_delay; // wait time between 2 recv requests
uint64_t tx_endpoint_delay; // wait time between 2 transmit requests
// callback interface to attached ethernet layer
ethmac_ifc_protocol_t ifc;
size_t parent_req_size;
mtx_t mutex;
} ax88772b_t;
typedef struct txn_info {
ethmac_netbuf_t netbuf;
list_node_t node;
} txn_info_t;
static void ax88772b_interrupt_complete(void* ctx, usb_request_t* request);
static void ax88772b_write_complete(void* ctx, usb_request_t* request);
static zx_status_t ax88772b_set_value(ax88772b_t* eth, uint8_t request, uint16_t value) {
return usb_control_out(&eth->usb, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
request, value, 0, ZX_TIME_INFINITE, NULL, 0);
}
static zx_status_t ax88772b_get_value(ax88772b_t* eth, uint8_t request, uint16_t* value_addr) {
return usb_control_in(&eth->usb, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
request, 0, 0, ZX_TIME_INFINITE, value_addr, sizeof(uint16_t), NULL);
}
static zx_status_t ax88772b_mdio_read(ax88772b_t* eth, uint8_t offset, uint16_t* value) {
zx_status_t status = ax88772b_set_value(eth, ASIX_REQ_SW_SERIAL_MGMT_CTRL, 0);
if (status < 0) {
zxlogf(ERROR, "ax88772b: ASIX_REQ_SW_SERIAL_MGMT_CTRL failed: %d\n", status);
return status;
}
status = usb_control_in(&eth->usb, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
ASIX_REQ_PHY_READ, eth->phy_id, offset, ZX_TIME_INFINITE,
value, sizeof(*value), NULL);
if (status < 0) {
zxlogf(ERROR, "ax88772b: ASIX_REQ_PHY_READ failed: %d\n", status);
return status;
}
status = ax88772b_set_value(eth, ASIX_REQ_HW_SERIAL_MGMT_CTRL, 0);
if (status < 0) {
zxlogf(ERROR, "ax88772b: ASIX_REQ_HW_SERIAL_MGMT_CTRL failed: %d\n", status);
return status;
}
return ZX_OK;
}
static zx_status_t ax88772b_mdio_write(ax88772b_t* eth, uint8_t offset, uint16_t value) {
zx_status_t status = ax88772b_set_value(eth, ASIX_REQ_SW_SERIAL_MGMT_CTRL, 0);
if (status < 0) {
zxlogf(ERROR, "ax88772b: ASIX_REQ_SW_SERIAL_MGMT_CTRL failed: %d\n", status);
return status;
}
status = usb_control_out(&eth->usb, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
ASIX_REQ_PHY_WRITE, eth->phy_id, offset, ZX_TIME_INFINITE,
&value, sizeof(value));
if (status < 0) {
zxlogf(ERROR, "ax88772b: ASIX_REQ_PHY_WRITE failed: %d\n", status);
return status;
}
status = ax88772b_set_value(eth, ASIX_REQ_HW_SERIAL_MGMT_CTRL, 0);
if (status < 0) {
zxlogf(ERROR, "ax88772b: ASIX_REQ_HW_SERIAL_MGMT_CTRL failed: %d\n", status);
return status;
}
return ZX_OK;
}
static zx_status_t ax88772b_wait_for_phy(ax88772b_t* eth) {
for (int i = 0; i < 100; i++) {
uint16_t bmsr;
zx_status_t status = ax88772b_mdio_read(eth, ASIX_PHY_BMSR, &bmsr);
if (status < 0) {
zxlogf(ERROR, "ax88772b: ax88772b_mdio_read failed: %d\n", status);
return status;
}
if (bmsr)
return ZX_OK;
usleep(50);
}
zxlogf(INFO, "ax88772b: ax88772b_wait_for_phy timeout\n");
return ZX_ERR_TIMED_OUT;
}
static void queue_interrupt_requests_locked(ax88772b_t* eth) {
usb_request_t* req;
usb_request_complete_t complete = {
.callback = ax88772b_interrupt_complete,
.ctx = eth,
};
while ((req = usb_req_list_remove_head(&eth->free_intr_reqs, eth->parent_req_size)) != NULL) {
usb_request_queue(&eth->usb, req, &complete);
}
}
static void ax88772b_recv(ax88772b_t* eth, usb_request_t* request) {
size_t len = request->response.actual;
uint8_t* pkt;
zx_status_t status = usb_request_mmap(request, (void*)&pkt);
if (status != ZX_OK) {
zxlogf(ERROR, "ax88772b: usb_request_mmap failed: %d\n", status);
return;
}
while (len > ETH_HEADER_SIZE) {
uint16_t length1 = (pkt[0] | (uint16_t)pkt[1] << 8) & 0x7FF;
uint16_t length2 = (~(pkt[2] | (uint16_t)pkt[3] << 8)) & 0x7FF;
pkt += ETH_HEADER_SIZE;
len -= ETH_HEADER_SIZE;
if (length1 != length2) {
zxlogf(ERROR, "ax88772b: invalid header: length1: %u length2: %u\n", length1, length2);
return;
}
if (length1 > len) {
return;
}
ethmac_ifc_recv(&eth->ifc, pkt, length1, 0);
pkt += length1;
len -= length1;
// align to uint16
if (length1 & 1) {
if (len == 0) {
return;
}
pkt++;
len--;
}
}
}
// Send a netbuf to the USB interface using the provided request
static zx_status_t ax88772b_send(ax88772b_t* eth, usb_request_t* request, ethmac_netbuf_t* netbuf) {
size_t length = netbuf->data_size;
if (length + ETH_HEADER_SIZE > USB_BUF_OUT_SIZE) {
zxlogf(ERROR, "ax88772b: unsupported packet length %zu\n", length);
return ZX_ERR_INVALID_ARGS;
}
// write 4 byte packet header
uint8_t header[ETH_HEADER_SIZE];
uint8_t lo = length & 0xFF;
uint8_t hi = length >> 8;
header[0] = lo;
header[1] = hi;
header[2] = lo ^ 0xFF;
header[3] = hi ^ 0xFF;
usb_request_copy_to(request, header, ETH_HEADER_SIZE, 0);
usb_request_copy_to(request, netbuf->data_buffer, length, ETH_HEADER_SIZE);
request->header.length = length + ETH_HEADER_SIZE;
zx_nanosleep(zx_deadline_after(ZX_USEC(eth->tx_endpoint_delay)));
usb_request_complete_t complete = {
.callback = ax88772b_write_complete,
.ctx = eth,
};
usb_request_queue(&eth->usb, request, &complete);
return ZX_OK;
}
static void ax88772b_read_complete(void* ctx, usb_request_t* request) {
ax88772b_t* eth = (ax88772b_t*)ctx;
if (request->response.status == ZX_ERR_IO_NOT_PRESENT) {
usb_request_release(request);
return;
}
mtx_lock(&eth->mutex);
if (request->response.status == ZX_ERR_IO_REFUSED) {
zxlogf(TRACE, "ax88772b_read_complete usb_reset_endpoint\n");
usb_reset_endpoint(&eth->usb, eth->bulk_in_addr);
} else if (request->response.status == ZX_ERR_IO_INVALID) {
zxlogf(TRACE, "ax88772b_read_complete Slowing down the requests by %d usec"
" and resetting the recv endpoint\n", ETHMAC_RECV_DELAY);
if (eth->rx_endpoint_delay < ETHMAC_MAX_RECV_DELAY) {
eth->rx_endpoint_delay += ETHMAC_RECV_DELAY;
}
usb_reset_endpoint(&eth->usb, eth->bulk_in_addr);
} else if ((request->response.status == ZX_OK) && eth->ifc.ops) {
ax88772b_recv(eth, request);
}
if (eth->online) {
zx_nanosleep(zx_deadline_after(ZX_USEC(eth->rx_endpoint_delay)));
usb_request_complete_t complete = {
.callback = ax88772b_read_complete,
.ctx = eth,
};
usb_request_queue(&eth->usb, request, &complete);
} else {
zx_status_t status = usb_req_list_add_head(&eth->free_read_reqs, request,
eth->parent_req_size);
ZX_DEBUG_ASSERT(status == ZX_OK);
}
mtx_unlock(&eth->mutex);
}
static void ax88772b_write_complete(void* ctx, usb_request_t* request) {
ax88772b_t* eth = (ax88772b_t*)ctx;
if (request->response.status == ZX_ERR_IO_NOT_PRESENT) {
usb_request_release(request);
return;
}
mtx_lock(&eth->mutex);
if (!list_is_empty(&eth->pending_netbufs)) {
// If we have any netbufs that are waiting to be sent, reuse the request we just got back
txn_info_t* txn = list_remove_head_type(&eth->pending_netbufs, txn_info_t, node);
zx_status_t send_result = ax88772b_send(eth, request, &txn->netbuf);
if (eth->ifc.ops) {
ethmac_ifc_complete_tx(&eth->ifc, &txn->netbuf, send_result);
}
} else {
zx_status_t status = usb_req_list_add_tail(&eth->free_write_reqs, request,
eth->parent_req_size);
ZX_DEBUG_ASSERT(status == ZX_OK);
}
if (request->response.status == ZX_ERR_IO_REFUSED) {
zxlogf(TRACE, "ax88772b_write_complete usb_reset_endpoint\n");
usb_reset_endpoint(&eth->usb, eth->bulk_out_addr);
} else if (request->response.status == ZX_ERR_IO_INVALID) {
zxlogf(TRACE, "ax88772b_write_complete Slowing down the requests by %d usec"
" and resetting the transmit endpoint\n", ETHMAC_TRANSMIT_DELAY);
if (eth->tx_endpoint_delay < ETHMAC_MAX_TRANSMIT_DELAY) {
eth->tx_endpoint_delay += ETHMAC_TRANSMIT_DELAY;
}
usb_reset_endpoint(&eth->usb, eth->bulk_out_addr);
}
mtx_unlock(&eth->mutex);
}
static void ax88772b_interrupt_complete(void* ctx, usb_request_t* request) {
ax88772b_t* eth = (ax88772b_t*)ctx;
if (request->response.status == ZX_ERR_IO_NOT_PRESENT) {
usb_request_release(request);
return;
}
mtx_lock(&eth->mutex);
if (request->response.status == ZX_OK && request->response.actual == sizeof(eth->status)) {
uint8_t status[INTR_REQ_SIZE];
usb_request_copy_from(request, status, sizeof(status), 0);
if (memcmp(eth->status, status, sizeof(eth->status))) {
const uint8_t* b = status;
zxlogf(TRACE, "ax88772b: status changed: %02X %02X %02X %02X %02X %02X %02X %02X\n",
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]);
memcpy(eth->status, status, sizeof(eth->status));
uint8_t bb = eth->status[2];
bool online = (bb & 1) != 0;
bool was_online = eth->online;
eth->online = online;
if (online && !was_online) {
if (eth->ifc.ops) {
ethmac_ifc_status(&eth->ifc, ETHMAC_STATUS_ONLINE);
}
// Now that we are online, queue all our read requests
usb_req_internal_t* req_int;
usb_req_internal_t* prev;
usb_request_t* req;
list_for_every_entry_safe (&eth->free_read_reqs, req_int, prev, usb_req_internal_t,
node) {
list_delete(&req_int->node);
req = REQ_INTERNAL_TO_USB_REQ(req_int, eth->parent_req_size);
usb_request_complete_t complete = {
.callback = ax88772b_read_complete,
.ctx = eth,
};
usb_request_queue(&eth->usb, req, &complete);
}
} else if (!online && was_online) {
if (eth->ifc.ops) {
ethmac_ifc_status(&eth->ifc, 0);
}
}
}
}
zx_status_t status = usb_req_list_add_head(&eth->free_intr_reqs, request,
eth->parent_req_size);
ZX_DEBUG_ASSERT(status == ZX_OK);
queue_interrupt_requests_locked(eth);
mtx_unlock(&eth->mutex);
}
static zx_status_t ax88772b_queue_tx(void* ctx, uint32_t options, ethmac_netbuf_t* netbuf) {
ax88772b_t* eth = ctx;
if (eth->dead) {
return ZX_ERR_PEER_CLOSED;
}
zx_status_t status = ZX_OK;
mtx_lock(&eth->mutex);
txn_info_t* txn = containerof(netbuf, txn_info_t, netbuf);
list_node_t* node = list_remove_head(&eth->free_write_reqs);
if (!node) {
list_add_tail(&eth->pending_netbufs, &txn->node);
status = ZX_ERR_SHOULD_WAIT;
goto out;
}
usb_req_internal_t* req_int = containerof(node, usb_req_internal_t, node);
usb_request_t* request = REQ_INTERNAL_TO_USB_REQ(req_int, eth->parent_req_size);
status = ax88772b_send(eth, request, netbuf);
out:
mtx_unlock(&eth->mutex);
return status;
}
static void ax88772b_unbind(void* ctx) {
ax88772b_t* eth = ctx;
mtx_lock(&eth->mutex);
eth->dead = true;
mtx_unlock(&eth->mutex);
// this must be last since this can trigger releasing the device
device_remove(eth->device);
}
static void ax88772b_free(ax88772b_t* eth) {
usb_request_t* req;
while ((req = usb_req_list_remove_head(&eth->free_read_reqs, eth->parent_req_size)) != NULL) {
usb_request_release(req);
}
while ((req = usb_req_list_remove_head(&eth->free_write_reqs, eth->parent_req_size)) != NULL) {
usb_request_release(req);
}
while ((req = usb_req_list_remove_head(&eth->free_intr_reqs, eth->parent_req_size)) != NULL) {
usb_request_release(req);
}
free(eth);
}
static void ax88772b_release(void* ctx) {
ax88772b_t* eth = ctx;
ax88772b_free(eth);
}
static zx_protocol_device_t ax88772b_device_proto = {
.version = DEVICE_OPS_VERSION,
.unbind = ax88772b_unbind,
.release = ax88772b_release,
};
static zx_status_t ax88772b_query(void* ctx, uint32_t options, ethmac_info_t* info) {
ax88772b_t* eth = ctx;
if (options) {
return ZX_ERR_INVALID_ARGS;
}
memset(info, 0, sizeof(*info));
ZX_DEBUG_ASSERT(USB_BUF_OUT_SIZE - ETH_HEADER_SIZE >= ETH_MTU);
info->mtu = ETH_MTU;
memcpy(info->mac, eth->mac_addr, sizeof(eth->mac_addr));
info->netbuf_size = sizeof(txn_info_t);
return ZX_OK;
}
static void ax88772b_stop(void* ctx) {
ax88772b_t* eth = ctx;
mtx_lock(&eth->mutex);
eth->ifc.ops = NULL;
mtx_unlock(&eth->mutex);
}
static zx_status_t ax88772b_start(void* ctx, const ethmac_ifc_protocol_t* ifc) {
ax88772b_t* eth = ctx;
zx_status_t status = ZX_OK;
mtx_lock(&eth->mutex);
if (eth->ifc.ops) {
status = ZX_ERR_BAD_STATE;
} else {
eth->ifc = *ifc;
ethmac_ifc_status(&eth->ifc, eth->online ? ETHMAC_STATUS_ONLINE : 0);
}
mtx_unlock(&eth->mutex);
return status;
}
static zx_status_t ax88772b_set_promisc(ax88772b_t *eth, bool on) {
uint16_t rx_bits;
zx_status_t status = ax88772b_get_value(eth, ASIX_REQ_RX_CONTROL_READ, &rx_bits);
if (status != ZX_OK) {
zxlogf(ERROR, "ax88772b: ASIX_REQ_RX_CONTROL_READ failed; set_promisc() will fail.\n");
return status;
}
if (on) {
rx_bits |= ASIX_RX_CTRL_PRO;
} else {
rx_bits &= ~ASIX_RX_CTRL_PRO;
}
status = ax88772b_set_value(eth, ASIX_REQ_RX_CONTROL_WRITE, rx_bits);
if (status != ZX_OK) {
zxlogf(ERROR, "ax88772b: ASIX_REQ_RX_CONTROL_WRITE failed\n");
}
return status;
}
static zx_status_t ax88772b_set_param(void *ctx, uint32_t param, int32_t value, const void* data,
size_t data_size) {
ax88772b_t* eth = ctx;
zx_status_t status = ZX_OK;
mtx_lock(&eth->mutex);
switch (param) {
case ETHMAC_SETPARAM_PROMISC:
status = ax88772b_set_promisc(eth, (bool)value);
break;
default:
status = ZX_ERR_NOT_SUPPORTED;
}
mtx_unlock(&eth->mutex);
return status;
}
static ethmac_protocol_ops_t ethmac_ops = {
.query = ax88772b_query,
.stop = ax88772b_stop,
.start = ax88772b_start,
.queue_tx = ax88772b_queue_tx,
.set_param = ax88772b_set_param,
};
static int ax88772b_start_thread(void* arg) {
ax88772b_t* eth = (ax88772b_t*)arg;
// set some GPIOs
zx_status_t status = ax88772b_set_value(eth, ASIX_REQ_GPIOS,
ASIX_GPIO_GPO2EN | ASIX_GPIO_GPO_2 | ASIX_GPIO_RSE);
if (status < 0) {
zxlogf(ERROR, "ax88772b: ASIX_REQ_WRITE_GPIOS failed: %d\n", status);
goto fail;
}
// select the PHY
uint8_t phy_addr[2];
status = usb_control_in(&eth->usb, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
ASIX_REQ_PHY_ADDR, 0, 0, ZX_TIME_INFINITE,
&phy_addr, sizeof(phy_addr), NULL);
if (status < 0) {
zxlogf(ERROR, "ax88772b: ASIX_REQ_READ_PHY_ADDR failed: %d\n", status);
goto fail;
}
eth->phy_id = phy_addr[1];
int embed_phy = (eth->phy_id & 0x1F) == 0x10 ? 1 : 0;
status = ax88772b_set_value(eth, ASIX_REQ_SW_PHY_SELECT, embed_phy);
if (status < 0) {
zxlogf(ERROR, "ax88772b: ASIX_REQ_SW_PHY_SELECT failed: %d\n", status);
goto fail;
}
// Reset
status = ax88772b_set_value(eth, ASIX_REQ_SW_RESET, ASIX_RESET_PRL | ASIX_RESET_IPPD);
if (status < 0) {
zxlogf(ERROR, "ax88772b: ASIX_REQ_SW_RESET failed: %d\n", status);
goto fail;
}
status = ax88772b_set_value(eth, ASIX_REQ_SW_RESET, 0);
if (status < 0) {
zxlogf(ERROR, "ax88772b: ASIX_REQ_SW_RESET failed: %d\n", status);
goto fail;
}
status = ax88772b_set_value(eth, ASIX_REQ_SW_RESET,
(embed_phy ? ASIX_RESET_IPRL : ASIX_RESET_PRTE));
if (status < 0) {
zxlogf(ERROR, "ax88772b: ASIX_REQ_SW_RESET failed: %d\n", status);
goto fail;
}
status = ax88772b_set_value(eth, ASIX_REQ_RX_CONTROL_WRITE, 0);
if (status < 0) {
zxlogf(ERROR, "ax88772b: ASIX_REQ_RX_CONTROL_WRITE failed: %d\n", status);
goto fail;
}
status = ax88772b_wait_for_phy(eth);
if (status < 0) {
goto fail;
}
uint16_t medium = ASIX_MEDIUM_MODE_FD | ASIX_MEDIUM_MODE_AC | ASIX_MEDIUM_MODE_RFC | ASIX_MEDIUM_MODE_TFC | ASIX_MEDIUM_MODE_JFE | ASIX_MEDIUM_MODE_RE | ASIX_MEDIUM_MODE_PS;
status = ax88772b_set_value(eth, ASIX_REQ_MEDIUM_MODE, medium);
if (status < 0) {
zxlogf(ERROR, "ax88772b: ASIX_REQ_MEDIUM_MODE failed: %d\n", status);
goto fail;
}
status = usb_control_out(&eth->usb, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
ASIX_REQ_IPG_WRITE, ASIX_IPG_DEFAULT | (ASIX_IPG1_DEFAULT << 8),
ASIX_IPG2_DEFAULT, ZX_TIME_INFINITE, NULL, 0);
if (status < 0) {
zxlogf(ERROR, "ax88772b: ASIX_REQ_IPG_WRITE failed: %d\n", status);
goto fail;
}
status = ax88772b_set_value(eth, ASIX_REQ_RX_CONTROL_WRITE, ASIX_RX_CTRL_AMALL | ASIX_RX_CTRL_AB | ASIX_RX_CTRL_S0);
if (status < 0) {
zxlogf(ERROR, "ax88772b: ASIX_REQ_RX_CONTROL_WRITE failed: %d\n", status);
goto fail;
}
status = usb_control_in(&eth->usb, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
ASIX_REQ_NODE_ID_READ, 0, 0, ZX_TIME_INFINITE,
eth->mac_addr, sizeof(eth->mac_addr), NULL);
if (status < 0) {
zxlogf(ERROR, "ax88772b: ASIX_REQ_NODE_ID_READ failed: %d\n", status);
goto fail;
}
zxlogf(INFO, "ax88772b: MAC address: %02x:%02x:%02x:%02x:%02x:%02x\n",
eth->mac_addr[0], eth->mac_addr[1], eth->mac_addr[2],
eth->mac_addr[3], eth->mac_addr[4], eth->mac_addr[5]);
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = "ax88772b",
.ctx = eth,
.ops = &ax88772b_device_proto,
.proto_id = ZX_PROTOCOL_ETHMAC,
.proto_ops = &ethmac_ops,
};
status = device_add(eth->usb_device, &args, &eth->device);
if (status < 0) {
zxlogf(ERROR, "ax88772b: failed to create device: %d\n", status);
goto fail;
}
mtx_lock(&eth->mutex);
queue_interrupt_requests_locked(eth);
mtx_unlock(&eth->mutex);
return ZX_OK;
fail:
ax88772b_free(eth);
return status;
}
static zx_status_t ax88772b_bind(void* ctx, zx_device_t* device) {
usb_protocol_t usb;
zx_status_t result = device_get_protocol(device, ZX_PROTOCOL_USB, &usb);
if (result != ZX_OK) {
return result;
}
// find our endpoints
usb_desc_iter_t iter;
result = usb_desc_iter_init(&usb, &iter);
if (result < 0) return result;
usb_interface_descriptor_t* intf = usb_desc_iter_next_interface(&iter, true);
if (!intf || intf->bNumEndpoints != 3) {
usb_desc_iter_release(&iter);
return ZX_ERR_NOT_SUPPORTED;
}
uint8_t bulk_in_addr = 0;
uint8_t bulk_out_addr = 0;
uint8_t intr_addr = 0;
usb_endpoint_descriptor_t* endp = usb_desc_iter_next_endpoint(&iter);
while (endp) {
if (usb_ep_direction(endp) == USB_ENDPOINT_OUT) {
if (usb_ep_type(endp) == USB_ENDPOINT_BULK) {
bulk_out_addr = endp->bEndpointAddress;
}
} else {
if (usb_ep_type(endp) == USB_ENDPOINT_BULK) {
bulk_in_addr = endp->bEndpointAddress;
} else if (usb_ep_type(endp) == USB_ENDPOINT_INTERRUPT) {
intr_addr = endp->bEndpointAddress;
}
}
endp = usb_desc_iter_next_endpoint(&iter);
}
usb_desc_iter_release(&iter);
if (!bulk_in_addr || !bulk_out_addr || !intr_addr) {
zxlogf(ERROR, "ax88772b: ax88772b_bind could not find endpoints\n");
return ZX_ERR_NOT_SUPPORTED;
}
ax88772b_t* eth = calloc(1, sizeof(ax88772b_t));
if (!eth) {
zxlogf(ERROR, "ax88772b: Not enough memory for ax88772b_t\n");
return ZX_ERR_NO_MEMORY;
}
list_initialize(&eth->free_read_reqs);
list_initialize(&eth->free_write_reqs);
list_initialize(&eth->free_intr_reqs);
list_initialize(&eth->pending_netbufs);
eth->usb_device = device;
memcpy(&eth->usb, &usb, sizeof(eth->usb));
eth->parent_req_size = usb_get_request_size(&eth->usb);
uint64_t req_size = eth->parent_req_size + sizeof(usb_req_internal_t);
eth->bulk_in_addr = bulk_in_addr;
eth->bulk_out_addr = bulk_out_addr;
eth->rx_endpoint_delay = ETHMAC_INITIAL_RECV_DELAY;
eth->tx_endpoint_delay = ETHMAC_INITIAL_TRANSMIT_DELAY;
zx_status_t status = ZX_OK;
for (int i = 0; i < READ_REQ_COUNT; i++) {
usb_request_t* req;
status = usb_request_alloc(&req, USB_BUF_IN_SIZE, bulk_in_addr, req_size);
if (status != ZX_OK) {
goto fail;
}
status = usb_req_list_add_head(&eth->free_read_reqs, req, eth->parent_req_size);
ZX_DEBUG_ASSERT(status == ZX_OK);
}
for (int i = 0; i < WRITE_REQ_COUNT; i++) {
usb_request_t* req;
status = usb_request_alloc(&req, USB_BUF_OUT_SIZE, bulk_out_addr, req_size);
if (status != ZX_OK) {
goto fail;
}
status = usb_req_list_add_head(&eth->free_write_reqs, req, eth->parent_req_size);
ZX_DEBUG_ASSERT(status == ZX_OK);
}
for (int i = 0; i < INTR_REQ_COUNT; i++) {
usb_request_t* req;
status = usb_request_alloc(&req, INTR_REQ_SIZE, intr_addr, req_size);
if (status != ZX_OK) {
goto fail;
}
status = usb_req_list_add_head(&eth->free_intr_reqs, req, eth->parent_req_size);
ZX_DEBUG_ASSERT(status == ZX_OK);
}
thrd_t thread;
thrd_create_with_name(&thread, ax88772b_start_thread, eth, "ax88772b_start_thread");
thrd_detach(thread);
return ZX_OK;
fail:
zxlogf(ERROR, "ax88772b: ax88772b_bind failed: %d\n", status);
ax88772b_free(eth);
return status;
}
static zx_driver_ops_t ax88772b_driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = ax88772b_bind,
};
ZIRCON_DRIVER_BEGIN(ethernet_ax88772b, ax88772b_driver_ops, "zircon", "0.1", 3)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_USB),
BI_ABORT_IF(NE, BIND_USB_VID, ASIX_VID),
BI_MATCH_IF(EQ, BIND_USB_PID, ASIX_PID),
ZIRCON_DRIVER_END(ethernet_ax88772b)