usb: add new experimental USB device stack

The device supprt brings support for multiple stack instances,
multiple configuration, asynchronous transfer model, ability to
change most of the properties of a device at runtime and
the composition of configuration and classes at runtime.

The stack requires new UDC driver API and is not compatible
with old driver API (usb_dc_). The classes (functions) of old
(current) USB device stack cannot be used with new ones and must
be ported.

Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
This commit is contained in:
Johann Fischer 2022-01-05 13:44:48 +01:00 committed by Carles Cufí
commit cb8b9ad38a
29 changed files with 5633 additions and 0 deletions

View file

@ -52,6 +52,8 @@ source "subsys/usb/usb_c/Kconfig"
source "subsys/sd/Kconfig"
source "subsys/usb/device_next/Kconfig"
source "subsys/dfu/Kconfig"
source "subsys/random/Kconfig"

View file

@ -2,4 +2,5 @@
# SPDX-License-Identifier: Apache-2.0
add_subdirectory_ifdef(CONFIG_USB_DEVICE_STACK device)
add_subdirectory_ifdef(CONFIG_USB_DEVICE_STACK_NEXT device_next)
add_subdirectory_ifdef(CONFIG_USBC_STACK usb_c)

View file

@ -0,0 +1,29 @@
# Copyright (c) 2022 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_include_directories(${CMAKE_CURRENT_SOURCE_DIR})
zephyr_library_sources(
usbd_device.c
usbd_desc.c
usbd_ch9.c
usbd_core.c
usbd_init.c
usbd_config.c
usbd_class.c
usbd_interface.c
usbd_endpoint.c
)
zephyr_library_sources_ifdef(
CONFIG_USBD_SHELL
usbd_shell.c
)
zephyr_library_sources_ifdef(
CONFIG_USBD_LOOPBACK_CLASS
class/loopback.c
)
zephyr_linker_sources(DATA_SECTIONS usbd_data.ld)

View file

@ -0,0 +1,46 @@
# Copyright (c) 2022 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: Apache-2.0
menuconfig USB_DEVICE_STACK_NEXT
bool "New USB device stack [EXPERIMENTAL]"
select EXPERIMENTAL
select UDC_DRIVER
select HWINFO
help
New experimental USB device stack.
if USB_DEVICE_STACK_NEXT
module = USBD
module-str = usbd
source "subsys/logging/Kconfig.template.log_config"
config USBD_SHELL
bool "USB device shell"
default y
depends on SHELL
help
Enable USB device shell.
config USBD_THREAD_INIT_PRIO
int
default 90
help
USB device thread initialization priority level.
config USBD_THREAD_STACK_SIZE
int "USB device stack thread stack size"
default 1024
help
USB device stack thread stack size in bytes.
config USBD_MAX_UDC_MSG
int "Maximum number of UDC events"
default 10
help
Maximum number of USB device controller events that can be queued.
rsource "class/Kconfig"
endif # USB_DEVICE_STACK_NEXT

View file

@ -0,0 +1,5 @@
# Copyright (c) 2022 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: Apache-2.0
rsource "Kconfig.loopback"

View file

@ -0,0 +1,18 @@
# Copyright (c) 2022 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: Apache-2.0
config USBD_LOOPBACK_CLASS
bool "USB Loopback Class"
help
USB device loopback class.
Primarily used for test and development purposes.
if USBD_LOOPBACK_CLASS
module = USBD_LOOPBACK
module-str = usbd loopback
default-count = 1
source "subsys/logging/Kconfig.template.log_config"
rsource "Kconfig.template.composite_device_number"
endif

View file

@ -0,0 +1,11 @@
# Kconfig template file for setting device count for
# various USB classes.
# Copyright (c) 2019 Intel Corporation.
# SPDX-License-Identifier: Apache-2.0
config $(module)_DEVICE_COUNT
int "Number of $(module) Devices"
default $(default-count)
help
Number of instances of this USB Device class.

View file

@ -0,0 +1,301 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/usb/usbd.h>
#include <zephyr/drivers/usb/udc.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(usb_loopback, CONFIG_USBD_LOOPBACK_LOG_LEVEL);
/*
* NOTE: this class is experimental and is in development.
* Primary purpose currently is testing of the class initialization and
* interface and endpoint configuration.
*/
/* Internal buffer for intermidiate test data */
static uint8_t lb_buf[1024];
#define LB_VENDOR_REQ_OUT 0x5b
#define LB_VENDOR_REQ_IN 0x5c
/* Make supported vendor request visible for the device stack */
static const struct usbd_cctx_vendor_req lb_vregs =
USBD_VENDOR_REQ(LB_VENDOR_REQ_OUT, LB_VENDOR_REQ_IN);
struct loopback_desc {
struct usb_if_descriptor if0;
struct usb_ep_descriptor if0_out_ep;
struct usb_ep_descriptor if0_in_ep;
struct usb_ep_descriptor if0_int_out_ep;
struct usb_ep_descriptor if0_int_in_ep;
struct usb_ep_descriptor if0_iso_out_ep;
struct usb_ep_descriptor if0_iso_in_ep;
struct usb_if_descriptor if1;
struct usb_if_descriptor if2;
struct usb_ep_descriptor if2_out_ep;
struct usb_ep_descriptor if2_in_ep;
struct usb_if_descriptor if3;
struct usb_ep_descriptor if3_out_ep;
struct usb_ep_descriptor if3_in_ep;
struct usb_desc_header term_desc;
} __packed;
#define DEFINE_LOOPBACK_DESCRIPTOR(x, _) \
static struct loopback_desc lb_desc_##x = { \
/* Interface descriptor 0 */ \
.if0 = { \
.bLength = sizeof(struct usb_if_descriptor), \
.bDescriptorType = USB_DESC_INTERFACE, \
.bInterfaceNumber = 0, \
.bAlternateSetting = 0, \
.bNumEndpoints = 6, \
.bInterfaceClass = USB_BCC_VENDOR, \
.bInterfaceSubClass = 0, \
.bInterfaceProtocol = 0, \
.iInterface = 0, \
}, \
\
/* Data Endpoint OUT */ \
.if0_out_ep = { \
.bLength = sizeof(struct usb_ep_descriptor), \
.bDescriptorType = USB_DESC_ENDPOINT, \
.bEndpointAddress = 0x01, \
.bmAttributes = USB_EP_TYPE_BULK, \
.wMaxPacketSize = 0, \
.bInterval = 0x00, \
}, \
\
/* Data Endpoint IN */ \
.if0_in_ep = { \
.bLength = sizeof(struct usb_ep_descriptor), \
.bDescriptorType = USB_DESC_ENDPOINT, \
.bEndpointAddress = 0x81, \
.bmAttributes = USB_EP_TYPE_BULK, \
.wMaxPacketSize = 0, \
.bInterval = 0x00, \
}, \
\
/* Interface Endpoint OUT */ \
.if0_int_out_ep = { \
.bLength = sizeof(struct usb_ep_descriptor), \
.bDescriptorType = USB_DESC_ENDPOINT, \
.bEndpointAddress = 0x03, \
.bmAttributes = USB_EP_TYPE_INTERRUPT, \
.wMaxPacketSize = 0, \
.bInterval = 0x01, \
}, \
\
/* Interrupt Endpoint IN */ \
.if0_int_in_ep = { \
.bLength = sizeof(struct usb_ep_descriptor), \
.bDescriptorType = USB_DESC_ENDPOINT, \
.bEndpointAddress = 0x83, \
.bmAttributes = USB_EP_TYPE_INTERRUPT, \
.wMaxPacketSize = 0, \
.bInterval = 0x01, \
}, \
\
/* Endpoint ISO OUT */ \
.if0_iso_out_ep = { \
.bLength = sizeof(struct usb_ep_descriptor), \
.bDescriptorType = USB_DESC_ENDPOINT, \
.bEndpointAddress = 0x03, \
.bmAttributes = USB_EP_TYPE_ISO, \
.wMaxPacketSize = 0, \
.bInterval = 0x01, \
}, \
\
/* Endpoint ISO IN */ \
.if0_iso_in_ep = { \
.bLength = sizeof(struct usb_ep_descriptor), \
.bDescriptorType = USB_DESC_ENDPOINT, \
.bEndpointAddress = 0x83, \
.bmAttributes = USB_EP_TYPE_ISO, \
.wMaxPacketSize = 0, \
.bInterval = 0x01, \
}, \
\
/* Interface descriptor 1, no endpoints */ \
.if1 = { \
.bLength = sizeof(struct usb_if_descriptor), \
.bDescriptorType = USB_DESC_INTERFACE, \
.bInterfaceNumber = 1, \
.bAlternateSetting = 0, \
.bNumEndpoints = 0, \
.bInterfaceClass = USB_BCC_VENDOR, \
.bInterfaceSubClass = 0, \
.bInterfaceProtocol = 0, \
.iInterface = 0, \
}, \
\
/* Interface descriptor 1 */ \
.if2 = { \
.bLength = sizeof(struct usb_if_descriptor), \
.bDescriptorType = USB_DESC_INTERFACE, \
.bInterfaceNumber = 1, \
.bAlternateSetting = 1, \
.bNumEndpoints = 2, \
.bInterfaceClass = USB_BCC_VENDOR, \
.bInterfaceSubClass = 0, \
.bInterfaceProtocol = 0, \
.iInterface = 0, \
}, \
\
/* Data Endpoint OUT */ \
.if2_out_ep = { \
.bLength = sizeof(struct usb_ep_descriptor), \
.bDescriptorType = USB_DESC_ENDPOINT, \
.bEndpointAddress = 0x02, \
.bmAttributes = USB_EP_TYPE_BULK, \
.wMaxPacketSize = 32, \
.bInterval = 0x00, \
}, \
\
/* Data Endpoint IN */ \
.if2_in_ep = { \
.bLength = sizeof(struct usb_ep_descriptor), \
.bDescriptorType = USB_DESC_ENDPOINT, \
.bEndpointAddress = 0x82, \
.bmAttributes = USB_EP_TYPE_BULK, \
.wMaxPacketSize = 32, \
.bInterval = 0x00, \
}, \
\
/* Interface descriptor 1 */ \
.if3 = { \
.bLength = sizeof(struct usb_if_descriptor), \
.bDescriptorType = USB_DESC_INTERFACE, \
.bInterfaceNumber = 1, \
.bAlternateSetting = 2, \
.bNumEndpoints = 2, \
.bInterfaceClass = USB_BCC_VENDOR, \
.bInterfaceSubClass = 0, \
.bInterfaceProtocol = 0, \
.iInterface = 0, \
}, \
\
/* Data Endpoint OUT, get wMaxPacketSize from UDC */ \
.if3_out_ep = { \
.bLength = sizeof(struct usb_ep_descriptor), \
.bDescriptorType = USB_DESC_ENDPOINT, \
.bEndpointAddress = 0x02, \
.bmAttributes = USB_EP_TYPE_BULK, \
.wMaxPacketSize = 0, \
.bInterval = 0x00, \
}, \
\
/* Data Endpoint IN, get wMaxPacketSize from UDC */ \
.if3_in_ep = { \
.bLength = sizeof(struct usb_ep_descriptor), \
.bDescriptorType = USB_DESC_ENDPOINT, \
.bEndpointAddress = 0x82, \
.bmAttributes = USB_EP_TYPE_BULK, \
.wMaxPacketSize = 0, \
.bInterval = 0x00, \
}, \
\
/* Termination descriptor */ \
.term_desc = { \
.bLength = 0, \
.bDescriptorType = 0, \
}, \
}; \
static void lb_update(struct usbd_class_node *c_nd,
uint8_t iface, uint8_t alternate)
{
LOG_DBG("Instance %p, interface %u alternate %u changed",
c_nd, iface, alternate);
}
static int lb_control_to_host(struct usbd_class_node *c_nd,
const struct usb_setup_packet *const setup,
struct net_buf *const buf)
{
struct usbd_contex *uds_ctx = c_nd->data->uds_ctx;
if (setup->RequestType.recipient != USB_REQTYPE_RECIPIENT_DEVICE) {
errno = -ENOTSUP;
return 0;
}
if (setup->bRequest == LB_VENDOR_REQ_IN) {
net_buf_add_mem(buf, lb_buf,
MIN(sizeof(lb_buf), setup->wLength));
usbd_ep_ctrl_enqueue(uds_ctx, buf);
LOG_WRN("Device-to-Host, wLength %u | %u", setup->wLength,
MIN(sizeof(lb_buf), setup->wLength));
return 0;
}
LOG_ERR("Class request 0x%x not supported", setup->bRequest);
errno = -ENOTSUP;
return 0;
}
static int lb_control_to_dev(struct usbd_class_node *c_nd,
const struct usb_setup_packet *const setup,
const struct net_buf *const buf)
{
if (setup->RequestType.recipient != USB_REQTYPE_RECIPIENT_DEVICE) {
errno = -ENOTSUP;
return 0;
}
if (setup->bRequest == LB_VENDOR_REQ_OUT) {
LOG_WRN("Host-to-Device, wLength %u | %u", setup->wLength,
MIN(sizeof(lb_buf), buf->len));
memcpy(lb_buf, buf->data, MIN(sizeof(lb_buf), buf->len));
return 0;
}
LOG_ERR("Class request 0x%x not supported", setup->bRequest);
errno = -ENOTSUP;
return 0;
}
static int lb_request_handler(struct usbd_class_node *c_nd,
struct net_buf *buf, int err)
{
struct udc_buf_info *bi = NULL;
bi = (struct udc_buf_info *)net_buf_user_data(buf);
LOG_DBG("%p -> ep 0x%02x, len %u, err %d", c_nd, bi->ep, buf->len, err);
usbd_ep_buf_free(c_nd->data->uds_ctx, buf);
return 0;
}
static int lb_init(struct usbd_class_node *c_nd)
{
LOG_DBG("Init class instance %p", c_nd);
return 0;
}
struct usbd_class_api lb_api = {
.update = lb_update,
.control_to_host = lb_control_to_host,
.control_to_dev = lb_control_to_dev,
.request = lb_request_handler,
.init = lb_init,
};
#define DEFINE_LOOPBACK_CLASS_DATA(x, _) \
static struct usbd_class_data lb_class_##x = { \
.desc = (struct usb_desc_header *)&lb_desc_##x, \
.v_reqs = &lb_vregs, \
}; \
\
USBD_DEFINE_CLASS(loopback_##x, &lb_api, &lb_class_##x);
LISTIFY(CONFIG_USBD_LOOPBACK_DEVICE_COUNT, DEFINE_LOOPBACK_DESCRIPTOR, ())
LISTIFY(CONFIG_USBD_LOOPBACK_DEVICE_COUNT, DEFINE_LOOPBACK_CLASS_DATA, ())

View file

@ -0,0 +1,857 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/usb/usbd.h>
#include <zephyr/drivers/usb/udc.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/slist.h>
#include "usbd_device.h"
#include "usbd_desc.h"
#include "usbd_ch9.h"
#include "usbd_config.h"
#include "usbd_class.h"
#include "usbd_class_api.h"
#include "usbd_interface.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(usbd_ch9, CONFIG_USBD_LOG_LEVEL);
#define CTRL_AWAIT_SETUP_DATA 0
#define CTRL_AWAIT_STATUS_STAGE 1
static bool reqtype_is_to_host(const struct usb_setup_packet *const setup)
{
return setup->wLength && USB_REQTYPE_GET_DIR(setup->bmRequestType);
}
static bool reqtype_is_to_device(const struct usb_setup_packet *const setup)
{
return !reqtype_is_to_host(setup);
}
static void ch9_set_ctrl_type(struct usbd_contex *const uds_ctx,
const int type)
{
uds_ctx->ch9_data.ctrl_type = type;
}
static int ch9_get_ctrl_type(struct usbd_contex *const uds_ctx)
{
return uds_ctx->ch9_data.ctrl_type;
}
static int set_address_after_status_stage(struct usbd_contex *const uds_ctx)
{
struct usb_setup_packet *setup = usbd_get_setup_pkt(uds_ctx);
int ret;
ret = udc_set_address(uds_ctx->dev, setup->wValue);
if (ret) {
LOG_ERR("Failed to set device address 0x%x", setup->wValue);
return ret;
}
uds_ctx->ch9_data.new_address = false;
return ret;
}
static int sreq_set_address(struct usbd_contex *const uds_ctx)
{
struct usb_setup_packet *setup = usbd_get_setup_pkt(uds_ctx);
/* Not specified if wLength is non-zero, treat as error */
if (setup->wValue > 127 || setup->wLength) {
errno = -ENOTSUP;
return 0;
}
if (setup->RequestType.recipient != USB_REQTYPE_RECIPIENT_DEVICE) {
errno = -ENOTSUP;
return 0;
}
if (usbd_state_is_configured(uds_ctx)) {
errno = -EPERM;
return 0;
}
uds_ctx->ch9_data.new_address = true;
if (usbd_state_is_address(uds_ctx) && setup->wValue == 0) {
uds_ctx->ch9_data.state = USBD_STATE_DEFAULT;
} else {
uds_ctx->ch9_data.state = USBD_STATE_ADDRESS;
}
return 0;
}
static int sreq_set_configuration(struct usbd_contex *const uds_ctx)
{
struct usb_setup_packet *setup = usbd_get_setup_pkt(uds_ctx);
int ret;
LOG_INF("Set Configuration Request value %u", setup->wValue);
/* Not specified if wLength is non-zero, treat as error */
if (setup->wValue > UINT8_MAX || setup->wLength) {
errno = -ENOTSUP;
return 0;
}
if (setup->RequestType.recipient != USB_REQTYPE_RECIPIENT_DEVICE) {
errno = -ENOTSUP;
return 0;
}
if (usbd_state_is_default(uds_ctx)) {
errno = -EPERM;
return 0;
}
if (setup->wValue && !usbd_config_exist(uds_ctx, setup->wValue)) {
errno = -EPERM;
return 0;
}
if (setup->wValue == usbd_get_config_value(uds_ctx)) {
LOG_DBG("Already in the configuration %u", setup->wValue);
return 0;
}
ret = usbd_config_set(uds_ctx, setup->wValue);
if (ret) {
LOG_ERR("Failed to set configuration %u, %d",
setup->wValue, ret);
return ret;
}
if (setup->wValue == 0) {
/* Enter address state */
uds_ctx->ch9_data.state = USBD_STATE_ADDRESS;
} else {
uds_ctx->ch9_data.state = USBD_STATE_CONFIGURED;
}
return ret;
}
static int sreq_set_interface(struct usbd_contex *const uds_ctx)
{
struct usb_setup_packet *setup = usbd_get_setup_pkt(uds_ctx);
uint8_t cur_alt;
int ret;
if (setup->RequestType.recipient != USB_REQTYPE_RECIPIENT_INTERFACE) {
errno = -ENOTSUP;
return 0;
}
/* Not specified if wLength is non-zero, treat as error */
if (setup->wLength) {
errno = -ENOTSUP;
return 0;
}
if (setup->wValue > UINT8_MAX || setup->wIndex > UINT8_MAX) {
errno = -ENOTSUP;
return 0;
}
if (!usbd_state_is_configured(uds_ctx)) {
errno = -EPERM;
return 0;
}
if (usbd_get_alt_value(uds_ctx, setup->wIndex, &cur_alt)) {
errno = -ENOTSUP;
return 0;
}
LOG_INF("Set Interfaces %u, alternate %u -> %u",
setup->wIndex, cur_alt, setup->wValue);
if (setup->wValue == cur_alt) {
return 0;
}
ret = usbd_interface_set(uds_ctx, setup->wIndex, setup->wValue);
if (ret == -ENOENT) {
LOG_INF("Interface alternate does not exist");
errno = ret;
ret = 0;
}
return ret;
}
static int sreq_clear_feature(struct usbd_contex *const uds_ctx)
{
struct usb_setup_packet *setup = usbd_get_setup_pkt(uds_ctx);
uint8_t ep = setup->wIndex;
int ret = 0;
/* Not specified if wLength is non-zero, treat as error */
if (setup->wLength) {
errno = -ENOTSUP;
return 0;
}
/* Not specified in default state, treat as error */
if (usbd_state_is_default(uds_ctx)) {
errno = -EPERM;
return 0;
}
if (usbd_state_is_address(uds_ctx) && setup->wIndex) {
errno = -EPERM;
return 0;
}
switch (setup->RequestType.recipient) {
case USB_REQTYPE_RECIPIENT_DEVICE:
if (setup->wIndex != 0) {
errno = -EPERM;
return 0;
}
if (setup->wValue == USB_SFS_REMOTE_WAKEUP) {
LOG_DBG("Clear feature remote wakeup");
uds_ctx->status.rwup = false;
}
break;
case USB_REQTYPE_RECIPIENT_ENDPOINT:
if (setup->wValue == USB_SFS_ENDPOINT_HALT) {
/* UDC checks if endpoint is enabled */
errno = usbd_ep_clear_halt(uds_ctx, ep);
ret = (errno == -EPERM) ? errno : 0;
/* TODO: notify class instance */
break;
}
break;
case USB_REQTYPE_RECIPIENT_INTERFACE:
default:
break;
}
return ret;
}
static int sreq_set_feature(struct usbd_contex *const uds_ctx)
{
struct usb_setup_packet *setup = usbd_get_setup_pkt(uds_ctx);
uint8_t ep = setup->wIndex;
int ret = 0;
/* Not specified if wLength is non-zero, treat as error */
if (setup->wLength) {
errno = -ENOTSUP;
return 0;
}
/*
* TEST_MODE is not supported yet, other are not specified
* in default state, treat as error.
*/
if (usbd_state_is_default(uds_ctx)) {
errno = -EPERM;
return 0;
}
if (usbd_state_is_address(uds_ctx) && setup->wIndex) {
errno = -EPERM;
return 0;
}
switch (setup->RequestType.recipient) {
case USB_REQTYPE_RECIPIENT_DEVICE:
if (setup->wIndex != 0) {
errno = -EPERM;
return 0;
}
if (setup->wValue == USB_SFS_REMOTE_WAKEUP) {
LOG_DBG("Set feature remote wakeup");
uds_ctx->status.rwup = true;
}
break;
case USB_REQTYPE_RECIPIENT_ENDPOINT:
if (setup->wValue == USB_SFS_ENDPOINT_HALT) {
/* UDC checks if endpoint is enabled */
errno = usbd_ep_set_halt(uds_ctx, ep);
ret = (errno == -EPERM) ? errno : 0;
/* TODO: notify class instance */
break;
}
break;
case USB_REQTYPE_RECIPIENT_INTERFACE:
default:
break;
}
return ret;
}
static int std_request_to_device(struct usbd_contex *const uds_ctx,
struct net_buf *const buf)
{
struct usb_setup_packet *setup = usbd_get_setup_pkt(uds_ctx);
int ret;
switch (setup->bRequest) {
case USB_SREQ_SET_ADDRESS:
ret = sreq_set_address(uds_ctx);
break;
case USB_SREQ_SET_CONFIGURATION:
ret = sreq_set_configuration(uds_ctx);
break;
case USB_SREQ_SET_INTERFACE:
ret = sreq_set_interface(uds_ctx);
break;
case USB_SREQ_CLEAR_FEATURE:
ret = sreq_clear_feature(uds_ctx);
break;
case USB_SREQ_SET_FEATURE:
ret = sreq_set_feature(uds_ctx);
break;
default:
errno = -ENOTSUP;
ret = 0;
break;
}
return ret;
}
static int sreq_get_status(struct usbd_contex *const uds_ctx,
struct net_buf *const buf)
{
struct usb_setup_packet *setup = usbd_get_setup_pkt(uds_ctx);
uint8_t ep = setup->wIndex;
uint16_t response = 0;
if (setup->wLength != sizeof(response)) {
errno = -ENOTSUP;
return 0;
}
/* Not specified in default state, treat as error */
if (usbd_state_is_default(uds_ctx)) {
errno = -EPERM;
return 0;
}
if (usbd_state_is_address(uds_ctx) && setup->wIndex) {
errno = -EPERM;
return 0;
}
switch (setup->RequestType.recipient) {
case USB_REQTYPE_RECIPIENT_DEVICE:
if (setup->wIndex != 0) {
errno = -EPERM;
return 0;
}
response = uds_ctx->status.rwup ?
USB_GET_STATUS_REMOTE_WAKEUP : 0;
break;
case USB_REQTYPE_RECIPIENT_ENDPOINT:
response = usbd_ep_is_halted(uds_ctx, ep) ? BIT(0) : 0;
break;
case USB_REQTYPE_RECIPIENT_INTERFACE:
/* Response is always reset to zero.
* TODO: add check if interface exist?
*/
break;
default:
break;
}
if (net_buf_tailroom(buf) < setup->wLength) {
errno = -ENOMEM;
return 0;
}
LOG_DBG("Get Status response 0x%04x", response);
net_buf_add_le16(buf, response);
return 0;
}
static int sreq_get_desc_cfg(struct usbd_contex *const uds_ctx,
struct net_buf *const buf,
const uint8_t idx)
{
struct usb_setup_packet *setup = usbd_get_setup_pkt(uds_ctx);
struct usb_cfg_descriptor *cfg_desc;
struct usbd_config_node *cfg_nd;
struct usbd_class_node *c_nd;
uint16_t len;
cfg_nd = usbd_config_get(uds_ctx, idx + 1);
if (cfg_nd == NULL) {
LOG_ERR("Configuration descriptor %u not found", idx + 1);
errno = -ENOTSUP;
return 0;
}
cfg_desc = cfg_nd->desc;
len = MIN(setup->wLength, net_buf_tailroom(buf));
net_buf_add_mem(buf, cfg_desc, MIN(len, cfg_desc->bLength));
SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
struct usb_desc_header *head = c_nd->data->desc;
size_t desc_len = usbd_class_desc_len(c_nd);
len = MIN(setup->wLength, net_buf_tailroom(buf));
net_buf_add_mem(buf, head, MIN(len, desc_len));
}
LOG_DBG("Get Configuration descriptor %u, len %u", idx, buf->len);
return 0;
}
static int sreq_get_desc(struct usbd_contex *const uds_ctx,
struct net_buf *const buf,
const uint8_t type, const uint8_t idx)
{
struct usb_setup_packet *setup = usbd_get_setup_pkt(uds_ctx);
struct usb_desc_header *head;
size_t len;
if (type == USB_DESC_DEVICE) {
head = uds_ctx->desc;
} else {
head = usbd_get_descriptor(uds_ctx, type, idx);
}
if (head == NULL) {
errno = -ENOTSUP;
return 0;
}
len = MIN(setup->wLength, net_buf_tailroom(buf));
net_buf_add_mem(buf, head, MIN(len, head->bLength));
return 0;
}
static int sreq_get_descriptor(struct usbd_contex *const uds_ctx,
struct net_buf *const buf)
{
struct usb_setup_packet *setup = usbd_get_setup_pkt(uds_ctx);
uint8_t desc_type = USB_GET_DESCRIPTOR_TYPE(setup->wValue);
uint8_t desc_idx = USB_GET_DESCRIPTOR_INDEX(setup->wValue);
LOG_DBG("Get Descriptor request type %u index %u",
desc_type, desc_idx);
switch (desc_type) {
case USB_DESC_DEVICE:
return sreq_get_desc(uds_ctx, buf, USB_DESC_DEVICE, 0);
case USB_DESC_CONFIGURATION:
return sreq_get_desc_cfg(uds_ctx, buf, desc_idx);
case USB_DESC_STRING:
return sreq_get_desc(uds_ctx, buf, USB_DESC_STRING, desc_idx);
case USB_DESC_INTERFACE:
case USB_DESC_ENDPOINT:
case USB_DESC_OTHER_SPEED:
default:
break;
}
errno = -ENOTSUP;
return 0;
}
static int sreq_get_configuration(struct usbd_contex *const uds_ctx,
struct net_buf *const buf)
{
struct usb_setup_packet *setup = usbd_get_setup_pkt(uds_ctx);
uint8_t cfg = usbd_get_config_value(uds_ctx);
/* Not specified in default state, treat as error */
if (usbd_state_is_default(uds_ctx)) {
errno = -EPERM;
return 0;
}
if (setup->wLength != sizeof(cfg)) {
errno = -ENOTSUP;
return 0;
}
if (net_buf_tailroom(buf) < setup->wLength) {
errno = -ENOMEM;
return 0;
}
net_buf_add_u8(buf, cfg);
return 0;
}
static int sreq_get_interface(struct usbd_contex *const uds_ctx,
struct net_buf *const buf)
{
struct usb_setup_packet *setup = usbd_get_setup_pkt(uds_ctx);
struct usb_cfg_descriptor *cfg_desc;
struct usbd_config_node *cfg_nd;
uint8_t cur_alt;
if (setup->RequestType.recipient != USB_REQTYPE_RECIPIENT_INTERFACE) {
errno = -EPERM;
return 0;
}
cfg_nd = usbd_config_get_current(uds_ctx);
cfg_desc = cfg_nd->desc;
if (setup->wIndex > UINT8_MAX ||
setup->wIndex > cfg_desc->bNumInterfaces) {
errno = -ENOTSUP;
return 0;
}
if (usbd_get_alt_value(uds_ctx, setup->wIndex, &cur_alt)) {
errno = -ENOTSUP;
return 0;
}
LOG_DBG("Get Interfaces %u, alternate %u",
setup->wIndex, cur_alt);
if (setup->wLength != sizeof(cur_alt)) {
errno = -ENOTSUP;
return 0;
}
if (net_buf_tailroom(buf) < setup->wLength) {
errno = -ENOMEM;
return 0;
}
net_buf_add_u8(buf, cur_alt);
return 0;
}
static int std_request_to_host(struct usbd_contex *const uds_ctx,
struct net_buf *const buf)
{
struct usb_setup_packet *setup = usbd_get_setup_pkt(uds_ctx);
int ret;
switch (setup->bRequest) {
case USB_SREQ_GET_STATUS:
ret = sreq_get_status(uds_ctx, buf);
break;
case USB_SREQ_GET_DESCRIPTOR:
ret = sreq_get_descriptor(uds_ctx, buf);
break;
case USB_SREQ_GET_CONFIGURATION:
ret = sreq_get_configuration(uds_ctx, buf);
break;
case USB_SREQ_GET_INTERFACE:
ret = sreq_get_interface(uds_ctx, buf);
break;
default:
errno = -ENOTSUP;
ret = 0;
break;
}
return ret;
}
static int nonstd_request(struct usbd_contex *const uds_ctx,
struct net_buf *const dbuf)
{
struct usb_setup_packet *setup = usbd_get_setup_pkt(uds_ctx);
struct usbd_class_node *c_nd = NULL;
int ret = 0;
switch (setup->RequestType.recipient) {
case USB_REQTYPE_RECIPIENT_ENDPOINT:
c_nd = usbd_class_get_by_ep(uds_ctx, setup->wIndex);
break;
case USB_REQTYPE_RECIPIENT_INTERFACE:
c_nd = usbd_class_get_by_iface(uds_ctx, setup->wIndex);
break;
case USB_REQTYPE_RECIPIENT_DEVICE:
c_nd = usbd_class_get_by_req(uds_ctx, setup->bRequest);
break;
default:
break;
}
if (c_nd != NULL) {
if (reqtype_is_to_device(setup)) {
ret = usbd_class_control_to_dev(c_nd, setup, dbuf);
} else {
ret = usbd_class_control_to_host(c_nd, setup, dbuf);
}
} else {
errno = -ENOTSUP;
}
return ret;
}
static int handle_setup_request(struct usbd_contex *const uds_ctx,
struct net_buf *const buf)
{
struct usb_setup_packet *setup = usbd_get_setup_pkt(uds_ctx);
int ret;
errno = 0;
switch (setup->RequestType.type) {
case USB_REQTYPE_TYPE_STANDARD:
if (reqtype_is_to_device(setup)) {
ret = std_request_to_device(uds_ctx, buf);
} else {
ret = std_request_to_host(uds_ctx, buf);
}
break;
case USB_REQTYPE_TYPE_CLASS:
case USB_REQTYPE_TYPE_VENDOR:
ret = nonstd_request(uds_ctx, buf);
break;
default:
errno = -ENOTSUP;
ret = 0;
}
if (errno) {
LOG_INF("protocol error:");
LOG_HEXDUMP_INF(setup, sizeof(*setup), "setup:");
if (errno == -ENOTSUP) {
LOG_INF("not supported");
}
if (errno == -EPERM) {
LOG_INF("not permitted in device state %u",
uds_ctx->ch9_data.state);
}
}
return ret;
}
static int ctrl_xfer_get_setup(struct usbd_contex *const uds_ctx,
struct net_buf *const buf)
{
struct usb_setup_packet *setup = usbd_get_setup_pkt(uds_ctx);
struct net_buf *buf_b;
struct udc_buf_info *bi, *bi_b;
if (buf->len < sizeof(struct usb_setup_packet)) {
return -EINVAL;
}
memcpy(setup, buf->data, sizeof(struct usb_setup_packet));
setup->wValue = sys_le16_to_cpu(setup->wValue);
setup->wIndex = sys_le16_to_cpu(setup->wIndex);
setup->wLength = sys_le16_to_cpu(setup->wLength);
bi = udc_get_buf_info(buf);
buf_b = buf->frags;
if (buf_b == NULL) {
LOG_ERR("Buffer for data|status is missing");
return -ENODATA;
}
bi_b = udc_get_buf_info(buf_b);
if (reqtype_is_to_device(setup)) {
if (setup->wLength) {
if (!bi_b->data) {
LOG_ERR("%p is not data", buf_b);
return -EINVAL;
}
} else {
if (!bi_b->status) {
LOG_ERR("%p is not status", buf_b);
return -EINVAL;
}
}
} else {
if (!setup->wLength) {
LOG_ERR("device-to-host with wLength zero");
return -ENOTSUP;
}
if (!bi_b->data) {
LOG_ERR("%p is not data", buf_b);
return -EINVAL;
}
}
return 0;
}
static struct net_buf *spool_data_out(struct net_buf *const buf)
{
struct net_buf *next_buf = buf;
struct udc_buf_info *bi;
while (next_buf) {
LOG_INF("spool %p", next_buf);
next_buf = net_buf_frag_del(NULL, next_buf);
if (next_buf) {
bi = udc_get_buf_info(next_buf);
if (bi->status) {
return next_buf;
}
}
}
return NULL;
}
int usbd_handle_ctrl_xfer(struct usbd_contex *const uds_ctx,
struct net_buf *const buf, const int err)
{
struct usb_setup_packet *setup = usbd_get_setup_pkt(uds_ctx);
struct udc_buf_info *bi;
int ret = 0;
bi = udc_get_buf_info(buf);
if (USB_EP_GET_IDX(bi->ep)) {
LOG_ERR("Can only handle control requests");
return -EIO;
}
if (err && err != -ENOMEM && !bi->setup) {
LOG_ERR("Control transfer for 0x%02x has error %d, halt",
bi->ep, err);
net_buf_unref(buf);
return err;
}
LOG_INF("Handle control %p ep 0x%02x, len %u, s:%u d:%u s:%u",
buf, bi->ep, buf->len, bi->setup, bi->data, bi->status);
if (bi->setup && bi->ep == USB_CONTROL_EP_OUT) {
struct net_buf *next_buf;
if (ctrl_xfer_get_setup(uds_ctx, buf)) {
LOG_ERR("Malformed setup packet");
net_buf_unref(buf);
goto ctrl_xfer_stall;
}
/* Remove setup packet buffer from the chain */
next_buf = net_buf_frag_del(NULL, buf);
if (next_buf == NULL) {
LOG_ERR("Buffer for data|status is missing");
goto ctrl_xfer_stall;
}
/*
* Handle request and data stage, next_buf is either
* data+status or status buffers.
*/
ret = handle_setup_request(uds_ctx, next_buf);
if (ret) {
net_buf_unref(next_buf);
return ret;
}
if (errno) {
/*
* Halt, only protocol errors are recoverable.
* Free data stage and linked status stage buffer.
*/
net_buf_unref(next_buf);
goto ctrl_xfer_stall;
}
ch9_set_ctrl_type(uds_ctx, CTRL_AWAIT_STATUS_STAGE);
if (reqtype_is_to_device(setup) && setup->wLength) {
/* Enqueue STATUS (IN) buffer */
next_buf = spool_data_out(next_buf);
if (next_buf == NULL) {
LOG_ERR("Buffer for status is missing");
goto ctrl_xfer_stall;
}
ret = usbd_ep_ctrl_enqueue(uds_ctx, next_buf);
} else {
/* Enqueue DATA (IN) or STATUS (OUT) buffer */
ret = usbd_ep_ctrl_enqueue(uds_ctx, next_buf);
}
return ret;
}
if (bi->status && bi->ep == USB_CONTROL_EP_OUT) {
if (ch9_get_ctrl_type(uds_ctx) == CTRL_AWAIT_STATUS_STAGE) {
LOG_INF("s-in-status finished");
} else {
LOG_WRN("Awaited s-in-status not finished");
}
net_buf_unref(buf);
return 0;
}
if (bi->status && bi->ep == USB_CONTROL_EP_IN) {
if (ch9_get_ctrl_type(uds_ctx) == CTRL_AWAIT_STATUS_STAGE) {
LOG_INF("s-(out)-status finished");
if (unlikely(uds_ctx->ch9_data.new_address)) {
return set_address_after_status_stage(uds_ctx);
}
} else {
LOG_WRN("Awaited s-(out)-status not finished");
}
net_buf_unref(buf);
return ret;
}
ctrl_xfer_stall:
/*
* Halt only the endpoint over which the host expects
* data or status stage. This facilitates the work of the drivers.
*
* In the case there is -ENOMEM for data OUT stage halt
* control OUT endpoint.
*/
if (reqtype_is_to_host(setup)) {
ret = udc_ep_set_halt(uds_ctx->dev, USB_CONTROL_EP_IN);
} else if (setup->wLength) {
uint8_t ep = (err == -ENOMEM) ? USB_CONTROL_EP_OUT : USB_CONTROL_EP_IN;
ret = udc_ep_set_halt(uds_ctx->dev, ep);
} else {
ret = udc_ep_set_halt(uds_ctx->dev, USB_CONTROL_EP_IN);
}
ch9_set_ctrl_type(uds_ctx, CTRL_AWAIT_SETUP_DATA);
return ret;
}
int usbd_init_control_pipe(struct usbd_contex *const uds_ctx)
{
uds_ctx->ch9_data.state = USBD_STATE_DEFAULT;
ch9_set_ctrl_type(uds_ctx, CTRL_AWAIT_SETUP_DATA);
return 0;
}

View file

@ -0,0 +1,151 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_USBD_CH9_H
#define ZEPHYR_INCLUDE_USBD_CH9_H
#include <zephyr/usb/usbd.h>
/**
* @brief Check whether USB device is in default state.
*
* @param[in] node Pointer to a device context
*
* @return true if USB device is in default state, false otherwise
*/
static inline bool usbd_state_is_default(const struct usbd_contex *const uds_ctx)
{
return (uds_ctx->ch9_data.state == USBD_STATE_DEFAULT) ? true : false;
}
/**
* @brief Check whether USB device is in address state.
*
* @param[in] node Pointer to a device context
*
* @return true if USB device is in address state, false otherwise
*/
static inline bool usbd_state_is_address(const struct usbd_contex *const uds_ctx)
{
return (uds_ctx->ch9_data.state == USBD_STATE_ADDRESS) ? true : false;
}
/**
* @brief Check whether USB device is in configured state.
*
* @param[in] node Pointer to a device context
*
* @return true if USB device is in configured state, false otherwise
*/
static inline bool usbd_state_is_configured(const struct usbd_contex *const uds_ctx)
{
return (uds_ctx->ch9_data.state == USBD_STATE_CONFIGURED) ? true : false;
}
/**
* @brief Get current configuration value
*
* @param[in] uds_ctx Pointer to a device context
*
* @return current configuration value
*/
static inline uint8_t usbd_get_config_value(const struct usbd_contex *const uds_ctx)
{
return uds_ctx->ch9_data.configuration;
}
/**
* @brief Set current configuration value
*
* @param[in] uds_ctx Pointer to a device context
* @param[in] value New configuration value
*/
static inline void usbd_set_config_value(struct usbd_contex *const uds_ctx,
const uint8_t value)
{
uds_ctx->ch9_data.configuration = value;
}
/**
* @brief Get interface alternate value
*
* @param[in] uds_ctx Pointer to a device context
* @param[in] iface Interface number
* @param[out] alt Alternate value
*
* @return 0 on success, other values on fail.
*/
static inline uint8_t usbd_get_alt_value(const struct usbd_contex *const uds_ctx,
const uint8_t iface,
uint8_t *const alt)
{
if (iface >= USBD_NUMOF_INTERFACES_MAX) {
return -ENOENT;
}
*alt = uds_ctx->ch9_data.alternate[iface];
return 0;
}
/**
* @brief Set interface alternate value
*
* @param[in] uds_ctx Pointer to a device context
* @param[in] iface Interface number
* @param[out] alt Alternate value
*
* @return 0 on success, other values on fail.
*/
static inline uint8_t usbd_set_alt_value(struct usbd_contex *const uds_ctx,
const uint8_t iface,
const uint8_t alt)
{
if (iface >= USBD_NUMOF_INTERFACES_MAX) {
return -ENOENT;
}
uds_ctx->ch9_data.alternate[iface] = alt;
return 0;
}
/**
* @brief Get pointer to last received setup packet
*
* @param[in] uds_ctx Pointer to a device context
*
* @return Pointer to last received setup packet
*/
static inline struct usb_setup_packet *
usbd_get_setup_pkt(struct usbd_contex *const uds_ctx)
{
return &uds_ctx->ch9_data.setup;
}
/**
* @brief Handle control endpoint transfer result
*
* @param[in] uds_ctx Pointer to a device context
* @param[in] buf Pointer to UDC request buffer
* @param[in] err Trasnfer status
*
* @return 0 on success, other values on fail.
*/
int usbd_handle_ctrl_xfer(struct usbd_contex *uds_ctx,
struct net_buf *buf, int err);
/**
* @brief Initialize control pipe to default values
*
* @param[in] uds_ctx Pointer to a device context
*
* @return 0 on success, other values on fail.
*/
int usbd_init_control_pipe(struct usbd_contex *uds_ctx);
#endif /* ZEPHYR_INCLUDE_USBD_CH9_H */

View file

@ -0,0 +1,348 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/usb/usbd.h>
#include <zephyr/toolchain/common.h>
#include <zephyr/drivers/usb/udc.h>
#include <zephyr/sys/slist.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 IS_ENABLED(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_node *const c_nd)
{
struct usbd_class_data *data = c_nd->data;
struct usb_desc_header *dh;
uint8_t *ptr;
size_t len = 0;
if (data->desc != NULL) {
dh = data->desc;
ptr = (uint8_t *)dh;
while (dh->bLength != 0) {
len += dh->bLength;
ptr += dh->bLength;
dh = (struct usb_desc_header *)ptr;
}
}
return len;
}
struct usbd_class_node *
usbd_class_get_by_config(struct usbd_contex *const uds_ctx,
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, cnum);
if (cfg_nd == NULL) {
return NULL;
}
SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
if (c_nd->data->iface_bm & BIT(inum)) {
return c_nd;
}
}
return NULL;
}
struct usbd_class_node *
usbd_class_get_by_iface(struct usbd_contex *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->data->iface_bm & BIT(inum)) {
return c_nd;
}
}
return NULL;
}
static bool xfer_owner_exist(struct usbd_contex *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) {
uint32_t ep_active = c_nd->data->ep_active;
uint32_t ep_assigned = c_nd->data->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_contex *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)) {
return usbd_class_request(bi->owner, buf, err);
}
}
SYS_SLIST_FOR_EACH_CONTAINER(&uds_ctx->configs, cfg_nd, node) {
if (xfer_owner_exist(uds_ctx, cfg_nd, buf)) {
return usbd_class_request(bi->owner, buf, err);
}
}
return -ENODATA;
}
return usbd_class_request(bi->owner, buf, err);
}
struct usbd_class_node *
usbd_class_get_by_ep(struct usbd_contex *const uds_ctx,
const uint8_t ep)
{
struct usbd_class_node *c_nd;
struct usbd_config_node *cfg_nd;
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);
cfg_nd = usbd_config_get(uds_ctx, cfg);
if (cfg_nd == NULL) {
return NULL;
}
SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
if (c_nd->data->ep_assigned & ep_bm) {
return c_nd;
}
}
return NULL;
}
struct usbd_class_node *
usbd_class_get_by_req(struct usbd_contex *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->data->v_reqs == NULL) {
continue;
}
for (int i = 0; i < c_nd->data->v_reqs->len; i++) {
/*
* First instance always wins.
* There is no other way to determine the recipient.
*/
if (c_nd->data->v_reqs->reqs[i] == request) {
return c_nd;
}
}
}
return NULL;
}
static struct usbd_class_node *usbd_class_node_get(const char *name)
{
STRUCT_SECTION_FOREACH(usbd_class_node, c_nd) {
if (strcmp(name, c_nd->name) == 0) {
return c_nd;
}
}
LOG_ERR("USB device class %s not found", name);
return NULL;
}
static int usbd_class_append(struct usbd_contex *const uds_ctx,
struct usbd_class_node *const c_nd,
const uint8_t cfg)
{
struct usbd_config_node *cfg_nd;
cfg_nd = usbd_config_get(uds_ctx, 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_contex *const uds_ctx,
struct usbd_class_node *const c_nd,
const uint8_t cfg)
{
struct usbd_config_node *cfg_nd;
cfg_nd = usbd_config_get(uds_ctx, cfg);
if (cfg_nd == NULL) {
return -ENODATA;
}
if (!sys_slist_find_and_remove(&cfg_nd->class_list, &c_nd->node)) {
return -ENODATA;
}
return 0;
}
/*
* All the functions below are part of public USB device support API.
*/
int usbd_register_class(struct usbd_contex *const uds_ctx,
const char *name,
const uint8_t cfg)
{
struct usbd_class_node *c_nd;
struct usbd_class_data *data;
int ret;
c_nd = usbd_class_node_get(name);
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;
}
data = c_nd->data;
if (data->desc == NULL) {
ret = -EINVAL;
goto register_class_error;
}
/* TODO: does it still need to be atomic ? */
if (atomic_test_bit(&data->state, USBD_CCTX_REGISTERED)) {
LOG_WRN("Class instance allready registered");
ret = -EBUSY;
goto register_class_error;
}
ret = usbd_class_append(uds_ctx, c_nd, cfg);
if (ret == 0) {
/* Initialize pointer back to the device struct */
atomic_set_bit(&data->state, USBD_CCTX_REGISTERED);
data->uds_ctx = uds_ctx;
}
register_class_error:
usbd_device_unlock(uds_ctx);
return ret;
}
int usbd_unregister_class(struct usbd_contex *const uds_ctx,
const char *name,
const uint8_t cfg)
{
struct usbd_class_node *c_nd;
struct usbd_class_data *data;
int ret;
c_nd = usbd_class_node_get(name);
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;
}
data = c_nd->data;
/* TODO: does it still need to be atomic ? */
if (!atomic_test_bit(&data->state, USBD_CCTX_REGISTERED)) {
LOG_WRN("Class instance not registered");
ret = -EBUSY;
goto unregister_class_error;
}
ret = usbd_class_remove(uds_ctx, c_nd, cfg);
if (ret == 0) {
atomic_clear_bit(&data->state, USBD_CCTX_REGISTERED);
data->uds_ctx = NULL;
}
unregister_class_error:
usbd_device_unlock(uds_ctx);
return ret;
}

View file

@ -0,0 +1,96 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_USBD_CLASS_H
#define ZEPHYR_INCLUDE_USBD_CLASS_H
#include <zephyr/usb/usbd.h>
/**
* @brief Handle endpoint transfer result
*
* @param[in] uds_ctx Pointer to device context
* @param[in] buf Pointer to UDC request buffer
* @param[in] err Trasnfer status
*
* @return usbd_class_request() return value
*/
int usbd_class_handle_xfer(struct usbd_contex *const uds_ctx,
struct net_buf *const buf,
const int err);
/**
* @brief Calculate the length of the class descriptor
*
* Calculate the length of the class instance descriptor.
* The descriptor must be terminated by a usb_desc_header structure
* set to {bLength = 0, bDescriptorType = 0,}.
* Calculated length does not include any string descriptors that may be
* used by the class instance.
*
* @param[in] node Pointer to a class node
*
* @return Length of the class descriptor
*/
size_t usbd_class_desc_len(struct usbd_class_node *node);
/**
* @brief Get class context by bInterfaceNumber value
*
* The function searches the class instance list for the interface number.
*
* @param[in] uds_ctx Pointer to device context
* @param[in] inum Interface number
*
* @return Class node pointer or NULL
*/
struct usbd_class_node *usbd_class_get_by_iface(struct usbd_contex *uds_ctx,
uint8_t i_n);
/**
* @brief Get class context by configuration and interface number
*
* @param[in] uds_ctx Pointer to device context
* @param[in] cnum Configuration number
* @param[in] inum Interface number
*
* @return Class node pointer or NULL
*/
struct usbd_class_node *usbd_class_get_by_config(struct usbd_contex *uds_ctx,
uint8_t cnum,
uint8_t inum);
/**
* @brief Get class context by endpoint address
*
* The function searches the class instance list for the endpoint address.
*
* @param[in] uds_ctx Pointer to device context
* @param[in] ep Endpoint address
*
* @return Class node pointer or NULL
*/
struct usbd_class_node *usbd_class_get_by_ep(struct usbd_contex *uds_ctx,
uint8_t ep);
/**
* @brief Get class context by request (bRequest)
*
* The function searches the class instance list and
* compares the vendor request table with request value.
* The function is only used if the request type is Vendor and
* request recipient is Device.
* Accordingly only the first class instance can be found.
*
* @param[in] uds_ctx Pointer to device context
* @param[in] request bRequest value
*
* @return Class node pointer or NULL
*/
struct usbd_class_node *usbd_class_get_by_req(struct usbd_contex *uds_ctx,
uint8_t request);
#endif /* ZEPHYR_INCLUDE_USBD_CLASS_H */

View file

@ -0,0 +1,226 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief USB device stack class instances API
*
* This file contains the USB device stack class instances API.
*/
#ifndef ZEPHYR_INCLUDE_USBD_CLASS_API_H
#define ZEPHYR_INCLUDE_USBD_CLASS_API_H
#include <zephyr/usb/usbd.h>
/**
* @brief Endpoint request completion event handler
*
* This is the event handler for all endpoint accommodated
* by a class instance.
*
* @param[in] dev Pointer to device struct of the class instance
* @param[in] buf Control Request Data buffer
* @param[in] err Result of the transfer. 0 if the transfer was successful.
*/
static inline int usbd_class_request(struct usbd_class_node *const node,
struct net_buf *const buf,
int err)
{
const struct usbd_class_api *api = node->api;
if (api->request != NULL) {
return api->request(node, buf, err);
}
return -ENOTSUP;
}
/**
* @brief USB control request handler
*
* Common handler for all control request.
* Regardless requests recipient, interface or endpoint,
* the USB device core will identify proper class instance
* and call this handler.
* For the vendor type request USBD_VENDOR_REQ macro must be used
* to identify the class, if more than one class instance is
* present, only the first one will be called.
*
* The execution of the handler must not block.
*
* @param[in] dev Pointer to device struct of the class instance
* @param[in] setup Pointer to USB Setup Packet
* @param[in] buf Control Request Data buffer
*
* @return 0 on success, other values on fail.
*/
static inline int usbd_class_control_to_host(struct usbd_class_node *const node,
struct usb_setup_packet *const setup,
struct net_buf *const buf)
{
const struct usbd_class_api *api = node->api;
if (api->control_to_host != NULL) {
return api->control_to_host(node, setup, buf);
}
errno = -ENOTSUP;
return 0;
}
/**
* @brief USB control request handler
*
* Common handler for all control request.
* Regardless requests recipient, interface or endpoint,
* the USB device core will identify proper class instance
* and call this handler.
* For the vendor type request USBD_VENDOR_REQ macro must be used
* to identify the class, if more than one class instance is
* present, only the first one will be called.
*
* The execution of the handler must not block.
*
* @param[in] dev Pointer to device struct of the class instance
* @param[in] setup Pointer to USB Setup Packet
* @param[in] buf Control Request Data buffer
*
* @return 0 on success, other values on fail.
*/
static inline int usbd_class_control_to_dev(struct usbd_class_node *const node,
struct usb_setup_packet *const setup,
struct net_buf *const buf)
{
const struct usbd_class_api *api = node->api;
if (api->control_to_dev != NULL) {
return api->control_to_dev(node, setup, buf);
}
errno = -ENOTSUP;
return 0;
}
/**
* @brief Configuration update handler
*
* Called when the configuration of the interface belonging
* to the instance has been changed, either because of
* Set Configuration or Set Interface request.
*
* The execution of the handler must not block.
*
* @param[in] dev Pointer to device struct of the class instance
* @param[in] setup Pointer to USB setup packet
*/
static inline void usbd_class_update(struct usbd_class_node *const node,
const uint8_t iface,
const uint8_t alternate)
{
const struct usbd_class_api *api = node->api;
if (api->update != NULL) {
api->update(node, iface, alternate);
}
}
/**
* @brief USB suspended handler
*
* @param[in] dev Pointer to device struct of the class instance
* @param[in] event Power management event
*
* @return 0 on success, other values on fail.
*/
static inline void usbd_class_suspended(struct usbd_class_node *const node)
{
const struct usbd_class_api *api = node->api;
if (api->suspended != NULL) {
api->suspended(node);
}
}
/**
* @brief USB resumed handler
*
* @param[in] dev Pointer to device struct of the class instance
* @param[in] event Power management event
*
* @return 0 on success, other values on fail.
*/
static inline void usbd_class_resumed(struct usbd_class_node *const node)
{
const struct usbd_class_api *api = node->api;
if (api->resumed != NULL) {
api->resumed(node);
}
}
/**
* @brief Class associated configuration activ handler
*
* @note The execution of the handler must not block.
*
* @param[in] dev Pointer to device struct of the class instance
*/
static inline void usbd_class_enable(struct usbd_class_node *const node)
{
const struct usbd_class_api *api = node->api;
if (api->enable != NULL) {
api->enable(node);
}
}
/**
* @brief Class associated configuration shutdown handler
*
* @note The execution of the handler must not block.
*
* @param[in] dev Pointer to device struct of the class instance
*/
static inline void usbd_class_disable(struct usbd_class_node *const node)
{
const struct usbd_class_api *api = node->api;
if (api->disable != NULL) {
api->disable(node);
}
}
/**
* @brief Initialization of the class implementation
*
* This is called for each instance during the initialization phase
* after the interface number and endpoint addresses are assigned
* to the corresponding instance.
* It can be used to initialize class specific descriptors or
* underlying systems.
*
* @note If this call fails the core will terminate stack initialization.
*
* @param[in] dev Pointer to device struct of the class instance
*
* @return 0 on success, other values on fail.
*/
static inline int usbd_class_init(struct usbd_class_node *const node)
{
const struct usbd_class_api *api = node->api;
if (api->init != NULL) {
return api->init(node);
}
return -ENOTSUP;
}
#endif /* ZEPHYR_INCLUDE_USBD_CLASS_API_H */

View file

@ -0,0 +1,271 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/usb/udc.h>
#include <zephyr/usb/usbd.h>
#include "usbd_device.h"
#include "usbd_config.h"
#include "usbd_interface.h"
#include "usbd_ch9.h"
#include "usbd_class_api.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(usbd_cfg, CONFIG_USBD_LOG_LEVEL);
struct usbd_config_node *usbd_config_get(struct usbd_contex *const uds_ctx,
const uint8_t cfg)
{
struct usbd_config_node *cfg_nd;
SYS_SLIST_FOR_EACH_CONTAINER(&uds_ctx->configs, cfg_nd, node) {
if (usbd_config_get_value(cfg_nd) == cfg) {
return cfg_nd;
}
}
return NULL;
}
struct usbd_config_node *
usbd_config_get_current(struct usbd_contex *const uds_ctx)
{
if (!usbd_state_is_configured(uds_ctx)) {
LOG_INF("No configuration set (Address state?)");
return NULL;
}
return usbd_config_get(uds_ctx, usbd_get_config_value(uds_ctx));
}
static void usbd_config_classes_enable(struct usbd_config_node *const cfg_nd,
const bool enable)
{
struct usbd_class_node *c_nd;
SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
if (enable) {
usbd_class_enable(c_nd);
} else {
usbd_class_disable(c_nd);
}
}
}
/* Reset configuration to addressed state, shutdown all endpoints */
static int usbd_config_reset(struct usbd_contex *const uds_ctx)
{
struct usbd_config_node *cfg_nd;
int ret = 0;
cfg_nd = usbd_config_get_current(uds_ctx);
if (cfg_nd == NULL) {
return -ENODATA;
}
ret = usbd_interface_shutdown(uds_ctx, cfg_nd);
memset(&uds_ctx->ch9_data.alternate, 0,
USBD_NUMOF_INTERFACES_MAX);
usbd_set_config_value(uds_ctx, 0);
usbd_config_classes_enable(cfg_nd, false);
return ret;
}
bool usbd_config_exist(struct usbd_contex *const uds_ctx,
const uint8_t cfg)
{
struct usbd_config_node *config;
config = usbd_config_get(uds_ctx, cfg);
return (config != NULL) ? true : false;
}
int usbd_config_set(struct usbd_contex *const uds_ctx,
const uint8_t new_cfg)
{
struct usbd_config_node *cfg_nd;
int ret;
if (usbd_get_config_value(uds_ctx) != 0) {
ret = usbd_config_reset(uds_ctx);
if (ret) {
LOG_ERR("Failed to reset configuration");
return ret;
}
}
if (new_cfg == 0) {
usbd_set_config_value(uds_ctx, new_cfg);
return 0;
}
cfg_nd = usbd_config_get(uds_ctx, new_cfg);
if (cfg_nd == NULL) {
return -ENODATA;
}
ret = usbd_interface_default(uds_ctx, cfg_nd);
if (ret) {
return ret;
}
usbd_set_config_value(uds_ctx, new_cfg);
usbd_config_classes_enable(cfg_nd, true);
return 0;
}
/*
* All the functions below are part of public USB device support API.
*/
int usbd_config_attrib_rwup(struct usbd_contex *const uds_ctx,
const uint8_t cfg, const bool enable)
{
struct usbd_config_node *cfg_nd;
struct usb_cfg_descriptor *desc;
struct udc_device_caps caps;
int ret = 0;
usbd_device_lock(uds_ctx);
if (usbd_is_enabled(uds_ctx)) {
ret = -EALREADY;
goto attrib_rwup_exit;
}
caps = udc_caps(uds_ctx->dev);
if (!caps.rwup) {
LOG_ERR("Feature not supported by controller");
ret = -ENOTSUP;
goto attrib_rwup_exit;
}
cfg_nd = usbd_config_get(uds_ctx, cfg);
if (cfg_nd == NULL) {
LOG_INF("Configuration %u not found", cfg);
ret = -ENODATA;
goto attrib_rwup_exit;
}
desc = cfg_nd->desc;
if (enable) {
desc->bmAttributes |= USB_SCD_REMOTE_WAKEUP;
} else {
desc->bmAttributes &= ~USB_SCD_REMOTE_WAKEUP;
}
attrib_rwup_exit:
usbd_device_unlock(uds_ctx);
return ret;
}
int usbd_config_attrib_self(struct usbd_contex *const uds_ctx,
const uint8_t cfg, const bool enable)
{
struct usbd_config_node *cfg_nd;
struct usb_cfg_descriptor *desc;
int ret = 0;
usbd_device_lock(uds_ctx);
if (usbd_is_enabled(uds_ctx)) {
ret = -EALREADY;
goto attrib_self_exit;
}
cfg_nd = usbd_config_get(uds_ctx, cfg);
if (cfg_nd == NULL) {
LOG_INF("Configuration %u not found", cfg);
ret = -ENODATA;
goto attrib_self_exit;
}
desc = cfg_nd->desc;
if (enable) {
desc->bmAttributes |= USB_SCD_SELF_POWERED;
} else {
desc->bmAttributes &= ~USB_SCD_SELF_POWERED;
}
attrib_self_exit:
usbd_device_unlock(uds_ctx);
return ret;
}
int usbd_config_maxpower(struct usbd_contex *const uds_ctx,
const uint8_t cfg, const uint8_t power)
{
struct usbd_config_node *cfg_nd;
struct usb_cfg_descriptor *desc;
int ret = 0;
usbd_device_lock(uds_ctx);
if (usbd_is_enabled(uds_ctx)) {
ret = -EALREADY;
goto maxpower_exit;
}
cfg_nd = usbd_config_get(uds_ctx, cfg);
if (cfg_nd == NULL) {
LOG_INF("Configuration %u not found", cfg);
ret = -ENODATA;
goto maxpower_exit;
}
desc = cfg_nd->desc;
desc->bMaxPower = power;
maxpower_exit:
usbd_device_unlock(uds_ctx);
return ret;
}
int usbd_add_configuration(struct usbd_contex *const uds_ctx,
struct usbd_config_node *const cfg_nd)
{
struct usb_cfg_descriptor *desc = cfg_nd->desc;
int ret = 0;
usbd_device_lock(uds_ctx);
if (usbd_is_initialized(uds_ctx)) {
LOG_ERR("USB device support is initialized");
ret = -EBUSY;
goto add_configuration_exit;
}
if (desc->bmAttributes & USB_SCD_REMOTE_WAKEUP) {
struct udc_device_caps caps = udc_caps(uds_ctx->dev);
if (!caps.rwup) {
LOG_ERR("Feature not supported by controller");
ret = -ENOTSUP;
goto add_configuration_exit;
}
}
if (sys_slist_find_and_remove(&uds_ctx->configs, &cfg_nd->node)) {
LOG_WRN("Configuration %u re-inserted",
usbd_config_get_value(cfg_nd));
} else {
usbd_config_set_value(cfg_nd, usbd_get_num_configs(uds_ctx) + 1);
usbd_set_num_configs(uds_ctx, usbd_get_num_configs(uds_ctx) + 1);
}
sys_slist_append(&uds_ctx->configs, &cfg_nd->node);
usbd_device_unlock(uds_ctx);
add_configuration_exit:
usbd_device_unlock(uds_ctx);
return ret;
}

View file

@ -0,0 +1,88 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_USBD_CONFIG_H
#define ZEPHYR_INCLUDE_USBD_CONFIG_H
#include <zephyr/usb/usbd.h>
/**
* @brief Get configuration descriptor bConfigurationValue value
*
* @param[in] cfg_nd Pointer to a configuration node structure
*
* @return bConfigurationValue value
*/
static inline uint8_t usbd_config_get_value(const struct usbd_config_node *const cfg_nd)
{
struct usb_cfg_descriptor *cfg_desc = cfg_nd->desc;
return cfg_desc->bConfigurationValue;
}
/**
* @brief Set configuration descriptor bConfigurationValue value
*
* @param[in] cfg_nd Pointer to a configuration node structure
*/
static inline void usbd_config_set_value(const struct usbd_config_node *const cfg_nd,
const uint8_t value)
{
struct usb_cfg_descriptor *cfg_desc = cfg_nd->desc;
cfg_desc->bConfigurationValue = value;
}
/**
* @brief Get configuration node
*
* Get configuration node with desired configuration number.
*
* @param[in] ctx Pointer to USB device support context
* @param[in] cfg Configuration number (bConfigurationValue)
*
* @return pointer to configuration node or NULL if does not exist
*/
struct usbd_config_node *usbd_config_get(struct usbd_contex *uds_ctx,
uint8_t cfg);
/**
* @brief Get selected configuration node
*
* Get configuration node based on configuration selected by the host.
*
* @param[in] ctx Pointer to USB device support context
*
* @return pointer to configuration node or NULL if does not exist
*/
struct usbd_config_node *usbd_config_get_current(struct usbd_contex *uds_ctx);
/**
* @brief Check whether a configuration exist
*
* @param[in] ctx Pointer to USB device support context
* @param[in] cfg Configuration number (bConfigurationValue)
*
* @return True if a configuration exist.
*/
bool usbd_config_exist(struct usbd_contex *const uds_ctx,
const uint8_t cfg);
/**
* @brief Setup new USB device configuration
*
* This function disables all active endpoints of current configuration
* and enables all interface alternate 0 endpoints of a new configuration.
* Determined to be called Set Configuration request.
*
* @param[in] ctx Pointer to USB device support context
* @param[in] new_cfg New configuration number (bConfigurationValue)
*
* @return 0 on success, other values on fail.
*/
int usbd_config_set(struct usbd_contex *uds_ctx, uint8_t new_cfg);
#endif /* ZEPHYR_INCLUDE_USBD_CONFIG_H */

View file

@ -0,0 +1,221 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/toolchain/common.h>
#include <zephyr/sys/slist.h>
#include <zephyr/drivers/usb/udc.h>
#include <zephyr/usb/usbd.h>
#include "usbd_device.h"
#include "usbd_config.h"
#include "usbd_init.h"
#include "usbd_ch9.h"
#include "usbd_class.h"
#include "usbd_class_api.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(usbd_core, CONFIG_USBD_LOG_LEVEL);
static K_KERNEL_STACK_DEFINE(usbd_stack, CONFIG_USBD_THREAD_STACK_SIZE);
static struct k_thread usbd_thread_data;
K_MSGQ_DEFINE(usbd_msgq, sizeof(struct udc_event),
CONFIG_USBD_MAX_UDC_MSG, sizeof(uint32_t));
static int usbd_event_carrier(const struct device *dev,
const struct udc_event *const event)
{
return k_msgq_put(&usbd_msgq, event, K_NO_WAIT);
}
static int event_handler_ep_request(struct usbd_contex *const uds_ctx,
const struct udc_event *const event)
{
struct udc_buf_info *bi;
int ret;
bi = udc_get_buf_info(event->buf);
if (USB_EP_GET_IDX(bi->ep) == 0) {
ret = usbd_handle_ctrl_xfer(uds_ctx, event->buf,
event->status);
} else {
ret = usbd_class_handle_xfer(uds_ctx, event->buf,
event->status);
}
if (ret) {
LOG_ERR("unrecoverable error %d, ep 0x%02x, buf %p",
ret, bi->ep, event->buf);
/* TODO: Shutdown USB device gracefully */
k_panic();
}
return ret;
}
static void usbd_class_bcast_event(struct usbd_contex *const uds_ctx,
struct udc_event *const event)
{
struct usbd_config_node *cfg_nd;
struct usbd_class_node *c_nd;
if (!usbd_state_is_configured(uds_ctx)) {
return;
}
cfg_nd = usbd_config_get_current(uds_ctx);
if (cfg_nd == NULL) {
LOG_ERR("Failed to get cfg_nd, despite configured state");
return;
}
SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
switch (event->type) {
case UDC_EVT_SUSPEND:
usbd_class_suspended(c_nd);
break;
case UDC_EVT_RESUME:
usbd_class_resumed(c_nd);
break;
default:
break;
}
}
}
static int event_handler_bus_reset(struct usbd_contex *const uds_ctx)
{
int ret;
LOG_WRN("Bus reset event");
usbd_status_suspended(uds_ctx, false);
ret = udc_set_address(uds_ctx->dev, 0);
if (ret) {
LOG_ERR("Failed to set default address after bus reset");
return ret;
}
ret = usbd_config_set(uds_ctx, 0);
if (ret) {
LOG_ERR("Failed to set default state after bus reset");
return ret;
}
LOG_INF("Actual device speed %d", udc_device_speed(uds_ctx->dev));
uds_ctx->ch9_data.state = USBD_STATE_DEFAULT;
return 0;
}
/* TODO: Add event broadcaster to user application */
static int ALWAYS_INLINE usbd_event_handler(struct usbd_contex *const uds_ctx,
struct udc_event *const event)
{
int ret = 0;
switch (event->type) {
case UDC_EVT_VBUS_REMOVED:
LOG_WRN("VBUS remove event");
break;
case UDC_EVT_VBUS_READY:
LOG_WRN("VBUS detected event");
break;
case UDC_EVT_SUSPEND:
LOG_WRN("SUSPEND event");
usbd_status_suspended(uds_ctx, true);
usbd_class_bcast_event(uds_ctx, event);
break;
case UDC_EVT_RESUME:
LOG_WRN("RESUME event");
usbd_status_suspended(uds_ctx, false);
usbd_class_bcast_event(uds_ctx, event);
break;
case UDC_EVT_SOF:
usbd_class_bcast_event(uds_ctx, event);
break;
case UDC_EVT_RESET:
LOG_WRN("RESET event");
ret = event_handler_bus_reset(uds_ctx);
break;
case UDC_EVT_EP_REQUEST:
ret = event_handler_ep_request(uds_ctx, event);
break;
case UDC_EVT_ERROR:
LOG_ERR("Error event");
break;
default:
break;
};
return ret;
}
static void usbd_thread(void)
{
struct udc_event event;
while (true) {
k_msgq_get(&usbd_msgq, &event, K_FOREVER);
STRUCT_SECTION_FOREACH(usbd_contex, uds_ctx) {
if (uds_ctx->dev == event.dev) {
usbd_event_handler(uds_ctx, &event);
}
}
}
}
int usbd_device_init_core(struct usbd_contex *const uds_ctx)
{
int ret;
ret = udc_init(uds_ctx->dev, usbd_event_carrier);
if (ret != 0) {
LOG_ERR("Failed to init device driver");
return ret;
}
usbd_set_config_value(uds_ctx, 0);
ret = usbd_init_configurations(uds_ctx);
if (ret != 0) {
udc_shutdown(uds_ctx->dev);
return ret;
}
return ret;
}
int usbd_device_shutdown_core(struct usbd_contex *const uds_ctx)
{
return udc_shutdown(uds_ctx->dev);
}
static int usbd_pre_init(const struct device *unused)
{
k_thread_create(&usbd_thread_data, usbd_stack,
K_KERNEL_STACK_SIZEOF(usbd_stack),
(k_thread_entry_t)usbd_thread,
NULL, NULL, NULL,
K_PRIO_COOP(8), 0, K_NO_WAIT);
k_thread_name_set(&usbd_thread_data, "usbd");
LOG_DBG("Available USB class nodes:");
STRUCT_SECTION_FOREACH(usbd_class_node, node) {
atomic_set(&node->data->state, 0);
LOG_DBG("\t%p, name %s", node, node->name);
}
return 0;
}
SYS_INIT(usbd_pre_init, POST_KERNEL, CONFIG_USBD_THREAD_INIT_PRIO);

View file

@ -0,0 +1,2 @@
ITERABLE_SECTION_RAM(usbd_contex, 4)
ITERABLE_SECTION_RAM(usbd_class_node, 4)

View file

@ -0,0 +1,171 @@
/*
* Copyright (c) 2017 PHYTEC Messtechnik GmbH
* Copyright (c) 2017, 2018 Intel Corporation
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/usb/usbd.h>
#include <zephyr/drivers/hwinfo.h>
#include "usbd_device.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(usbd_desc, CONFIG_USBD_LOG_LEVEL);
/*
* The last index of the initializer_string without null character is:
* ascii_idx_max = bLength / 2 - 2
* Use this macro to determine the last index of ASCII7 string.
*/
#define USB_BSTRING_ASCII_IDX_MAX(n) (n / 2 - 2)
/*
* The last index of the bString is:
* utf16le_idx_max = sizeof(initializer_string) * 2 - 2 - 1
* utf16le_idx_max = bLength - 2 - 1
* Use this macro to determine the last index of UTF16LE string.
*/
#define USB_BSTRING_UTF16LE_IDX_MAX(n) (n - 3)
/**
* @brief Transform ASCII-7 string descriptor to UTF16-LE
*
* This function transforms ASCII-7 string descriptor
* into a UTF16-LE.
*
* @param[in] dn Pointer to descriptor node
*/
static void usbd_ascii7_to_utf16le(struct usbd_desc_node *const dn)
{
struct usb_string_descriptor *desc = dn->desc;
int idx_max = USB_BSTRING_UTF16LE_IDX_MAX(desc->bLength);
int ascii_idx_max = USB_BSTRING_ASCII_IDX_MAX(desc->bLength);
uint8_t *buf = (uint8_t *)&desc->bString;
LOG_DBG("idx_max %d, ascii_idx_max %d, buf %p",
idx_max, ascii_idx_max, buf);
for (int i = idx_max; i >= 0; i -= 2) {
LOG_DBG("char %c : %x, idx %d -> %d",
buf[ascii_idx_max],
buf[ascii_idx_max],
ascii_idx_max, i);
__ASSERT(buf[ascii_idx_max] > 0x1F && buf[ascii_idx_max] < 0x7F,
"Only printable ascii-7 characters are allowed in USB "
"string descriptors");
buf[i] = 0U;
buf[i - 1] = buf[ascii_idx_max--];
}
}
/**
* @brief Get common USB descriptor
*
* Get descriptor from internal descrptor list.
*
* @param[in] dn Pointer to descriptor node
*
* @return 0 on success, other values on fail.
*/
static int usbd_get_sn_from_hwid(struct usbd_desc_node *const dn)
{
static const char hex[] = "0123456789ABCDEF";
struct usb_string_descriptor *desc = dn->desc;
uint8_t *desc_data = (uint8_t *)&desc->bString;
uint8_t hwid[16];
ssize_t hwid_len;
ssize_t min_len;
hwid_len = hwinfo_get_device_id(hwid, sizeof(hwid));
if (hwid_len < 0) {
if (hwid_len == -ENOSYS) {
LOG_WRN("hwinfo not implemented");
return 0;
}
return hwid_len;
}
min_len = MIN(hwid_len, desc->bLength / 2);
for (size_t i = 0; i < min_len; i++) {
desc_data[i * 2] = hex[hwid[i] >> 4];
desc_data[i * 2 + 1] = hex[hwid[i] & 0xF];
}
LOG_HEXDUMP_DBG(&desc->bString, desc->bLength, "SerialNumber");
return 0;
}
void *usbd_get_descriptor(struct usbd_contex *const uds_ctx,
const uint8_t type, const uint8_t idx)
{
struct usbd_desc_node *tmp;
struct usb_desc_header *dh;
SYS_SLIST_FOR_EACH_CONTAINER(&uds_ctx->descriptors, tmp, node) {
dh = tmp->desc;
if (tmp->idx == idx && dh->bDescriptorType == type) {
return tmp->desc;
}
}
return NULL;
}
int usbd_add_descriptor(struct usbd_contex *const uds_ctx,
struct usbd_desc_node *const desc_nd)
{
struct usb_desc_header *head;
uint8_t type;
int ret = 0;
usbd_device_lock(uds_ctx);
head = desc_nd->desc;
type = head->bDescriptorType;
if (usbd_get_descriptor(uds_ctx, type, desc_nd->idx)) {
ret = -EALREADY;
goto add_descriptor_error;
}
if (type == USB_DESC_STRING) {
struct usb_device_descriptor *dev_desc = uds_ctx->desc;
if (dev_desc == NULL) {
ret = -EPERM;
goto add_descriptor_error;
}
switch (desc_nd->idx) {
case USBD_DESC_MANUFACTURER_IDX:
dev_desc->iManufacturer = desc_nd->idx;
break;
case USBD_DESC_PRODUCT_IDX:
dev_desc->iProduct = desc_nd->idx;
break;
case USBD_DESC_SERIAL_NUMBER_IDX:
/* FIXME, should we force the use of hwid here? */
ret = usbd_get_sn_from_hwid(desc_nd);
dev_desc->iSerialNumber = desc_nd->idx;
break;
default:
break;
}
if (desc_nd->idx) {
/* FIXME, should we force ascii7 -> utf16le? */
usbd_ascii7_to_utf16le(desc_nd);
}
}
sys_slist_append(&uds_ctx->descriptors, &desc_nd->node);
add_descriptor_error:
usbd_device_unlock(uds_ctx);
return ret;
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_USBD_DESC_H
#define ZEPHYR_INCLUDE_USBD_DESC_H
#include <zephyr/usb/usbd.h>
/**
* @brief Get common USB descriptor
*
* Get descriptor from internal descrptor list.
*
* @param[in] ctx Pointer to USB device support context
* @param[in] type Descriptor type (bDescriptorType)
* @param[in] idx Descriptor index
*
* @return pointer to descriptor or NULL if not found.
*/
void *usbd_get_descriptor(struct usbd_contex *uds_ctx,
const uint8_t type, const uint8_t idx);
#endif /* ZEPHYR_INCLUDE_USBD_DESC_H */

View file

@ -0,0 +1,292 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/usb/udc.h>
#include <zephyr/usb/usbd.h>
#include "usbd_device.h"
#include "usbd_config.h"
#include "usbd_class.h"
#include "usbd_ch9.h"
#include "usbd_desc.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(usbd_dev, CONFIG_USBD_LOG_LEVEL);
/*
* All the functions below are part of public USB device support API.
*/
int usbd_device_set_bcd(struct usbd_contex *const uds_ctx,
const uint16_t bcd)
{
struct usb_device_descriptor *desc = uds_ctx->desc;
int ret = 0;
usbd_device_lock(uds_ctx);
if (usbd_is_enabled(uds_ctx)) {
ret = -EALREADY;
goto set_bcd_exit;
}
desc->bcdUSB = sys_cpu_to_le16(bcd);
set_bcd_exit:
usbd_device_unlock(uds_ctx);
return ret;
}
int usbd_device_set_vid(struct usbd_contex *const uds_ctx,
const uint16_t vid)
{
struct usb_device_descriptor *desc = uds_ctx->desc;
int ret = 0;
usbd_device_lock(uds_ctx);
if (usbd_is_enabled(uds_ctx)) {
ret = -EALREADY;
goto set_vid_exit;
}
desc->idVendor = sys_cpu_to_le16(vid);
set_vid_exit:
usbd_device_unlock(uds_ctx);
return ret;
}
int usbd_device_set_pid(struct usbd_contex *const uds_ctx,
const uint16_t pid)
{
struct usb_device_descriptor *desc = uds_ctx->desc;
int ret = 0;
usbd_device_lock(uds_ctx);
if (usbd_is_enabled(uds_ctx)) {
ret = -EALREADY;
goto set_pid_exit;
}
desc->idProduct = sys_cpu_to_le16(pid);
set_pid_exit:
usbd_device_unlock(uds_ctx);
return ret;
}
int usbd_device_set_class(struct usbd_contex *const uds_ctx,
const uint8_t value)
{
struct usb_device_descriptor *desc = uds_ctx->desc;
int ret = 0;
usbd_device_lock(uds_ctx);
if (usbd_is_enabled(uds_ctx)) {
ret = -EALREADY;
goto set_class_exit;
}
desc->bDeviceClass = value;
set_class_exit:
usbd_device_unlock(uds_ctx);
return ret;
}
int usbd_device_set_subclass(struct usbd_contex *const uds_ctx,
const uint8_t value)
{
struct usb_device_descriptor *desc = uds_ctx->desc;
int ret = 0;
usbd_device_lock(uds_ctx);
if (usbd_is_enabled(uds_ctx)) {
ret = -EALREADY;
goto set_subclass_exit;
}
desc->bDeviceSubClass = value;
set_subclass_exit:
usbd_device_unlock(uds_ctx);
return ret;
}
int usbd_device_set_proto(struct usbd_contex *const uds_ctx,
const uint8_t value)
{
struct usb_device_descriptor *desc = uds_ctx->desc;
int ret = 0;
usbd_device_lock(uds_ctx);
if (usbd_is_enabled(uds_ctx)) {
ret = -EALREADY;
goto set_proto_exit;
}
desc->bDeviceProtocol = value;
set_proto_exit:
usbd_device_unlock(uds_ctx);
return ret;
}
int usbd_wakeup_request(struct usbd_contex *const uds_ctx)
{
struct udc_device_caps caps = udc_caps(uds_ctx->dev);
int ret = 0;
usbd_device_lock(uds_ctx);
if (!caps.rwup) {
LOG_ERR("Remote wakeup feature not supported");
ret = -ENOTSUP;
goto wakeup_request_error;
}
if (!uds_ctx->status.rwup || !usbd_is_suspended(uds_ctx)) {
LOG_ERR("Remote wakeup feature not enabled or not suspended");
ret = -EACCES;
goto wakeup_request_error;
}
ret = udc_host_wakeup(uds_ctx->dev);
wakeup_request_error:
usbd_device_unlock(uds_ctx);
return ret;
}
bool usbd_is_suspended(struct usbd_contex *uds_ctx)
{
return uds_ctx->status.suspended;
}
int usbd_init(struct usbd_contex *const uds_ctx)
{
int ret;
usbd_device_lock(uds_ctx);
if (uds_ctx->dev == NULL) {
ret = -ENODEV;
goto init_exit;
}
if (usbd_is_initialized(uds_ctx)) {
LOG_WRN("USB device support is already initialized");
ret = -EALREADY;
goto init_exit;
}
if (!device_is_ready(uds_ctx->dev)) {
LOG_ERR("USB device controller is not ready");
ret = -ENODEV;
goto init_exit;
}
ret = usbd_device_init_core(uds_ctx);
if (ret) {
goto init_exit;
}
memset(&uds_ctx->ch9_data, 0, sizeof(struct usbd_ch9_data));
uds_ctx->status.initialized = true;
init_exit:
usbd_device_unlock(uds_ctx);
return ret;
}
int usbd_enable(struct usbd_contex *const uds_ctx)
{
int ret;
usbd_device_lock(uds_ctx);
if (!usbd_is_initialized(uds_ctx)) {
LOG_WRN("USB device support is not initialized");
ret = -EPERM;
goto enable_exit;
}
if (usbd_is_enabled(uds_ctx)) {
LOG_WRN("USB device support is already enabled");
ret = -EALREADY;
goto enable_exit;
}
ret = udc_enable(uds_ctx->dev);
if (ret != 0) {
LOG_ERR("Failed to enable controller");
goto enable_exit;
}
ret = usbd_init_control_pipe(uds_ctx);
if (ret != 0) {
udc_disable(uds_ctx->dev);
goto enable_exit;
}
uds_ctx->status.enabled = true;
enable_exit:
usbd_device_unlock(uds_ctx);
return ret;
}
int usbd_disable(struct usbd_contex *const uds_ctx)
{
int ret;
if (!usbd_is_enabled(uds_ctx)) {
LOG_WRN("USB device support is already disabled");
return -EALREADY;
}
usbd_device_lock(uds_ctx);
ret = usbd_config_set(uds_ctx, 0);
if (ret) {
LOG_ERR("Failed to reset configuration");
}
ret = udc_disable(uds_ctx->dev);
if (ret) {
LOG_ERR("Failed to disable USB device");
}
uds_ctx->status.enabled = false;
usbd_device_unlock(uds_ctx);
return ret;
}
int usbd_shutdown(struct usbd_contex *const uds_ctx)
{
int ret;
usbd_device_lock(uds_ctx);
/* TODO: control request dequeue ? */
ret = usbd_device_shutdown_core(uds_ctx);
if (ret) {
LOG_ERR("Failed to shutdown USB device");
}
uds_ctx->status.initialized = false;
usbd_device_unlock(uds_ctx);
return 0;
}

View file

@ -0,0 +1,115 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_USBD_DEVICE_H
#define ZEPHYR_INCLUDE_USBD_DEVICE_H
#include <zephyr/usb/usbd.h>
/**
* @brief Get device descriptor bNumConfigurations value
*
* @param[in] uds_ctx Pointer to a device context
*
* @return bNumConfigurations value
*/
static inline uint8_t usbd_get_num_configs(const struct usbd_contex *const uds_ctx)
{
struct usb_device_descriptor *desc = uds_ctx->desc;
return desc->bNumConfigurations;
}
/**
* @brief Set device descriptor bNumConfigurations value
*
* @param[in] uds_ctx Pointer to a device context
* @param[in] value new bNumConfigurations value
*/
static inline void usbd_set_num_configs(struct usbd_contex *const uds_ctx,
const uint8_t value)
{
struct usb_device_descriptor *desc = uds_ctx->desc;
desc->bNumConfigurations = value;
}
/**
* @brief Check whether USB device is enabled
*
* @param[in] node Pointer to a device context
*
* @return true if USB device is in enabled, false otherwise
*/
static inline bool usbd_is_enabled(const struct usbd_contex *const uds_ctx)
{
return uds_ctx->status.enabled;
}
/**
* @brief Check whether USB device is enabled
*
* @param[in] node Pointer to a device context
*
* @return true if USB device is in enabled, false otherwise
*/
static inline bool usbd_is_initialized(const struct usbd_contex *const uds_ctx)
{
return uds_ctx->status.initialized;
}
/**
* @brief Set device suspended status
*
* @param[in] uds_ctx Pointer to a device context
* @param[in] value new suspended value
*/
static inline void usbd_status_suspended(struct usbd_contex *const uds_ctx,
const bool value)
{
uds_ctx->status.suspended = value;
}
/**
* @brief Lock USB device stack context
*
* @param[in] node Pointer to a device context
*/
static inline void usbd_device_lock(struct usbd_contex *const uds_ctx)
{
k_mutex_lock(&uds_ctx->mutex, K_FOREVER);
}
/**
* @brief Lock USB device stack context
*
* @param[in] node Pointer to a device context
*/
static inline void usbd_device_unlock(struct usbd_contex *const uds_ctx)
{
k_mutex_unlock(&uds_ctx->mutex);
}
/**
* @brief Init USB device stack core
*
* @param[in] uds_ctx Pointer to a device context
*
* @return 0 on success, other values on fail.
*/
int usbd_device_init_core(struct usbd_contex *uds_ctx);
/**
* @brief Shutdown USB device stack core
*
* @param[in] uds_ctx Pointer to a device context
*
* @return 0 on success, other values on fail.
*/
int usbd_device_shutdown_core(struct usbd_contex *const uds_ctx);
#endif /* ZEPHYR_INCLUDE_USBD_DEVICE_H */

View file

@ -0,0 +1,195 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/sys/util.h>
#include <zephyr/drivers/usb/udc.h>
#include <zephyr/usb/usbd.h>
#include "usbd_device.h"
#include "usbd_class.h"
#include "usbd_ch9.h"
#include "usbd_desc.h"
#include "usbd_endpoint.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(usbd_ep, CONFIG_USBD_LOG_LEVEL);
int usbd_ep_enable(const struct device *dev,
const struct usb_ep_descriptor *const ed,
uint32_t *const ep_bm)
{
int ret;
ret = udc_ep_enable(dev, ed->bEndpointAddress, ed->bmAttributes,
ed->wMaxPacketSize, ed->bInterval);
if (ret == 0) {
usbd_ep_bm_set(ep_bm, ed->bEndpointAddress);
}
return ret;
}
int usbd_ep_disable(const struct device *dev,
const uint8_t ep,
uint32_t *const ep_bm)
{
int ret;
ret = udc_ep_disable(dev, ep);
if (ret) {
return ret;
}
usbd_ep_bm_clear(ep_bm, ep);
ret = udc_ep_dequeue(dev, ep);
if (ret) {
return ret;
}
k_yield();
return ret;
}
static void usbd_ep_ctrl_set_zlp(struct usbd_contex *const uds_ctx,
struct net_buf *const buf)
{
struct usb_setup_packet *setup = usbd_get_setup_pkt(uds_ctx);
size_t min_len = MIN(setup->wLength, buf->len);
if (buf->len == 0) {
return;
}
/*
* Set ZLP flag when host asks for a bigger length and the
* last chunk is wMaxPacketSize long, to indicate the last
* packet.
*/
if (setup->wLength > min_len && !(min_len % USB_CONTROL_EP_MPS)) {
/*
* Transfer length is less as requested by wLength and
* is multiple of wMaxPacketSize.
*/
LOG_DBG("add ZLP, wLength %u buf length %u",
setup->wLength, min_len);
udc_ep_buf_set_zlp(buf);
}
}
/*
* All the functions below are part of public USB device support API.
*/
struct net_buf *usbd_ep_ctrl_buf_alloc(struct usbd_contex *const uds_ctx,
const uint8_t ep, const size_t size)
{
if (USB_EP_GET_IDX(ep)) {
/* Not a control endpoint */
return NULL;
}
return udc_ep_buf_alloc(uds_ctx->dev, ep, size);
}
int usbd_ep_ctrl_enqueue(struct usbd_contex *const uds_ctx,
struct net_buf *const buf)
{
struct udc_buf_info *bi;
bi = udc_get_buf_info(buf);
if (USB_EP_GET_IDX(bi->ep)) {
/* Not a control endpoint */
return -EINVAL;
}
if (USB_EP_DIR_IS_IN(bi->ep)) {
if (usbd_is_suspended(uds_ctx)) {
LOG_ERR("device is suspended");
return -EPERM;
}
usbd_ep_ctrl_set_zlp(uds_ctx, buf);
}
return udc_ep_enqueue(uds_ctx->dev, buf);
}
struct net_buf *usbd_ep_buf_alloc(const struct usbd_class_node *const c_nd,
const uint8_t ep, const size_t size)
{
struct usbd_contex *uds_ctx = c_nd->data->uds_ctx;
return udc_ep_buf_alloc(uds_ctx->dev, ep, size);
}
int usbd_ep_enqueue(const struct usbd_class_node *const c_nd,
struct net_buf *const buf)
{
struct usbd_contex *uds_ctx = c_nd->data->uds_ctx;
struct udc_buf_info *bi = udc_get_buf_info(buf);
if (USB_EP_DIR_IS_IN(bi->ep)) {
if (usbd_is_suspended(uds_ctx)) {
return -EPERM;
}
}
bi->owner = (void *)c_nd;
return udc_ep_enqueue(uds_ctx->dev, buf);
}
int usbd_ep_buf_free(struct usbd_contex *const uds_ctx, struct net_buf *buf)
{
return udc_ep_buf_free(uds_ctx->dev, buf);
}
int usbd_ep_dequeue(struct usbd_contex *const uds_ctx, const uint8_t ep)
{
return udc_ep_dequeue(uds_ctx->dev, ep);
}
int usbd_ep_set_halt(struct usbd_contex *const uds_ctx, const uint8_t ep)
{
struct usbd_ch9_data *ch9_data = &uds_ctx->ch9_data;
int ret;
ret = udc_ep_set_halt(uds_ctx->dev, ep);
if (ret) {
LOG_WRN("Set halt 0x%02x failed", ep);
return ret;
}
usbd_ep_bm_set(&ch9_data->ep_halt, ep);
return ret;
}
int usbd_ep_clear_halt(struct usbd_contex *const uds_ctx, const uint8_t ep)
{
struct usbd_ch9_data *ch9_data = &uds_ctx->ch9_data;
int ret;
ret = udc_ep_clear_halt(uds_ctx->dev, ep);
if (ret) {
LOG_WRN("Clear halt 0x%02x failed", ep);
return ret;
}
usbd_ep_bm_clear(&ch9_data->ep_halt, ep);
return ret;
}
bool usbd_ep_is_halted(struct usbd_contex *const uds_ctx, const uint8_t ep)
{
struct usbd_ch9_data *ch9_data = &uds_ctx->ch9_data;
return usbd_ep_bm_is_set(&ch9_data->ep_halt, ep);
}

View file

@ -0,0 +1,99 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_USBD_ENDPOINT_H
#define ZEPHYR_INCLUDE_USBD_ENDPOINT_H
#include <zephyr/usb/usbd.h>
/**
* @brief Set bit associated with the endpoint
*
* The IN endpoints are mapped in the upper nibble.
*
* @param[in] ep_bm Pointer to endpoint bitmap
* @param[in] ep Endpoint address
*/
static inline void usbd_ep_bm_set(uint32_t *const ep_bm, const uint8_t ep)
{
if (USB_EP_DIR_IS_IN(ep)) {
*ep_bm |= BIT(USB_EP_GET_IDX(ep) + 16U);
} else {
*ep_bm |= BIT(USB_EP_GET_IDX(ep));
}
}
/**
* @brief Clear bit associated with the endpoint
*
* The IN endpoints are mapped in the upper nibble.
*
* @param[in] ep_bm Pointer to endpoint bitmap
* @param[in] ep Endpoint address
*/
static inline void usbd_ep_bm_clear(uint32_t *const ep_bm, const uint8_t ep)
{
if (USB_EP_DIR_IS_IN(ep)) {
*ep_bm &= ~BIT(USB_EP_GET_IDX(ep) + 16U);
} else {
*ep_bm &= ~BIT(USB_EP_GET_IDX(ep));
}
}
/**
* @brief Check whether bit associated with the endpoint is set
*
* The IN endpoints are mapped in the upper nibble.
*
* @param[in] ep_bm Pointer to endpoint bitmap
* @param[in] ep Endpoint address
*
* @return true if bit is set, false otherwise
*/
static inline bool usbd_ep_bm_is_set(const uint32_t *const ep_bm, const uint8_t ep)
{
unsigned int bit;
if (USB_EP_DIR_IS_IN(ep)) {
bit = USB_EP_GET_IDX(ep) + 16U;
} else {
bit = USB_EP_GET_IDX(ep);
}
return (*ep_bm & BIT(bit)) ? true : false;
}
/**
* @brief Enable endpoint
*
* This function enables endpoint and sets corresponding bit.
*
* @param[in] dev Pointer to UDC device
* @param[in] ed Pointer to endpoint descriptor
* @param[in] ep_bm Pointer to endpoint bitmap
*
* @return 0 on success, other values on fail.
*/
int usbd_ep_enable(const struct device *dev,
const struct usb_ep_descriptor *const ed,
uint32_t *const ep_bm);
/**
* @brief Disable endpoint
*
* This function disables endpoint and clears corresponding bit.
*
* @param[in] dev Pointer to UDC device
* @param[in] ep Endpoint address
* @param[in] ep_bm Pointer to endpoint bitmap
*
* @return 0 on success, other values on fail.
*/
int usbd_ep_disable(const struct device *dev,
const uint8_t ep,
uint32_t *const ep_bm);
#endif /* ZEPHYR_INCLUDE_USBD_ENDPOINT_H */

View file

@ -0,0 +1,259 @@
/*
* Copyright (c) 2021-2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/sys/slist.h>
#include <zephyr/drivers/usb/udc.h>
#include <zephyr/usb/usbd.h>
#include "usbd_device.h"
#include "usbd_config.h"
#include "usbd_class.h"
#include "usbd_class_api.h"
#include "usbd_endpoint.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(usbd_init, CONFIG_USBD_LOG_LEVEL);
/* TODO: Allow to disable automatic assignment of endpoint features */
/* Assign endpoint address and update wMaxPacketSize */
static int assign_ep_addr(const struct device *dev,
struct usb_ep_descriptor *const ed,
uint32_t *const config_ep_bm,
uint32_t *const class_ep_bm)
{
int ret = -ENODEV;
for (unsigned int idx = 1; idx < 16U; idx++) {
uint16_t mps = ed->wMaxPacketSize;
uint8_t ep;
if (USB_EP_DIR_IS_IN(ed->bEndpointAddress)) {
ep = USB_EP_DIR_IN | idx;
} else {
ep = idx;
}
if (usbd_ep_bm_is_set(config_ep_bm, ep) ||
usbd_ep_bm_is_set(class_ep_bm, ep)) {
continue;
}
ret = udc_ep_try_config(dev, ep,
ed->bmAttributes, &mps,
ed->bInterval);
if (ret == 0) {
LOG_DBG("ep 0x%02x -> 0x%02x", ed->bEndpointAddress, ep);
ed->bEndpointAddress = ep;
ed->wMaxPacketSize = mps;
usbd_ep_bm_set(class_ep_bm, ed->bEndpointAddress);
usbd_ep_bm_set(config_ep_bm, ed->bEndpointAddress);
return 0;
}
}
return ret;
}
/* Unassign all endpoint of a class instance based on class_ep_bm */
static int unassign_eps(struct usbd_contex *const uds_ctx,
uint32_t *const config_ep_bm,
uint32_t *const class_ep_bm)
{
for (unsigned int idx = 1; idx < 16U && *class_ep_bm; idx++) {
uint8_t ep_in = USB_EP_DIR_IN | idx;
uint8_t ep_out = idx;
if (usbd_ep_bm_is_set(class_ep_bm, ep_in)) {
if (!usbd_ep_bm_is_set(config_ep_bm, ep_in)) {
LOG_ERR("Endpoing 0x%02x not assigned", ep_in);
return -EINVAL;
}
usbd_ep_bm_clear(config_ep_bm, ep_in);
usbd_ep_bm_clear(class_ep_bm, ep_in);
}
if (usbd_ep_bm_is_set(class_ep_bm, ep_out)) {
if (!usbd_ep_bm_is_set(config_ep_bm, ep_out)) {
LOG_ERR("Endpoing 0x%02x not assigned", ep_out);
return -EINVAL;
}
usbd_ep_bm_clear(config_ep_bm, ep_out);
usbd_ep_bm_clear(class_ep_bm, ep_out);
}
}
return 0;
}
/*
* Configure all interfaces and endpoints of a class instance
*
* The total number of interfaces is stored in the configuration descriptor's
* value bNumInterfaces. This value is reset at the beginning of configuration
* initialization and is increased according to the number of interfaces.
* The respective bInterfaceNumber must be assigned to all interfaces
* of a class instance.
*
* Like bInterfaceNumber the endpoint addresses must be assigned
* for all registered instances and respective endpoint descriptors.
* We use config_ep_bm variable as map for assigned endpoint for an
* USB device configuration.
*/
static int init_configuration_inst(struct usbd_contex *const uds_ctx,
struct usbd_class_data *const data,
uint32_t *const config_ep_bm,
uint8_t *const nif)
{
struct usb_desc_header *dh = data->desc;
uint8_t *ptr = (uint8_t *)dh;
struct usb_if_descriptor *ifd = NULL;
struct usb_ep_descriptor *ed;
uint32_t class_ep_bm = 0;
uint8_t tmp_nif;
int ret;
tmp_nif = *nif;
data->iface_bm = 0U;
data->ep_active = 0U;
while (dh->bLength != 0) {
if (dh->bDescriptorType == USB_DESC_INTERFACE) {
ifd = (struct usb_if_descriptor *)ptr;
data->ep_active |= class_ep_bm;
if (ifd->bAlternateSetting == 0) {
ifd->bInterfaceNumber = tmp_nif;
data->iface_bm |= BIT(tmp_nif);
tmp_nif++;
} else {
ifd->bInterfaceNumber = tmp_nif - 1;
/*
* Unassign endpoints from last alternate,
* to work properly it requires that the
* characteristics of endpoints in alternate
* interfaces are ascending.
*/
unassign_eps(uds_ctx, config_ep_bm, &class_ep_bm);
}
class_ep_bm = 0;
LOG_INF("interface %u alternate %u",
ifd->bInterfaceNumber, ifd->bAlternateSetting);
}
if (dh->bDescriptorType == USB_DESC_ENDPOINT) {
ed = (struct usb_ep_descriptor *)ptr;
ret = assign_ep_addr(uds_ctx->dev, ed,
config_ep_bm, &class_ep_bm);
if (ret) {
return ret;
}
LOG_INF("\tep 0x%02x interface ep-bm 0x%08x",
ed->bEndpointAddress, class_ep_bm);
}
ptr += dh->bLength;
dh = (struct usb_desc_header *)ptr;
}
if (tmp_nif <= *nif) {
return -EINVAL;
}
*nif = tmp_nif;
data->ep_active |= class_ep_bm;
LOG_INF("Instance iface-bm 0x%08x ep-bm 0x%08x",
data->iface_bm, data->ep_active);
return 0;
}
/*
* Initialize a device configuration
*
* Iterate on a list of all classes in a configuration
*/
static int init_configuration(struct usbd_contex *const uds_ctx,
struct usbd_config_node *const cfg_nd)
{
struct usb_cfg_descriptor *cfg_desc = cfg_nd->desc;
struct usbd_class_node *c_nd;
uint32_t config_ep_bm = 0;
size_t cfg_len = 0;
uint8_t nif = 0;
int ret;
SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
ret = init_configuration_inst(uds_ctx, c_nd->data,
&config_ep_bm, &nif);
if (ret != 0) {
LOG_ERR("Failed to assign endpoint addresses");
return ret;
}
ret = usbd_class_init(c_nd);
if (ret != 0) {
LOG_ERR("Failed to initialize class instance");
return ret;
}
LOG_INF("Init class node %p, descriptor length %u",
c_nd, usbd_class_desc_len(c_nd));
cfg_len += usbd_class_desc_len(c_nd);
}
/* Update wTotalLength and bNumInterfaces of configuration descriptor */
sys_put_le16(sizeof(struct usb_cfg_descriptor) + cfg_len,
(uint8_t *)&cfg_desc->wTotalLength);
cfg_desc->bNumInterfaces = nif;
LOG_INF("bNumInterfaces %u wTotalLength %u",
cfg_desc->bNumInterfaces,
cfg_desc->wTotalLength);
/* Finally reset configuration's endpoint assignment */
SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
c_nd->data->ep_assigned = c_nd->data->ep_active;
ret = unassign_eps(uds_ctx, &config_ep_bm, &c_nd->data->ep_active);
if (ret != 0) {
return ret;
}
}
return 0;
}
int usbd_init_configurations(struct usbd_contex *const uds_ctx)
{
struct usbd_config_node *cfg_nd;
SYS_SLIST_FOR_EACH_CONTAINER(&uds_ctx->configs, cfg_nd, node) {
int ret;
ret = init_configuration(uds_ctx, cfg_nd);
if (ret) {
LOG_ERR("Failed to init configuration %u",
usbd_config_get_value(cfg_nd));
return ret;
}
LOG_INF("bNumConfigurations %u",
usbd_get_num_configs(uds_ctx));
}
return 0;
}

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_USBD_INIT_H
#define ZEPHYR_INCLUDE_USBD_INIT_H
#include <zephyr/usb/usbd.h>
/**
* @brief Initialize all device configurations
*
* Iterate on a list of all configurations and initialize all
* configurations and interfaces. Called only once in sequence per
* usbd_init().
*
* @param[in] uds_ctx Pointer to a device context
*
* @return 0 on success, other values on fail.
*/
int usbd_init_configurations(struct usbd_contex *const uds_ctx);
#endif /* ZEPHYR_INCLUDE_USBD_INIT_H */

View file

@ -0,0 +1,208 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/usb/udc.h>
#include <zephyr/usb/usbd.h>
#include "usbd_device.h"
#include "usbd_class.h"
#include "usbd_class_api.h"
#include "usbd_endpoint.h"
#include "usbd_ch9.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(usbd_iface, CONFIG_USBD_LOG_LEVEL);
enum ep_op {
EP_OP_TEST, /* Test if interface alternate available */
EP_OP_UP, /* Enable endpoint and update endpoints bitmap */
EP_OP_DOWN, /* Disable endpoint and update endpoints bitmap */
};
static int handle_ep_op(struct usbd_contex *const uds_ctx,
const enum ep_op op,
struct usb_ep_descriptor *const ed,
uint32_t *const ep_bm)
{
const uint8_t ep = ed->bEndpointAddress;
int ret;
switch (op) {
case EP_OP_TEST:
ret = 0;
break;
case EP_OP_UP:
ret = usbd_ep_enable(uds_ctx->dev, ed, ep_bm);
break;
case EP_OP_DOWN:
ret = usbd_ep_disable(uds_ctx->dev, ep, ep_bm);
break;
}
if (ret) {
LOG_ERR("Failed to handle op %d, ep 0x%02x, bm 0x%08x, %d",
op, ep, *ep_bm, ret);
}
return ret;
}
static int usbd_interface_modify(struct usbd_contex *const uds_ctx,
struct usbd_class_node *const node,
const enum ep_op op,
const uint8_t iface,
const uint8_t alt)
{
struct usb_desc_header *dh;
bool found_iface = false;
uint8_t *ptr;
int ret;
dh = node->data->desc;
ptr = (uint8_t *)dh;
while (dh->bLength != 0) {
struct usb_if_descriptor *ifd;
struct usb_ep_descriptor *ed;
if (dh->bDescriptorType == USB_DESC_INTERFACE) {
ifd = (struct usb_if_descriptor *)ptr;
if (found_iface) {
break;
}
if (ifd->bInterfaceNumber == iface &&
ifd->bAlternateSetting == alt) {
found_iface = true;
LOG_DBG("Found interface %u %p", iface, node);
if (ifd->bNumEndpoints == 0) {
LOG_INF("No endpoints, skip interface");
break;
}
}
}
if (dh->bDescriptorType == USB_DESC_ENDPOINT && found_iface) {
ed = (struct usb_ep_descriptor *)ptr;
ret = handle_ep_op(uds_ctx, op, ed, &node->data->ep_active);
if (ret) {
return ret;
}
LOG_INF("Modify interface %u ep 0x%02x by op %u ep_bm %x",
iface, ed->bEndpointAddress,
op, node->data->ep_active);
}
ptr += dh->bLength;
dh = (struct usb_desc_header *)ptr;
}
/* TODO: rollback ep_bm on error? */
return found_iface ? 0 : -ENODATA;
}
int usbd_interface_shutdown(struct usbd_contex *const uds_ctx,
struct usbd_config_node *const cfg_nd)
{
struct usbd_class_node *c_nd;
SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
uint32_t *ep_bm = &c_nd->data->ep_active;
for (int idx = 1; idx < 16 && *ep_bm; idx++) {
uint8_t ep_in = USB_EP_DIR_IN | idx;
uint8_t ep_out = idx;
int ret;
if (usbd_ep_bm_is_set(ep_bm, ep_in)) {
ret = usbd_ep_disable(uds_ctx->dev, ep_in, ep_bm);
if (ret) {
return ret;
}
}
if (usbd_ep_bm_is_set(ep_bm, ep_out)) {
ret = usbd_ep_disable(uds_ctx->dev, ep_out, ep_bm);
if (ret) {
return ret;
}
}
}
}
return 0;
}
int usbd_interface_default(struct usbd_contex *const uds_ctx,
struct usbd_config_node *const cfg_nd)
{
struct usb_cfg_descriptor *desc = cfg_nd->desc;
const uint8_t new_cfg = desc->bConfigurationValue;
/* Set default alternate for all interfaces */
for (int i = 0; i < desc->bNumInterfaces; i++) {
struct usbd_class_node *class;
int ret;
class = usbd_class_get_by_config(uds_ctx, new_cfg, i);
if (class == NULL) {
return -ENODATA;
}
ret = usbd_interface_modify(uds_ctx, class, EP_OP_UP, i, 0);
if (ret) {
return ret;
}
}
return 0;
}
int usbd_interface_set(struct usbd_contex *const uds_ctx,
const uint8_t iface,
const uint8_t alt)
{
struct usbd_class_node *class;
uint8_t cur_alt;
int ret;
ret = usbd_get_alt_value(uds_ctx, iface, &cur_alt);
if (ret) {
return ret;
}
class = usbd_class_get_by_iface(uds_ctx, iface);
if (class == NULL) {
return -ENODATA;
}
/* Test if interface or interface alternate exist */
ret = usbd_interface_modify(uds_ctx, class, EP_OP_TEST, iface, alt);
if (ret) {
return -ENOENT;
}
/* Shutdown current interface alternate */
ret = usbd_interface_modify(uds_ctx, class, EP_OP_DOWN, iface, cur_alt);
if (ret) {
return ret;
}
/* Setup new interface alternate */
ret = usbd_interface_modify(uds_ctx, class, EP_OP_UP, iface, alt);
if (ret) {
/* TODO: rollback on error? */
return ret;
}
usbd_class_update(class, iface, alt);
usbd_set_alt_value(uds_ctx, iface, alt);
return 0;
}

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_USBD_INTERFACE_H
#define ZEPHYR_INCLUDE_USBD_INTERFACE_H
#include <zephyr/usb/usbd.h>
/**
* @brief Shutdown all interfaces in a configuration.
*
* @param[in] uds_ctx Pointer to USB device support context
* @param[in] cfg_nd Pointer to configuration node
*
* @return 0 on success, other values on fail.
*/
int usbd_interface_shutdown(struct usbd_contex *const uds_ctx,
struct usbd_config_node *const cfg_nd);
/**
* @brief Setup all interfaces in a configuration to default alternate.
*
* @note Used only for configuration change.
*
* @param[in] uds_ctx Pointer to USB device support context
* @param[in] cfg_nd Pointer to configuration node
*
* @return 0 on success, other values on fail.
*/
int usbd_interface_default(struct usbd_contex *const uds_ctx,
struct usbd_config_node *const cfg_nd);
/**
* @brief Set interface alternate
*
* @note Used only for configuration change.
*
* @param[in] uds_ctx Pointer to USB device support context
* @param[in] iface Interface number (bInterfaceNumber)
* @param[in] alternate Interface alternate (bAlternateSetting)
*
* @return 0 on success, other values on fail.
*/
int usbd_interface_set(struct usbd_contex *uds_ctx,
const uint8_t iface,
const uint8_t alternate);
#endif /* ZEPHYR_INCLUDE_USBD_INTERFACE_H */

View file

@ -0,0 +1,813 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <zephyr/sys/util.h>
#include <zephyr/shell/shell.h>
#include <zephyr/usb/usbd.h>
#include <zephyr/drivers/usb/udc.h>
#include <zephyr/sys/byteorder.h>
const struct shell *ctx_shell;
/*
* We define foobaz USB device class which is to be used for the
* specific shell commands like register and submit.
*/
#define FOOBAZ_VREQ_OUT 0x5b
#define FOOBAZ_VREQ_IN 0x5c
static uint8_t foobaz_buf[512];
/* Make supported vendor request visible for the device stack */
static const struct usbd_cctx_vendor_req foobaz_vregs =
USBD_VENDOR_REQ(FOOBAZ_VREQ_OUT, FOOBAZ_VREQ_IN);
struct foobaz_iface_desc {
struct usb_if_descriptor if0;
struct usb_if_descriptor if1;
struct usb_ep_descriptor if1_out_ep;
struct usb_ep_descriptor if1_in_ep;
struct usb_desc_header term_desc;
} __packed;
static struct foobaz_iface_desc foobaz_desc = {
.if0 = {
.bLength = sizeof(struct usb_if_descriptor),
.bDescriptorType = USB_DESC_INTERFACE,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = 0,
.bInterfaceClass = USB_BCC_VENDOR,
.bInterfaceSubClass = 0,
.bInterfaceProtocol = 0,
.iInterface = 0,
},
.if1 = {
.bLength = sizeof(struct usb_if_descriptor),
.bDescriptorType = USB_DESC_INTERFACE,
.bInterfaceNumber = 0,
.bAlternateSetting = 1,
.bNumEndpoints = 2,
.bInterfaceClass = USB_BCC_VENDOR,
.bInterfaceSubClass = 0,
.bInterfaceProtocol = 0,
.iInterface = 0,
},
.if1_out_ep = {
.bLength = sizeof(struct usb_ep_descriptor),
.bDescriptorType = USB_DESC_ENDPOINT,
.bEndpointAddress = 0x01,
.bmAttributes = USB_EP_TYPE_BULK,
.wMaxPacketSize = 0,
.bInterval = 0x00,
},
.if1_in_ep = {
.bLength = sizeof(struct usb_ep_descriptor),
.bDescriptorType = USB_DESC_ENDPOINT,
.bEndpointAddress = 0x81,
.bmAttributes = USB_EP_TYPE_BULK,
.wMaxPacketSize = 0,
.bInterval = 0x00,
},
/* Termination descriptor */
.term_desc = {
.bLength = 0,
.bDescriptorType = 0,
},
};
static size_t foobaz_get_ep_mps(struct usbd_class_data *const data)
{
struct foobaz_iface_desc *desc = data->desc;
return desc->if1_out_ep.wMaxPacketSize;
}
static size_t foobaz_ep_addr_out(struct usbd_class_data *const data)
{
struct foobaz_iface_desc *desc = data->desc;
return desc->if1_out_ep.bEndpointAddress;
}
static size_t foobaz_ep_addr_in(struct usbd_class_data *const data)
{
struct foobaz_iface_desc *desc = data->desc;
return desc->if1_in_ep.bEndpointAddress;
}
static void foobaz_update(struct usbd_class_node *const node,
uint8_t iface, uint8_t alternate)
{
shell_info(ctx_shell,
"dev: New configuration, interface %u alternate %u",
iface, alternate);
}
static int foobaz_cth(struct usbd_class_node *const node,
const struct usb_setup_packet *const setup,
struct net_buf *const buf)
{
size_t min_len = MIN(sizeof(foobaz_buf), setup->wLength);
if (setup->bRequest == FOOBAZ_VREQ_IN) {
if (buf == NULL) {
errno = -ENOMEM;
return 0;
}
net_buf_add_mem(buf, foobaz_buf, min_len);
shell_info(ctx_shell,
"dev: conrol transfer to host, wLength %u | %u",
setup->wLength, min_len);
return 0;
}
errno = -ENOTSUP;
return 0;
}
static int foobaz_ctd(struct usbd_class_node *const node,
const struct usb_setup_packet *const setup,
const struct net_buf *const buf)
{
size_t min_len = MIN(sizeof(foobaz_buf), setup->wLength);
if (setup->bRequest == FOOBAZ_VREQ_OUT) {
shell_info(ctx_shell,
"dev: control transfer to device, wLength %u | %u",
setup->wLength, min_len);
memcpy(foobaz_buf, buf->data, min_len);
return 0;
}
errno = -ENOTSUP;
return 0;
}
static int foobaz_request_cancelled(struct usbd_class_node *const node,
struct net_buf *buf)
{
struct udc_buf_info *bi;
bi = udc_get_buf_info(buf);
shell_warn(ctx_shell, "Request ep 0x%02x cancelled", bi->ep);
shell_warn(ctx_shell, "|-> %p", buf);
for (struct net_buf *n = buf; n->frags != NULL; n = n->frags) {
shell_warn(ctx_shell, "|-> %p", n->frags);
}
return 0;
}
static int foobaz_ep_request(struct usbd_class_node *const node,
struct net_buf *buf, int err)
{
struct usbd_contex *uds_ctx = node->data->uds_ctx;
struct udc_buf_info *bi;
bi = udc_get_buf_info(buf);
shell_info(ctx_shell, "dev: Handle request ep 0x%02x, len %u",
bi->ep, buf->len);
if (err) {
if (err == -ECONNABORTED) {
foobaz_request_cancelled(node, buf);
} else {
shell_error(ctx_shell,
"dev: Request failed (%d) ep 0x%02x, len %u",
err, bi->ep, buf->len);
}
}
if (err == 0 && USB_EP_DIR_IS_OUT(bi->ep)) {
shell_hexdump(ctx_shell, buf->data, buf->len);
}
return usbd_ep_buf_free(uds_ctx, buf);
}
static void foobaz_suspended(struct usbd_class_node *const node)
{
shell_info(ctx_shell, "dev: Device suspended");
}
static void foobaz_resumed(struct usbd_class_node *const node)
{
shell_info(ctx_shell, "dev: Device resumed");
}
static int foobaz_init(struct usbd_class_node *const node)
{
return 0;
}
/* Define foobaz interface to USB device class API */
struct usbd_class_api foobaz_api = {
.update = foobaz_update,
.control_to_host = foobaz_cth,
.control_to_dev = foobaz_ctd,
.request = foobaz_ep_request,
.suspended = foobaz_suspended,
.resumed = foobaz_resumed,
.init = foobaz_init,
};
static struct usbd_class_data foobaz_data = {
.desc = (struct usb_desc_header *)&foobaz_desc,
.v_reqs = &foobaz_vregs,
};
USBD_DEFINE_CLASS(foobaz, &foobaz_api, &foobaz_data);
USBD_CONFIGURATION_DEFINE(config_foo, USB_SCD_SELF_POWERED, 200);
USBD_CONFIGURATION_DEFINE(config_baz, USB_SCD_REMOTE_WAKEUP, 200);
USBD_DESC_LANG_DEFINE(lang);
USBD_DESC_STRING_DEFINE(mfr, "ZEPHYR", 1);
USBD_DESC_STRING_DEFINE(product, "Zephyr USBD foobaz", 2);
USBD_DESC_STRING_DEFINE(sn, "0123456789ABCDEF", 3);
USBD_DEVICE_DEFINE(uds_ctx, DEVICE_DT_GET(DT_NODELABEL(zephyr_udc0)),
0x2fe3, 0xffff);
int cmd_wakeup_request(const struct shell *sh,
size_t argc, char **argv)
{
int err;
err = usbd_wakeup_request(&uds_ctx);
if (err) {
shell_error(sh, "dev: Failed to wakeup remote %d", err);
} else {
shell_print(sh, "dev: Requested remote wakeup");
}
return err;
}
static int cmd_submit_request(const struct shell *sh,
size_t argc, char **argv)
{
struct net_buf *buf;
size_t len;
uint8_t ep;
int ret;
ep = strtol(argv[1], NULL, 16);
if (ep != foobaz_ep_addr_out(&foobaz_data) &&
ep != foobaz_ep_addr_in(&foobaz_data)) {
struct foobaz_iface_desc *desc = foobaz_data.desc;
shell_error(sh, "dev: Endpoint address not valid %x",
desc->if1_out_ep.bEndpointAddress);
return -EINVAL;
}
if (argc > 2) {
len = strtol(argv[2], NULL, 10);
} else {
len = foobaz_get_ep_mps(&foobaz_data);
}
if (USB_EP_DIR_IS_IN(ep)) {
len = MIN(len, sizeof(foobaz_buf));
}
buf = usbd_ep_buf_alloc(&foobaz, ep, len);
if (buf == NULL) {
return -ENOMEM;
}
if (USB_EP_DIR_IS_IN(ep)) {
net_buf_add_mem(buf, foobaz_buf, len);
}
shell_print(sh, "dev: Submit ep 0x%02x len %u buf %p", ep, len, buf);
ret = usbd_ep_enqueue(&foobaz, buf);
if (ret) {
shell_print(sh, "dev: Failed to queue request buffer");
usbd_ep_buf_free(&uds_ctx, buf);
}
return ret;
}
static int cmd_cancel_request(const struct shell *sh,
size_t argc, char **argv)
{
uint8_t ep;
int ret;
shell_print(sh, "dev: Request %s %s", argv[1], argv[2]);
ep = strtol(argv[1], NULL, 16);
if (ep != foobaz_ep_addr_out(&foobaz_data) &&
ep != foobaz_ep_addr_in(&foobaz_data)) {
shell_error(sh, "dev: Endpoint address not valid");
return -EINVAL;
}
ret = usbd_ep_dequeue(&uds_ctx, ep);
if (ret) {
shell_print(sh, "dev: Failed to dequeue request buffer");
}
return ret;
}
static int cmd_endpoint_halt(const struct shell *sh,
size_t argc, char **argv)
{
uint8_t ep;
int ret = 0;
ep = strtol(argv[1], NULL, 16);
if (ep != foobaz_ep_addr_out(&foobaz_data) &&
ep != foobaz_ep_addr_in(&foobaz_data)) {
shell_error(sh, "dev: Endpoint address not valid");
return -EINVAL;
}
if (!strcmp(argv[2], "set")) {
ret = usbd_ep_set_halt(&uds_ctx, ep);
} else if (!strcmp(argv[2], "clear")) {
ret = usbd_ep_clear_halt(&uds_ctx, ep);
} else {
shell_error(sh, "dev: Invalid argument: %s", argv[1]);
return -EINVAL;
}
if (ret) {
shell_print(sh, "dev: endpoint %s %s halt failed",
argv[2], argv[1]);
} else {
shell_print(sh, "dev: endpoint %s %s halt successful",
argv[2], argv[1]);
}
return ret;
}
static int cmd_register(const struct shell *sh,
size_t argc, char **argv)
{
uint8_t cfg;
int ret;
cfg = strtol(argv[2], NULL, 10);
ret = usbd_register_class(&uds_ctx, argv[1], cfg);
if (ret) {
shell_error(sh,
"dev: failed to add USB class %s to configuration %u",
argv[1], cfg);
} else {
shell_print(sh,
"dev: added USB class %s to configuration %u",
argv[1], cfg);
}
return ret;
}
static int cmd_unregister(const struct shell *sh,
size_t argc, char **argv)
{
uint8_t cfg;
int ret;
cfg = strtol(argv[2], NULL, 10);
ret = usbd_unregister_class(&uds_ctx, argv[1], cfg);
if (ret) {
shell_error(sh,
"dev: failed to remove USB class %s from configuration %u",
argv[1], cfg);
} else {
shell_print(sh,
"dev: removed USB class %s from configuration %u",
argv[1], cfg);
}
return ret;
}
static int cmd_usbd_magic(const struct shell *sh,
size_t argc, char **argv)
{
int err;
err = usbd_add_descriptor(&uds_ctx, &lang);
err |= usbd_add_descriptor(&uds_ctx, &mfr);
err |= usbd_add_descriptor(&uds_ctx, &product);
err |= usbd_add_descriptor(&uds_ctx, &sn);
if (err) {
shell_error(sh, "dev: Failed to initialize descriptors, %d", err);
}
err = usbd_add_configuration(&uds_ctx, &config_foo);
if (err) {
shell_error(sh, "dev: Failed to add configuration");
}
err = usbd_register_class(&uds_ctx, "foobaz", 1);
if (err) {
shell_error(sh, "dev: Failed to add foobaz class");
}
ctx_shell = sh;
err = usbd_init(&uds_ctx);
if (err) {
shell_error(sh, "dev: Failed to initialize device support");
}
err = usbd_enable(&uds_ctx);
if (err) {
shell_error(sh, "dev: Failed to enable device support");
}
return err;
}
static int cmd_usbd_defaults(const struct shell *sh,
size_t argc, char **argv)
{
int err;
err = usbd_add_descriptor(&uds_ctx, &lang);
err |= usbd_add_descriptor(&uds_ctx, &mfr);
err |= usbd_add_descriptor(&uds_ctx, &product);
err |= usbd_add_descriptor(&uds_ctx, &sn);
if (err) {
shell_error(sh, "dev: Failed to initialize descriptors, %d", err);
} else {
shell_print(sh, "dev: USB descriptors initialized");
}
return err;
}
static int cmd_usbd_init(const struct shell *sh,
size_t argc, char **argv)
{
int err;
ctx_shell = sh;
err = usbd_init(&uds_ctx);
if (err == -EALREADY) {
shell_error(sh, "dev: USB already initialized");
} else if (err) {
shell_error(sh, "dev: Failed to initialize %d", err);
} else {
shell_print(sh, "dev: USB initialized");
}
return err;
}
static int cmd_usbd_enable(const struct shell *sh,
size_t argc, char **argv)
{
int err;
err = usbd_enable(&uds_ctx);
if (err == -EALREADY) {
shell_error(sh, "dev: USB already enabled");
} else if (err) {
shell_error(sh, "dev: Failed to enable USB, error %d", err);
} else {
shell_print(sh, "dev: USB enabled");
}
return err;
}
static int cmd_usbd_disable(const struct shell *sh,
size_t argc, char **argv)
{
int err;
err = usbd_disable(&uds_ctx);
if (err) {
shell_error(sh, "dev: Failed to disable USB");
return err;
}
shell_print(sh, "dev: USB disabled");
return 0;
}
static int cmd_usbd_shutdown(const struct shell *sh,
size_t argc, char **argv)
{
int err;
err = usbd_shutdown(&uds_ctx);
if (err) {
shell_error(sh, "dev: Failed to shutdown USB");
return err;
}
shell_print(sh, "dev: USB completely disabled");
return 0;
}
static int cmd_device_bcd(const struct shell *sh, size_t argc,
char *argv[])
{
uint16_t bcd;
int ret;
bcd = strtol(argv[1], NULL, 16);
ret = usbd_device_set_bcd(&uds_ctx, bcd);
if (ret) {
shell_error(sh, "dev: failed to set device bcdUSB to %x", bcd);
}
return ret;
}
static int cmd_device_pid(const struct shell *sh, size_t argc,
char *argv[])
{
uint16_t pid;
int ret;
pid = strtol(argv[1], NULL, 16);
ret = usbd_device_set_pid(&uds_ctx, pid);
if (ret) {
shell_error(sh, "dev: failed to set device idProduct to %x", pid);
}
return ret;
}
static int cmd_device_vid(const struct shell *sh, size_t argc,
char *argv[])
{
uint16_t vid;
int ret;
vid = strtol(argv[1], NULL, 16);
ret = usbd_device_set_vid(&uds_ctx, vid);
if (ret) {
shell_error(sh, "dev: failed to set device idVendor to %x", vid);
}
return ret;
}
static int cmd_device_class(const struct shell *sh, size_t argc,
char *argv[])
{
uint8_t value;
int ret;
value = strtol(argv[1], NULL, 16);
ret = usbd_device_set_class(&uds_ctx, value);
if (ret) {
shell_error(sh, "dev: failed to set device class to %x", value);
}
return ret;
}
static int cmd_device_subclass(const struct shell *sh, size_t argc,
char *argv[])
{
uint8_t value;
int ret;
value = strtol(argv[1], NULL, 16);
ret = usbd_device_set_subclass(&uds_ctx, value);
if (ret) {
shell_error(sh, "dev: failed to set device subclass to %x", value);
}
return ret;
}
static int cmd_device_proto(const struct shell *sh, size_t argc,
char *argv[])
{
uint8_t value;
int ret;
value = strtol(argv[1], NULL, 16);
ret = usbd_device_set_proto(&uds_ctx, value);
if (ret) {
shell_error(sh, "dev: failed to set device proto to %x", value);
}
return ret;
}
static int cmd_config_add(const struct shell *sh, size_t argc,
char *argv[])
{
uint8_t cfg;
int ret;
cfg = strtol(argv[1], NULL, 10);
if (cfg == 1) {
ret = usbd_add_configuration(&uds_ctx, &config_foo);
} else if (cfg == 2) {
ret = usbd_add_configuration(&uds_ctx, &config_baz);
} else {
shell_error(sh, "dev: Configuration %u not available", cfg);
return -EINVAL;
}
if (ret) {
shell_error(sh, "dev: failed to add configuration %u", cfg);
}
return ret;
}
static int cmd_config_self(const struct shell *sh, size_t argc,
char *argv[])
{
bool self;
uint8_t cfg;
int ret;
cfg = strtol(argv[1], NULL, 10);
if (!strcmp(argv[2], "yes")) {
self = true;
} else {
self = false;
}
ret = usbd_config_attrib_self(&uds_ctx, cfg, self);
if (ret) {
shell_error(sh,
"dev: failed to set attribute self powered to %u",
cfg);
}
return ret;
}
static int cmd_config_rwup(const struct shell *sh, size_t argc,
char *argv[])
{
bool rwup;
uint8_t cfg;
int ret;
cfg = strtol(argv[1], NULL, 10);
if (!strcmp(argv[2], "yes")) {
rwup = true;
} else {
rwup = false;
}
ret = usbd_config_attrib_rwup(&uds_ctx, cfg, rwup);
if (ret) {
shell_error(sh,
"dev: failed to set attribute remote wakeup to %x",
cfg);
}
return ret;
}
static int cmd_config_power(const struct shell *sh, size_t argc,
char *argv[])
{
uint8_t cfg;
uint8_t power;
int ret;
cfg = strtol(argv[1], NULL, 10);
power = strtol(argv[1], NULL, 10);
ret = usbd_config_maxpower(&uds_ctx, cfg, power);
if (ret) {
shell_error(sh, "dev: failed to set bMaxPower value to %u", cfg);
}
return ret;
}
static void class_node_name_lookup(size_t idx, struct shell_static_entry *entry)
{
size_t match_idx = 0;
entry->syntax = NULL;
entry->handler = NULL;
entry->help = NULL;
entry->subcmd = NULL;
STRUCT_SECTION_FOREACH(usbd_class_node, node) {
if ((node->name != NULL) && (strlen(node->name) != 0)) {
if (match_idx == idx) {
entry->syntax = node->name;
break;
}
++match_idx;
}
}
}
SHELL_DYNAMIC_CMD_CREATE(dsub_node_name, class_node_name_lookup);
SHELL_STATIC_SUBCMD_SET_CREATE(device_cmds,
SHELL_CMD_ARG(bcd, NULL, "<bcdUSB>",
cmd_device_bcd, 2, 0),
SHELL_CMD_ARG(pid, NULL, "<idProduct>",
cmd_device_pid, 2, 0),
SHELL_CMD_ARG(vid, NULL, "<idVendor>",
cmd_device_vid, 2, 0),
SHELL_CMD_ARG(class, NULL, "<bDeviceClass>",
cmd_device_class, 2, 0),
SHELL_CMD_ARG(subclass, NULL, "<bDeviceSubClass>",
cmd_device_subclass, 2, 0),
SHELL_CMD_ARG(proto, NULL, "<bDeviceProtocol>",
cmd_device_proto, 2, 0),
SHELL_SUBCMD_SET_END
);
SHELL_STATIC_SUBCMD_SET_CREATE(config_cmds,
SHELL_CMD_ARG(add, NULL, "<configuration>",
cmd_config_add, 2, 0),
SHELL_CMD_ARG(power, NULL, "<configuration> <bMaxPower>",
cmd_config_power, 3, 0),
SHELL_CMD_ARG(rwup, NULL, "<configuration> <yes, no>",
cmd_config_rwup, 3, 0),
SHELL_CMD_ARG(self, NULL, "<configuration> <yes, no>",
cmd_config_self, 3, 0),
SHELL_SUBCMD_SET_END
);
SHELL_STATIC_SUBCMD_SET_CREATE(class_cmds,
SHELL_CMD_ARG(add, &dsub_node_name, "<name> <configuration>",
cmd_register, 3, 0),
SHELL_CMD_ARG(add, &dsub_node_name, "<name> <configuration>",
cmd_unregister, 3, 0),
SHELL_SUBCMD_SET_END
);
SHELL_STATIC_SUBCMD_SET_CREATE(endpoint_cmds,
SHELL_CMD_ARG(halt, NULL, "<endpoint> <set clear>",
cmd_endpoint_halt, 3, 0),
SHELL_CMD_ARG(submit, NULL, "<endpoint> [length]",
cmd_submit_request, 2, 1),
SHELL_CMD_ARG(cancel, NULL, "<endpoint>",
cmd_cancel_request, 2, 0),
SHELL_SUBCMD_SET_END
);
SHELL_STATIC_SUBCMD_SET_CREATE(sub_usbd_cmds,
SHELL_CMD_ARG(wakeup, NULL, "[none]",
cmd_wakeup_request, 1, 0),
SHELL_CMD_ARG(magic, NULL, "[none]",
cmd_usbd_magic, 1, 0),
SHELL_CMD_ARG(defaults, NULL, "[none]",
cmd_usbd_defaults, 1, 0),
SHELL_CMD_ARG(init, NULL, "[none]",
cmd_usbd_init, 1, 0),
SHELL_CMD_ARG(enable, NULL, "[none]",
cmd_usbd_enable, 1, 0),
SHELL_CMD_ARG(disable, NULL, "[none]",
cmd_usbd_disable, 1, 0),
SHELL_CMD_ARG(shutdown, NULL, "[none]",
cmd_usbd_shutdown, 1, 0),
SHELL_CMD_ARG(device, &device_cmds, "device commands",
NULL, 1, 0),
SHELL_CMD_ARG(config, &config_cmds, "configuration commands",
NULL, 1, 0),
SHELL_CMD_ARG(class, &class_cmds, "class commands",
NULL, 1, 0),
SHELL_CMD_ARG(endpoint, &endpoint_cmds, "endpoint commands",
NULL, 1, 0),
SHELL_SUBCMD_SET_END
);
SHELL_CMD_REGISTER(usbd, &sub_usbd_cmds, "USB device support commands", NULL);