blob: 3f5df244f1d55c72da43df4b97d6cf7820c14805 [file] [log] [blame]
// Copyright 2017 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 <ddk/binding.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/protocol/ethernet.h>
#include <driver/usb.h>
#include <zircon/hw/usb-cdc.h>
#include <sync/completion.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#define CDC_ECM_DEBUG 0
#if CDC_ECM_DEBUG
# define xprintf(args...) printf(args)
#else
# define xprintf(args...)
#endif
#define CDC_SUPPORTED_VERSION 0x0110 /* 1.10 */
// The maximum amount of memory we are willing to allocate to transaction buffers
#define MAX_TX_BUF_SZ 32768
#define MAX_RX_BUF_SZ 32768
const char* module_name = "usb-cdc-ecm";
typedef struct {
uint8_t addr;
uint16_t max_packet_size;
} ecm_endpoint_t;
typedef struct {
zx_device_t* mxdev;
zx_device_t* usb_device;
usb_protocol_t usb;
mtx_t ethmac_mutex;
ethmac_ifc_t* ethmac_ifc;
void* ethmac_cookie;
// Device attributes
uint8_t mac_addr[ETH_MAC_SIZE];
uint16_t mtu;
// Connection attributes
bool online;
uint32_t ds_bps;
uint32_t us_bps;
// Interrupt handling
ecm_endpoint_t int_endpoint;
iotxn_t* int_txn_buf;
completion_t completion;
thrd_t int_thread;
// Send context
mtx_t tx_mutex;
ecm_endpoint_t tx_endpoint;
list_node_t tx_txn_bufs;
// Receive context
ecm_endpoint_t rx_endpoint;
} ecm_ctx_t;
static void ecm_unbind(void* cookie) {
xprintf("%s: unbinding\n", module_name);
ecm_ctx_t* ctx = cookie;
device_remove(ctx->mxdev);
}
static void ecm_free(ecm_ctx_t* ctx) {
xprintf("%s: deallocating memory\n", module_name);
if (ctx->int_thread) {
thrd_join(ctx->int_thread, NULL);
}
iotxn_t* txn;
while ((txn = list_remove_head_type(&ctx->tx_txn_bufs, iotxn_t, node)) != NULL) {
iotxn_release(txn);
}
iotxn_release(ctx->int_txn_buf);
mtx_destroy(&ctx->ethmac_mutex);
mtx_destroy(&ctx->tx_mutex);
free(ctx);
}
static void ecm_release(void* ctx) {
ecm_ctx_t* eth = ctx;
ecm_free(eth);
}
static zx_protocol_device_t ecm_device_proto = {
.version = DEVICE_OPS_VERSION,
.unbind = ecm_unbind,
.release = ecm_release,
};
static void ecm_update_online_status(ecm_ctx_t* ctx, bool is_online) {
mtx_lock(&ctx->ethmac_mutex);
if ((is_online && ctx->online) || (!is_online && !ctx->online)) {
goto done;
}
if (is_online) {
printf("%s: connected to network\n", module_name);
ctx->online = true;
if (ctx->ethmac_ifc) {
ctx->ethmac_ifc->status(ctx->ethmac_cookie, ETH_STATUS_ONLINE);
} else {
xprintf("%s: not connected to ethermac interface!\n", module_name);
}
} else {
printf("%s: no connection to network\n", module_name);
ctx->online = false;
if (ctx->ethmac_ifc) {
ctx->ethmac_ifc->status(ctx->ethmac_cookie, 0);
}
}
done:
mtx_unlock(&ctx->ethmac_mutex);
}
static zx_status_t ethmac_query(void* ctx, uint32_t options, ethmac_info_t* info) {
ecm_ctx_t* eth = ctx;
xprintf("%s: ethmac_query called\n", module_name);
// No options are supported
if (options) {
printf("%s: unexpected options (0x%"PRIx32") to ethmac_query\n", module_name, options);
return ZX_ERR_INVALID_ARGS;
}
memset(info, 0, sizeof(*info));
info->mtu = eth->mtu;
memcpy(info->mac, eth->mac_addr, sizeof(eth->mac_addr));
return ZX_OK;
}
static void ethmac_stop(void* cookie) {
xprintf("%s: ethmac_stop called\n", module_name);
ecm_ctx_t* ctx = cookie;
mtx_lock(&ctx->ethmac_mutex);
ctx->ethmac_ifc = NULL;
mtx_unlock(&ctx->ethmac_mutex);
}
static zx_status_t ethmac_start(void* ctx_cookie, ethmac_ifc_t* ifc, void* ethmac_cookie) {
xprintf("%s: ethmac_start called\n", module_name);
ecm_ctx_t* ctx = ctx_cookie;
zx_status_t status = ZX_OK;
mtx_lock(&ctx->ethmac_mutex);
if (ctx->ethmac_ifc) {
status = ZX_ERR_ALREADY_BOUND;
} else {
ctx->ethmac_ifc = ifc;
ctx->ethmac_cookie = ethmac_cookie;
ctx->ethmac_ifc->status(ethmac_cookie, ctx->online ? ETH_STATUS_ONLINE : 0);
}
mtx_unlock(&ctx->ethmac_mutex);
return status;
}
static void usb_write_complete(iotxn_t* request, void* cookie) {
ecm_ctx_t* ctx = cookie;
if (request->status == ZX_ERR_IO_NOT_PRESENT) {
iotxn_release(request);
return;
}
mtx_lock(&ctx->tx_mutex);
// Return transmission buffer to pool
list_add_tail(&ctx->tx_txn_bufs, &request->node);
if (request->status == ZX_ERR_IO_REFUSED) {
xprintf("%s: resetting transmit endpoint\n", module_name);
usb_reset_endpoint(&ctx->usb, ctx->tx_endpoint.addr);
}
mtx_unlock(&ctx->tx_mutex);
// When the interface is offline, the transaction will complete with status set to
// ZX_ERR_IO_NOT_PRESENT. There's not much we can do except ignore it.
}
// Note: the assumption made here is that no rx transmissions will be processed in parallel,
// so we do not maintain an rx mutex.
static void usb_recv(ecm_ctx_t* ctx, iotxn_t* request) {
size_t len = request->actual;
uint8_t* read_data = NULL;
iotxn_mmap(request, (void*)&read_data);
mtx_lock(&ctx->ethmac_mutex);
if (ctx->ethmac_ifc) {
ctx->ethmac_ifc->recv(ctx->ethmac_cookie, read_data, len, 0);
}
mtx_unlock(&ctx->ethmac_mutex);
}
static void usb_read_complete(iotxn_t* request, void* cookie) {
ecm_ctx_t* ctx = cookie;
if (request->status != ZX_OK) {
xprintf("%s: usb_read_complete called with status %d\n",
module_name, (int)request->status);
}
if (request->status == ZX_ERR_IO_NOT_PRESENT) {
iotxn_release(request);
return;
}
if (request->status == ZX_ERR_IO_REFUSED) {
xprintf("%s: resetting receive endpoint\n", module_name);
usb_reset_endpoint(&ctx->usb, ctx->rx_endpoint.addr);
} else if (request->status == ZX_OK) {
usb_recv(ctx, request);
}
iotxn_queue(ctx->usb_device, request);
}
static void ethmac_send(void* cookie, uint32_t options, void* data, size_t length) {
ecm_ctx_t* ctx = cookie;
uint8_t* byte_data = data;
if (length > ctx->mtu || length == 0) {
return;
}
xprintf("%s: sending %d bytes to endpoint 0x%"PRIx8"\n",
module_name, length, ctx->tx_endpoint.addr);
mtx_lock(&ctx->tx_mutex);
// As per the CDC-ECM spec, we need to send a zero-length packet to signify the end of
// transmission when the endpoint max packet size is a factor of the total transmission size.
bool send_terminal_packet = (length % ctx->tx_endpoint.max_packet_size == 0);
// Make sure that we can get all of the tx buffers we need to use
iotxn_t* tx_req = list_remove_head_type(&ctx->tx_txn_bufs, iotxn_t, node);
if (tx_req == NULL) {
printf("%s: no free write txns, dropping packet\n", module_name);
goto done;
}
iotxn_t* tx_req2;
if (send_terminal_packet) {
tx_req2 = list_remove_head_type(&ctx->tx_txn_bufs, iotxn_t, node);
if (tx_req2 == NULL) {
printf("%s: no free write txns, dropping packet\n", module_name);
list_add_tail(&ctx->tx_txn_bufs, &tx_req->node);
goto done;
}
}
// Send data
tx_req->length = length;
ssize_t bytes_copied = iotxn_copyto(tx_req, byte_data, tx_req->length, 0);
if (bytes_copied < 0) {
printf("%s: failed to copy data into send txn (error %zd)\n", module_name, bytes_copied);
list_add_tail(&ctx->tx_txn_bufs, &tx_req->node);
if (send_terminal_packet) {
list_add_tail(&ctx->tx_txn_bufs, &tx_req2->node);
}
goto done;
}
iotxn_queue(ctx->usb_device, tx_req);
// Send zero-length terminal packet, if needed
if (send_terminal_packet) {
tx_req2->length = 0;
bytes_copied = iotxn_copyto(tx_req2, byte_data, 0, 0);
if (bytes_copied < 0) {
// This leaves us in a very awkward situation, since failing to send the zero-length
// packet means the ethernet packet will be improperly terminated.
printf("%s: failed to copy data into send txn (error %zd)\n",
module_name, bytes_copied);
list_add_tail(&ctx->tx_txn_bufs, &tx_req2->node);
goto done;
}
iotxn_queue(ctx->usb_device, tx_req2);
}
done:
mtx_unlock(&ctx->tx_mutex);
}
static ethmac_protocol_ops_t ethmac_ops = {
.query = ethmac_query,
.stop = ethmac_stop,
.start = ethmac_start,
.send = ethmac_send,
};
static void ecm_interrupt_complete(iotxn_t* request, void* cookie) {
ecm_ctx_t* ctx = cookie;
completion_signal(&ctx->completion);
}
static void ecm_handle_interrupt(ecm_ctx_t* ctx, iotxn_t* request) {
if (request->actual < sizeof(usb_cdc_notification_t)) {
printf("%s: ignored interrupt (size = %ld)\n", module_name, (long)request->actual);
return;
}
usb_cdc_notification_t usb_req;
iotxn_copyfrom(request, &usb_req, sizeof(usb_cdc_notification_t), 0);
if (usb_req.bmRequestType == (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) &&
usb_req.bNotification == USB_CDC_NC_NETWORK_CONNECTION) {
ecm_update_online_status(ctx, usb_req.wValue != 0);
} else if (usb_req.bmRequestType == (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) &&
usb_req.bNotification == USB_CDC_NC_CONNECTION_SPEED_CHANGE) {
// The ethermac driver doesn't care about speed changes, so even though we track this
// information, it's currently unused.
if (usb_req.wLength != 8) {
printf("%s: invalid size (%"PRIu16") for CONNECTION_SPEED_CHANGE notification\n",
module_name, usb_req.wLength);
return;
}
// Data immediately follows notification in packet
uint32_t new_us_bps, new_ds_bps;
iotxn_copyfrom(request, &new_us_bps, 4, sizeof(usb_cdc_notification_t));
iotxn_copyfrom(request, &new_ds_bps, 4, sizeof(usb_cdc_notification_t) + 4);
if (new_us_bps != ctx->us_bps) {
printf("%s: connection speed change... upstream bits/s: %"PRIu32"\n",
module_name, new_us_bps);
ctx->us_bps = new_us_bps;
}
if (new_ds_bps != ctx->ds_bps) {
printf("%s: connection speed change... downstream bits/s: %"PRIu32"\n",
module_name, new_ds_bps);
ctx->ds_bps = new_ds_bps;
}
} else {
printf("%s: ignored interrupt (type = %"PRIu8", request = %"PRIu8")\n",
module_name, usb_req.bmRequestType, usb_req.bNotification);
return;
}
}
static int ecm_int_handler_thread(void* cookie) {
ecm_ctx_t* ctx = cookie;
iotxn_t* txn = ctx->int_txn_buf;
while (true) {
completion_reset(&ctx->completion);
iotxn_queue(ctx->usb_device, txn);
completion_wait(&ctx->completion, ZX_TIME_INFINITE);
if (txn->status == ZX_OK) {
ecm_handle_interrupt(ctx, txn);
} else if (txn->status == ZX_ERR_PEER_CLOSED || txn->status == ZX_ERR_IO_NOT_PRESENT) {
xprintf("%s: terminating interrupt handling thread\n", module_name);
return txn->status;
} else if (txn->status == ZX_ERR_IO_REFUSED) {
xprintf("%s: resetting interrupt endpoint\n", module_name);
usb_reset_endpoint(&ctx->usb, ctx->int_endpoint.addr);
} else {
printf("%s: error (%ld) waiting for interrupt - ignoring\n",
module_name, (long)txn->status);
}
}
}
static bool parse_cdc_header(usb_cs_header_interface_descriptor_t* header_desc) {
// Check for supported CDC version
xprintf("%s: device reports CDC version as 0x%x\n", module_name, header_desc->bcdCDC);
return header_desc->bcdCDC >= CDC_SUPPORTED_VERSION;
}
static bool parse_cdc_ethernet_descriptor(ecm_ctx_t* ctx,
usb_cs_ethernet_interface_descriptor_t* desc) {
ctx->mtu = desc->wMaxSegmentSize;
// MAC address is stored in a string descriptor in UTF-16 format, so we get one byte of
// address for each 32 bits of text.
const size_t expected_str_size = sizeof(usb_string_descriptor_t) + ETH_MAC_SIZE * 4;
char str_desc_buf[expected_str_size];
// Read string descriptor for MAC address (string index is in iMACAddress field)
zx_status_t result = usb_get_descriptor(&ctx->usb, 0, USB_DT_STRING, desc->iMACAddress,
str_desc_buf, sizeof(str_desc_buf), ZX_TIME_INFINITE);
if (result < 0) {
printf("%s: error reading MAC address\n", module_name);
return false;
}
if ((size_t)result != expected_str_size) {
printf("%s: MAC address string incorrect length (saw %zd, expected %zd)\n",
module_name, (size_t)result, expected_str_size);
return false;
}
// Convert MAC address to something more machine-friendly
usb_string_descriptor_t* str_desc = (usb_string_descriptor_t*)str_desc_buf;
uint8_t* str = str_desc->bString;
size_t ndx;
for (ndx = 0; ndx < ETH_MAC_SIZE * 4; ndx++) {
if (ndx % 2 == 1) {
if (str[ndx] != 0) {
printf("%s: MAC address contains invalid characters\n", module_name);
return false;
}
continue;
}
uint8_t value;
if (str[ndx] >= '0' && str[ndx] <= '9') {
value = str[ndx] - '0';
} else if (str[ndx] >= 'A' && str[ndx] <= 'F') {
value = (str[ndx] - 'A') + 0xa;
} else {
printf("%s: MAC address contains invalid characters\n", module_name);
return false;
}
if (ndx % 4 == 0) {
ctx->mac_addr[ndx/4] = value << 4;
} else {
ctx->mac_addr[ndx/4] |= value;
}
}
printf("%s: MAC address is %02X:%02X:%02X:%02X:%02X:%02X\n", module_name,
ctx->mac_addr[0], ctx->mac_addr[1], ctx->mac_addr[2],
ctx->mac_addr[3], ctx->mac_addr[4], ctx->mac_addr[5]);
return true;
}
static void copy_endpoint_info(ecm_endpoint_t* ep_info, usb_endpoint_descriptor_t* desc) {
ep_info->addr = desc->bEndpointAddress;
ep_info->max_packet_size = desc->wMaxPacketSize;
}
static bool want_interface(usb_interface_descriptor_t* intf, void* arg) {
return intf->bInterfaceClass == USB_CLASS_CDC;
}
static zx_status_t ecm_bind(void* ctx, zx_device_t* device, void** cookie) {
xprintf("%s: starting ecm_bind\n", module_name);
usb_protocol_t usb;
zx_status_t result = device_get_protocol(device, ZX_PROTOCOL_USB, &usb);
if (result != ZX_OK) {
return result;
}
// Allocate context
ecm_ctx_t* ecm_ctx = calloc(1, sizeof(ecm_ctx_t));
if (!ecm_ctx) {
printf("%s: failed to allocate memory for USB CDC ECM driver\n", module_name);
return ZX_ERR_NO_MEMORY;
}
result = usb_claim_additional_interfaces(&usb, want_interface, NULL);
if (result != ZX_OK) {
goto fail;
}
// Initialize context
ecm_ctx->usb_device = device;
memcpy(&ecm_ctx->usb, &usb, sizeof(ecm_ctx->usb));
list_initialize(&ecm_ctx->tx_txn_bufs);
mtx_init(&ecm_ctx->ethmac_mutex, mtx_plain);
mtx_init(&ecm_ctx->tx_mutex, mtx_plain);
usb_desc_iter_t iter;
result = usb_desc_iter_init(&usb, &iter);
if (result != ZX_OK) {
goto fail;
}
result = ZX_ERR_NOT_SUPPORTED;
// Find the CDC descriptors and endpoints
usb_descriptor_header_t* desc = usb_desc_iter_next(&iter);
usb_cs_header_interface_descriptor_t* cdc_header_desc = NULL;
usb_cs_ethernet_interface_descriptor_t* cdc_eth_desc = NULL;
usb_endpoint_descriptor_t* int_ep = NULL;
usb_endpoint_descriptor_t* tx_ep = NULL;
usb_endpoint_descriptor_t* rx_ep = NULL;
usb_interface_descriptor_t* default_ifc = NULL;
usb_interface_descriptor_t* data_ifc = NULL;
while (desc) {
if (desc->bDescriptorType == USB_DT_INTERFACE) {
usb_interface_descriptor_t* ifc_desc = (void*)desc;
if (ifc_desc->bInterfaceClass == USB_CLASS_CDC) {
if (ifc_desc->bNumEndpoints == 0) {
if (default_ifc) {
printf("%s: multiple default interfaces found\n", module_name);
goto fail;
}
default_ifc = ifc_desc;
} else if (ifc_desc->bNumEndpoints == 2) {
if (data_ifc) {
printf("%s: multiple data interfaces found\n", module_name);
goto fail;
}
data_ifc = ifc_desc;
}
}
} else if (desc->bDescriptorType == USB_DT_CS_INTERFACE) {
usb_cs_interface_descriptor_t* cs_ifc_desc = (void*)desc;
if (cs_ifc_desc->bDescriptorSubType == USB_CDC_DST_HEADER) {
if (cdc_header_desc != NULL) {
printf("%s: multiple CDC headers\n", module_name);
goto fail;
}
cdc_header_desc = (void*)cs_ifc_desc;
} else if (cs_ifc_desc->bDescriptorSubType == USB_CDC_DST_ETHERNET) {
if (cdc_eth_desc != NULL) {
printf("%s: multiple CDC ethernet descriptors\n", module_name);
goto fail;
}
cdc_eth_desc = (void*)cs_ifc_desc;
}
} else if (desc->bDescriptorType == USB_DT_ENDPOINT) {
usb_endpoint_descriptor_t* endpoint_desc = (void*)desc;
if (usb_ep_direction(endpoint_desc) == USB_ENDPOINT_IN &&
usb_ep_type(endpoint_desc) == USB_ENDPOINT_INTERRUPT) {
if (int_ep != NULL) {
printf("%s: multiple interrupt endpoint descriptors\n", module_name);
goto fail;
}
int_ep = endpoint_desc;
} else if (usb_ep_direction(endpoint_desc) == USB_ENDPOINT_OUT &&
usb_ep_type(endpoint_desc) == USB_ENDPOINT_BULK) {
if (tx_ep != NULL) {
printf("%s: multiple tx endpoint descriptors\n", module_name);
goto fail;
}
tx_ep = endpoint_desc;
} else if (usb_ep_direction(endpoint_desc) == USB_ENDPOINT_IN &&
usb_ep_type(endpoint_desc) == USB_ENDPOINT_BULK) {
if (rx_ep != NULL) {
printf("%s: multiple rx endpoint descriptors\n", module_name);
goto fail;
}
rx_ep = endpoint_desc;
} else {
printf("%s: unrecognized endpoint\n", module_name);
goto fail;
}
}
desc = usb_desc_iter_next(&iter);
}
if (cdc_header_desc == NULL || cdc_eth_desc == NULL) {
xprintf("%s: CDC %s descriptor(s) not found", module_name,
cdc_header_desc ? "ethernet" : cdc_eth_desc ? "header" : "ethernet and header");
goto fail;
}
if (int_ep == NULL || tx_ep == NULL || rx_ep == NULL) {
xprintf("%s: missing one or more required endpoints\n", module_name);
goto fail;
}
if (default_ifc == NULL) {
xprintf("%s: unable to find CDC default interface\n", module_name);
goto fail;
}
if (data_ifc == NULL) {
xprintf("%s: unable to find CDC data interface\n", module_name);
goto fail;
}
// Parse the information in the CDC descriptors
if (!parse_cdc_header(cdc_header_desc)) {
goto fail;
}
if (!parse_cdc_ethernet_descriptor(ecm_ctx, cdc_eth_desc)) {
goto fail;
}
// Parse endpoint information
copy_endpoint_info(&ecm_ctx->int_endpoint, int_ep);
copy_endpoint_info(&ecm_ctx->tx_endpoint, tx_ep);
copy_endpoint_info(&ecm_ctx->rx_endpoint, rx_ep);
// Reset by selecting default interface followed by data interface. We can't start
// queueing transactions until this is complete.
usb_set_interface(&usb, default_ifc->bInterfaceNumber, default_ifc->bAlternateSetting);
usb_set_interface(&usb, data_ifc->bInterfaceNumber, data_ifc->bAlternateSetting);
// Allocate interrupt transaction buffer
iotxn_t* int_buf = usb_alloc_iotxn(ecm_ctx->int_endpoint.addr,
ecm_ctx->int_endpoint.max_packet_size);
if (!int_buf) {
result = ZX_ERR_NO_MEMORY;
goto fail;
}
int_buf->length = ecm_ctx->int_endpoint.max_packet_size;
int_buf->complete_cb = ecm_interrupt_complete;
int_buf->cookie = ecm_ctx;
ecm_ctx->int_txn_buf = int_buf;
// Allocate tx transaction buffers
uint16_t tx_buf_sz = ecm_ctx->mtu;
if (tx_buf_sz > MAX_TX_BUF_SZ) {
printf("%s: insufficient space for even a single tx buffer\n", module_name);
goto fail;
}
size_t tx_buf_remain = MAX_TX_BUF_SZ;
while (tx_buf_remain >= tx_buf_sz) {
iotxn_t* tx_buf = usb_alloc_iotxn(ecm_ctx->tx_endpoint.addr, tx_buf_sz);
if (!tx_buf) {
result = ZX_ERR_NO_MEMORY;
goto fail;
}
tx_buf->complete_cb = usb_write_complete;
tx_buf->cookie = ecm_ctx;
list_add_head(&ecm_ctx->tx_txn_bufs, &tx_buf->node);
tx_buf_remain -= tx_buf_sz;
}
// Allocate rx transaction buffers
uint16_t rx_buf_sz = ecm_ctx->mtu;
if (rx_buf_sz > MAX_RX_BUF_SZ) {
printf("%s: insufficient space for even a single rx buffer\n", module_name);
goto fail;
}
size_t rx_buf_remain = MAX_RX_BUF_SZ;
while (rx_buf_remain >= rx_buf_sz) {
iotxn_t* rx_buf = usb_alloc_iotxn(ecm_ctx->rx_endpoint.addr, rx_buf_sz);
if (!rx_buf) {
result = ZX_ERR_NO_MEMORY;
goto fail;
}
rx_buf->complete_cb = usb_read_complete;
rx_buf->cookie = ecm_ctx;
rx_buf->length = rx_buf_sz;
iotxn_queue(ecm_ctx->usb_device, rx_buf);
rx_buf_remain -= rx_buf_sz;
}
// Kick off the handler thread
int thread_result = thrd_create_with_name(&ecm_ctx->int_thread, ecm_int_handler_thread,
ecm_ctx, "ecm_int_handler_thread");
if (thread_result != thrd_success) {
printf("%s: failed to create interrupt handler thread (%d)\n", module_name, thread_result);
goto fail;
}
// Add the device
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = "usb-cdc-ecm",
.ctx = ecm_ctx,
.ops = &ecm_device_proto,
.proto_id = ZX_PROTOCOL_ETHERMAC,
.proto_ops = &ethmac_ops,
};
result = device_add(ecm_ctx->usb_device, &args, &ecm_ctx->mxdev);
if (result < 0) {
printf("%s: failed to add device: %d\n", module_name, (int)result);
goto fail;
}
usb_desc_iter_release(&iter);
return ZX_OK;
fail:
usb_desc_iter_release(&iter);
ecm_free(ecm_ctx);
printf("%s: failed to bind\n", module_name);
return result;
}
static zx_driver_ops_t ecm_driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = ecm_bind,
};
ZIRCON_DRIVER_BEGIN(ethernet_usb_cdc_ecm, ecm_driver_ops, "zircon", "0.1", 4)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_USB),
BI_ABORT_IF(NE, BIND_USB_CLASS, USB_CLASS_COMM),
BI_ABORT_IF(NE, BIND_USB_SUBCLASS, USB_CDC_SUBCLASS_ETHERNET),
BI_MATCH_IF(EQ, BIND_USB_PROTOCOL, 0),
ZIRCON_DRIVER_END(ethernet_usb_cdc_ecm)