diff --git a/include/zephyr/usb/usbd.h b/include/zephyr/usb/usbd.h new file mode 100644 index 00000000000..f8c97c15baa --- /dev/null +++ b/include/zephyr/usb/usbd.h @@ -0,0 +1,706 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief New experimental USB device stack APIs and structures + * + * This file contains the USB device stack APIs and structures. + */ + +#ifndef ZEPHYR_INCLUDE_USBD_H_ +#define ZEPHYR_INCLUDE_USBD_H_ + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * The USB Unicode bString is encoded in UTF16LE, which means it takes up + * twice the amount of bytes than the same string encoded in ASCII7. + * Use this macro to determine the length of the bString array. + * + * bString length without null character: + * bString_length = (sizeof(initializer_string) - 1) * 2 + * or: + * bString_length = sizeof(initializer_string) * 2 - 2 + */ +#define USB_BSTRING_LENGTH(s) (sizeof(s) * 2 - 2) + +/* + * The length of the string descriptor (bLength) is calculated from the + * size of the two octets bLength and bDescriptorType plus the + * length of the UTF16LE string: + * + * bLength = 2 + bString_length + * bLength = 2 + sizeof(initializer_string) * 2 - 2 + * bLength = sizeof(initializer_string) * 2 + * Use this macro to determine the bLength of the string descriptor. + */ +#define USB_STRING_DESCRIPTOR_LENGTH(s) (sizeof(s) * 2) + +#define USBD_DESC_MANUFACTURER_IDX 1 +#define USBD_DESC_PRODUCT_IDX 2 +#define USBD_DESC_SERIAL_NUMBER_IDX 3 + +/** + * Descriptor node + * + * Descriptor node is used to manage descriptors that are not + * directly part of a structure string, and bos descriptors. + */ +struct usbd_desc_node { + /** slist node struct */ + sys_snode_t node; + /** Optional descriptor index, required for string descriptors */ + uint32_t idx; + /** Pointer to a descriptor */ + void *desc; +}; + +/** + * Device configuration node + * + * Configuration node is used to manage device configurations, + * at least one configuration is required. It does not have an index, + * instead bConfigurationValue of the descriptor is used for + * identification. + */ +struct usbd_config_node { + /** slist node struct */ + sys_snode_t node; + /** Pointer to configuration descriptor */ + void *desc; + /** List of registered classes (functions) */ + sys_slist_t class_list; +}; + +/* TODO: Kconfig option USBD_NUMOF_INTERFACES_MAX? */ +#define USBD_NUMOF_INTERFACES_MAX 16U + +/** + * USB device support middle layer runtime state + * + * Part of USB device states without suspended and powered + * states, as it is better to track them separately. + */ +enum usbd_ch9_state { + USBD_STATE_DEFAULT = 0, + USBD_STATE_ADDRESS, + USBD_STATE_CONFIGURED, +}; + + +/** + * USB device support middle layer runtime data + */ +struct usbd_ch9_data { + /** Setup packet, up-to-date for the respective control request */ + struct usb_setup_packet setup; + /** Control type, internaly used for stage verification */ + int ctrl_type; + /** Protocol state of the USB device stack */ + enum usbd_ch9_state state; + /** Halted endpoints bitmap */ + uint32_t ep_halt; + /** USB device stack selected configuration */ + uint8_t configuration; + /** Indicate new device address */ + bool new_address; + /** Array to track interfaces alternate settings */ + uint8_t alternate[USBD_NUMOF_INTERFACES_MAX]; +}; + +/** + * USB device support status + */ +struct usbd_status { + /** USB device support is initialized */ + unsigned int initialized : 1; + /** USB device support is enabled */ + unsigned int enabled : 1; + /** USB device is suspended */ + unsigned int suspended : 1; + /** USB remote wake-up feature is enabled */ + unsigned int rwup : 1; +}; + +/** + * USB device support runtime context + * + * Main structure that organizes all descriptors, configuration, + * and interfaces. An UDC device must be assigned to this structure. + */ +struct usbd_contex { + /** Name of the USB device */ + const char *name; + /** Access mutex */ + struct k_mutex mutex; + /** Pointer to UDC device */ + const struct device *dev; + /** Middle layer runtime data */ + struct usbd_ch9_data ch9_data; + /** slist to manage descriptors like string, bos */ + sys_slist_t descriptors; + /** slist to manage device configurations */ + sys_slist_t configs; + /** Status of the USB device support */ + struct usbd_status status; + /** Pointer to device descriptor */ + void *desc; +}; + +/** + * @brief Vendor Requests Table + */ +struct usbd_cctx_vendor_req { + /** Array of vendor requests supportd by the class */ + const uint8_t *reqs; + /** Length of the array */ + uint8_t len; +}; + +/** USB Class instance registered flag */ +#define USBD_CCTX_REGISTERED 0 + +struct usbd_class_node; + +/** + * @brief USB device support class instance API + */ +struct usbd_class_api { + /** Configuration update handler */ + void (*update)(struct usbd_class_node *const node, + uint8_t iface, uint8_t alternate); + + /** USB control request handler to device */ + int (*control_to_dev)(struct usbd_class_node *const node, + const struct usb_setup_packet *const setup, + const struct net_buf *const buf); + + /** USB control request handler to host */ + int (*control_to_host)(struct usbd_class_node *const node, + const struct usb_setup_packet *const setup, + struct net_buf *const buf); + + /** Endpoint request completion event handler */ + int (*request)(struct usbd_class_node *const node, + struct net_buf *buf, int err); + + /** USB power management handler suspended */ + void (*suspended)(struct usbd_class_node *const node); + + /** USB power management handler resumed */ + void (*resumed)(struct usbd_class_node *const node); + + /** Class associated configuration is selected */ + void (*enable)(struct usbd_class_node *const node); + + /** Class associated configuration is disabled */ + void (*disable)(struct usbd_class_node *const node); + + /** Initialization of the class implementation */ + int (*init)(struct usbd_class_node *const node); + + /** Shutdown of the class implementation (TODO) */ + int (*shutdown)(struct usbd_class_node *const node); +}; + +/** + * @brief USB device support class data + */ +struct usbd_class_data { + /** Pointer to USB device stack context structure */ + struct usbd_contex *uds_ctx; + /** Terminated descriptor for class implementation */ + void *desc; + /** Supported vendor request table, can be NULL */ + const struct usbd_cctx_vendor_req *v_reqs; + /** Bitmap of all endpoints assigned to the instance. + * The IN endpoints are mapped in the upper halfword. + */ + uint32_t ep_assigned; + /** Bitmap of the enabled endpoints of the instance. + * The IN endpoints are mapped in the upper halfword. + */ + uint32_t ep_active; + /** Bitmap of the bInterfaceNumbers of the class instance */ + uint32_t iface_bm; + /** Variable to store the state of the class instance */ + atomic_t state; + /** Pointer to private data */ + void *priv; +}; + +struct usbd_class_node { + /** Node information for the slist. */ + sys_snode_t node; + /** Name of the USB device class instance */ + const char *name; + /** Pointer to device support class API */ + const struct usbd_class_api *api; + /** Pointer to USB device support class data */ + struct usbd_class_data *data; +}; + +/** + * @brief New USB device stack core API + * @defgroup usbd_api USB device core API + * @ingroup usb + * @{ + */ + +#define USBD_DEVICE_DEFINE(device_name, uhc_dev, vid, pid) \ + static struct usb_device_descriptor \ + desc_##device_name = { \ + .bLength = sizeof(struct usb_device_descriptor), \ + .bDescriptorType = USB_DESC_DEVICE, \ + .bcdUSB = sys_cpu_to_le16(USB_SRN_2_0), \ + .bDeviceClass = USB_BCC_MISCELLANEOUS, \ + .bDeviceSubClass = 2, \ + .bDeviceProtocol = 1, \ + .bMaxPacketSize0 = USB_CONTROL_EP_MPS, \ + .idVendor = vid, \ + .idProduct = pid, \ + .bcdDevice = sys_cpu_to_le16(USB_BCD_DRN), \ + .iManufacturer = 0, \ + .iProduct = 0, \ + .iSerialNumber = 0, \ + .bNumConfigurations = 0, \ + }; \ + static STRUCT_SECTION_ITERABLE(usbd_contex, device_name) = { \ + .name = STRINGIFY(device_name), \ + .dev = uhc_dev, \ + .desc = &desc_##device_name, \ + } + +#define USBD_CONFIGURATION_DEFINE(name, attrib, power) \ + static struct usb_cfg_descriptor \ + cfg_desc_##name = { \ + .bLength = sizeof(struct usb_cfg_descriptor), \ + .bDescriptorType = USB_DESC_CONFIGURATION, \ + .wTotalLength = 0, \ + .bNumInterfaces = 0, \ + .bConfigurationValue = 1, \ + .iConfiguration = 0, \ + .bmAttributes = USB_SCD_RESERVED | (attrib), \ + .bMaxPower = (power), \ + }; \ + BUILD_ASSERT((power) < 256, "Too much power"); \ + static struct usbd_config_node name = { \ + .desc = &cfg_desc_##name, \ + } + + +#define USBD_DESC_LANG_DEFINE(name) \ + static struct usb_string_descriptor \ + string_desc_##name = { \ + .bLength = sizeof(struct usb_string_descriptor), \ + .bDescriptorType = USB_DESC_STRING, \ + .bString = sys_cpu_to_le16(0x0409), \ + }; \ + static struct usbd_desc_node name = { \ + .idx = 0, \ + .desc = &string_desc_##name, \ + } + +#define USBD_DESC_STRING_DEFINE(d_name, d_string, d_idx) \ + struct usb_string_descriptor_##d_name { \ + uint8_t bLength; \ + uint8_t bDescriptorType; \ + uint8_t bString[USB_BSTRING_LENGTH(d_string)]; \ + } __packed; \ + static struct usb_string_descriptor_##d_name \ + string_desc_##d_name = { \ + .bLength = USB_STRING_DESCRIPTOR_LENGTH(d_string), \ + .bDescriptorType = USB_DESC_STRING, \ + .bString = d_string, \ + }; \ + BUILD_ASSERT(d_idx != 0, "Index 0 is not allowed"); \ + static struct usbd_desc_node d_name = { \ + .idx = d_idx, \ + .desc = &string_desc_##d_name, \ + } + +#define USBD_DEFINE_CLASS(class_name, class_api, class_data) \ + static STRUCT_SECTION_ITERABLE(usbd_class_node, class_name) = { \ + .name = STRINGIFY(class_name), \ + .api = class_api, \ + .data = class_data, \ + } + +/** @brief Helper to declare request table of usbd_cctx_vendor_req + * + * @param _reqs Pointer to the vendor request field + * @param _len Number of supported vendor requests + */ +#define VENDOR_REQ_DEFINE(_reqs, _len) \ + { \ + .reqs = (const uint8_t *)(_reqs), \ + .len = (_len), \ + } + +/** @brief Helper to declare supported vendor requests + * + * @param _reqs Variable number of vendor requests + */ +#define USBD_VENDOR_REQ(_reqs...) \ + VENDOR_REQ_DEFINE(((uint8_t []) { _reqs }), \ + sizeof((uint8_t []) { _reqs })) + + +/** + * @brief Add common USB descriptor + * + * Add common descriptor like string or bos. + * + * @param[in] uds_ctx Pointer to USB device support context + * @param[in] dn Pointer to USB descriptor node + * + * @return 0 on success, other values on fail. + */ +int usbd_add_descriptor(struct usbd_contex *uds_ctx, + struct usbd_desc_node *dn); + +/** + * @brief Add a USB device configuration + * + * @param[in] uds_ctx Pointer to USB device support context + * @param[in] cd Pointer to USB configuration node + * + * @return 0 on success, other values on fail. + */ +int usbd_add_configuration(struct usbd_contex *uds_ctx, + struct usbd_config_node *cd); + +/** + * @brief Register an USB class instance + * + * An USB class implementation can have one or more instances. + * To identify the instances we use device drivers API. + * Device names have a prefix derived from the name of the class, + * for example CDC_ACM for CDC ACM class instance, + * and can also be easily identified in the shell. + * Class instance can only be registered when the USB device stack + * is disabled. + * Registered instances are initialized at initialization + * of the USB device stack, and the interface descriptors + * of each instance are adapted to the whole context. + * + * @param[in] uds_ctx Pointer to USB device support context + * @param[in] name Class instance name + * @param[in] cfg Configuration value (similar to bConfigurationValue) + * + * @return 0 on success, other values on fail. + */ +int usbd_register_class(struct usbd_contex *uds_ctx, + const char *name, + uint8_t cfg); + +/** + * @brief Unregister an USB class instance + * + * USB class instance will be removed and will not appear + * on the next start of the stack. Instance can only be unregistered + * when the USB device stack is disabled. + * + * @param[in] uds_ctx Pointer to USB device support context + * @param[in] name Class instance name + * @param[in] cfg Configuration value (similar to bConfigurationValue) + * + * @return 0 on success, other values on fail. + */ +int usbd_unregister_class(struct usbd_contex *uds_ctx, + const char *name, + uint8_t cfg); + +/** + * @brief Initialize USB device + * + * Initialize USB device descriptors and configuration, + * initialize USB device controller. + * Class instances should be registered before they are involved. + * However, the stack should also initialize without registered instances, + * even if the host would complain about missing interfaces. + * + * @param[in] uds_ctx Pointer to USB device support context + * + * @return 0 on success, other values on fail. + */ +int usbd_init(struct usbd_contex *uds_ctx); + +/** + * @brief Enable the USB device support and registered class instances + * + * This function enables the USB device support. + * + * @param[in] uds_ctx Pointer to USB device support context + * + * @return 0 on success, other values on fail. + */ +int usbd_enable(struct usbd_contex *uds_ctx); + +/** + * @brief Disable the USB device support + * + * This function disables the USB device support. + * + * @param[in] uds_ctx Pointer to USB device support context + * + * @return 0 on success, other values on fail. + */ +int usbd_disable(struct usbd_contex *uds_ctx); + +/** + * @brief Shutdown the USB device support + * + * This function completely disables the USB device support. + * + * @param[in] uds_ctx Pointer to USB device support context + * + * @return 0 on success, other values on fail. + */ +int usbd_shutdown(struct usbd_contex *const uds_ctx); + +/** + * @brief Halt endpoint + * + * @param[in] uds_ctx Pointer to USB device support context + * @param[in] ep Endpoint address + * + * @return 0 on success, or error from udc_ep_set_halt() + */ +int usbd_ep_set_halt(struct usbd_contex *uds_ctx, uint8_t ep); + +/** + * @brief Clear endpoint halt + * + * @param[in] uds_ctx Pointer to USB device support context + * @param[in] ep Endpoint address + * + * @return 0 on success, or error from udc_ep_clear_halt() + */ +int usbd_ep_clear_halt(struct usbd_contex *uds_ctx, uint8_t ep); + +/** + * @brief Checks whether the endpoint is halted. + * + * @param[in] uds_ctx Pointer to USB device support context + * @param[in] ep Endpoint address + * + * @return true if endpoint is halted, false otherwise + */ +bool usbd_ep_is_halted(struct usbd_contex *uds_ctx, uint8_t ep); + +/** + * @brief Allocate buffer for USB device control request + * + * Allocate a new buffer from controller's driver buffer pool. + * + * @param[in] uds_ctx Pointer to USB device support context + * @param[in] ep Endpoint address + * @param[in] size Size of the request buffer + * + * @return pointer to allocated request or NULL on error. + */ +struct net_buf *usbd_ep_ctrl_buf_alloc(struct usbd_contex *const uds_ctx, + const uint8_t ep, const size_t size); + +/** + * @brief Allocate buffer for USB device request + * + * Allocate a new buffer from controller's driver buffer pool. + * + * @param[in] c_nd Pointer to USB device class node + * @param[in] ep Endpoint address + * @param[in] size Size of the request buffer + * + * @return pointer to allocated request or NULL on error. + */ +struct net_buf *usbd_ep_buf_alloc(const struct usbd_class_node *const c_nd, + const uint8_t ep, const size_t size); + +/** + * @brief Queue USB device control request + * + * Add control request to the queue. + * + * @param[in] uds_ctx Pointer to USB device support context + * @param[in] buf Pointer to UDC request buffer + * + * @return 0 on success, all other values should be treated as error. + */ +int usbd_ep_ctrl_enqueue(struct usbd_contex *const uds_ctx, + struct net_buf *const buf); + +/** + * @brief Queue USB device request + * + * Add request to the queue. + * + * @param[in] c_nd Pointer to USB device class node + * @param[in] buf Pointer to UDC request buffer + * + * @return 0 on success, or error from udc_ep_enqueue() + */ +int usbd_ep_enqueue(const struct usbd_class_node *const c_nd, + struct net_buf *const buf); + +/** + * @brief Remove all USB device controller requests from endpoint queue + * + * @param[in] uds_ctx Pointer to USB device support context + * @param[in] ep Endpoint address + * + * @return 0 on success, or error from udc_ep_dequeue() + */ +int usbd_ep_dequeue(struct usbd_contex *uds_ctx, const uint8_t ep); + +/** + * @brief Free USB device request buffer + * + * Put the buffer back into the request buffer pool. + * + * @param[in] uds_ctx Pointer to USB device support context + * @param[in] buf Pointer to UDC request buffer + * + * @return 0 on success, all other values should be treated as error. + */ +int usbd_ep_buf_free(struct usbd_contex *uds_ctx, struct net_buf *buf); + +/** + * @brief Checks whether the USB device controller is suspended. + * + * @param[in] uds_ctx Pointer to USB device support context + * + * @return true if endpoint is halted, false otherwise + */ +bool usbd_is_suspended(struct usbd_contex *uds_ctx); + +/** + * @brief Initiate the USB remote wakeup (TBD) + * + * @return 0 on success, other values on fail. + */ +int usbd_wakeup_request(struct usbd_contex *uds_ctx); + +/** + * @brief Set USB device descriptor value bcdUSB + * + * @param[in] uds_ctx Pointer to USB device support context + * @param[in] bcd bcdUSB value + * + * @return 0 on success, other values on fail. + */ +int usbd_device_set_bcd(struct usbd_contex *const uds_ctx, + const uint16_t bcd); + +/** + * @brief Set USB device descriptor value idVendor + * + * @param[in] uds_ctx Pointer to USB device support context + * @param[in] vid idVendor value + * + * @return 0 on success, other values on fail. + */ +int usbd_device_set_vid(struct usbd_contex *const uds_ctx, + const uint16_t vid); + +/** + * @brief Set USB device descriptor value idProduct + * + * @param[in] uds_ctx Pointer to USB device support context + * @param[in] pid idProduct value + * + * @return 0 on success, other values on fail. + */ +int usbd_device_set_pid(struct usbd_contex *const uds_ctx, + const uint16_t pid); + +/** + * @brief Set USB device descriptor value bDeviceClass + * + * @param[in] uds_ctx Pointer to USB device support context + * @param[in] value bDeviceClass value + * + * @return 0 on success, other values on fail. + */ +int usbd_device_set_class(struct usbd_contex *const uds_ctx, + const uint8_t value); + +/** + * @brief Set USB device descriptor value bDeviceSubClass + * + * @param[in] uds_ctx Pointer to USB device support context + * @param[in] value bDeviceSubClass value + * + * @return 0 on success, other values on fail. + */ +int usbd_device_set_subclass(struct usbd_contex *const uds_ctx, + const uint8_t value); + +/** + * @brief Set USB device descriptor value bDeviceProtocol + * + * @param[in] uds_ctx Pointer to USB device support context + * @param[in] value bDeviceProtocol value + * + * @return 0 on success, other values on fail. + */ +int usbd_device_set_proto(struct usbd_contex *const uds_ctx, + const uint8_t value); + +/** + * @brief Setup USB device configuration attribute Remote Wakeup + * + * @param[in] uds_ctx Pointer to USB device support context + * @param[in] cfg Configuration number + * @param[in] enable Sets attribute if true, clears it otherwise + * + * @return 0 on success, other values on fail. + */ +int usbd_config_attrib_rwup(struct usbd_contex *const uds_ctx, + const uint8_t cfg, const bool enable); + +/** + * @brief Setup USB device configuration attribute Self-powered + * + * @param[in] uds_ctx Pointer to USB device support context + * @param[in] cfg Configuration number + * @param[in] enable Sets attribute if true, clears it otherwise + * + * @return 0 on success, other values on fail. + */ +int usbd_config_attrib_self(struct usbd_contex *const uds_ctx, + const uint8_t cfg, const bool enable); + +/** + * @brief Setup USB device configuration power consumption + * + * @param[in] uds_ctx Pointer to USB device support context + * @param[in] cfg Configuration number + * @param[in] power Maximum power consumption value (bMaxPower) + * + * @return 0 on success, other values on fail. + */ +int usbd_config_maxpower(struct usbd_contex *const uds_ctx, + const uint8_t cfg, const uint8_t power); +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_USBD_H_ */ diff --git a/subsys/Kconfig b/subsys/Kconfig index a37bd623d58..66d69d3414b 100644 --- a/subsys/Kconfig +++ b/subsys/Kconfig @@ -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" diff --git a/subsys/usb/CMakeLists.txt b/subsys/usb/CMakeLists.txt index f03282ccce6..d7ad66a1b1d 100644 --- a/subsys/usb/CMakeLists.txt +++ b/subsys/usb/CMakeLists.txt @@ -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) diff --git a/subsys/usb/device_next/CMakeLists.txt b/subsys/usb/device_next/CMakeLists.txt new file mode 100644 index 00000000000..32b778961b2 --- /dev/null +++ b/subsys/usb/device_next/CMakeLists.txt @@ -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) diff --git a/subsys/usb/device_next/Kconfig b/subsys/usb/device_next/Kconfig new file mode 100644 index 00000000000..d6ccd4d7bd6 --- /dev/null +++ b/subsys/usb/device_next/Kconfig @@ -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 diff --git a/subsys/usb/device_next/class/Kconfig b/subsys/usb/device_next/class/Kconfig new file mode 100644 index 00000000000..0bbfdfa1df2 --- /dev/null +++ b/subsys/usb/device_next/class/Kconfig @@ -0,0 +1,5 @@ +# Copyright (c) 2022 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +rsource "Kconfig.loopback" diff --git a/subsys/usb/device_next/class/Kconfig.loopback b/subsys/usb/device_next/class/Kconfig.loopback new file mode 100644 index 00000000000..d46434d5ef9 --- /dev/null +++ b/subsys/usb/device_next/class/Kconfig.loopback @@ -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 diff --git a/subsys/usb/device_next/class/Kconfig.template.composite_device_number b/subsys/usb/device_next/class/Kconfig.template.composite_device_number new file mode 100644 index 00000000000..5d1368afe04 --- /dev/null +++ b/subsys/usb/device_next/class/Kconfig.template.composite_device_number @@ -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. diff --git a/subsys/usb/device_next/class/loopback.c b/subsys/usb/device_next/class/loopback.c new file mode 100644 index 00000000000..ac9fa2b15dc --- /dev/null +++ b/subsys/usb/device_next/class/loopback.c @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +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, ()) diff --git a/subsys/usb/device_next/usbd_ch9.c b/subsys/usb/device_next/usbd_ch9.c new file mode 100644 index 00000000000..c7475ed01c8 --- /dev/null +++ b/subsys/usb/device_next/usbd_ch9.c @@ -0,0 +1,857 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#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 +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; +} diff --git a/subsys/usb/device_next/usbd_ch9.h b/subsys/usb/device_next/usbd_ch9.h new file mode 100644 index 00000000000..8757a187bf2 --- /dev/null +++ b/subsys/usb/device_next/usbd_ch9.h @@ -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 + +/** + * @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 */ diff --git a/subsys/usb/device_next/usbd_class.c b/subsys/usb/device_next/usbd_class.c new file mode 100644 index 00000000000..8b1df8a7ac1 --- /dev/null +++ b/subsys/usb/device_next/usbd_class.c @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include "usbd_device.h" +#include "usbd_class_api.h" +#include "usbd_config.h" +#include "usbd_endpoint.h" +#include "usbd_ch9.h" + +#include +#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; +} diff --git a/subsys/usb/device_next/usbd_class.h b/subsys/usb/device_next/usbd_class.h new file mode 100644 index 00000000000..caa4262ceff --- /dev/null +++ b/subsys/usb/device_next/usbd_class.h @@ -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 + +/** + * @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 */ diff --git a/subsys/usb/device_next/usbd_class_api.h b/subsys/usb/device_next/usbd_class_api.h new file mode 100644 index 00000000000..f57aa4b8aec --- /dev/null +++ b/subsys/usb/device_next/usbd_class_api.h @@ -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 + +/** + * @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 */ diff --git a/subsys/usb/device_next/usbd_config.c b/subsys/usb/device_next/usbd_config.c new file mode 100644 index 00000000000..5ac859a5e3a --- /dev/null +++ b/subsys/usb/device_next/usbd_config.c @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "usbd_device.h" +#include "usbd_config.h" +#include "usbd_interface.h" +#include "usbd_ch9.h" +#include "usbd_class_api.h" + +#include +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; +} diff --git a/subsys/usb/device_next/usbd_config.h b/subsys/usb/device_next/usbd_config.h new file mode 100644 index 00000000000..b3e8794ee6c --- /dev/null +++ b/subsys/usb/device_next/usbd_config.h @@ -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 + +/** + * @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 */ diff --git a/subsys/usb/device_next/usbd_core.c b/subsys/usb/device_next/usbd_core.c new file mode 100644 index 00000000000..4683d303bb9 --- /dev/null +++ b/subsys/usb/device_next/usbd_core.c @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include +#include + +#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 +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); diff --git a/subsys/usb/device_next/usbd_data.ld b/subsys/usb/device_next/usbd_data.ld new file mode 100644 index 00000000000..a1966db8a20 --- /dev/null +++ b/subsys/usb/device_next/usbd_data.ld @@ -0,0 +1,2 @@ +ITERABLE_SECTION_RAM(usbd_contex, 4) +ITERABLE_SECTION_RAM(usbd_class_node, 4) diff --git a/subsys/usb/device_next/usbd_desc.c b/subsys/usb/device_next/usbd_desc.c new file mode 100644 index 00000000000..9390fa10299 --- /dev/null +++ b/subsys/usb/device_next/usbd_desc.c @@ -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 +#include +#include +#include + +#include "usbd_device.h" + +#include +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; +} diff --git a/subsys/usb/device_next/usbd_desc.h b/subsys/usb/device_next/usbd_desc.h new file mode 100644 index 00000000000..e52d22fd8fd --- /dev/null +++ b/subsys/usb/device_next/usbd_desc.h @@ -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 + +/** + * @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 */ diff --git a/subsys/usb/device_next/usbd_device.c b/subsys/usb/device_next/usbd_device.c new file mode 100644 index 00000000000..c836c271ed3 --- /dev/null +++ b/subsys/usb/device_next/usbd_device.c @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "usbd_device.h" +#include "usbd_config.h" +#include "usbd_class.h" +#include "usbd_ch9.h" +#include "usbd_desc.h" + +#include +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; +} diff --git a/subsys/usb/device_next/usbd_device.h b/subsys/usb/device_next/usbd_device.h new file mode 100644 index 00000000000..083420da2e3 --- /dev/null +++ b/subsys/usb/device_next/usbd_device.h @@ -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 + +/** + * @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 */ diff --git a/subsys/usb/device_next/usbd_endpoint.c b/subsys/usb/device_next/usbd_endpoint.c new file mode 100644 index 00000000000..5a8f292a1b6 --- /dev/null +++ b/subsys/usb/device_next/usbd_endpoint.c @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "usbd_device.h" +#include "usbd_class.h" +#include "usbd_ch9.h" +#include "usbd_desc.h" +#include "usbd_endpoint.h" + +#include +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); +} diff --git a/subsys/usb/device_next/usbd_endpoint.h b/subsys/usb/device_next/usbd_endpoint.h new file mode 100644 index 00000000000..50ac9980428 --- /dev/null +++ b/subsys/usb/device_next/usbd_endpoint.h @@ -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 + +/** + * @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 */ diff --git a/subsys/usb/device_next/usbd_init.c b/subsys/usb/device_next/usbd_init.c new file mode 100644 index 00000000000..dc5de660022 --- /dev/null +++ b/subsys/usb/device_next/usbd_init.c @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2021-2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "usbd_device.h" +#include "usbd_config.h" +#include "usbd_class.h" +#include "usbd_class_api.h" +#include "usbd_endpoint.h" + +#include +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; +} diff --git a/subsys/usb/device_next/usbd_init.h b/subsys/usb/device_next/usbd_init.h new file mode 100644 index 00000000000..8f5fb574df8 --- /dev/null +++ b/subsys/usb/device_next/usbd_init.h @@ -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 + +/** + * @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 */ diff --git a/subsys/usb/device_next/usbd_interface.c b/subsys/usb/device_next/usbd_interface.c new file mode 100644 index 00000000000..59acafdbedd --- /dev/null +++ b/subsys/usb/device_next/usbd_interface.c @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "usbd_device.h" +#include "usbd_class.h" +#include "usbd_class_api.h" +#include "usbd_endpoint.h" +#include "usbd_ch9.h" + +#include +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; +} diff --git a/subsys/usb/device_next/usbd_interface.h b/subsys/usb/device_next/usbd_interface.h new file mode 100644 index 00000000000..e3355545057 --- /dev/null +++ b/subsys/usb/device_next/usbd_interface.h @@ -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 + +/** + * @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 */ diff --git a/subsys/usb/device_next/usbd_shell.c b/subsys/usb/device_next/usbd_shell.c new file mode 100644 index 00000000000..c4dea9bb563 --- /dev/null +++ b/subsys/usb/device_next/usbd_shell.c @@ -0,0 +1,813 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +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, "", + cmd_device_bcd, 2, 0), + SHELL_CMD_ARG(pid, NULL, "", + cmd_device_pid, 2, 0), + SHELL_CMD_ARG(vid, NULL, "", + cmd_device_vid, 2, 0), + SHELL_CMD_ARG(class, NULL, "", + cmd_device_class, 2, 0), + SHELL_CMD_ARG(subclass, NULL, "", + cmd_device_subclass, 2, 0), + SHELL_CMD_ARG(proto, NULL, "", + cmd_device_proto, 2, 0), + SHELL_SUBCMD_SET_END +); + +SHELL_STATIC_SUBCMD_SET_CREATE(config_cmds, + SHELL_CMD_ARG(add, NULL, "", + cmd_config_add, 2, 0), + SHELL_CMD_ARG(power, NULL, " ", + cmd_config_power, 3, 0), + SHELL_CMD_ARG(rwup, NULL, " ", + cmd_config_rwup, 3, 0), + SHELL_CMD_ARG(self, NULL, " ", + cmd_config_self, 3, 0), + SHELL_SUBCMD_SET_END +); + +SHELL_STATIC_SUBCMD_SET_CREATE(class_cmds, + SHELL_CMD_ARG(add, &dsub_node_name, " ", + cmd_register, 3, 0), + SHELL_CMD_ARG(add, &dsub_node_name, " ", + cmd_unregister, 3, 0), + SHELL_SUBCMD_SET_END +); + +SHELL_STATIC_SUBCMD_SET_CREATE(endpoint_cmds, + SHELL_CMD_ARG(halt, NULL, " ", + cmd_endpoint_halt, 3, 0), + SHELL_CMD_ARG(submit, NULL, " [length]", + cmd_submit_request, 2, 1), + SHELL_CMD_ARG(cancel, NULL, "", + 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);