From 7c2f63de0df7a80015d0c4f084ac66a5956040ba Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Sat, 8 Aug 2026 14:52:17 +0800 Subject: [PATCH] usbhost: Report each device as it is enumerated. A host that enumerates a device says nothing about it unless the whole of CONFIG_DEBUG_USB_INFO is on, and then it says a great deal else besides. The quietest case is the one that matters most: a device no class driver claims produces no output at all, so a user with an unsupported device sees exactly what a user with no device sees. Add CONFIG_USBHOST_ANNOUNCE, reporting each device once, in the shape a reader is likely to recognise from other systems: where it is, what it is, its vendor, product and release, and the maker, product and serial number it reports in its own string descriptors. Those cost a control transfer each, so they are read only where a report was asked for, and only once the device is addressed. The report is made after binding rather than from within it, because a composite device never reaches the class lookup: usbhost_composite() is tried first and binds it. Whether a driver claimed the device is tracked rather than read from the returned status, which the per interface loop sets to OK whatever happened. The port is given as the path from the root hub, and the path names the bus, because a device on the first port of a hub and one on the first port of a controller are otherwise reported identically. struct usbhost_roothubport_s gains that bus number for the purpose; a driver that does not set it reports zero, which is the only bus it has. Class codes are translated where a name is more use than a number, which includes the HID boot protocols, so a keyboard is reported as a keyboard. Default n, so no existing configuration changes. Assisted-by: Claude:claude-opus-5 Signed-off-by: Justin Hammond --- drivers/usbhost/Kconfig | 15 ++ drivers/usbhost/usbhost_enumerate.c | 298 ++++++++++++++++++++++++++++ include/nuttx/usb/usbhost.h | 8 + 3 files changed, 321 insertions(+) diff --git a/drivers/usbhost/Kconfig b/drivers/usbhost/Kconfig index 4d98e382bb67d..d054ccecbd569 100644 --- a/drivers/usbhost/Kconfig +++ b/drivers/usbhost/Kconfig @@ -62,6 +62,21 @@ config USBHOST_CONFIGURATION_SELECTION choose by VID/PID or any other info it can acquire from a USB descriptor. Board file must supply board_usbhost_select_configuration(). +config USBHOST_ANNOUNCE + bool "Report enumerated devices" + default n + ---help--- + Report every device the host enumerates with one syslog line, + naming the hub port it was found on, what it is, its vendor and + product, and whether a class driver claimed it. + + A device that no driver claims is reported too. That is the case + a user most needs to be told about and the one that otherwise + passes in silence. + + The same information is available through CONFIG_DEBUG_USB_INFO, + among a great deal else. This is the one line on its own. + config USBHOST_WAITER bool "USB Host Waiter Support" default n diff --git a/drivers/usbhost/usbhost_enumerate.c b/drivers/usbhost/usbhost_enumerate.c index c66036f2928e2..cd27b9f842798 100644 --- a/drivers/usbhost/usbhost_enumerate.c +++ b/drivers/usbhost/usbhost_enumerate.c @@ -32,12 +32,16 @@ #include #include #include +#include +#include + #include #include #include #include #include +#include #include #include @@ -208,6 +212,258 @@ static inline int usbhost_configdesc(const uint8_t *configdesc, int cfglen, * ****************************************************************************/ +#ifdef CONFIG_USBHOST_ANNOUNCE +/**************************************************************************** + * Name: usbhost_portpath + * + * Description: + * The path to a device, as bus number and the port at each tier below it. + * + ****************************************************************************/ + +static void usbhost_portpath(FAR struct usbhost_hubport_s *hport, + FAR char *path, size_t pathlen) +{ + FAR struct usbhost_hubport_s *root = hport; + uint8_t ports[8]; + size_t used = 0; + size_t n = 0; + size_t i; + + /* Port numbers repeat at every tier, so the path needs all of them. + * Walking up collects them backwards. + */ + + while (n < sizeof(ports) && hport != NULL) + { + ports[n++] = hport->port + 1; + root = hport; + +#ifdef CONFIG_USBHOST_HUB + hport = hport->parent; +#else + break; +#endif + } + + /* The bus first, as the port numbers below it repeat on every other one */ + + used = snprintf(path, pathlen, "%d-", + ((FAR struct usbhost_roothubport_s *)root)->bus); + + for (i = n; i > 0 && used < pathlen - 1; i--) + { + used += snprintf(path + used, pathlen - used, "%s%d", + i == n ? "" : ".", ports[i - 1]); + } +} + +/**************************************************************************** + * Name: usbhost_classname + * + * Description: + * A readable name for a USB class code. Classes a host is unlikely to + * meet are left to be reported by number. + * + ****************************************************************************/ + +static FAR const char *usbhost_classname(FAR const struct usbhost_id_s *id) +{ + switch (id->base) + { + case USB_CLASS_PER_INTERFACE: + return "composite"; + case USB_CLASS_AUDIO: + return "audio"; + case USB_CLASS_CDC: + return "CDC"; + case USB_CLASS_HID: + /* The boot protocol names the device before its report + * descriptor has been read. + */ + + if (id->subclass == USBHID_SUBCLASS_BOOTIF) + { + if (id->proto == USBHID_PROTOCOL_KEYBOARD) + { + return "keyboard"; + } + else if (id->proto == USBHID_PROTOCOL_MOUSE) + { + return "mouse"; + } + } + + return "HID"; + case USB_CLASS_PRINTER: + return "printer"; + case USB_CLASS_MASS_STORAGE: + return "mass storage"; + case USB_CLASS_HUB: + return "hub"; + case USB_CLASS_CDC_DATA: + return "CDC data"; + case USB_CLASS_WIRELESS_CONTROLLER: + return "wireless"; + case USB_CLASS_MISC: + return "misc"; + case USB_CLASS_VENDOR_SPEC: + return "vendor specific"; + default: + return NULL; + } +} + +/**************************************************************************** + * Name: usbhost_getstring + * + * Description: + * Fetch one string descriptor and render it as plain text. + * + * String descriptors are UTF-16. Anything outside ASCII is replaced + * rather than dropped, so the rendered length matches the descriptor. + * Index zero means the device has no such string. + * + ****************************************************************************/ + +static int usbhost_getstring(FAR struct usbhost_hubport_s *hport, + FAR struct usb_ctrlreq_s *ctrlreq, + FAR uint8_t *buffer, uint8_t index, + uint16_t langid, FAR char *out, size_t outlen) +{ + size_t len; + size_t i; + int ret; + + out[0] = '\0'; + + if (index == 0) + { + return -ENOENT; + } + + ctrlreq->type = USB_REQ_DIR_IN | USB_REQ_RECIPIENT_DEVICE; + ctrlreq->req = USB_REQ_GETDESCRIPTOR; + usbhost_putle16(ctrlreq->value, (USB_DESC_TYPE_STRING << 8) | index); + usbhost_putle16(ctrlreq->index, langid); + usbhost_putle16(ctrlreq->len, 255); + + ret = DRVR_CTRLIN(hport->drvr, hport->ep0, ctrlreq, buffer); + if (ret < 0) + { + return ret; + } + + /* buffer[0] is the whole descriptor including its own two byte header, so + * anything shorter than that header is not a descriptor. + */ + + if (buffer[0] < 2) + { + return -EIO; + } + + len = (buffer[0] - 2) / 2; + if (len > outlen - 1) + { + len = outlen - 1; + } + + for (i = 0; i < len; i++) + { + uint16_t ch = buffer[2 + i * 2] | (buffer[3 + i * 2] << 8); + + out[i] = (ch >= 0x20 && ch < 0x7f) ? (char)ch : '?'; + } + + while (len > 0 && out[len - 1] == ' ') + { + len--; + } + + out[len] = '\0'; + return OK; +} + +/**************************************************************************** + * Name: usbhost_announce + * + * Description: + * Report one enumerated device. + * + * Called once per device, after binding, because the binding paths are + * alternatives: a composite device never reaches the class lookup. + * + ****************************************************************************/ + +static void usbhost_announce(FAR struct usbhost_hubport_s *hport, + FAR const struct usbhost_id_s *id, bool bound, + FAR struct usb_ctrlreq_s *ctrlreq, + FAR uint8_t *buffer, uint16_t bcd, + uint8_t imfgr, uint8_t iprod, uint8_t iserno) +{ + FAR const char *name = usbhost_classname(id); + char unnamed[16]; + char path[24]; + + if (name == NULL) + { + snprintf(unnamed, sizeof(unnamed), "class %d", id->base); + name = unnamed; + } + + /* Reported whether or not anything drives it; an unclaimed device is + * the case worth knowing about. + */ + + usbhost_portpath(hport, path, sizeof(path)); + + syslog(LOG_INFO, + "usb %s: new device, idVendor=%04x, idProduct=%04x, " + "bcdDevice=%d.%02d\n", + path, id->vid, id->pid, bcd >> 8, bcd & 0xff); + syslog(LOG_INFO, "usb %s: %s, %s\n", + path, name, bound ? "driver attached" : "no driver"); + + /* A control transfer each, so read only when a report was asked for. */ + + if (ctrlreq != NULL) + { + char str[40]; + uint16_t langid = 0x0409; + + /* String zero is the list of languages the device has. Ask for the + * first rather than assuming, since a device with none has nothing + * to report either. + */ + + if (usbhost_getstring(hport, ctrlreq, buffer, 0, 0, + str, sizeof(str)) >= 0 && buffer[0] >= 4) + { + langid = buffer[2] | (buffer[3] << 8); + } + + if (usbhost_getstring(hport, ctrlreq, buffer, iprod, langid, + str, sizeof(str)) >= 0) + { + syslog(LOG_INFO, "usb %s: Product: %s\n", path, str); + } + + if (usbhost_getstring(hport, ctrlreq, buffer, imfgr, langid, + str, sizeof(str)) >= 0) + { + syslog(LOG_INFO, "usb %s: Manufacturer: %s\n", path, str); + } + + if (usbhost_getstring(hport, ctrlreq, buffer, iserno, langid, + str, sizeof(str)) >= 0) + { + syslog(LOG_INFO, "usb %s: SerialNumber: %s\n", path, str); + } + } +} +#endif + static inline int usbhost_classbind(FAR struct usbhost_hubport_s *hport, const uint8_t *configdesc, int desclen, struct usbhost_id_s *id, @@ -302,6 +558,13 @@ int usbhost_enumerate(FAR struct usbhost_hubport_s *hport, uint8_t cfgidx = 0; FAR uint8_t *buffer = NULL; int ret; +#ifdef CONFIG_USBHOST_ANNOUNCE + bool bound = false; + uint16_t bcd = 0; + uint8_t imfgr = 0; + uint8_t iprod = 0; + uint8_t iserno = 0; +#endif DEBUGASSERT(hport != NULL && hport->drvr != NULL); @@ -454,6 +717,17 @@ int usbhost_enumerate(FAR struct usbhost_hubport_s *hport, DEBUGASSERT(hport->funcaddr == 0 && funcaddr != 0); hport->funcaddr = funcaddr; +#ifdef CONFIG_USBHOST_ANNOUNCE + /* Keep what is needed to describe the device before the buffer holding + * its descriptor is reused for the configuration descriptor. + */ + + bcd = usbhost_getle16(((FAR struct usb_devdesc_s *)buffer)->device); + imfgr = ((FAR struct usb_devdesc_s *)buffer)->imfgr; + iprod = ((FAR struct usb_devdesc_s *)buffer)->iproduct; + iserno = ((FAR struct usb_devdesc_s *)buffer)->serno; +#endif + /* And reconfigure EP0 with the correct address */ DRVR_EP0CONFIGURE(hport->drvr, hport->ep0, hport->funcaddr, @@ -564,6 +838,12 @@ int usbhost_enumerate(FAR struct usbhost_hubport_s *hport, { uerr("ERROR: usbhost_classbind failed %d\n", ret); } +#ifdef CONFIG_USBHOST_ANNOUNCE + else + { + bound = true; + } +#endif ret = OK; } @@ -585,6 +865,9 @@ int usbhost_enumerate(FAR struct usbhost_hubport_s *hport, if (ret >= 0) { uinfo("usbhost_composite has bound the composite device\n"); +#ifdef CONFIG_USBHOST_ANNOUNCE + bound = true; +#endif } /* Apparently this is not a composite device */ @@ -603,9 +886,24 @@ int usbhost_enumerate(FAR struct usbhost_hubport_s *hport, { uerr("ERROR: usbhost_classbind failed %d\n", ret); } +#ifdef CONFIG_USBHOST_ANNOUNCE + else + { + bound = true; + } +#endif } } + /* Report the device now that every path has had its turn at binding one, + * and now that the descriptor buffer is free to be used for the strings. + */ + +#ifdef CONFIG_USBHOST_ANNOUNCE + usbhost_announce(hport, &id, bound, ctrlreq, buffer, bcd, + imfgr, iprod, iserno); +#endif + errout: if (ret < 0) { diff --git a/include/nuttx/usb/usbhost.h b/include/nuttx/usb/usbhost.h index 69f7aacd8b8c4..3a43bb3a4aeb5 100644 --- a/include/nuttx/usb/usbhost.h +++ b/include/nuttx/usb/usbhost.h @@ -736,6 +736,14 @@ struct usbhost_roothubport_s struct usbhost_hubport_s hport; /* Common hub port definitions */ FAR struct usbhost_devaddr_s *pdevgen; /* Address generation data pointer */ + + /* The host controller this root port belongs to. Each controller numbers + * its own ports from the same start, so a port number alone does not + * identify a port on a system with more than one. Zero is the only bus a + * driver that does not set it has. + */ + + uint8_t bus; }; /* struct usbhost_class_s provides access from the USB host driver to the