blob: 9248bdb008ea81956408aaddaf912693c4d57da7 [file] [log] [blame]
/*
* 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;
}
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;
}