blob: ec6de3c362e031b98e3883665864cfaaa2b0c428 [file] [log] [blame]
// Copyright 2020 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 "usb-cdc-ecm-lib.h"
#include <usb/usb.h>
const char* module_name = "usb-cdc-ecm";
static bool parse_cdc_header(usb_cs_header_interface_descriptor_t* header_desc) {
// Check for supported CDC version
zxlogf(DEBUG, "%s: device reports CDC version as 0x%x", 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)
size_t out_length;
zx_status_t result =
usb_get_descriptor(&ctx->usb, 0, USB_DT_STRING, desc->iMACAddress, (uint8_t*)str_desc_buf,
sizeof(str_desc_buf), ZX_TIME_INFINITE, &out_length);
if (result < 0) {
zxlogf(ERROR, "%s: error reading MAC address", module_name);
return false;
}
if (out_length != expected_str_size) {
zxlogf(ERROR, "%s: MAC address string incorrect length (saw %zd, expected %zd)", module_name,
out_length, 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) {
zxlogf(ERROR, "%s: MAC address contains invalid characters", 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 {
zxlogf(ERROR, "%s: MAC address contains invalid characters", module_name);
return false;
}
if (ndx % 4 == 0) {
ctx->mac_addr[ndx / 4] = (uint8_t)(value << 4);
} else {
ctx->mac_addr[ndx / 4] |= value;
}
}
zxlogf(INFO, "%s: MAC address is %02X:%02X:%02X:%02X:%02X:%02X", 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;
}
zx_status_t parse_usb_descriptor(usb_desc_iter_t* iter, usb_endpoint_descriptor_t** int_ep,
usb_endpoint_descriptor_t** tx_ep,
usb_endpoint_descriptor_t** rx_ep,
usb_interface_descriptor_t** default_ifc,
usb_interface_descriptor_t** data_ifc, ecm_ctx_t* ecm_ctx) {
zx_status_t result = ZX_ERR_NOT_SUPPORTED;
usb_descriptor_header_t* desc;
usb_cs_header_interface_descriptor_t* cdc_header_desc = NULL;
usb_cs_ethernet_interface_descriptor_t* cdc_eth_desc = NULL;
*int_ep = NULL;
*tx_ep = NULL;
*rx_ep = NULL;
*default_ifc = NULL;
*data_ifc = NULL;
while ((desc = usb_desc_iter_peek(iter)) != NULL) {
if (desc->bDescriptorType == USB_DT_INTERFACE) {
usb_interface_descriptor_t* ifc_desc =
usb_desc_iter_get_structure(iter, sizeof(usb_interface_descriptor_t));
if (ifc_desc == NULL) {
goto fail;
}
if (ifc_desc->b_interface_class == USB_CLASS_CDC) {
if (ifc_desc->b_num_endpoints == 0) {
if (*default_ifc) {
zxlogf(ERROR, "%s: multiple default interfaces found", module_name);
goto fail;
}
*default_ifc = ifc_desc;
} else if (ifc_desc->b_num_endpoints == 2) {
if (*data_ifc) {
zxlogf(ERROR, "%s: multiple data interfaces found", module_name);
goto fail;
}
*data_ifc = ifc_desc;
}
}
} else if (desc->bDescriptorType == USB_DT_CS_INTERFACE) {
usb_cs_interface_descriptor_t* cs_ifc_desc =
usb_desc_iter_get_structure(iter, sizeof(usb_cs_interface_descriptor_t));
if (cs_ifc_desc == NULL) {
goto fail;
}
if (cs_ifc_desc->bDescriptorSubType == USB_CDC_DST_HEADER) {
if (cdc_header_desc != NULL) {
zxlogf(ERROR, "%s: multiple CDC headers", module_name);
goto fail;
}
cdc_header_desc =
usb_desc_iter_get_structure(iter, sizeof(usb_cs_header_interface_descriptor_t));
} else if (cs_ifc_desc->bDescriptorSubType == USB_CDC_DST_ETHERNET) {
if (cdc_eth_desc != NULL) {
zxlogf(ERROR, "%s: multiple CDC ethernet descriptors", module_name);
goto fail;
}
cdc_eth_desc =
usb_desc_iter_get_structure(iter, sizeof(usb_cs_ethernet_interface_descriptor_t));
}
} else if (desc->bDescriptorType == USB_DT_ENDPOINT) {
usb_endpoint_descriptor_t* endpoint_desc =
usb_desc_iter_get_structure(iter, sizeof(usb_endpoint_descriptor_t));
if (endpoint_desc == NULL) {
goto fail;
}
if (usb_ep_direction(endpoint_desc) == USB_ENDPOINT_IN &&
usb_ep_type(endpoint_desc) == USB_ENDPOINT_INTERRUPT) {
if (*int_ep != NULL) {
zxlogf(ERROR, "%s: multiple interrupt endpoint descriptors", 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) {
zxlogf(ERROR, "%s: multiple tx endpoint descriptors", 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) {
zxlogf(ERROR, "%s: multiple rx endpoint descriptors", module_name);
goto fail;
}
*rx_ep = endpoint_desc;
} else {
zxlogf(ERROR, "%s: unrecognized endpoint", module_name);
goto fail;
}
}
usb_desc_iter_advance(iter);
}
if (cdc_header_desc == NULL || cdc_eth_desc == NULL) {
zxlogf(ERROR, "%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) {
zxlogf(ERROR, "%s: missing one or more required endpoints", module_name);
goto fail;
}
if (*default_ifc == NULL) {
zxlogf(ERROR, "%s: unable to find CDC default interface", module_name);
goto fail;
}
if (*data_ifc == NULL) {
zxlogf(ERROR, "%s: unable to find CDC data interface", module_name);
goto fail;
}
// Parse the information in the CDC descriptors
if (!parse_cdc_header(cdc_header_desc)) {
zxlogf(ERROR, "%s: unable to parse cdc header", module_name);
goto fail;
}
if (!parse_cdc_ethernet_descriptor(ecm_ctx, cdc_eth_desc)) {
zxlogf(ERROR, "%s: unable to parse cdc ethernet descriptor", module_name);
goto fail;
}
result = ZX_OK;
fail:
return result;
}