From 8b482bedb84ae5a85bede7bc626d140eac4696df Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 12 Nov 2026 17:59:52 +0000 Subject: [PATCH] refactor(usb): the configuration-descriptor walk moved out of the event loop, unchanged xhci.c's XHCI_XFER_CONFIG_DESC_FULL case walked the descriptor set inline, in the event loop, where nothing could test it. The walk is now usb_config_parse() (kernel/src/usb/usb_config.c), returning what it found; the case acts on it as before -- the same messages, the same decisions: the first Interface descriptor only, a BOT interface's bulk endpoints (the last of each direction), a boot keyboard's first interrupt IN endpoint, bLength 0 ending the walk, nothing read past len. One difference, in debug output only: the bulk endpoints are logged IN then OUT, not in descriptor order. kernel/tests/test_usb_config.c, 7 checks, pins the walk as moved: a BOT stick as QEMU presents it, a boot keyboard with its HID descriptor, a mouse before a drive (the first interface only), a drive with bulk IN only, bLength 0, an endpoint cut by len, an interface cut before its protocol byte. All pass. Built for amd64, aarch64, riscv64; the boots come with the protocol (a data stick enumerates through this path). The fixes for #170 and #171 follow. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm --- kernel/include/starkernel/usb_config.h | 35 +++++ kernel/src/usb/usb_config.c | 81 +++++++++++ kernel/src/usb/xhci.c | 187 ++++++++----------------- kernel/tests/Makefile | 3 +- kernel/tests/test_usb_config.c | 73 ++++++++++ 5 files changed, 247 insertions(+), 132 deletions(-) create mode 100644 kernel/include/starkernel/usb_config.h create mode 100644 kernel/src/usb/usb_config.c create mode 100644 kernel/tests/test_usb_config.c diff --git a/kernel/include/starkernel/usb_config.h b/kernel/include/starkernel/usb_config.h new file mode 100644 index 00000000..d39cacc4 --- /dev/null +++ b/kernel/include/starkernel/usb_config.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: LicenseRef-Starship-1.0 + * Copyright (c) 2023-2026 Robert A. James + */ +/* usb_config.h -- a USB configuration descriptor set, read: what xhci.c's event loop needs to know + * of a device (its first interface, and that interface's endpoints). Moved out of the loop's + * XHCI_XFER_CONFIG_DESC_FULL case unchanged, so it can be tested on the host + * (kernel/tests/test_usb_config.c; docs/v4.0.0/plans/2026-11-12-kernel-host-tests.md). + */ +#ifndef STARKERNEL_USB_CONFIG_H +#define STARKERNEL_USB_CONFIG_H + +#include + +#define USB_CONFIG_NO_INTERFACE 0 /* no Interface descriptor in the set */ +#define USB_CONFIG_MSC 1 /* Mass Storage / SCSI / Bulk-Only Transport */ +#define USB_CONFIG_KBD 2 /* a HID boot keyboard */ +#define USB_CONFIG_OTHER 3 /* anything else -- not used */ + +typedef struct { + int kind; /* USB_CONFIG_* */ + uint8_t iface_class, iface_subclass, iface_protocol; + uint8_t config_value; /* bConfigurationValue, for SET_CONFIGURATION */ + uint8_t bulk_in_ep, bulk_out_ep; /* USB_CONFIG_MSC: 0 when not found */ + uint16_t bulk_in_max, bulk_out_max; + uint8_t hid_iface; /* USB_CONFIG_KBD */ + uint8_t hid_ep, hid_interval; /* hid_ep 0 when not found */ + uint16_t hid_max; +} usb_config_t; + +/* Read the first `len` bytes of a configuration descriptor set (the Configuration descriptor and + * the Interface and Endpoint descriptors after it). Only the first Interface descriptor counts; + * a descriptor with bLength 0 ends the walk. Never reads past `len`. */ +void usb_config_parse(const uint8_t *desc, uint16_t len, usb_config_t *out); + +#endif diff --git a/kernel/src/usb/usb_config.c b/kernel/src/usb/usb_config.c new file mode 100644 index 00000000..c83e3694 --- /dev/null +++ b/kernel/src/usb/usb_config.c @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: LicenseRef-Starship-1.0 + * Copyright (c) 2023-2026 Robert A. James + */ +/* usb_config.c -- see usb_config.h. Moved from xhci.c's XHCI_XFER_CONFIG_DESC_FULL case, the walk + * unchanged: found by bDescriptorType, not by a fixed offset; the first Interface descriptor only; + * a Mass Storage/SCSI/BOT interface's bulk endpoints up to the next Interface descriptor (the last + * of each direction wins); a boot keyboard's first interrupt IN endpoint. + */ +#include "starkernel/usb_config.h" +#include "starkernel/xhci.h" +#include + +static uint16_t le16(const uint8_t *p) { return (uint16_t)(p[0] | ((uint16_t)p[1] << 8)); } + +void usb_config_parse(const uint8_t *desc, uint16_t len, usb_config_t *out) +{ + uint16_t off = 0; + + memset(out, 0, sizeof *out); + out->kind = USB_CONFIG_NO_INTERFACE; + out->config_value = desc[USB_CONFIG_OFF_CONFIG_VALUE]; + + while (off + 2 <= len) { + uint8_t desc_len = desc[off + USB_DESC_OFF_LENGTH]; + uint8_t desc_type = desc[off + USB_DESC_OFF_TYPE]; + if (desc_len == 0) break; /* malformed -- avoid an infinite loop */ + if (desc_type == USB_DESC_TYPE_INTERFACE && off + USB_IFACE_OFF_PROTOCOL < len) { + uint16_t ep_off = (uint16_t)(off + desc_len); + out->iface_class = desc[off + USB_IFACE_OFF_CLASS]; + out->iface_subclass = desc[off + USB_IFACE_OFF_SUBCLASS]; + out->iface_protocol = desc[off + USB_IFACE_OFF_PROTOCOL]; + if (out->iface_class == USB_CLASS_MASS_STORAGE && out->iface_subclass == USB_SUBCLASS_SCSI && + out->iface_protocol == USB_PROTOCOL_BOT) { + out->kind = USB_CONFIG_MSC; + while (ep_off + 2 <= len) { + uint8_t ep_len = desc[ep_off + USB_DESC_OFF_LENGTH]; + uint8_t ep_type = desc[ep_off + USB_DESC_OFF_TYPE]; + if (ep_len == 0) break; + if (ep_type == USB_DESC_TYPE_INTERFACE) break; + if (ep_type == USB_DESC_TYPE_ENDPOINT && ep_off + USB_EP_OFF_MAX_PACKET_SIZE + 1 < len) { + uint8_t ep_addr = desc[ep_off + USB_EP_OFF_ADDRESS]; + uint8_t ep_attr = desc[ep_off + USB_EP_OFF_ATTRIBUTES]; + if ((ep_attr & USB_EP_ATTR_TYPE_MASK) == USB_EP_TYPE_BULK) { + if (ep_addr & USB_EP_ADDR_DIR_MASK) { + out->bulk_in_ep = ep_addr; out->bulk_in_max = le16(desc + ep_off + USB_EP_OFF_MAX_PACKET_SIZE); + } else { + out->bulk_out_ep = ep_addr; out->bulk_out_max = le16(desc + ep_off + USB_EP_OFF_MAX_PACKET_SIZE); + } + } + } + ep_off = (uint16_t)(ep_off + ep_len); + } + } else if (out->iface_class == USB_CLASS_HID && out->iface_subclass == USB_SUBCLASS_HID_BOOT && + out->iface_protocol == USB_PROTOCOL_HID_KEYBOARD) { + out->kind = USB_CONFIG_KBD; + out->hid_iface = desc[off + USB_IFACE_OFF_NUMBER]; + while (ep_off + 2 <= len) { + uint8_t ep_len = desc[ep_off + USB_DESC_OFF_LENGTH]; + uint8_t ep_type = desc[ep_off + USB_DESC_OFF_TYPE]; + if (ep_len == 0) break; + if (ep_type == USB_DESC_TYPE_INTERFACE) break; + if (ep_type == USB_DESC_TYPE_ENDPOINT && ep_off + USB_EP_OFF_INTERVAL < len) { + uint8_t ep_addr = desc[ep_off + USB_EP_OFF_ADDRESS]; + uint8_t ep_attr = desc[ep_off + USB_EP_OFF_ATTRIBUTES]; + if ((ep_attr & USB_EP_ATTR_TYPE_MASK) == USB_EP_TYPE_INTERRUPT && (ep_addr & USB_EP_ADDR_DIR_MASK)) { + out->hid_ep = ep_addr; + out->hid_max = (uint16_t)(le16(desc + ep_off + USB_EP_OFF_MAX_PACKET_SIZE) & 0x7FFu); + out->hid_interval = desc[ep_off + USB_EP_OFF_INTERVAL]; + break; + } + } + ep_off = (uint16_t)(ep_off + ep_len); + } + } else { + out->kind = USB_CONFIG_OTHER; + } + return; /* the first Interface descriptor only */ + } + off = (uint16_t)(off + desc_len); + } +} diff --git a/kernel/src/usb/xhci.c b/kernel/src/usb/xhci.c index f21519df..764e35e3 100644 --- a/kernel/src/usb/xhci.c +++ b/kernel/src/usb/xhci.c @@ -26,6 +26,7 @@ #include "starkernel/xhci.h" #include "starkernel/xhci_driver.h" #include "starkernel/usb_kbd.h" /* A3: a boot keyboard's reports and lights */ +#include "starkernel/usb_config.h" /* the configuration descriptor set, read */ #include "starkernel/console.h" /* A3: "USB keyboard ready" */ #include "starkernel/kmalloc.h" #include "starkernel/timer.h" @@ -2221,140 +2222,64 @@ static void xhci_poll_one(xhci_dev_t *dev) } case XHCI_XFER_CONFIG_DESC_FULL: { log_message(LOG_DEBUG, "xhci: full config descriptor received"); - /* Walk the concatenated descriptor stream - * (Config + Interface + Endpoint descriptors - * back to back) looking for the Interface - * descriptor -- its fixed offset within the - * stream isn't guaranteed, has to be found by - * bDescriptorType, not assumed. */ + /* The walk of the descriptor set is usb_config_parse() (usb_config.c, + * moved out of here unchanged and tested on the host, + * kernel/tests/test_usb_config.c); what it found is acted on here, as + * before. */ + usb_config_t cfg; uint16_t len = xms->config_total_length; if (len > sizeof(xms->config_descriptor)) len = (uint16_t)sizeof(xms->config_descriptor); - uint16_t off = 0; - int found = 0; - while (off + 2 <= len) { - uint8_t desc_len = xms->config_descriptor[off + USB_DESC_OFF_LENGTH]; - uint8_t desc_type = xms->config_descriptor[off + USB_DESC_OFF_TYPE]; - if (desc_len == 0) break; /* malformed -- avoid an infinite loop */ - if (desc_type == USB_DESC_TYPE_INTERFACE && - off + USB_IFACE_OFF_PROTOCOL < len) { - uint8_t iface_class = xms->config_descriptor[off + USB_IFACE_OFF_CLASS]; - uint8_t iface_subclass = xms->config_descriptor[off + USB_IFACE_OFF_SUBCLASS]; - uint8_t iface_protocol = xms->config_descriptor[off + USB_IFACE_OFF_PROTOCOL]; - xhci_log_hex32("xhci: bInterfaceClass=", iface_class); - xhci_log_hex32("xhci: bInterfaceSubClass=", iface_subclass); - xhci_log_hex32("xhci: bInterfaceProtocol=", iface_protocol); - if (iface_class == USB_CLASS_MASS_STORAGE && - iface_subclass == USB_SUBCLASS_SCSI && - iface_protocol == USB_PROTOCOL_BOT) { - log_message(LOG_DEBUG, "xhci: confirmed Mass Storage / SCSI / BOT device"); - /* Walk the Endpoint descriptors that - * follow this Interface descriptor, - * stopping at the next Interface - * descriptor (start of a different - * interface's endpoints) or end of - * the stream. Only bulk endpoints - * are of interest for BOT. */ - uint16_t ep_off = (uint16_t)(off + desc_len); - while (ep_off + 2 <= len) { - uint8_t ep_desc_len = xms->config_descriptor[ep_off + USB_DESC_OFF_LENGTH]; - uint8_t ep_desc_type = xms->config_descriptor[ep_off + USB_DESC_OFF_TYPE]; - if (ep_desc_len == 0) break; /* malformed -- avoid an infinite loop */ - if (ep_desc_type == USB_DESC_TYPE_INTERFACE) break; - if (ep_desc_type == USB_DESC_TYPE_ENDPOINT && - ep_off + USB_EP_OFF_MAX_PACKET_SIZE + 1 < len) { - uint8_t ep_addr = xms->config_descriptor[ep_off + USB_EP_OFF_ADDRESS]; - uint8_t ep_attr = xms->config_descriptor[ep_off + USB_EP_OFF_ATTRIBUTES]; - uint16_t ep_max_packet = (uint16_t)(xms->config_descriptor[ep_off + USB_EP_OFF_MAX_PACKET_SIZE] | - ((uint16_t)xms->config_descriptor[ep_off + USB_EP_OFF_MAX_PACKET_SIZE + 1] << 8)); - if ((ep_attr & USB_EP_ATTR_TYPE_MASK) == USB_EP_TYPE_BULK) { - if (ep_addr & USB_EP_ADDR_DIR_MASK) { - xms->bulk_in_ep_addr = ep_addr; - xms->bulk_in_max_packet = ep_max_packet; - xhci_log_hex32("xhci: bulk IN endpoint=", ep_addr); - } else { - xms->bulk_out_ep_addr = ep_addr; - xms->bulk_out_max_packet = ep_max_packet; - xhci_log_hex32("xhci: bulk OUT endpoint=", ep_addr); - } - } - } - ep_off = (uint16_t)(ep_off + ep_desc_len); - } - /* Deferred (see xhci_dev_t's - * next_action doc comment) rather - * than called directly here -- - * same doorbell-ordering hazard - * as the device/config descriptor - * chaining above. next_action_ - * config_value is staged now but - * not consumed until SET_CONFIG - * actually runs, after Configure - * Endpoint completes below. */ - xms->next_action_config_value = - xms->config_descriptor[USB_CONFIG_OFF_CONFIG_VALUE]; - if (xms->bulk_in_ep_addr == 0 || xms->bulk_out_ep_addr == 0) { - /* Can't Configure Endpoint - * without knowing both bulk - * endpoints -- go straight to - * SET_CONFIGURATION so the - * device is still usable for - * whatever doesn't need BOT - * (nothing, today, but this - * keeps the two concerns - * separate rather than - * failing enumeration - * outright). */ - log_message(LOG_WARN, "xhci: warning -- BOT device missing a bulk IN or OUT endpoint, skipping configure endpoint"); - xms->next_action = XHCI_NEXT_ACTION_SET_CONFIG; - } else { - xms->next_action = XHCI_NEXT_ACTION_CONFIGURE_ENDPOINT; - } - } else if (iface_class == USB_CLASS_HID && - iface_subclass == USB_SUBCLASS_HID_BOOT && - iface_protocol == USB_PROTOCOL_HID_KEYBOARD) { - /* A boot keyboard (2026-10-14, A3): its - * interface number, and the first - * interrupt IN endpoint after it. */ - uint16_t ep_off = (uint16_t)(off + desc_len); - xms->hid_iface = xms->config_descriptor[off + USB_IFACE_OFF_NUMBER]; - while (ep_off + 2 <= len) { - uint8_t ep_desc_len = xms->config_descriptor[ep_off + USB_DESC_OFF_LENGTH]; - uint8_t ep_desc_type = xms->config_descriptor[ep_off + USB_DESC_OFF_TYPE]; - if (ep_desc_len == 0) break; - if (ep_desc_type == USB_DESC_TYPE_INTERFACE) break; - if (ep_desc_type == USB_DESC_TYPE_ENDPOINT && - ep_off + USB_EP_OFF_INTERVAL < len) { - uint8_t ep_addr = xms->config_descriptor[ep_off + USB_EP_OFF_ADDRESS]; - uint8_t ep_attr = xms->config_descriptor[ep_off + USB_EP_OFF_ATTRIBUTES]; - if ((ep_attr & USB_EP_ATTR_TYPE_MASK) == USB_EP_TYPE_INTERRUPT && - (ep_addr & USB_EP_ADDR_DIR_MASK)) { - xms->hid_ep_addr = ep_addr; - xms->hid_max_packet = (uint16_t)(xms->config_descriptor[ep_off + USB_EP_OFF_MAX_PACKET_SIZE] | - ((uint16_t)xms->config_descriptor[ep_off + USB_EP_OFF_MAX_PACKET_SIZE + 1] << 8)) & 0x7FFu; - xms->hid_interval = xms->config_descriptor[ep_off + USB_EP_OFF_INTERVAL]; - break; - } - } - ep_off = (uint16_t)(ep_off + ep_desc_len); - } - xms->next_action_config_value = - xms->config_descriptor[USB_CONFIG_OFF_CONFIG_VALUE]; - if (xms->hid_ep_addr) { - xms->hid_kbd = 1; - xms->next_action = XHCI_NEXT_ACTION_CONFIGURE_ENDPOINT; - } else { - log_message(LOG_WARN, "xhci: keyboard without an interrupt IN endpoint -- not used"); - } - } else { - log_message(LOG_WARN, "xhci: neither a Mass Storage/SCSI/BOT drive nor a boot keyboard -- not used"); - } - found = 1; - break; - } - off = (uint16_t)(off + desc_len); - } - if (!found) { + usb_config_parse(xms->config_descriptor, len, &cfg); + if (cfg.kind == USB_CONFIG_NO_INTERFACE) { log_message(LOG_WARN, "xhci: no Interface descriptor found in config set"); + break; + } + xhci_log_hex32("xhci: bInterfaceClass=", cfg.iface_class); + xhci_log_hex32("xhci: bInterfaceSubClass=", cfg.iface_subclass); + xhci_log_hex32("xhci: bInterfaceProtocol=", cfg.iface_protocol); + if (cfg.kind == USB_CONFIG_MSC) { + log_message(LOG_DEBUG, "xhci: confirmed Mass Storage / SCSI / BOT device"); + if (cfg.bulk_in_ep) { + xms->bulk_in_ep_addr = cfg.bulk_in_ep; + xms->bulk_in_max_packet = cfg.bulk_in_max; + xhci_log_hex32("xhci: bulk IN endpoint=", cfg.bulk_in_ep); + } + if (cfg.bulk_out_ep) { + xms->bulk_out_ep_addr = cfg.bulk_out_ep; + xms->bulk_out_max_packet = cfg.bulk_out_max; + xhci_log_hex32("xhci: bulk OUT endpoint=", cfg.bulk_out_ep); + } + /* Deferred (see xhci_dev_t's next_action doc comment) rather than + * called directly here -- the same doorbell-ordering hazard as the + * device/config descriptor chaining above. */ + xms->next_action_config_value = cfg.config_value; + if (xms->bulk_in_ep_addr == 0 || xms->bulk_out_ep_addr == 0) { + /* Can't Configure Endpoint without both bulk endpoints -- go + * straight to SET_CONFIGURATION, keeping the two concerns + * separate rather than failing enumeration outright. */ + log_message(LOG_WARN, "xhci: warning -- BOT device missing a bulk IN or OUT endpoint, skipping configure endpoint"); + xms->next_action = XHCI_NEXT_ACTION_SET_CONFIG; + } else { + xms->next_action = XHCI_NEXT_ACTION_CONFIGURE_ENDPOINT; + } + } else if (cfg.kind == USB_CONFIG_KBD) { + /* A boot keyboard (2026-10-14, A3): its interface number, and the + * first interrupt IN endpoint after it. */ + xms->hid_iface = cfg.hid_iface; + if (cfg.hid_ep) { + xms->hid_ep_addr = cfg.hid_ep; + xms->hid_max_packet = cfg.hid_max; + xms->hid_interval = cfg.hid_interval; + } + xms->next_action_config_value = cfg.config_value; + if (xms->hid_ep_addr) { + xms->hid_kbd = 1; + xms->next_action = XHCI_NEXT_ACTION_CONFIGURE_ENDPOINT; + } else { + log_message(LOG_WARN, "xhci: keyboard without an interrupt IN endpoint -- not used"); + } + } else { + log_message(LOG_WARN, "xhci: neither a Mass Storage/SCSI/BOT drive nor a boot keyboard -- not used"); } break; } diff --git a/kernel/tests/Makefile b/kernel/tests/Makefile index 9e8ee6af..a15ae74e 100644 --- a/kernel/tests/Makefile +++ b/kernel/tests/Makefile @@ -19,8 +19,9 @@ CC ?= cc CFLAGS := -std=c99 -Wall -Wextra -Werror -O2 -g -I$(HERE) -I$(ROOT)/kernel/include \ -I$(ROOT)/kernel/include/starkernel -I$(ROOT)/v3/include -TESTS := test_gpt +TESTS := test_gpt test_usb_config SRC_test_gpt := $(ROOT)/kernel/src/gpt.c +SRC_test_usb_config := $(ROOT)/kernel/src/usb/usb_config.c define TEST_RULE $(BUILD)/$(1): $(HERE)/$(1).c $(HERE)/fakes.c $(HERE)/fakes.h $(HERE)/harness.h $$(SRC_$(1)) diff --git a/kernel/tests/test_usb_config.c b/kernel/tests/test_usb_config.c new file mode 100644 index 00000000..ebf49f7c --- /dev/null +++ b/kernel/tests/test_usb_config.c @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: LicenseRef-Starship-1.0 + * Copyright (c) 2023-2026 Robert A. James + */ +/* test_usb_config.c -- kernel/src/usb/usb_config.c on the host: the configuration descriptor sets a + * device can send, read as xhci.c's event loop reads them (moved out of it unchanged; plan + * 2026-11-12-kernel-host-tests, increment 2). Sets built byte by byte, as a device sends them. + */ +#include "harness.h" +#include "starkernel/usb_config.h" +#include + +static uint8_t set[256]; +static uint16_t at; + +static void begin(uint8_t config_value) { memset(set, 0, sizeof set); at = 0; + set[at++] = 9; set[at++] = 2; at += 2; set[at++] = 1; set[at++] = config_value; at += 3; } +static void iface(uint8_t number, uint8_t cls, uint8_t sub, uint8_t proto, uint8_t neps) { + set[at++] = 9; set[at++] = 4; set[at++] = number; set[at++] = 0; set[at++] = neps; + set[at++] = cls; set[at++] = sub; set[at++] = proto; set[at++] = 0; } +static void hid_desc(void) { set[at++] = 9; set[at++] = 0x21; at += 7; } +static void endpoint(uint8_t addr, uint8_t attr, uint16_t max, uint8_t interval) { + set[at++] = 7; set[at++] = 5; set[at++] = addr; set[at++] = attr; + set[at++] = (uint8_t)max; set[at++] = (uint8_t)(max >> 8); set[at++] = interval; } +static uint16_t end(void) { set[2] = (uint8_t)at; set[3] = (uint8_t)(at >> 8); return at; } + +int main(void) +{ + usb_config_t c; + uint16_t n; + + /* a USB stick as QEMU's usb-storage presents it: Mass Storage / SCSI / BOT, bulk IN and OUT */ + begin(1); iface(0, 0x08, 0x06, 0x50, 2); endpoint(0x81, 0x02, 512, 0); endpoint(0x02, 0x02, 512, 0); + n = end(); usb_config_parse(set, n, &c); + CHECK(c.kind == USB_CONFIG_MSC && c.bulk_in_ep == 0x81 && c.bulk_out_ep == 0x02 && c.bulk_in_max == 512 && + c.bulk_out_max == 512 && c.config_value == 1, "a BOT stick: MSC, bulk 0x81 and 0x02, 512 each (kind %d, in %#x, out %#x)", + c.kind, c.bulk_in_ep, c.bulk_out_ep); + + /* a boot keyboard: its HID descriptor between the interface and the endpoint */ + begin(1); iface(0, 0x03, 0x01, 0x01, 1); hid_desc(); endpoint(0x81, 0x03, 8, 10); + n = end(); usb_config_parse(set, n, &c); + CHECK(c.kind == USB_CONFIG_KBD && c.hid_iface == 0 && c.hid_ep == 0x81 && c.hid_max == 8 && c.hid_interval == 10, + "a boot keyboard: KBD, interrupt IN 0x81, 8 bytes, interval 10 (kind %d, ep %#x)", c.kind, c.hid_ep); + + /* only the first interface counts: a mouse first, a BOT drive second */ + begin(1); iface(0, 0x03, 0x01, 0x02, 1); endpoint(0x81, 0x03, 4, 10); + iface(1, 0x08, 0x06, 0x50, 2); endpoint(0x82, 0x02, 512, 0); endpoint(0x03, 0x02, 512, 0); + n = end(); usb_config_parse(set, n, &c); + CHECK(c.kind == USB_CONFIG_OTHER && c.bulk_in_ep == 0, "a mouse first, a drive second: OTHER -- the first interface only (kind %d)", c.kind); + + /* a BOT drive with no bulk OUT endpoint */ + begin(2); iface(0, 0x08, 0x06, 0x50, 1); endpoint(0x81, 0x02, 64, 0); + n = end(); usb_config_parse(set, n, &c); + CHECK(c.kind == USB_CONFIG_MSC && c.bulk_in_ep == 0x81 && c.bulk_out_ep == 0 && c.config_value == 2, + "a BOT drive with bulk IN only: MSC, OUT 0 (the caller then skips Configure Endpoint)"); + + /* a descriptor of length 0 ends the walk: no interface found, and no loop */ + begin(1); set[at++] = 0; set[at++] = 4; at += 7; + n = end(); usb_config_parse(set, n, &c); + CHECK(c.kind == USB_CONFIG_NO_INTERFACE, "bLength 0: no interface, the walk ends (kind %d)", c.kind); + + /* an endpoint cut off by len is not read */ + begin(1); iface(0, 0x08, 0x06, 0x50, 2); endpoint(0x81, 0x02, 512, 0); endpoint(0x02, 0x02, 512, 0); + n = end(); usb_config_parse(set, (uint16_t)(n - 3), &c); + CHECK(c.kind == USB_CONFIG_MSC && c.bulk_in_ep == 0x81 && c.bulk_out_ep == 0, + "the last endpoint cut by len: not read (out %#x)", c.bulk_out_ep); + + /* a set too short to hold its interface's protocol byte */ + begin(1); iface(0, 0x08, 0x06, 0x50, 0); + usb_config_parse(set, 16, &c); + CHECK(c.kind == USB_CONFIG_NO_INTERFACE, "an interface cut before its protocol byte: none found (kind %d)", c.kind); + + return DONE(); +}