blob: b3acf2c154adca4717d3fcba2b3022036968c59e [file] [log] [blame]
// Copyright 2022 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 <lib/fit/defer.h>
#include <zircon/errors.h>
#include "fuchsia/hardware/usb/descriptor/c/banjo.h"
#include "usb/usb.h"
namespace usb_cdc_ecm {
zx::result<MacAddress> UsbCdcDescriptorParser::ParseMacAddress(
usb::UsbDevice& usb, const usb_cs_ethernet_interface_descriptor_t* desc) {
// Read string descriptor for MAC address (string index is in iMACAddress field)
size_t out_length;
uint8_t str_desc_buf[kExpectedStringSize];
zx_status_t status = usb.GetDescriptor(0, USB_DT_STRING, desc->iMACAddress, str_desc_buf,
sizeof(str_desc_buf), ZX_TIME_INFINITE, &out_length);
if (status != ZX_OK) {
zxlogf(ERROR, "Error reading MAC address");
return zx::error(status);
}
if (out_length != kExpectedStringSize) {
zxlogf(ERROR, "MAC address string incorrect length (saw %zd, expected %zd)", out_length,
kExpectedStringSize);
return zx::error(ZX_ERR_IO);
}
// Convert MAC address to something more machine-friendly
auto str_desc = reinterpret_cast<usb_string_descriptor_t*>(str_desc_buf);
uint8_t* str = str_desc->b_string;
size_t ndx;
MacAddress mac_addr;
for (ndx = 0; ndx < ETH_MAC_SIZE * 4; ndx++) {
if (ndx % 2 == 1) {
if (str[ndx] != 0) {
zxlogf(ERROR, "MAC address contains invalid characters");
return zx::error(ZX_ERR_IO);
}
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, "MAC address contains invalid characters");
return zx::error(ZX_ERR_IO);
}
if (ndx % 4 == 0) {
mac_addr[ndx / 4] = (uint8_t)(value << 4);
} else {
mac_addr[ndx / 4] |= value;
}
}
zxlogf(INFO, "MAC address is %02X:%02X:%02X:%02X:%02X:%02X", mac_addr[0], mac_addr[1],
mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
return zx::ok(mac_addr);
}
zx::result<UsbCdcDescriptorParser> UsbCdcDescriptorParser::Parse(usb::UsbDevice& usb) {
std::optional<usb::InterfaceList> interfaces;
zx_status_t status = usb::InterfaceList::Create(usb, false, &interfaces);
if (status != ZX_OK) {
return zx::error(status);
}
std::optional<EcmEndpoint> int_ep;
std::optional<EcmEndpoint> tx_ep;
std::optional<EcmEndpoint> rx_ep;
std::optional<EcmInterface> default_ifc;
std::optional<EcmInterface> data_ifc;
// Find default interface.
for (const usb::Interface& interface : *interfaces) {
const usb_interface_descriptor_t* desc = interface.descriptor();
if (desc->b_interface_class != USB_CLASS_CDC) {
continue;
}
if (desc->b_num_endpoints != 0) {
continue;
}
if (default_ifc.has_value()) {
zxlogf(ERROR, "Multiple default interfaces found");
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
default_ifc = EcmInterface(desc);
}
// Find data interface.
for (const usb::Interface& interface : *interfaces) {
const usb_interface_descriptor_t* desc = interface.descriptor();
if (desc->b_interface_class != USB_CLASS_CDC) {
continue;
}
if (desc->b_num_endpoints != 2) {
continue;
}
if (data_ifc.has_value()) {
zxlogf(ERROR, "Multiple data interfaces found");
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
data_ifc = EcmInterface(desc);
for (const auto& endpoint : interface.GetEndpointList()) {
const usb_endpoint_descriptor_t* endpoint_desc = endpoint.descriptor();
if (usb_ep_direction(endpoint_desc) == USB_ENDPOINT_OUT &&
usb_ep_type(endpoint_desc) == USB_ENDPOINT_BULK) {
if (tx_ep.has_value()) {
zxlogf(ERROR, "Multiple tx endpoint descriptors");
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
tx_ep = EcmEndpoint(endpoint_desc);
} else if (usb_ep_direction(endpoint_desc) == USB_ENDPOINT_IN &&
usb_ep_type(endpoint_desc) == USB_ENDPOINT_BULK) {
if (rx_ep.has_value()) {
zxlogf(ERROR, "Multiple rx endpoint descriptors");
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
rx_ep = EcmEndpoint(endpoint_desc);
} else {
zxlogf(ERROR, "Unrecognized endpoint");
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
}
}
// Find communications interface, which has CDC headers and interrupt endpoint.
const usb_cs_header_interface_descriptor_t* cdc_header_desc = nullptr;
const usb_cs_ethernet_interface_descriptor_t* cdc_eth_desc = nullptr;
for (const usb::Interface& interface : *interfaces) {
if (interface.descriptor()->b_interface_class != USB_CLASS_COMM) {
continue;
}
for (auto& descriptor : interface.GetDescriptorList()) {
if (descriptor.b_descriptor_type != USB_DT_CS_INTERFACE) {
continue;
}
if (descriptor.b_length < sizeof(usb_cs_interface_descriptor_t)) {
zxlogf(WARNING, "Malformed class specific descriptor");
continue;
}
const usb_cs_interface_descriptor_t* cs_ifc_desc =
reinterpret_cast<const usb_cs_interface_descriptor_t*>(&descriptor);
if (cs_ifc_desc->b_descriptor_sub_type == USB_CDC_DST_HEADER) {
if (cdc_header_desc != nullptr) {
zxlogf(ERROR, "Multiple CDC headers");
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
if (descriptor.b_length < sizeof(usb_cs_header_interface_descriptor_t)) {
zxlogf(WARNING, "Malformed CDC header descriptor");
continue;
}
cdc_header_desc =
reinterpret_cast<const usb_cs_header_interface_descriptor_t*>(&descriptor);
} else if (cs_ifc_desc->b_descriptor_sub_type == USB_CDC_DST_ETHERNET) {
if (cdc_eth_desc != nullptr) {
zxlogf(ERROR, "Multiple CDC ethernet descriptors");
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
if (descriptor.b_length < sizeof(usb_cs_ethernet_interface_descriptor_t)) {
zxlogf(WARNING, "Malformed CDC ethernet descriptor");
continue;
}
cdc_eth_desc = reinterpret_cast<const usb_cs_ethernet_interface_descriptor_t*>(&descriptor);
}
}
for (const auto& endpoint : interface.GetEndpointList()) {
const usb_endpoint_descriptor_t* endpoint_desc = endpoint.descriptor();
if (usb_ep_direction(endpoint_desc) == USB_ENDPOINT_IN &&
usb_ep_type(endpoint_desc) == USB_ENDPOINT_INTERRUPT) {
if (int_ep.has_value()) {
zxlogf(ERROR, "Multiple interrupt endpoint descriptors");
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
int_ep = EcmEndpoint(endpoint_desc);
}
}
}
if (cdc_header_desc == nullptr || cdc_eth_desc == nullptr) {
zxlogf(ERROR, "CDC %s descriptor(s) not found",
cdc_header_desc ? "ethernet"
: cdc_eth_desc ? "header"
: "ethernet and header");
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
if (!int_ep.has_value() || !tx_ep.has_value() || !rx_ep.has_value()) {
zxlogf(ERROR, "Missing one or more required endpoints");
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
if (!default_ifc.has_value()) {
zxlogf(ERROR, "Unable to find CDC default interface");
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
if (!data_ifc.has_value()) {
zxlogf(ERROR, "Unable to find CDC data interface");
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
// Parse the information in the CDC descriptors
zxlogf(DEBUG, "Device reports CDC version as 0x%x", cdc_header_desc->bcdCDC);
if (cdc_header_desc->bcdCDC < kCdcSupportedVersion) {
zxlogf(ERROR, "Unable to parse cdc header");
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
const uint16_t mtu = cdc_eth_desc->wMaxSegmentSize;
auto mac_addr = UsbCdcDescriptorParser::ParseMacAddress(usb, cdc_eth_desc);
if (mac_addr.is_error()) {
zxlogf(ERROR, "Unable to parse cdc ethernet descriptor");
return mac_addr.take_error();
}
return zx::ok(UsbCdcDescriptorParser(int_ep.value(), tx_ep.value(), rx_ep.value(),
default_ifc.value(), data_ifc.value(), mtu,
mac_addr.value()));
}
} // namespace usb_cdc_ecm