The device command is broken due to the overhaul of how USB devices are handled in the host stack. Add a device address argument to all device commands and obtain a pointer to the USB device during command execution. Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
531 lines
12 KiB
C
531 lines
12 KiB
C
/*
|
|
* Copyright (c) 2023,2025 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/usb/usbh.h>
|
|
#include <zephyr/sys/byteorder.h>
|
|
|
|
#include "usbh_device.h"
|
|
#include "usbh_ch9.h"
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(usbh_dev, CONFIG_USBH_LOG_LEVEL);
|
|
|
|
K_MEM_SLAB_DEFINE_STATIC(usb_device_slab, sizeof(struct usb_device),
|
|
CONFIG_USBH_USB_DEVICE_MAX, sizeof(void *));
|
|
|
|
K_HEAP_DEFINE(usb_device_heap, CONFIG_USBH_USB_DEVICE_HEAP);
|
|
|
|
struct usb_device *usbh_device_alloc(struct usbh_contex *const uhs_ctx)
|
|
{
|
|
struct usb_device *udev;
|
|
|
|
if (k_mem_slab_alloc(&usb_device_slab, (void **)&udev, K_NO_WAIT)) {
|
|
LOG_ERR("Failed to allocate USB device memory");
|
|
return NULL;
|
|
}
|
|
|
|
memset(udev, 0, sizeof(struct usb_device));
|
|
udev->ctx = uhs_ctx;
|
|
sys_dlist_append(&uhs_ctx->udevs, &udev->node);
|
|
k_mutex_init(&udev->mutex);
|
|
|
|
return udev;
|
|
}
|
|
|
|
void usbh_device_free(struct usb_device *const udev)
|
|
{
|
|
struct usbh_contex *const uhs_ctx = udev->ctx;
|
|
|
|
sys_bitarray_clear_bit(uhs_ctx->addr_ba, udev->addr);
|
|
sys_dlist_remove(&udev->node);
|
|
if (udev->cfg_desc != NULL) {
|
|
k_heap_free(&usb_device_heap, udev->cfg_desc);
|
|
}
|
|
|
|
k_mem_slab_free(&usb_device_slab, (void *)udev);
|
|
}
|
|
|
|
struct usb_device *usbh_device_get_any(struct usbh_contex *const uhs_ctx)
|
|
{
|
|
sys_dnode_t *const node = sys_dlist_peek_head(&uhs_ctx->udevs);
|
|
struct usb_device *udev;
|
|
|
|
udev = SYS_DLIST_CONTAINER(node, udev, node);
|
|
|
|
return udev;
|
|
}
|
|
|
|
struct usb_device *usbh_device_get(struct usbh_contex *const uhs_ctx, const uint8_t addr)
|
|
{
|
|
struct usb_device *udev;
|
|
|
|
SYS_DLIST_FOR_EACH_CONTAINER(&uhs_ctx->udevs, udev, node) {
|
|
if (addr == udev->addr) {
|
|
return udev;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int validate_device_mps0(const struct usb_device *const udev)
|
|
{
|
|
const uint8_t mps0 = udev->dev_desc.bMaxPacketSize0;
|
|
|
|
if (udev->speed == USB_SPEED_SPEED_SS || udev->speed == USB_SPEED_SPEED_LS) {
|
|
LOG_ERR("USB device speed not supported");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if (udev->speed == USB_SPEED_SPEED_HS) {
|
|
if (mps0 != 64) {
|
|
LOG_ERR("HS device has wrong bMaxPacketSize0 %u", mps0);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
if (udev->speed == USB_SPEED_SPEED_FS) {
|
|
if (mps0 != 8 && mps0 != 16 && mps0 != 32 && mps0 != 64) {
|
|
LOG_ERR("FS device has wrong bMaxPacketSize0 %u", mps0);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int alloc_device_address(struct usb_device *const udev, uint8_t *const addr)
|
|
{
|
|
struct usbh_contex *const uhs_ctx = udev->ctx;
|
|
int val;
|
|
int err;
|
|
|
|
for (unsigned int i = 1; i < 128; i++) {
|
|
err = sys_bitarray_test_and_set_bit(uhs_ctx->addr_ba, i, &val);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
if (val == 0) {
|
|
*addr = i;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
enum ep_op {
|
|
EP_OP_TEST, /* Verify endpont descriptor */
|
|
EP_OP_UP, /* Enable endpoint and update endpoint pointers */
|
|
EP_OP_DOWN, /* Disable endpoint and update endpoint pointers */
|
|
};
|
|
|
|
static void assign_ep_desc_ptr(struct usb_device *const udev,
|
|
const uint8_t ep, void *const ptr)
|
|
{
|
|
uint8_t idx = USB_EP_GET_IDX(ep) & 0xF;
|
|
|
|
if (USB_EP_DIR_IS_IN(ep)) {
|
|
udev->ep_in[idx].desc = ptr;
|
|
} else {
|
|
udev->ep_out[idx].desc = ptr;
|
|
}
|
|
}
|
|
|
|
static int handle_ep_op(struct usb_device *const udev,
|
|
const enum ep_op op, const uint8_t ep,
|
|
struct usb_ep_descriptor *const ep_desc)
|
|
{
|
|
switch (op) {
|
|
case EP_OP_TEST:
|
|
break;
|
|
case EP_OP_UP:
|
|
if (ep_desc == NULL) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
assign_ep_desc_ptr(udev, ep_desc->bEndpointAddress, ep_desc);
|
|
break;
|
|
case EP_OP_DOWN:
|
|
assign_ep_desc_ptr(udev, ep, NULL);
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int device_interface_modify(struct usb_device *const udev,
|
|
const enum ep_op op,
|
|
const uint8_t iface, const uint8_t alt)
|
|
{
|
|
struct usb_cfg_descriptor *cfg_desc = udev->cfg_desc;
|
|
struct usb_if_descriptor *if_desc = NULL;
|
|
struct usb_ep_descriptor *ep_desc;
|
|
struct usb_desc_header *dhp;
|
|
bool found_iface = false;
|
|
void *desc_end;
|
|
int err;
|
|
|
|
dhp = udev->ifaces[iface].dhp;
|
|
desc_end = (void *)((uint8_t *)udev->cfg_desc + cfg_desc->wTotalLength);
|
|
|
|
while (dhp != NULL && (void *)dhp < desc_end) {
|
|
if (dhp->bDescriptorType == USB_DESC_INTERFACE) {
|
|
if_desc = (struct usb_if_descriptor *)dhp;
|
|
|
|
if (found_iface) {
|
|
break;
|
|
}
|
|
|
|
if (if_desc->bInterfaceNumber == iface &&
|
|
if_desc->bAlternateSetting == alt) {
|
|
found_iface = true;
|
|
LOG_DBG("Found interface %u alternate %u", iface, alt);
|
|
if (if_desc->bNumEndpoints == 0) {
|
|
LOG_DBG("No endpoints, skip interface");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dhp->bDescriptorType == USB_DESC_ENDPOINT && found_iface) {
|
|
ep_desc = (struct usb_ep_descriptor *)dhp;
|
|
err = handle_ep_op(udev, op, ep_desc->bEndpointAddress, ep_desc);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
LOG_INF("Modify interface %u ep 0x%02x by op %u",
|
|
iface, ep_desc->bEndpointAddress, op);
|
|
}
|
|
|
|
dhp = (void *)((uint8_t *)dhp + dhp->bLength);
|
|
}
|
|
|
|
|
|
return found_iface ? 0 : -ENODATA;
|
|
}
|
|
|
|
int usbh_device_interface_set(struct usb_device *const udev,
|
|
const uint8_t iface, const uint8_t alt,
|
|
const bool dry)
|
|
{
|
|
uint8_t cur_alt;
|
|
int err;
|
|
|
|
if (iface > UHC_INTERFACES_MAX) {
|
|
LOG_ERR("Unsupported number of interfaces");
|
|
return -EINVAL;
|
|
}
|
|
|
|
err = k_mutex_lock(&udev->mutex, K_NO_WAIT);
|
|
if (err) {
|
|
LOG_ERR("Failed to lock USB device");
|
|
return err;
|
|
}
|
|
|
|
if (!dry) {
|
|
err = usbh_req_set_alt(udev, iface, alt);
|
|
if (err) {
|
|
LOG_ERR("Set Interface %u alternate %u request failed", iface, alt);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
cur_alt = udev->ifaces[iface].alternate;
|
|
LOG_INF("Set Interfaces %u, alternate %u -> %u", iface, cur_alt, alt);
|
|
if (alt == cur_alt) {
|
|
LOG_DBG("Already active interface alternate");
|
|
goto error;
|
|
}
|
|
|
|
/* Test if interface and interface alternate exist */
|
|
err = device_interface_modify(udev, EP_OP_TEST, iface, alt);
|
|
if (err) {
|
|
LOG_ERR("No interface %u with alternate %u", iface, alt);
|
|
goto error;
|
|
}
|
|
|
|
/* Shutdown current interface alternate */
|
|
err = device_interface_modify(udev, EP_OP_DOWN, iface, cur_alt);
|
|
if (err) {
|
|
LOG_ERR("Failed to shutdown interface %u alternate %u", iface, alt);
|
|
goto error;
|
|
}
|
|
|
|
/* Setup new interface alternate */
|
|
err = device_interface_modify(udev, EP_OP_UP, iface, alt);
|
|
if (err) {
|
|
LOG_ERR("Failed to setup interface %u alternate %u", iface, cur_alt);
|
|
goto error;
|
|
}
|
|
|
|
udev->ifaces[iface].alternate = alt;
|
|
|
|
error:
|
|
k_mutex_unlock(&udev->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_configuration_descriptor(struct usb_device *const udev)
|
|
{
|
|
struct usb_cfg_descriptor *cfg_desc = udev->cfg_desc;
|
|
struct usb_association_descriptor *iad = NULL;
|
|
struct usb_if_descriptor *if_desc = NULL;
|
|
struct usb_ep_descriptor *ep_desc;
|
|
struct usb_desc_header *dhp;
|
|
uint8_t tmp_nif = 0;
|
|
void *desc_end;
|
|
|
|
dhp = (void *)((uint8_t *)udev->cfg_desc + cfg_desc->bLength);
|
|
desc_end = (void *)((uint8_t *)udev->cfg_desc + cfg_desc->wTotalLength);
|
|
|
|
while ((dhp->bDescriptorType != 0 || dhp->bLength != 0) && (void *)dhp < desc_end) {
|
|
if (dhp->bDescriptorType == USB_DESC_INTERFACE_ASSOC) {
|
|
iad = (struct usb_association_descriptor *)dhp;
|
|
LOG_DBG("bFirstInterface %u", iad->bFirstInterface);
|
|
}
|
|
|
|
if (dhp->bDescriptorType == USB_DESC_INTERFACE) {
|
|
if_desc = (struct usb_if_descriptor *)dhp;
|
|
LOG_DBG("bInterfaceNumber %u bAlternateSetting %u",
|
|
if_desc->bInterfaceNumber, if_desc->bAlternateSetting);
|
|
|
|
if (if_desc->bAlternateSetting == 0) {
|
|
if (tmp_nif >= UHC_INTERFACES_MAX) {
|
|
LOG_ERR("Unsupported number of interfaces");
|
|
return -EINVAL;
|
|
}
|
|
|
|
udev->ifaces[tmp_nif].dhp = dhp;
|
|
tmp_nif++;
|
|
}
|
|
}
|
|
|
|
if (dhp->bDescriptorType == USB_DESC_ENDPOINT) {
|
|
ep_desc = (struct usb_ep_descriptor *)dhp;
|
|
|
|
ep_desc->wMaxPacketSize = sys_le16_to_cpu(ep_desc->wMaxPacketSize);
|
|
LOG_DBG("bEndpointAddress 0x%02x wMaxPacketSize %u",
|
|
ep_desc->bEndpointAddress, ep_desc->wMaxPacketSize);
|
|
|
|
if (if_desc != NULL && if_desc->bAlternateSetting == 0) {
|
|
assign_ep_desc_ptr(udev, ep_desc->bEndpointAddress, ep_desc);
|
|
}
|
|
}
|
|
|
|
dhp = (void *)((uint8_t *)dhp + dhp->bLength);
|
|
}
|
|
|
|
if (cfg_desc->bNumInterfaces != tmp_nif) {
|
|
LOG_ERR("The configuration has an incorrect number of interfaces");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void reset_configuration(struct usb_device *const udev)
|
|
{
|
|
/* Reset all endpoint pointers */
|
|
memset(udev->ep_in, 0, sizeof(udev->ep_in));
|
|
memset(udev->ep_out, 0, sizeof(udev->ep_out));
|
|
|
|
/* Reset all interface pointers */
|
|
memset(udev->ifaces, 0, sizeof(udev->ifaces));
|
|
|
|
udev->actual_cfg = 0;
|
|
udev->state = USB_STATE_ADDRESSED;
|
|
}
|
|
|
|
int usbh_device_set_configuration(struct usb_device *const udev, const uint8_t num)
|
|
{
|
|
struct usb_cfg_descriptor cfg_desc;
|
|
uint8_t idx;
|
|
int err;
|
|
|
|
err = k_mutex_lock(&udev->mutex, K_NO_WAIT);
|
|
if (err) {
|
|
LOG_ERR("Failed to lock USB device");
|
|
return err;
|
|
}
|
|
|
|
if (udev->actual_cfg == num) {
|
|
LOG_INF("Already active device configuration");
|
|
goto error;
|
|
}
|
|
|
|
if (num == 0) {
|
|
reset_configuration(udev);
|
|
err = usbh_req_set_cfg(udev, num);
|
|
if (err) {
|
|
LOG_ERR("Set Configuration %u request failed", num);
|
|
}
|
|
|
|
goto error;
|
|
}
|
|
|
|
idx = num - 1;
|
|
|
|
err = usbh_req_desc_cfg(udev, idx, sizeof(cfg_desc), &cfg_desc);
|
|
if (err) {
|
|
LOG_ERR("Failed to read configuration %u descriptor", num);
|
|
goto error;
|
|
}
|
|
|
|
if (cfg_desc.bDescriptorType != USB_DESC_CONFIGURATION) {
|
|
LOG_ERR("Failed to read configuration descriptor");
|
|
err = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
if (cfg_desc.bNumInterfaces == 0) {
|
|
LOG_ERR("Configuration %u has no interfaces", cfg_desc.bNumInterfaces);
|
|
err = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
if (cfg_desc.bNumInterfaces >= UHC_INTERFACES_MAX) {
|
|
LOG_ERR("Unsupported number of interfaces");
|
|
err = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
udev->cfg_desc = k_heap_alloc(&usb_device_heap,
|
|
cfg_desc.wTotalLength,
|
|
K_NO_WAIT);
|
|
if (udev->cfg_desc == NULL) {
|
|
LOG_ERR("Failed to allocate memory for configuration descriptor");
|
|
err = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
err = usbh_req_set_cfg(udev, num);
|
|
if (err) {
|
|
LOG_ERR("Set Configuration %u request failed", num);
|
|
goto error;
|
|
}
|
|
|
|
memset(udev->cfg_desc, 0, cfg_desc.wTotalLength);
|
|
if (udev->state == USB_STATE_CONFIGURED) {
|
|
reset_configuration(udev);
|
|
}
|
|
|
|
err = usbh_req_desc_cfg(udev, idx, cfg_desc.wTotalLength, udev->cfg_desc);
|
|
if (err) {
|
|
LOG_ERR("Failed to read configuration descriptor");
|
|
k_heap_free(&usb_device_heap, udev->cfg_desc);
|
|
goto error;
|
|
}
|
|
|
|
if (memcmp(udev->cfg_desc, &cfg_desc, sizeof(cfg_desc))) {
|
|
LOG_ERR("Configuration descriptor read mismatch");
|
|
k_heap_free(&usb_device_heap, udev->cfg_desc);
|
|
goto error;
|
|
}
|
|
|
|
LOG_INF("Configuration %u bNumInterfaces %u",
|
|
cfg_desc.bConfigurationValue, cfg_desc.bNumInterfaces);
|
|
|
|
err = parse_configuration_descriptor(udev);
|
|
if (err) {
|
|
k_heap_free(&usb_device_heap, udev->cfg_desc);
|
|
goto error;
|
|
}
|
|
|
|
udev->actual_cfg = num;
|
|
udev->state = USB_STATE_CONFIGURED;
|
|
|
|
error:
|
|
k_mutex_unlock(&udev->mutex);
|
|
|
|
return err;
|
|
}
|
|
|
|
int usbh_device_init(struct usb_device *const udev)
|
|
{
|
|
struct usbh_contex *const uhs_ctx = udev->ctx;
|
|
uint8_t new_addr;
|
|
int err;
|
|
|
|
if (udev->state != USB_STATE_DEFAULT) {
|
|
LOG_ERR("USB device is not in default state");
|
|
return -EALREADY;
|
|
}
|
|
|
|
err = k_mutex_lock(&udev->mutex, K_NO_WAIT);
|
|
if (err) {
|
|
LOG_ERR("Failed to lock USB device");
|
|
return err;
|
|
}
|
|
|
|
/* FIXME: The port to which the device is connected should be reset. */
|
|
err = uhc_bus_reset(uhs_ctx->dev);
|
|
if (err) {
|
|
LOG_ERR("Failed to signal bus reset");
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Limit mps0 to the minimum supported by full-speed devices until the
|
|
* device descriptor is read.
|
|
*/
|
|
udev->dev_desc.bMaxPacketSize0 = 8;
|
|
err = usbh_req_desc_dev(udev, 8, &udev->dev_desc);
|
|
if (err) {
|
|
LOG_ERR("Failed to read device descriptor");
|
|
goto error;
|
|
}
|
|
|
|
err = validate_device_mps0(udev);
|
|
if (err) {
|
|
goto error;
|
|
}
|
|
|
|
err = usbh_req_desc_dev(udev, sizeof(udev->dev_desc), &udev->dev_desc);
|
|
if (err) {
|
|
LOG_ERR("Failed to read device descriptor");
|
|
goto error;
|
|
}
|
|
|
|
if (!udev->dev_desc.bNumConfigurations) {
|
|
LOG_ERR("Device has no configurations, bNumConfigurations %d",
|
|
udev->dev_desc.bNumConfigurations);
|
|
goto error;
|
|
}
|
|
|
|
err = alloc_device_address(udev, &new_addr);
|
|
if (err) {
|
|
LOG_ERR("Failed to allocate device address");
|
|
goto error;
|
|
}
|
|
|
|
err = usbh_req_set_address(udev, new_addr);
|
|
if (err) {
|
|
LOG_ERR("Failed to set device address");
|
|
udev->addr = 0;
|
|
|
|
goto error;
|
|
}
|
|
|
|
udev->addr = new_addr;
|
|
udev->state = USB_STATE_ADDRESSED;
|
|
|
|
LOG_INF("New device with address %u state %u", udev->addr, udev->state);
|
|
|
|
err = usbh_device_set_configuration(udev, 1);
|
|
if (err) {
|
|
LOG_ERR("Failed to configure new device with address %u", udev->addr);
|
|
}
|
|
|
|
error:
|
|
k_mutex_unlock(&udev->mutex);
|
|
|
|
return err;
|
|
}
|