mirror of
https://github.com/apache/nuttx.git
synced 2026-08-20 13:08:26 +00:00
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 <justin@dynam.ac>
929 lines
28 KiB
C
929 lines
28 KiB
C
/****************************************************************************
|
|
* drivers/usbhost/usbhost_enumerate.c
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <syslog.h>
|
|
|
|
#include <nuttx/debug.h>
|
|
|
|
#include <nuttx/arch.h>
|
|
#include <nuttx/signal.h>
|
|
#include <nuttx/usb/usb.h>
|
|
#include <nuttx/usb/usbhost.h>
|
|
#include <nuttx/usb/hid.h>
|
|
#include <nuttx/usb/hub.h>
|
|
#include <nuttx/usb/usbhost_devaddr.h>
|
|
|
|
#include "usbhost_composite.h"
|
|
|
|
#ifdef CONFIG_USBHOST_CONFIGURATION_SELECTION
|
|
# include <nuttx/board.h>
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
static inline uint16_t usbhost_getle16(const uint8_t *val);
|
|
static void usbhost_putle16(uint8_t *dest, uint16_t val);
|
|
|
|
static inline int usbhost_devdesc(const struct usb_devdesc_s *devdesc,
|
|
FAR struct usbhost_id_s *id);
|
|
static inline int usbhost_configdesc(const uint8_t *configdesc, int desclen,
|
|
uint8_t start_ifnum,
|
|
uint8_t *ret_ifnum,
|
|
struct usbhost_id_s *id);
|
|
static inline int usbhost_classbind(FAR struct usbhost_hubport_s *hport,
|
|
FAR const uint8_t *configdesc, int desclen,
|
|
FAR struct usbhost_id_s *id,
|
|
FAR struct usbhost_class_s **devclass);
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: usbhost_getle16
|
|
*
|
|
* Description:
|
|
* Get a (possibly unaligned) 16-bit little endian value.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline uint16_t usbhost_getle16(const uint8_t *val)
|
|
{
|
|
return (uint16_t)val[1] << 8 | (uint16_t)val[0];
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbhost_putle16
|
|
*
|
|
* Description:
|
|
* Put a (possibly unaligned) 16-bit little endian value.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void usbhost_putle16(uint8_t *dest, uint16_t val)
|
|
{
|
|
dest[0] = val & 0xff; /* Little endian means LS byte first in byte stream */
|
|
dest[1] = val >> 8;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbhost_devdesc
|
|
*
|
|
* Description:
|
|
* A configuration descriptor has been obtained from the device. Find the
|
|
* ID information for the class that supports this device.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline int usbhost_devdesc(FAR const struct usb_devdesc_s *devdesc,
|
|
FAR struct usbhost_id_s *id)
|
|
{
|
|
/* Clear the ID info */
|
|
|
|
memset(id, 0, sizeof(struct usbhost_id_s));
|
|
|
|
/* Pick off the class ID info */
|
|
|
|
id->base = devdesc->classid;
|
|
id->subclass = devdesc->subclass;
|
|
id->proto = devdesc->protocol;
|
|
|
|
/* Pick off the VID and PID as well (for vendor specific devices) */
|
|
|
|
id->vid = usbhost_getle16(devdesc->vendor);
|
|
id->pid = usbhost_getle16(devdesc->product);
|
|
|
|
uinfo("class:%d subclass:%d protocol:%d vid:%04x pid:%04x\n",
|
|
id->base, id->subclass, id->proto, id->vid, id->pid);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbhost_configdesc
|
|
*
|
|
* Description:
|
|
* A configuration descriptor has been obtained from the device. Find the
|
|
* ID information for the class that supports this device.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static inline int usbhost_configdesc(const uint8_t *configdesc, int cfglen,
|
|
uint8_t start_ifnum,
|
|
uint8_t *ret_ifnum,
|
|
struct usbhost_id_s *id)
|
|
{
|
|
FAR struct usb_cfgdesc_s *cfgdesc;
|
|
FAR struct usb_ifdesc_s *ifdesc;
|
|
int remaining;
|
|
|
|
DEBUGASSERT(configdesc != NULL && cfglen >= USB_SIZEOF_CFGDESC);
|
|
|
|
/* Verify that we were passed a configuration descriptor */
|
|
|
|
cfgdesc = (struct usb_cfgdesc_s *)configdesc;
|
|
uinfo("cfg len:%d total len:%d\n", cfgdesc->len, cfglen);
|
|
|
|
if (cfgdesc->type != USB_DESC_TYPE_CONFIG)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Skip to the next entry descriptor */
|
|
|
|
configdesc += cfgdesc->len;
|
|
remaining = cfglen - cfgdesc->len;
|
|
|
|
/* Loop while there are more descriptors to examine */
|
|
|
|
memset(id, 0, sizeof(FAR struct usb_desc_s));
|
|
while (remaining >= sizeof(struct usb_desc_s))
|
|
{
|
|
/* What is the next descriptor? Is it an interface descriptor? */
|
|
|
|
ifdesc = (struct usb_ifdesc_s *)configdesc;
|
|
if (ifdesc->type == USB_DESC_TYPE_INTERFACE &&
|
|
ifdesc->ifno >= start_ifnum)
|
|
{
|
|
/* Yes, extract the class information from the interface
|
|
* descriptor.
|
|
* Typically these values are zero meaning that the "real" ID
|
|
* information resides in the device descriptor.
|
|
*/
|
|
|
|
DEBUGASSERT(remaining >= sizeof(struct usb_ifdesc_s));
|
|
id->base = ifdesc->classid;
|
|
id->subclass = ifdesc->subclass;
|
|
id->proto = ifdesc->protocol;
|
|
uinfo("class:%d subclass:%d protocol:%d\n",
|
|
id->base, id->subclass, id->proto);
|
|
*ret_ifnum = ifdesc->ifno;
|
|
return OK;
|
|
}
|
|
|
|
/* Increment the address of the next descriptor */
|
|
|
|
configdesc += ifdesc->len;
|
|
remaining -= ifdesc->len;
|
|
}
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: usbhost_classbind
|
|
*
|
|
* Description:
|
|
* A configuration descriptor has been obtained from the device. Try to
|
|
* bind this configuration descriptor with a supported class.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#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,
|
|
FAR struct usbhost_class_s **usbclass)
|
|
{
|
|
FAR struct usbhost_class_s *devclass;
|
|
FAR const struct usbhost_registry_s *reg;
|
|
int ret = -EINVAL;
|
|
|
|
/* Is there is a class implementation registered to support this device. */
|
|
|
|
reg = usbhost_findclass(id);
|
|
uinfo("usbhost_findclass: %p\n", reg);
|
|
if (reg != NULL)
|
|
{
|
|
/* Yes.. there is a class for this device. Get an instance of
|
|
* its interface.
|
|
*/
|
|
|
|
ret = -ENOMEM;
|
|
devclass = CLASS_CREATE(reg, hport, id);
|
|
uinfo("CLASS_CREATE: %p\n", devclass);
|
|
if (devclass != NULL)
|
|
{
|
|
/* Then bind the newly instantiated class instance */
|
|
|
|
ret = CLASS_CONNECT(devclass, configdesc, desclen);
|
|
if (ret < 0)
|
|
{
|
|
/* On failures, call the class disconnect method which
|
|
* should then free the allocated devclass instance.
|
|
*/
|
|
|
|
uerr("ERROR: CLASS_CONNECT failed: %d\n", ret);
|
|
CLASS_DISCONNECTED(devclass);
|
|
}
|
|
else
|
|
{
|
|
*usbclass = devclass;
|
|
}
|
|
}
|
|
}
|
|
|
|
uinfo("Returning: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: usbhost_enumerate
|
|
*
|
|
* Description:
|
|
* Enumerate the connected device. As part of this enumeration process,
|
|
* the driver will (1) get the device's configuration descriptor, (2)
|
|
* extract the class ID info from the configuration descriptor, (3) call
|
|
* usbhost_findclass() to find the class that supports this device, (4)
|
|
* call the create() method on the struct usbhost_registry_s interface
|
|
* to get a class instance, and finally (5) call the configdesc() method
|
|
* of the struct usbhost_class_s interface. After that, the class is in
|
|
* charge of the sequence of operations.
|
|
*
|
|
* Input Parameters:
|
|
* hport - The hub port that manages the new class.
|
|
* devclass - If the class driver for the device is successful located
|
|
* and bound to the hub port, the allocated class instance is returned
|
|
* into this caller-provided memory location.
|
|
*
|
|
* Returned Value:
|
|
* On success, zero (OK) is returned. On a failure, a negated errno value
|
|
* is returned indicating the nature of the failure
|
|
*
|
|
* Assumptions:
|
|
* - Only a single class bound to a single device is supported.
|
|
* - Called from a single thread so no mutual exclusion is required.
|
|
* - Never called from an interrupt handler.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int usbhost_enumerate(FAR struct usbhost_hubport_s *hport,
|
|
FAR struct usbhost_class_s **devclass)
|
|
{
|
|
FAR struct usb_ctrlreq_s *ctrlreq = NULL;
|
|
struct usbhost_id_s id;
|
|
size_t maxlen;
|
|
unsigned int cfglen;
|
|
uint16_t maxpacketsize;
|
|
uint8_t descsize;
|
|
uint8_t funcaddr = 0;
|
|
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);
|
|
|
|
/* Allocate descriptor buffers for use in this function. We will need two:
|
|
* One for the request and one for the data buffer.
|
|
*/
|
|
|
|
ret = DRVR_ALLOC(hport->drvr, (FAR uint8_t **)&ctrlreq, &maxlen);
|
|
if (ret < 0)
|
|
{
|
|
uerr("ERROR: DRVR_ALLOC failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = DRVR_ALLOC(hport->drvr, &buffer, &maxlen);
|
|
if (ret < 0)
|
|
{
|
|
uerr("ERROR: DRVR_ALLOC failed: %d\n", ret);
|
|
goto errout;
|
|
}
|
|
|
|
/* Pick an appropriate packet size for this device
|
|
*
|
|
* USB 2.0, Paragraph 5.5.3 "Control Transfer Packet Size Constraints"
|
|
*
|
|
* "An endpoint for control transfers specifies the maximum data
|
|
* payload size that the endpoint can accept from or transmit to
|
|
* the bus. The allowable maximum control transfer data payload
|
|
* sizes for full-speed devices is 8, 16, 32, or 64 bytes; for
|
|
* high-speed devices, it is 64 bytes and for low-speed devices,
|
|
* it is 8 bytes. This maximum applies to the data payloads of the
|
|
* Data packets following a Setup..."
|
|
*/
|
|
|
|
if (hport->speed >= USB_SPEED_SUPER)
|
|
{
|
|
/* For super-speed, we must use 512 bytes */
|
|
|
|
maxpacketsize = 512;
|
|
descsize = USB_SIZEOF_DEVDESC;
|
|
}
|
|
else if (hport->speed == USB_SPEED_HIGH)
|
|
{
|
|
/* For high-speed, we must use 64 bytes */
|
|
|
|
maxpacketsize = 64;
|
|
descsize = USB_SIZEOF_DEVDESC;
|
|
}
|
|
else
|
|
{
|
|
/* Eight will work for both low- and full-speed */
|
|
|
|
maxpacketsize = 8;
|
|
descsize = 8;
|
|
}
|
|
|
|
/* Configure EP0 with the initial maximum packet size */
|
|
|
|
DRVR_EP0CONFIGURE(hport->drvr, hport->ep0, 0, hport->speed,
|
|
maxpacketsize);
|
|
|
|
/* Read first bytes of the device descriptor */
|
|
|
|
ctrlreq->type = USB_REQ_DIR_IN | USB_REQ_RECIPIENT_DEVICE;
|
|
ctrlreq->req = USB_REQ_GETDESCRIPTOR;
|
|
usbhost_putle16(ctrlreq->value, (USB_DESC_TYPE_DEVICE << 8));
|
|
usbhost_putle16(ctrlreq->index, 0);
|
|
usbhost_putle16(ctrlreq->len, descsize);
|
|
|
|
ret = DRVR_CTRLIN(hport->drvr, hport->ep0, ctrlreq, buffer);
|
|
if (ret < 0)
|
|
{
|
|
uerr("ERROR: Failed to get device descriptor, length=%d: %d\n",
|
|
descsize, ret);
|
|
goto errout;
|
|
}
|
|
|
|
/* Extract the correct max packetsize from the device descriptor */
|
|
|
|
maxpacketsize = ((struct usb_devdesc_s *)buffer)->mxpacketsize;
|
|
if (hport->speed >= USB_SPEED_SUPER && maxpacketsize == 9)
|
|
{
|
|
/* For super-speed, we must use 512 bytes */
|
|
|
|
maxpacketsize = 512;
|
|
}
|
|
|
|
uinfo("maxpacksetsize: %d speed: %d\n", maxpacketsize, hport->speed);
|
|
|
|
/* And reconfigure EP0 with the correct maximum packet size */
|
|
|
|
DRVR_EP0CONFIGURE(hport->drvr, hport->ep0, 0, hport->speed,
|
|
maxpacketsize);
|
|
|
|
/* Now read the full device descriptor (if we have not already done so) */
|
|
|
|
if (descsize < USB_SIZEOF_DEVDESC)
|
|
{
|
|
ctrlreq->type = USB_REQ_DIR_IN | USB_REQ_RECIPIENT_DEVICE;
|
|
ctrlreq->req = USB_REQ_GETDESCRIPTOR;
|
|
usbhost_putle16(ctrlreq->value, (USB_DESC_TYPE_DEVICE << 8));
|
|
usbhost_putle16(ctrlreq->index, 0);
|
|
usbhost_putle16(ctrlreq->len, USB_SIZEOF_DEVDESC);
|
|
|
|
ret = DRVR_CTRLIN(hport->drvr, hport->ep0, ctrlreq, buffer);
|
|
if (ret < 0)
|
|
{
|
|
uerr("ERROR: Failed to get device descriptor, length=%d: %d\n",
|
|
USB_SIZEOF_DEVDESC, ret);
|
|
goto errout;
|
|
}
|
|
}
|
|
|
|
/* Get class identification information from the device descriptor. Most
|
|
* devices set this to USB_CLASS_PER_INTERFACE (zero) and provide the
|
|
* identification information in the interface descriptor(s). That allows
|
|
* a device to support multiple, different classes.
|
|
*/
|
|
|
|
usbhost_devdesc((struct usb_devdesc_s *)buffer, &id);
|
|
|
|
/* Assign a function address to the device connected to this port */
|
|
|
|
funcaddr = usbhost_devaddr_create(hport);
|
|
if (funcaddr < 0)
|
|
{
|
|
uerr("ERROR: usbhost_devaddr_create failed: %d\n", ret);
|
|
goto errout;
|
|
}
|
|
|
|
/* Set the USB device address */
|
|
|
|
ctrlreq->type = USB_REQ_DIR_OUT | USB_REQ_RECIPIENT_DEVICE;
|
|
ctrlreq->req = USB_REQ_SETADDRESS;
|
|
usbhost_putle16(ctrlreq->value, (uint16_t)funcaddr);
|
|
usbhost_putle16(ctrlreq->index, 0);
|
|
usbhost_putle16(ctrlreq->len, 0);
|
|
|
|
ret = DRVR_CTRLOUT(hport->drvr, hport->ep0, ctrlreq, NULL);
|
|
if (ret < 0)
|
|
{
|
|
uerr("ERROR: Failed to set address: %d\n", ret);
|
|
goto errout;
|
|
}
|
|
|
|
nxsched_usleep(2 * 1000);
|
|
|
|
/* Assign the function address to the port */
|
|
|
|
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,
|
|
hport->speed, maxpacketsize);
|
|
|
|
#ifdef CONFIG_USBHOST_CONFIGURATION_SELECTION
|
|
/* Board specific callback to choose which configuration to use for
|
|
* this device. Supplied by board file
|
|
*/
|
|
|
|
cfgidx = board_usbhost_select_configuration(hport,
|
|
(struct usb_devdesc_s *)buffer, &id);
|
|
#endif
|
|
|
|
/* Get the configuration descriptor */
|
|
|
|
ctrlreq->type = USB_REQ_DIR_IN | USB_REQ_RECIPIENT_DEVICE;
|
|
ctrlreq->req = USB_REQ_GETDESCRIPTOR;
|
|
usbhost_putle16(ctrlreq->value, (USB_DESC_TYPE_CONFIG << 8) | cfgidx);
|
|
usbhost_putle16(ctrlreq->index, 0);
|
|
usbhost_putle16(ctrlreq->len, USB_SIZEOF_CFGDESC);
|
|
|
|
ret = DRVR_CTRLIN(hport->drvr, hport->ep0, ctrlreq, buffer);
|
|
if (ret < 0)
|
|
{
|
|
uerr("ERROR: Failed to get configuration descriptor, length=%d: %d\n",
|
|
USB_SIZEOF_CFGDESC, ret);
|
|
goto errout;
|
|
}
|
|
|
|
/* Extract the full size of the configuration data */
|
|
|
|
cfglen = (unsigned int)
|
|
usbhost_getle16(((struct usb_cfgdesc_s *)buffer)->totallen);
|
|
uinfo("sizeof config data: %d\n", cfglen);
|
|
|
|
if (cfglen > maxlen)
|
|
{
|
|
uerr("ERROR: Configuration doesn't fit in buffer, "
|
|
"length=%d, maxlen=%zu\n",
|
|
cfglen, maxlen);
|
|
ret = -E2BIG;
|
|
goto errout;
|
|
}
|
|
|
|
/* Get all of the configuration descriptor data */
|
|
|
|
ctrlreq->type = USB_REQ_DIR_IN | USB_REQ_RECIPIENT_DEVICE;
|
|
ctrlreq->req = USB_REQ_GETDESCRIPTOR;
|
|
usbhost_putle16(ctrlreq->value, (USB_DESC_TYPE_CONFIG << 8) | cfgidx);
|
|
usbhost_putle16(ctrlreq->index, 0);
|
|
usbhost_putle16(ctrlreq->len, cfglen);
|
|
|
|
ret = DRVR_CTRLIN(hport->drvr, hport->ep0, ctrlreq, buffer);
|
|
if (ret < 0)
|
|
{
|
|
uerr("ERROR: Failed to get configuration descriptor, length=%d: %d\n",
|
|
cfglen, ret);
|
|
goto errout;
|
|
}
|
|
|
|
/* Select this configuration using its bConfigurationValue */
|
|
|
|
ctrlreq->type = USB_REQ_DIR_OUT | USB_REQ_RECIPIENT_DEVICE;
|
|
ctrlreq->req = USB_REQ_SETCONFIGURATION;
|
|
usbhost_putle16(ctrlreq->value,
|
|
((struct usb_cfgdesc_s *)buffer)->cfgvalue);
|
|
usbhost_putle16(ctrlreq->index, 0);
|
|
usbhost_putle16(ctrlreq->len, 0);
|
|
|
|
ret = DRVR_CTRLOUT(hport->drvr, hport->ep0, ctrlreq, NULL);
|
|
if (ret < 0)
|
|
{
|
|
uerr("ERROR: Failed to set configuration: %d\n", ret);
|
|
goto errout;
|
|
}
|
|
|
|
/* Some devices may require some delay before initialization */
|
|
|
|
nxsched_usleep(100 * 1000);
|
|
|
|
/* Was the class identification information provided in the device
|
|
* descriptor? Or do we need to find it in the interface descriptor(s)?
|
|
*/
|
|
|
|
if (id.base == USB_CLASS_PER_INTERFACE)
|
|
{
|
|
/* Get the class identification information for this device from the
|
|
* interface descriptor(s). Hmmm.. More logic is need to handle the
|
|
* case of multiple interface descriptors.
|
|
*/
|
|
|
|
uint8_t ninterfaces = ((struct usb_cfgdesc_s *)buffer)->ninterfaces;
|
|
uint8_t ifnum = 0;
|
|
|
|
for (ifnum = 0; ifnum < ninterfaces; ifnum++)
|
|
{
|
|
uinfo("Parsing interface: %d\n", ifnum);
|
|
ret = usbhost_configdesc(buffer, cfglen, ifnum, &ifnum, &id);
|
|
if (ret < 0)
|
|
{
|
|
uerr("ERROR: usbhost_configdesc failed: %d\n", ret);
|
|
goto errout;
|
|
}
|
|
|
|
ret = usbhost_classbind(hport, buffer, cfglen, &id, devclass);
|
|
if (ret < 0)
|
|
{
|
|
uerr("ERROR: usbhost_classbind failed %d\n", ret);
|
|
}
|
|
#ifdef CONFIG_USBHOST_ANNOUNCE
|
|
else
|
|
{
|
|
bound = true;
|
|
}
|
|
#endif
|
|
|
|
ret = OK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#ifdef CONFIG_USBHOST_COMPOSITE
|
|
/* Check if the device attached to the downstream port if a USB
|
|
* composite device and, if so, create the composite device wrapper
|
|
* and bind it to the HCD.
|
|
*
|
|
* usbhost_composite() will return a negated errno value is on any
|
|
* failure. The value -ENOENT, in particular means that the attached
|
|
* device is not a composite device. Other values would indicate other
|
|
* various, unexpected failures. We make no real distinction here.
|
|
*/
|
|
|
|
ret = usbhost_composite(hport, buffer, cfglen, &id, devclass);
|
|
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 */
|
|
|
|
else
|
|
#endif
|
|
{
|
|
/* Parse the configuration descriptor and bind to the class
|
|
* instance for the device. This needs to be the last thing
|
|
* done because the class driver will begin configuring the
|
|
* device.
|
|
*/
|
|
|
|
ret = usbhost_classbind(hport, buffer, cfglen, &id, devclass);
|
|
if (ret < 0)
|
|
{
|
|
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)
|
|
{
|
|
/* Release the device function address on any failure */
|
|
|
|
usbhost_devaddr_destroy(hport, funcaddr);
|
|
hport->funcaddr = 0;
|
|
}
|
|
|
|
/* Release temporary buffers in any event */
|
|
|
|
if (buffer != NULL)
|
|
{
|
|
DRVR_FREE(hport->drvr, buffer);
|
|
}
|
|
|
|
if (ctrlreq)
|
|
{
|
|
DRVR_FREE(hport->drvr, (FAR uint8_t *)ctrlreq);
|
|
}
|
|
|
|
return ret;
|
|
}
|