zephyr/subsys/usb/device_next/usbd_class.c

482 lines
11 KiB
C
Raw Permalink Normal View History

/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/usb/usbd.h>
#include <zephyr/toolchain.h>
#include <zephyr/drivers/usb/udc.h>
#include <zephyr/sys/slist.h>
#include <zephyr/sys/iterable_sections.h>
#include "usbd_device.h"
#include "usbd_class_api.h"
#include "usbd_config.h"
#include "usbd_endpoint.h"
#include "usbd_ch9.h"
#include <zephyr/logging/log.h>
#if defined(CONFIG_USBD_LOG_LEVEL)
#define USBD_CLASS_LOG_LEVEL CONFIG_USBD_LOG_LEVEL
#else
#define USBD_CLASS_LOG_LEVEL LOG_LEVEL_NONE
#endif
LOG_MODULE_REGISTER(usbd_class, CONFIG_USBD_LOG_LEVEL);
size_t usbd_class_desc_len(struct usbd_class_data *const c_data,
usb: device_next: make HS support compliant with the USB2.0 specification For specification-compliant high-speed support, we need to support device quilifiers and other-speed-configuration descriptor requests. We also need to store different configurations of the class/function descriptors, which typically only affect the endpoint descriptors. With this change, the stack expects class/function descriptors to be passed as an array of struct usb_desc_header pointers to e.g. interface, interface-specific, and endpoint descriptors, with the last element of the array pointing to a nil descriptor. And also passed for a specific speed, for now we support full and high speed configurations. During instantiation, the class/function implementation must choose the correct configuration in the full-speed and high-speed descriptor sets for values such as maximum packet size and bInterval values of interrupt and isochronous endpoints. During initialization, the stack reads the highest speed supported by the controller and uses it to get the appropriate descriptors set from the instance. If the controller supports only full speed, the stack configures the class/function descriptor for full speed only, if the controller supports high speed, the stack configures the descriptors for high speed only, and a class/function must update the full speed descriptor during the init callback processing. During device operation, the class/function implementation must check the actual speed of the device and use the correct configuration, such as the endpoint address or maximum packet size. Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
2024-01-02 17:10:06 +01:00
const enum usbd_speed speed)
{
usb: device_next: make HS support compliant with the USB2.0 specification For specification-compliant high-speed support, we need to support device quilifiers and other-speed-configuration descriptor requests. We also need to store different configurations of the class/function descriptors, which typically only affect the endpoint descriptors. With this change, the stack expects class/function descriptors to be passed as an array of struct usb_desc_header pointers to e.g. interface, interface-specific, and endpoint descriptors, with the last element of the array pointing to a nil descriptor. And also passed for a specific speed, for now we support full and high speed configurations. During instantiation, the class/function implementation must choose the correct configuration in the full-speed and high-speed descriptor sets for values such as maximum packet size and bInterval values of interrupt and isochronous endpoints. During initialization, the stack reads the highest speed supported by the controller and uses it to get the appropriate descriptors set from the instance. If the controller supports only full speed, the stack configures the class/function descriptor for full speed only, if the controller supports high speed, the stack configures the descriptors for high speed only, and a class/function must update the full speed descriptor during the init callback processing. During device operation, the class/function implementation must check the actual speed of the device and use the correct configuration, such as the endpoint address or maximum packet size. Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
2024-01-02 17:10:06 +01:00
struct usb_desc_header **dhp;
size_t len = 0;
dhp = usbd_class_get_desc(c_data, speed);
usb: device_next: make HS support compliant with the USB2.0 specification For specification-compliant high-speed support, we need to support device quilifiers and other-speed-configuration descriptor requests. We also need to store different configurations of the class/function descriptors, which typically only affect the endpoint descriptors. With this change, the stack expects class/function descriptors to be passed as an array of struct usb_desc_header pointers to e.g. interface, interface-specific, and endpoint descriptors, with the last element of the array pointing to a nil descriptor. And also passed for a specific speed, for now we support full and high speed configurations. During instantiation, the class/function implementation must choose the correct configuration in the full-speed and high-speed descriptor sets for values such as maximum packet size and bInterval values of interrupt and isochronous endpoints. During initialization, the stack reads the highest speed supported by the controller and uses it to get the appropriate descriptors set from the instance. If the controller supports only full speed, the stack configures the class/function descriptor for full speed only, if the controller supports high speed, the stack configures the descriptors for high speed only, and a class/function must update the full speed descriptor during the init callback processing. During device operation, the class/function implementation must check the actual speed of the device and use the correct configuration, such as the endpoint address or maximum packet size. Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
2024-01-02 17:10:06 +01:00
/*
* If the desired descriptor is available, count to the last element,
* which must be a pointer to a nil descriptor.
*/
while (dhp != NULL && (*dhp != NULL) && (*dhp)->bLength != 0U) {
usb: device_next: make HS support compliant with the USB2.0 specification For specification-compliant high-speed support, we need to support device quilifiers and other-speed-configuration descriptor requests. We also need to store different configurations of the class/function descriptors, which typically only affect the endpoint descriptors. With this change, the stack expects class/function descriptors to be passed as an array of struct usb_desc_header pointers to e.g. interface, interface-specific, and endpoint descriptors, with the last element of the array pointing to a nil descriptor. And also passed for a specific speed, for now we support full and high speed configurations. During instantiation, the class/function implementation must choose the correct configuration in the full-speed and high-speed descriptor sets for values such as maximum packet size and bInterval values of interrupt and isochronous endpoints. During initialization, the stack reads the highest speed supported by the controller and uses it to get the appropriate descriptors set from the instance. If the controller supports only full speed, the stack configures the class/function descriptor for full speed only, if the controller supports high speed, the stack configures the descriptors for high speed only, and a class/function must update the full speed descriptor during the init callback processing. During device operation, the class/function implementation must check the actual speed of the device and use the correct configuration, such as the endpoint address or maximum packet size. Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
2024-01-02 17:10:06 +01:00
len += (*dhp)->bLength;
dhp++;
}
return len;
}
struct usbd_class_node *
usbd_class_get_by_config(struct usbd_context *const uds_ctx,
const enum usbd_speed speed,
const uint8_t cnum,
const uint8_t inum)
{
struct usbd_class_node *c_nd;
struct usbd_config_node *cfg_nd;
cfg_nd = usbd_config_get(uds_ctx, speed, cnum);
if (cfg_nd == NULL) {
return NULL;
}
SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
if (c_nd->iface_bm & BIT(inum)) {
return c_nd;
}
}
return NULL;
}
struct usbd_class_node *
usbd_class_get_by_iface(struct usbd_context *const uds_ctx,
const uint8_t inum)
{
struct usbd_class_node *c_nd;
struct usbd_config_node *cfg_nd;
cfg_nd = usbd_config_get_current(uds_ctx);
if (cfg_nd == NULL) {
return NULL;
}
SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
if (c_nd->iface_bm & BIT(inum)) {
return c_nd;
}
}
return NULL;
}
static bool xfer_owner_exist(struct usbd_context *const uds_ctx,
struct usbd_config_node *const cfg_nd,
struct net_buf *const buf)
{
struct udc_buf_info *bi = udc_get_buf_info(buf);
struct usbd_class_node *c_nd;
SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
if (bi->owner == c_nd->c_data) {
uint32_t ep_active = c_nd->ep_active;
uint32_t ep_assigned = c_nd->ep_assigned;
if (!usbd_ep_bm_is_set(&ep_active, bi->ep)) {
LOG_DBG("ep 0x%02x is not active", bi->ep);
}
if (!usbd_ep_bm_is_set(&ep_assigned, bi->ep)) {
LOG_DBG("ep 0x%02x is not assigned", bi->ep);
}
return true;
}
}
return false;
}
int usbd_class_handle_xfer(struct usbd_context *const uds_ctx,
struct net_buf *const buf,
const int err)
{
struct udc_buf_info *bi = udc_get_buf_info(buf);
if (unlikely(USBD_CLASS_LOG_LEVEL == LOG_LEVEL_DBG)) {
struct usbd_config_node *cfg_nd;
if (usbd_state_is_configured(uds_ctx)) {
cfg_nd = usbd_config_get_current(uds_ctx);
if (!xfer_owner_exist(uds_ctx, cfg_nd, buf)) {
LOG_DBG("Class request without owner");
}
} else {
LOG_DBG("Class request on not configured device");
}
}
return usbd_class_request(bi->owner, buf, err);
}
struct usbd_class_node *
usbd_class_get_by_ep(struct usbd_context *const uds_ctx,
const uint8_t ep)
{
struct usbd_class_node *c_nd;
struct usbd_config_node *cfg_nd;
enum usbd_speed speed;
uint8_t ep_idx = USB_EP_GET_IDX(ep);
uint8_t cfg;
uint32_t ep_bm;
if (USB_EP_DIR_IS_IN(ep)) {
ep_bm = BIT(ep_idx + 16);
} else {
ep_bm = BIT(ep_idx);
}
if (!usbd_state_is_configured(uds_ctx)) {
LOG_ERR("No configuration set (Address state)");
return NULL;
}
cfg = usbd_get_config_value(uds_ctx);
speed = usbd_bus_speed(uds_ctx);
cfg_nd = usbd_config_get(uds_ctx, speed, cfg);
if (cfg_nd == NULL) {
return NULL;
}
SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
if (c_nd->ep_assigned & ep_bm) {
return c_nd;
}
}
return NULL;
}
struct usbd_class_node *
usbd_class_get_by_req(struct usbd_context *const uds_ctx,
const uint8_t request)
{
struct usbd_config_node *cfg_nd;
struct usbd_class_node *c_nd;
cfg_nd = usbd_config_get_current(uds_ctx);
if (cfg_nd == NULL) {
return NULL;
}
SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
if (c_nd->c_data->v_reqs == NULL) {
continue;
}
for (int i = 0; i < c_nd->c_data->v_reqs->len; i++) {
/*
* First instance always wins.
* There is no other way to determine the recipient.
*/
if (c_nd->c_data->v_reqs->reqs[i] == request) {
return c_nd;
}
}
}
return NULL;
}
static struct usbd_class_node *
usbd_class_node_get(const char *name, const enum usbd_speed speed)
{
if (speed == USBD_SPEED_FS) {
STRUCT_SECTION_FOREACH_ALTERNATE(usbd_class_fs,
usbd_class_node, c_nd) {
if (strcmp(name, c_nd->c_data->name) == 0) {
return c_nd;
}
}
} else if (speed == USBD_SPEED_HS) {
STRUCT_SECTION_FOREACH_ALTERNATE(usbd_class_hs,
usbd_class_node, c_nd) {
if (strcmp(name, c_nd->c_data->name) == 0) {
return c_nd;
}
}
}
LOG_ERR("USB device class %s not found", name);
return NULL;
}
static int usbd_class_append(struct usbd_context *const uds_ctx,
struct usbd_class_node *const c_nd,
const enum usbd_speed speed,
const uint8_t cfg)
{
struct usbd_config_node *cfg_nd;
cfg_nd = usbd_config_get(uds_ctx, speed, cfg);
if (cfg_nd == NULL) {
return -ENODATA;
}
sys_slist_append(&cfg_nd->class_list, &c_nd->node);
return 0;
}
static int usbd_class_remove(struct usbd_context *const uds_ctx,
struct usbd_class_node *const c_nd,
const enum usbd_speed speed,
const uint8_t cfg)
{
struct usbd_config_node *cfg_nd;
cfg_nd = usbd_config_get(uds_ctx, speed, cfg);
if (cfg_nd == NULL) {
return -ENODATA;
}
if (!sys_slist_find_and_remove(&cfg_nd->class_list, &c_nd->node)) {
return -ENODATA;
}
return 0;
}
int usbd_class_remove_all(struct usbd_context *const uds_ctx,
const enum usbd_speed speed,
const uint8_t cfg)
{
struct usbd_config_node *cfg_nd;
struct usbd_class_node *c_nd;
sys_snode_t *node;
cfg_nd = usbd_config_get(uds_ctx, speed, cfg);
if (cfg_nd == NULL) {
return -ENODATA;
}
while ((node = sys_slist_get(&cfg_nd->class_list))) {
c_nd = CONTAINER_OF(node, struct usbd_class_node, node);
atomic_clear_bit(&c_nd->state, USBD_CCTX_REGISTERED);
usbd_class_shutdown(c_nd->c_data);
LOG_DBG("Remove class node %p from configuration %u", c_nd, cfg);
}
return 0;
}
/*
* All the functions below are part of public USB device support API.
*/
int usbd_register_class(struct usbd_context *const uds_ctx,
const char *name,
const enum usbd_speed speed, const uint8_t cfg)
{
struct usbd_class_node *c_nd;
struct usbd_class_data *c_data;
int ret;
c_nd = usbd_class_node_get(name, speed);
if (c_nd == NULL) {
return -ENODEV;
}
usbd_device_lock(uds_ctx);
if (usbd_is_initialized(uds_ctx)) {
LOG_ERR("USB device support is initialized");
ret = -EBUSY;
goto register_class_error;
}
c_data = c_nd->c_data;
/* TODO: does it still need to be atomic ? */
if (atomic_test_bit(&c_nd->state, USBD_CCTX_REGISTERED)) {
LOG_WRN("Class instance already registered");
ret = -EBUSY;
goto register_class_error;
}
if ((c_data->uds_ctx != NULL) && (c_data->uds_ctx != uds_ctx)) {
LOG_ERR("Class registered to other context at different speed");
ret = -EBUSY;
goto register_class_error;
}
ret = usbd_class_append(uds_ctx, c_nd, speed, cfg);
if (ret == 0) {
/* Initialize pointer back to the device struct */
atomic_set_bit(&c_nd->state, USBD_CCTX_REGISTERED);
c_data->uds_ctx = uds_ctx;
}
register_class_error:
usbd_device_unlock(uds_ctx);
return ret;
}
int usbd_register_all_classes(struct usbd_context *const uds_ctx,
const enum usbd_speed speed, const uint8_t cfg)
{
int ret;
if (speed == USBD_SPEED_HS) {
STRUCT_SECTION_FOREACH_ALTERNATE(usbd_class_hs, usbd_class_node, c_nd) {
ret = usbd_register_class(uds_ctx, c_nd->c_data->name,
speed, cfg);
if (ret) {
LOG_ERR("Failed to register %s to HS configuration %u",
c_nd->c_data->name, cfg);
return ret;
}
}
return 0;
}
if (speed == USBD_SPEED_FS) {
STRUCT_SECTION_FOREACH_ALTERNATE(usbd_class_fs, usbd_class_node, c_nd) {
ret = usbd_register_class(uds_ctx, c_nd->c_data->name,
speed, cfg);
if (ret) {
LOG_ERR("Failed to register %s to FS configuration %u",
c_nd->c_data->name, cfg);
return ret;
}
}
return 0;
}
return -ENOTSUP;
}
int usbd_unregister_class(struct usbd_context *const uds_ctx,
const char *name,
const enum usbd_speed speed, const uint8_t cfg)
{
struct usbd_class_node *c_nd;
struct usbd_class_data *c_data;
bool can_release_data = true;
int ret;
c_nd = usbd_class_node_get(name, speed);
if (c_nd == NULL) {
return -ENODEV;
}
usbd_device_lock(uds_ctx);
if (usbd_is_initialized(uds_ctx)) {
LOG_ERR("USB device support is initialized");
ret = -EBUSY;
goto unregister_class_error;
}
c_data = c_nd->c_data;
/* TODO: does it still need to be atomic ? */
if (!atomic_test_bit(&c_nd->state, USBD_CCTX_REGISTERED)) {
LOG_WRN("Class instance not registered");
ret = -EBUSY;
goto unregister_class_error;
}
/* TODO: The use of atomic here does not make this code thread safe.
* The atomic should be changed to something else.
*/
if (speed == USBD_SPEED_HS) {
STRUCT_SECTION_FOREACH_ALTERNATE(usbd_class_fs,
usbd_class_node, i) {
if ((i->c_data == c_nd->c_data) &&
atomic_test_bit(&i->state, USBD_CCTX_REGISTERED)) {
can_release_data = false;
break;
}
}
} else {
STRUCT_SECTION_FOREACH_ALTERNATE(usbd_class_hs,
usbd_class_node, i) {
if ((i->c_data == c_nd->c_data) &&
atomic_test_bit(&i->state, USBD_CCTX_REGISTERED)) {
can_release_data = false;
break;
}
}
}
ret = usbd_class_remove(uds_ctx, c_nd, speed, cfg);
if (ret == 0) {
atomic_clear_bit(&c_nd->state, USBD_CCTX_REGISTERED);
usbd_class_shutdown(c_nd->c_data);
if (can_release_data) {
c_data->uds_ctx = NULL;
}
}
unregister_class_error:
usbd_device_unlock(uds_ctx);
return ret;
}
int usbd_unregister_all_classes(struct usbd_context *const uds_ctx,
const enum usbd_speed speed, const uint8_t cfg)
{
int ret;
if (speed == USBD_SPEED_HS) {
STRUCT_SECTION_FOREACH_ALTERNATE(usbd_class_hs, usbd_class_node, c_nd) {
ret = usbd_unregister_class(uds_ctx, c_nd->c_data->name,
speed, cfg);
if (ret) {
LOG_ERR("Failed to unregister %s to HS configuration %u",
c_nd->c_data->name, cfg);
return ret;
}
}
return 0;
}
if (speed == USBD_SPEED_FS) {
STRUCT_SECTION_FOREACH_ALTERNATE(usbd_class_fs, usbd_class_node, c_nd) {
ret = usbd_unregister_class(uds_ctx, c_nd->c_data->name,
speed, cfg);
if (ret) {
LOG_ERR("Failed to unregister %s to FS configuration %u",
c_nd->c_data->name, cfg);
return ret;
}
}
return 0;
}
return -ENOTSUP;
}