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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017RzZgrf7V7Zk2iczBXDRbm
This commit is contained in:
@@ -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 <stdint.h>
|
||||
|
||||
#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
|
||||
@@ -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 <string.h>
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
+56
-131
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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 <string.h>
|
||||
|
||||
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();
|
||||
}
|
||||
Reference in New Issue
Block a user