| // 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 "rndishost.h" |
| |
| #include <ddk/binding.h> |
| #include <ddk/debug.h> |
| #include <ddk/device.h> |
| #include <ddk/driver.h> |
| #include <ddk/protocol/ethernet.h> |
| #include <ddk/protocol/usb-old.h> |
| #include <ddk/usb/usb.h> |
| #include <usb/usb-request.h> |
| #include <zircon/hw/usb/cdc.h> |
| #include <zircon/hw/usb.h> |
| #include <zircon/listnode.h> |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <threads.h> |
| |
| #define READ_REQ_COUNT 8 |
| #define WRITE_REQ_COUNT 4 |
| #define ETH_HEADER_SIZE 4 |
| |
| #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* zxdev; |
| zx_device_t* usb_zxdev; |
| usb_protocol_t usb; |
| |
| uint8_t mac_addr[6]; |
| uint8_t control_intf; |
| uint32_t request_id; |
| uint32_t mtu; |
| |
| uint8_t bulk_in_addr; |
| uint8_t bulk_out_addr; |
| |
| list_node_t free_read_reqs; |
| list_node_t free_write_reqs; |
| |
| uint64_t rx_endpoint_delay; // wait time between 2 recv requests |
| uint64_t tx_endpoint_delay; // wait time between 2 transmit requests |
| |
| // Interface to the ethernet layer. |
| ethmac_ifc_t ifc; |
| |
| thrd_t thread; |
| bool thread_started; |
| size_t parent_req_size; |
| |
| mtx_t mutex; |
| } rndishost_t; |
| |
| static void dump_buffer(void* buf) { |
| uint8_t* p = buf; |
| for (int i = 0; i < RNDIS_BUFFER_SIZE; i += 4) { |
| if (i != 0 && i % 24 == 0) { |
| zxlogf(DEBUG1, "\n"); |
| } |
| zxlogf(DEBUG1, "%08x ", p[i] | p[i + 1] << 8 | p[i + 2] << 16 | p[i + 3] << 24); |
| } |
| zxlogf(DEBUG1, "\n"); |
| } |
| |
| static bool command_succeeded(void* buf, uint32_t type, uint32_t length) { |
| rndis_header_complete* header = buf; |
| if (header->msg_type != type) { |
| zxlogf(DEBUG1, "Bad type: Actual: %x, Expected: %x.\n", |
| header->msg_type, type); |
| return false; |
| } |
| if (header->msg_length != length) { |
| zxlogf(DEBUG1, "Bad length: Actual: %d, Expected: %d.\n", |
| header->msg_length, length); |
| return false; |
| } |
| if (header->status != RNDIS_STATUS_SUCCESS) { |
| zxlogf(DEBUG1, "Bad status: %x.\n", header->status); |
| return false; |
| } |
| return true; |
| } |
| |
| static zx_status_t rndis_command(rndishost_t* eth, void* buf) { |
| rndis_header* header = buf; |
| uint32_t request_id = eth->request_id++; |
| header->request_id = request_id; |
| |
| zx_status_t status; |
| status = usb_control(ð->usb, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, |
| USB_CDC_SEND_ENCAPSULATED_COMMAND, |
| 0, eth->control_intf, buf, header->msg_length, RNDIS_CONTROL_TIMEOUT, |
| NULL); |
| |
| if (status < 0) { |
| return status; |
| } |
| |
| status = usb_control(ð->usb, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, |
| USB_CDC_GET_ENCAPSULATED_RESPONSE, |
| 0, eth->control_intf, buf, RNDIS_BUFFER_SIZE, RNDIS_CONTROL_TIMEOUT, NULL); |
| |
| if (header->request_id != request_id) { |
| return ZX_ERR_IO_DATA_INTEGRITY; |
| } |
| |
| return status; |
| } |
| |
| static void rndishost_recv(rndishost_t* eth, usb_request_t* request) { |
| size_t len = request->response.actual; |
| |
| uint8_t* read_data; |
| zx_status_t status = usb_request_mmap(request, (void*)&read_data); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "rndishost receive: usb_request_mmap failed: %d\n", status); |
| return; |
| } |
| |
| while (len > sizeof(rndis_packet_header)) { |
| rndis_packet_header* header = (rndis_packet_header*)read_data; |
| |
| // The |data_offset| field contains the offset to the payload measured from the start of |
| // the field itself. |
| size_t data_offset = offsetof(rndis_packet_header, data_offset) + header->data_offset; |
| |
| if (header->msg_type != RNDIS_PACKET_MSG || len < header->msg_length || |
| len < data_offset + header->data_length) { |
| zxlogf(DEBUG1, "rndis bad packet\n"); |
| return; |
| } |
| |
| if (header->data_length == 0) { |
| // No more data. |
| return; |
| } |
| |
| ethmac_ifc_recv(ð->ifc, read_data + data_offset, header->data_length, 0); |
| |
| read_data += header->msg_length; |
| len -= header->msg_length; |
| } |
| } |
| |
| static void rndis_read_complete(usb_request_t* request, void* cookie) { |
| rndishost_t* eth = (rndishost_t*)cookie; |
| |
| if (request->response.status == ZX_ERR_IO_NOT_PRESENT) { |
| usb_request_release(request); |
| return; |
| } |
| |
| mtx_lock(ð->mutex); |
| if (request->response.status == ZX_ERR_IO_REFUSED) { |
| zxlogf(TRACE, "rndis_read_complete usb_reset_endpoint\n"); |
| usb_reset_endpoint(ð->usb, eth->bulk_in_addr); |
| } else if (request->response.status == ZX_ERR_IO_INVALID) { |
| zxlogf(TRACE, "rndis_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(ð->usb, eth->bulk_in_addr); |
| } |
| if ((request->response.status == ZX_OK) && eth->ifc.ops) { |
| rndishost_recv(eth, request); |
| } else { |
| zxlogf(DEBUG1, "rndis read complete: bad status = %d\n", request->response.status); |
| } |
| |
| // TODO: Only usb_request_queue if the device is online. |
| zx_nanosleep(zx_deadline_after(ZX_USEC(eth->rx_endpoint_delay))); |
| usb_request_queue(ð->usb, request, rndis_read_complete, eth); |
| |
| mtx_unlock(ð->mutex); |
| } |
| |
| static void rndis_write_complete(usb_request_t* request, void* cookie) { |
| rndishost_t* eth = (rndishost_t*)cookie; |
| |
| if (request->response.status == ZX_ERR_IO_NOT_PRESENT) { |
| zxlogf(ERROR, "rndis_write_complete zx_err_io_not_present\n"); |
| usb_request_release(request); |
| return; |
| } |
| |
| mtx_lock(ð->mutex); |
| if (request->response.status == ZX_ERR_IO_REFUSED) { |
| zxlogf(TRACE, "rndishost usb_reset_endpoint\n"); |
| usb_reset_endpoint(ð->usb, eth->bulk_out_addr); |
| } else if (request->response.status == ZX_ERR_IO_INVALID) { |
| zxlogf(TRACE, "rndis_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(ð->usb, eth->bulk_out_addr); |
| } |
| |
| zx_status_t status = usb_req_list_add_tail(ð->free_write_reqs, request, |
| eth->parent_req_size); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| mtx_unlock(ð->mutex); |
| } |
| |
| static void rndishost_free(rndishost_t* eth) { |
| usb_request_t* txn; |
| while ((txn = usb_req_list_remove_head(ð->free_read_reqs, eth->parent_req_size)) != NULL) { |
| usb_request_release(txn); |
| } |
| while ((txn = usb_req_list_remove_head(ð->free_write_reqs, eth->parent_req_size)) != NULL) { |
| usb_request_release(txn); |
| } |
| free(eth); |
| } |
| |
| static zx_status_t rndishost_query(void* ctx, uint32_t options, ethmac_info_t* info) { |
| rndishost_t* eth = (rndishost_t*)ctx; |
| |
| if (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)); |
| info->netbuf_size = sizeof(ethmac_netbuf_t); |
| |
| return ZX_OK; |
| } |
| |
| static void rndishost_stop(void* ctx) { |
| rndishost_t* eth = (rndishost_t*)ctx; |
| mtx_lock(ð->mutex); |
| eth->ifc.ops = NULL; |
| mtx_unlock(ð->mutex); |
| } |
| |
| static zx_status_t rndishost_start(void* ctx, const ethmac_ifc_t* ifc) { |
| rndishost_t* eth = (rndishost_t*)ctx; |
| zx_status_t status = ZX_OK; |
| |
| mtx_lock(ð->mutex); |
| if (eth->ifc.ops) { |
| status = ZX_ERR_ALREADY_BOUND; |
| } else { |
| eth->ifc = *ifc; |
| // TODO: Check that the device is online before sending ETH_STATUS_ONLINE. |
| ethmac_ifc_status(ð->ifc, ETHMAC_STATUS_ONLINE); |
| } |
| mtx_unlock(ð->mutex); |
| |
| return status; |
| } |
| |
| static zx_status_t rndishost_queue_tx(void* ctx, uint32_t options, ethmac_netbuf_t* netbuf) { |
| size_t length = netbuf->data_size; |
| rndishost_t* eth = (rndishost_t*)ctx; |
| const uint8_t* byte_data = netbuf->data_buffer; |
| zx_status_t status = ZX_OK; |
| |
| mtx_lock(ð->mutex); |
| |
| usb_request_t* req = usb_req_list_remove_head(ð->free_write_reqs, eth->parent_req_size); |
| if (req == NULL) { |
| zxlogf(TRACE, "rndishost dropped a packet\n"); |
| status = ZX_ERR_NO_RESOURCES; |
| goto done; |
| } |
| |
| if (length + sizeof(rndis_packet_header) > RNDIS_MAX_XFER_SIZE) { |
| zxlogf(TRACE, "rndishost attempted to send a packet that's too large.\n"); |
| status = usb_req_list_add_tail(ð->free_write_reqs, req, eth->parent_req_size); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| status = ZX_ERR_INVALID_ARGS; |
| goto done; |
| } |
| |
| rndis_packet_header header; |
| uint8_t* header_data = (uint8_t*)&header; |
| memset(header_data, 0, sizeof(rndis_packet_header)); |
| header.msg_type = RNDIS_PACKET_MSG; |
| header.msg_length = sizeof(rndis_packet_header) + length; |
| // The offset should be given from the beginning of the data_offset field. |
| // So subtract 8 bytes for msg_type and msg_length. |
| header.data_offset = sizeof(rndis_packet_header) - 8; |
| header.data_length = length; |
| |
| usb_request_copy_to(req, header_data, sizeof(rndis_packet_header), 0); |
| ssize_t bytes_copied = usb_request_copy_to(req, byte_data, length, |
| sizeof(rndis_packet_header)); |
| req->header.length = sizeof(rndis_packet_header) + length; |
| if (bytes_copied < 0) { |
| printf("rndishost: failed to copy data into send txn (error %zd)\n", bytes_copied); |
| status = usb_req_list_add_tail(ð->free_write_reqs, req, eth->parent_req_size); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| goto done; |
| } |
| zx_nanosleep(zx_deadline_after(ZX_USEC(eth->tx_endpoint_delay))); |
| usb_request_queue(ð->usb, req, rndis_write_complete, eth); |
| |
| done: |
| mtx_unlock(ð->mutex); |
| return status; |
| } |
| |
| static void rndishost_unbind(void* ctx) { |
| rndishost_t* eth = (rndishost_t*)ctx; |
| device_remove(eth->zxdev); |
| } |
| |
| static void rndishost_release(void* ctx) { |
| rndishost_t* eth = (rndishost_t*)ctx; |
| mtx_lock(ð->mutex); |
| bool should_join = eth->thread_started; |
| mtx_unlock(ð->mutex); |
| if (should_join) { |
| thrd_join(eth->thread, NULL); |
| } |
| rndishost_free(eth); |
| } |
| |
| static zx_status_t rndishost_set_param(void *ctx, uint32_t param, int32_t value, const void* data, |
| size_t data_size) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| static ethmac_protocol_ops_t ethmac_ops = { |
| .query = rndishost_query, |
| .stop = rndishost_stop, |
| .start = rndishost_start, |
| .queue_tx = rndishost_queue_tx, |
| .set_param = rndishost_set_param, |
| }; |
| |
| static zx_protocol_device_t rndishost_device_proto = { |
| .version = DEVICE_OPS_VERSION, |
| .unbind = rndishost_unbind, |
| .release = rndishost_release, |
| }; |
| |
| static int rndis_start_thread(void* arg) { |
| rndishost_t* eth = (rndishost_t*)arg; |
| void* buf = malloc(RNDIS_BUFFER_SIZE); |
| memset(buf, 0, RNDIS_BUFFER_SIZE); |
| |
| // Send an initialization message to the device. |
| rndis_init* init = buf; |
| init->msg_type = RNDIS_INITIALIZE_MSG; |
| init->msg_length = sizeof(rndis_init); |
| init->major_version = RNDIS_MAJOR_VERSION; |
| init->minor_version = RNDIS_MINOR_VERSION; |
| init->max_xfer_size = RNDIS_MAX_XFER_SIZE; |
| |
| zx_status_t status = rndis_command(eth, buf); |
| if (status < 0) { |
| zxlogf(ERROR, "rndishost bad status on initial message. %d\n", status); |
| goto fail; |
| } |
| |
| rndis_init_complete* init_cmplt = buf; |
| if (!command_succeeded(buf, RNDIS_INITIALIZE_CMPLT, sizeof(*init_cmplt))) { |
| zxlogf(ERROR, "rndishost initialization failed.\n"); |
| status = ZX_ERR_IO; |
| goto fail; |
| } |
| eth->mtu = init_cmplt->max_xfer_size; |
| |
| // Check the PHY, this is optional and may not be supported by the device. |
| uint32_t* phy; |
| memset(buf, 0, RNDIS_BUFFER_SIZE); |
| rndis_query* query = buf; |
| query->msg_type = RNDIS_QUERY_MSG; |
| query->msg_length = sizeof(rndis_query) + sizeof(*phy); |
| query->oid = OID_GEN_PHYSICAL_MEDIUM; |
| query->info_buffer_length = sizeof(*phy); |
| query->info_buffer_offset = RNDIS_QUERY_BUFFER_OFFSET; |
| status = rndis_command(eth, buf); |
| if (status == ZX_OK) { |
| // TODO: Do something with this information. |
| rndis_query_complete* phy_query_cmplt = buf; |
| if (command_succeeded(buf, |
| RNDIS_QUERY_CMPLT, |
| sizeof(*phy_query_cmplt) + phy_query_cmplt->info_buffer_length)) { |
| // The offset given in the reply is from the beginning of the request_id |
| // field. So, add 8 for the msg_type and msg_length fields. |
| phy = buf + 8 + phy_query_cmplt->info_buffer_offset; |
| } |
| } |
| |
| // Query the device for a MAC address. |
| memset(buf, 0, RNDIS_BUFFER_SIZE); |
| query->msg_type = RNDIS_QUERY_MSG; |
| query->msg_length = sizeof(rndis_query) + 48; |
| query->oid = OID_802_3_PERMANENT_ADDRESS; |
| query->info_buffer_length = 48; |
| query->info_buffer_offset = RNDIS_QUERY_BUFFER_OFFSET; |
| status = rndis_command(eth, buf); |
| if (status < 0) { |
| zxlogf(ERROR, "Couldn't get device physical address\n"); |
| goto fail; |
| } |
| |
| rndis_query_complete* mac_query_cmplt = buf; |
| if (!command_succeeded(buf, RNDIS_QUERY_CMPLT, sizeof(*mac_query_cmplt) + |
| mac_query_cmplt->info_buffer_length)) { |
| zxlogf(ERROR, "rndishost MAC query failed.\n"); |
| status = ZX_ERR_IO; |
| goto fail; |
| } |
| uint8_t* mac_addr = buf + 8 + mac_query_cmplt->info_buffer_offset; |
| memcpy(eth->mac_addr, mac_addr, sizeof(eth->mac_addr)); |
| zxlogf(INFO, "rndishost 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]); |
| |
| // Enable data transfers |
| memset(buf, 0, RNDIS_BUFFER_SIZE); |
| rndis_set* set = buf; |
| set->msg_type = RNDIS_SET_MSG; |
| set->msg_length = sizeof(rndis_set) + 4; // 4 bytes for the filter |
| set->oid = OID_GEN_CURRENT_PACKET_FILTER; |
| set->info_buffer_length = 4; |
| // Offset should begin at oid, so subtract 8 bytes for msg_type and msg_length. |
| set->info_buffer_offset = sizeof(rndis_set) - 8; |
| uint8_t* filter = buf + sizeof(rndis_set); |
| *filter = RNDIS_PACKET_TYPE_DIRECTED | |
| RNDIS_PACKET_TYPE_BROADCAST | |
| RNDIS_PACKET_TYPE_ALL_MULTICAST | |
| RNDIS_PACKET_TYPE_PROMISCUOUS; |
| status = rndis_command(eth, buf); |
| if (status < 0) { |
| zxlogf(ERROR, "Couldn't set the packet filter.\n"); |
| goto fail; |
| } |
| |
| if (!command_succeeded(buf, RNDIS_SET_CMPLT, sizeof(rndis_set_complete))) { |
| zxlogf(ERROR, "rndishost set filter failed.\n"); |
| status = ZX_ERR_IO; |
| goto fail; |
| } |
| |
| // Queue read requests |
| mtx_lock(ð->mutex); |
| usb_request_t* txn; |
| while ((txn = usb_req_list_remove_head(ð->free_read_reqs, eth->parent_req_size)) != NULL) { |
| usb_request_queue(ð->usb, txn, rndis_read_complete, eth); |
| } |
| mtx_unlock(ð->mutex); |
| |
| free(buf); |
| device_make_visible(eth->zxdev); |
| return ZX_OK; |
| |
| fail: |
| free(buf); |
| device_remove(eth->zxdev); |
| return status; |
| } |
| |
| static zx_status_t rndishost_bind(void* ctx, zx_device_t* device) { |
| usb_protocol_t usb; |
| zx_status_t status = device_get_protocol(device, ZX_PROTOCOL_USB_OLD, &usb); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // Find our endpoints. |
| usb_desc_iter_t iter; |
| status = usb_desc_iter_init(&usb, &iter); |
| if (status < 0) { |
| return status; |
| } |
| |
| // We should have two interfaces: the CDC classified interface the bulk in |
| // and out endpoints, and the RNDIS interface for control. The RNDIS |
| // interface will be classified as USB_CLASS_WIRELESS when the device is |
| // used for tethering. |
| // TODO: Figure out how to handle other RNDIS use cases. |
| usb_interface_descriptor_t* intf = usb_desc_iter_next_interface(&iter, false); |
| uint8_t bulk_in_addr = 0; |
| uint8_t bulk_out_addr = 0; |
| uint8_t intr_addr = 0; |
| uint8_t control_intf = 0; |
| while (intf) { |
| if (intf->bInterfaceClass == USB_CLASS_WIRELESS) { |
| control_intf = intf->bInterfaceNumber; |
| if (intf->bNumEndpoints != 1) { |
| usb_desc_iter_release(&iter); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| usb_endpoint_descriptor_t* endp = usb_desc_iter_next_endpoint(&iter); |
| while (endp) { |
| if (usb_ep_direction(endp) == USB_ENDPOINT_IN && |
| usb_ep_type(endp) == USB_ENDPOINT_INTERRUPT) { |
| intr_addr = endp->bEndpointAddress; |
| } |
| endp = usb_desc_iter_next_endpoint(&iter); |
| } |
| } else if (intf->bInterfaceClass == USB_CLASS_CDC) { |
| if (intf->bNumEndpoints != 2) { |
| usb_desc_iter_release(&iter); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| 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_direction(endp) == USB_ENDPOINT_IN) { |
| if (usb_ep_type(endp) == USB_ENDPOINT_BULK) { |
| bulk_in_addr = endp->bEndpointAddress; |
| } |
| } |
| endp = usb_desc_iter_next_endpoint(&iter); |
| } |
| } else { |
| usb_desc_iter_release(&iter); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| intf = usb_desc_iter_next_interface(&iter, false); |
| } |
| usb_desc_iter_release(&iter); |
| |
| if (!bulk_in_addr || !bulk_out_addr || !intr_addr) { |
| zxlogf(ERROR, "rndishost couldn't find endpoints\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| rndishost_t* eth = calloc(1, sizeof(rndishost_t)); |
| if (!eth) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| list_initialize(ð->free_read_reqs); |
| list_initialize(ð->free_write_reqs); |
| |
| mtx_init(ð->mutex, mtx_plain); |
| |
| eth->usb_zxdev = device; |
| eth->control_intf = control_intf; |
| eth->bulk_in_addr = bulk_in_addr; |
| eth->bulk_out_addr = bulk_out_addr; |
| eth->ifc.ops = NULL; |
| memcpy(ð->usb, &usb, sizeof(eth->usb)); |
| |
| eth->parent_req_size = usb_get_request_size(ð->usb); |
| uint64_t req_size = eth->parent_req_size + sizeof(usb_req_internal_t); |
| |
| for (int i = 0; i < READ_REQ_COUNT; i++) { |
| usb_request_t* req; |
| zx_status_t alloc_result = usb_request_alloc(&req, RNDIS_BUFFER_SIZE, bulk_in_addr, |
| req_size); |
| if (alloc_result != ZX_OK) { |
| status = alloc_result; |
| goto fail; |
| } |
| status = usb_req_list_add_head(ð->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; |
| // TODO: Allocate based on mtu. |
| zx_status_t alloc_result = usb_request_alloc(&req, RNDIS_BUFFER_SIZE, bulk_out_addr, |
| req_size); |
| if (alloc_result != ZX_OK) { |
| status = alloc_result; |
| goto fail; |
| } |
| status = usb_req_list_add_head(ð->free_write_reqs, req, eth->parent_req_size); |
| ZX_DEBUG_ASSERT(status == ZX_OK); |
| } |
| |
| device_add_args_t args = { |
| .version = DEVICE_ADD_ARGS_VERSION, |
| .name = "rndishost", |
| .ctx = eth, |
| .ops = &rndishost_device_proto, |
| .proto_id = ZX_PROTOCOL_ETHMAC, |
| .proto_ops = ðmac_ops, |
| .flags = DEVICE_ADD_INVISIBLE, |
| }; |
| |
| mtx_lock(ð->mutex); |
| status = device_add(eth->usb_zxdev, &args, ð->zxdev); |
| if (status < 0) { |
| mtx_unlock(ð->mutex); |
| zxlogf(ERROR, "rndishost: failed to create device: %d\n", status); |
| goto fail; |
| } |
| |
| eth->thread_started = true; |
| int ret = thrd_create_with_name(ð->thread, rndis_start_thread, |
| eth, "rndishost_start_thread"); |
| if (ret != thrd_success) { |
| eth->thread_started = false; |
| mtx_unlock(ð->mutex); |
| device_remove(eth->zxdev); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| mtx_unlock(ð->mutex); |
| return ZX_OK; |
| |
| fail: |
| zxlogf(ERROR, "rndishost_bind failed: %d\n", status); |
| rndishost_free(eth); |
| return status; |
| } |
| |
| static zx_driver_ops_t rndis_driver_ops = { |
| .version = DRIVER_OPS_VERSION, |
| .bind = rndishost_bind, |
| }; |
| |
| // TODO: Make sure we can bind to all RNDIS use cases. USB_CLASS_WIRELESS only |
| // covers the tethered device case. |
| // clang-format off |
| ZIRCON_DRIVER_BEGIN(rndishost, rndis_driver_ops, "zircon", "0.1", 4) |
| BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_USB_OLD), |
| BI_ABORT_IF(NE, BIND_USB_CLASS, USB_CLASS_WIRELESS), |
| BI_ABORT_IF(NE, BIND_USB_SUBCLASS, RNDIS_SUBCLASS), |
| BI_MATCH_IF(EQ, BIND_USB_PROTOCOL, RNDIS_PROTOCOL), |
| ZIRCON_DRIVER_END(rndishost) |