diff --git a/include/zephyr/usb/usbd.h b/include/zephyr/usb/usbd.h index 77784ac198963..fde10e5e6b818 100644 --- a/include/zephyr/usb/usbd.h +++ b/include/zephyr/usb/usbd.h @@ -59,6 +59,8 @@ extern "C" { */ #define USB_STRING_DESCRIPTOR_LENGTH(s) (sizeof(s) * 2) +struct usbd_context; + /** Used internally to keep descriptors in order * @cond INTERNAL_HIDDEN */ @@ -72,6 +74,7 @@ enum usbd_str_desc_utype { enum usbd_bos_desc_utype { USBD_DUT_BOS_NONE, + USBD_DUT_BOS_VREQ, }; /** @endcond */ @@ -89,12 +92,63 @@ struct usbd_str_desc_data { unsigned int use_hwinfo : 1; }; +/** + * USBD vendor request node + * + * Vendor request node is identified by the vendor code and is used to register + * callbacks to handle the vendor request with the receiving device. + * When the device stack receives a request with type Vendor and recipient + * Device, and bRequest value equal to the vendor request code, it will call + * the vendor callbacks depending on the direction of the request. + * + * Example callback code fragment: + * + * @code{.c} + * static int foo_to_host_cb(const struct usbd_context *const ctx, + * const struct usb_setup_packet *const setup, + * struct net_buf *const buf) + * { + * if (setup->wIndex == WEBUSB_REQ_GET_URL) { + * uint8_t index = USB_GET_DESCRIPTOR_INDEX(setup->wValue); + * + * if (index != SAMPLE_WEBUSB_LANDING_PAGE) { + * return -ENOTSUP; + * } + * + * net_buf_add_mem(buf, &webusb_origin_url, + * MIN(net_buf_tailroom(buf), sizeof(webusb_origin_url))); + * + * return 0; + * } + * + * return -ENOTSUP; + * } + * @endcode + */ +struct usbd_vreq_node { + /** Node information for the dlist */ + sys_dnode_t node; + /** Vendor code (bRequest value) */ + const uint8_t code; + /** Vendor request callback for device-to-host direction */ + int (*to_host)(const struct usbd_context *const ctx, + const struct usb_setup_packet *const setup, + struct net_buf *const buf); + /** Vendor request callback for host-to-device direction */ + int (*to_dev)(const struct usbd_context *const ctx, + const struct usb_setup_packet *const setup, + const struct net_buf *const buf); +}; + /** * USBD BOS Device Capability descriptor data */ struct usbd_bos_desc_data { /** Descriptor usage type (not bDescriptorType) */ enum usbd_bos_desc_utype utype : 8; + union { + struct usbd_vreq_node *const vreq_nd; + }; }; /** @@ -199,8 +253,6 @@ struct usbd_status { enum usbd_speed speed : 2; }; -struct usbd_context; - /** * @brief Callback type definition for USB device message delivery * @@ -238,6 +290,8 @@ struct usbd_context { sys_slist_t fs_configs; /** slist to manage High-Speed device configurations */ sys_slist_t hs_configs; + /** dlist to manage vendor requests with recipient device */ + sys_dlist_t vreqs; /** Status of the USB device support */ struct usbd_status status; /** Pointer to Full-Speed device descriptor */ @@ -597,6 +651,49 @@ static inline void *usbd_class_get_private(const struct usbd_class_data *const c .bDescriptorType = USB_DESC_BOS, \ } +/** + * @brief Define a vendor request with recipient device + * + * @param name Vendor request identifier + * @param vcode Vendor request code + * @param vto_host Vendor callback for to-host direction request + * @param vto_dev Vendor callback for to-device direction request + */ +#define USBD_VREQUEST_DEFINE(name, vcode, vto_host, vto_dev) \ + static struct usbd_vreq_node name = { \ + .code = vcode, \ + .to_host = vto_host, \ + .to_dev = vto_dev, \ + } + +/** + * @brief Define BOS Device Capability descriptor node with vendor request + * + * This macro defines a BOS descriptor, usually a platform capability, with a + * vendor request node. + * + * USBD_DESC_BOS_VREQ_DEFINE(bos_vreq_webusb, sizeof(bos_cap_webusb), &bos_cap_webusb, + * SAMPLE_WEBUSB_VENDOR_CODE, webusb_to_host_cb, NULL); + * + * @param name Descriptor node identifier + * @param len Device Capability descriptor length + * @param subset Pointer to a Device Capability descriptor + * @param vcode Vendor request code + * @param vto_host Vendor callback for to-host direction request + * @param vto_dev Vendor callback for to-device direction request + */ +#define USBD_DESC_BOS_VREQ_DEFINE(name, len, subset, vcode, vto_host, vto_dev) \ + USBD_VREQUEST_DEFINE(vreq_nd_##name, vcode, vto_host, vto_dev); \ + static struct usbd_desc_node name = { \ + .bos = { \ + .utype = USBD_DUT_BOS_VREQ, \ + .vreq_nd = &vreq_nd_##name, \ + }, \ + .ptr = subset, \ + .bLength = len, \ + .bDescriptorType = USB_DESC_BOS, \ + } + /** * @brief Define USB device support class data * @@ -1056,6 +1153,20 @@ int usbd_config_maxpower(struct usbd_context *const uds_ctx, */ bool usbd_can_detect_vbus(struct usbd_context *const uds_ctx); +/** + * @brief Register an USB vendor request with recipient device + * + * The vendor request with the recipient device applies to all configurations + * within the device. + * + * @param[in] uds_ctx Pointer to USB device support context + * @param[in] vreq_nd Pointer to vendor request node + * + * @return 0 on success, other values on fail. + */ +int usbd_device_register_vreq(struct usbd_context *const uds_ctx, + struct usbd_vreq_node *const vreq_nd); + /** * @} */ diff --git a/samples/subsys/usb/common/sample_usbd.h b/samples/subsys/usb/common/sample_usbd.h index 11779fef3f5e9..b12a62323d70c 100644 --- a/samples/subsys/usb/common/sample_usbd.h +++ b/samples/subsys/usb/common/sample_usbd.h @@ -29,4 +29,11 @@ */ struct usbd_context *sample_usbd_init_device(usbd_msg_cb_t msg_cb); +/* + * This function is similar to sample_usbd_init_device(), but does not + * initialize the device. It allows the application to set additional features, + * such as additional descriptors. + */ +struct usbd_context *sample_usbd_setup_device(usbd_msg_cb_t msg_cb); + #endif /* ZEPHYR_SAMPLES_SUBSYS_USB_COMMON_SAMPLE_USBD_H */ diff --git a/samples/subsys/usb/common/sample_usbd_init.c b/samples/subsys/usb/common/sample_usbd_init.c index 1cca20d85d21a..36e5444c10f12 100644 --- a/samples/subsys/usb/common/sample_usbd_init.c +++ b/samples/subsys/usb/common/sample_usbd_init.c @@ -82,7 +82,7 @@ static void sample_fix_code_triple(struct usbd_context *uds_ctx, } } -struct usbd_context *sample_usbd_init_device(usbd_msg_cb_t msg_cb) +struct usbd_context *sample_usbd_setup_device(usbd_msg_cb_t msg_cb) { int err; @@ -169,6 +169,17 @@ struct usbd_context *sample_usbd_init_device(usbd_msg_cb_t msg_cb) } } + return &sample_usbd; +} + +struct usbd_context *sample_usbd_init_device(usbd_msg_cb_t msg_cb) +{ + int err; + + if (sample_usbd_setup_device(msg_cb) == NULL) { + return NULL; + } + /* doc device init start */ err = usbd_init(&sample_usbd); if (err) { diff --git a/samples/subsys/usb/webusb-next/CMakeLists.txt b/samples/subsys/usb/webusb-next/CMakeLists.txt new file mode 100644 index 0000000000000..97a240805ee9e --- /dev/null +++ b/samples/subsys/usb/webusb-next/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(webusb) + +include(${ZEPHYR_BASE}/samples/subsys/usb/common/common.cmake) +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/samples/subsys/usb/webusb-next/Kconfig b/samples/subsys/usb/webusb-next/Kconfig new file mode 100644 index 0000000000000..96c5455894806 --- /dev/null +++ b/samples/subsys/usb/webusb-next/Kconfig @@ -0,0 +1,9 @@ +# Copyright (c) 2023 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +# Source common USB sample options used to initialize new experimental USB +# device stack. The scope of these options is limited to USB samples in project +# tree, you cannot use them in your own application. +source "samples/subsys/usb/common/Kconfig.sample_usbd" + +source "Kconfig.zephyr" diff --git a/samples/subsys/usb/webusb-next/README.rst b/samples/subsys/usb/webusb-next/README.rst new file mode 100644 index 0000000000000..262c6ee62722a --- /dev/null +++ b/samples/subsys/usb/webusb-next/README.rst @@ -0,0 +1,73 @@ +.. zephyr:code-sample:: webusb-next + :name: WebUSB-next + :relevant-api: usbd_api + + Receive and echo data from a web page using WebUSB API. + +Overview +******** + +This sample demonstrates how to use the Binary Device Object Store (BOS), +Microsoft OS 2.0 descriptors, and WebUSB descriptors to implement a WebUSB +sample application. The sample USB function receives the data and echoes back +to the WebUSB API based application running in the browser on your local host. +This sample can be found at :zephyr_file:`samples/subsys/usb/webusb-next` in the +Zephyr project tree. + +Requirements +************ + +This project requires a USB device controller driver using the UDC API. +On your host computer, this project requires a web browser that supports the +WebUSB API, such as Chromium or a Chromium-based browser. + +Building and Running +******************** + +Build and flash webusb sample with: + +.. zephyr-app-commands:: + :zephyr-app: samples/subsys/usb/webusb-next + :board: + :goals: flash + :compact: + +Demonstration +************* + +The sample includes a simple WebUSB API application and can be found in the +sample directory: :zephyr_file:`samples/subsys/usb/webusb-next/index.html`. + +There are two ways to access this sample page: + +* Using browser go to :doc:`demo` + +* Start a web server in the sample directory: + + .. code-block:: console + + $ python -m http.server + +Then follow these steps: + +#. Connect the board to your host. + +#. Once the device has booted, you may see a notification from the browser: "Go + to localhost to connect". Click on the notification to open the demo page. If + there is no notification from the browser, open the URL http://localhost:8001/ + in your browser. + +#. Click on the :guilabel:`Connect` button to connect to the device. + +#. Send some text to the device by clicking on the :guilabel:`Send` button. + The demo application will receive the same text from the device and display + it in the text area. + +References +*********** + +WebUSB API Specification: +https://wicg.github.io/webusb/ + +Chrome for Developers, "Access USB Devices on the Web": +https://developer.chrome.com/docs/capabilities/usb diff --git a/samples/subsys/usb/webusb-next/demo.rst b/samples/subsys/usb/webusb-next/demo.rst new file mode 100644 index 0000000000000..6925a80258093 --- /dev/null +++ b/samples/subsys/usb/webusb-next/demo.rst @@ -0,0 +1,7 @@ +:orphan: + +WebUSB HTML Demo App +==================== + +.. raw:: html + :file: index.html diff --git a/samples/subsys/usb/webusb-next/index.html b/samples/subsys/usb/webusb-next/index.html new file mode 100644 index 0000000000000..b6cfc96d447eb --- /dev/null +++ b/samples/subsys/usb/webusb-next/index.html @@ -0,0 +1,126 @@ + + + + WebUSB Serial Sample Application + + + + + +


+ +
+

+
+ + + diff --git a/samples/subsys/usb/webusb-next/prj.conf b/samples/subsys/usb/webusb-next/prj.conf new file mode 100644 index 0000000000000..1c730a0ec56a3 --- /dev/null +++ b/samples/subsys/usb/webusb-next/prj.conf @@ -0,0 +1,7 @@ +CONFIG_USB_DEVICE_STACK_NEXT=y + +CONFIG_LOG=y +CONFIG_USBD_LOG_LEVEL_WRN=y +CONFIG_UDC_DRIVER_LOG_LEVEL_WRN=y +CONFIG_SAMPLE_USBD_PID=0x000A +CONFIG_SAMPLE_USBD_20_EXTENSION_DESC=y diff --git a/samples/subsys/usb/webusb-next/sample.yaml b/samples/subsys/usb/webusb-next/sample.yaml new file mode 100644 index 0000000000000..d3beb068981cc --- /dev/null +++ b/samples/subsys/usb/webusb-next/sample.yaml @@ -0,0 +1,15 @@ +sample: + name: WebUSB +tests: + sample.usb.webusb-next: + depends_on: usbd + tags: usb + integration_platforms: + - nrf52840dk/nrf52840 + - nrf54h20dk/nrf54h20/cpuapp + - frdm_k64f + - stm32f723e_disco + - nucleo_f413zh + - mimxrt685_evk/mimxrt685s/cm33 + - mimxrt1060_evk + harness: TBD diff --git a/samples/subsys/usb/webusb-next/src/main.c b/samples/subsys/usb/webusb-next/src/main.c new file mode 100644 index 0000000000000..fe600e9d08d11 --- /dev/null +++ b/samples/subsys/usb/webusb-next/src/main.c @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2023-2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(main, LOG_LEVEL_INF); + +/* + * There are three BOS descriptors used in the sample, a USB 2.0 EXTENSION from + * the USB samples common code, a Microsoft OS 2.0 platform capability + * descriptor, and a WebUSB platform capability descriptor. + */ +#include "webusb.h" +#include "msosv2.h" + +static void msg_cb(struct usbd_context *const usbd_ctx, + const struct usbd_msg *const msg) +{ + LOG_INF("USBD message: %s", usbd_msg_type_string(msg->type)); + + if (usbd_can_detect_vbus(usbd_ctx)) { + if (msg->type == USBD_MSG_VBUS_READY) { + if (usbd_enable(usbd_ctx)) { + LOG_ERR("Failed to enable device support"); + } + } + + if (msg->type == USBD_MSG_VBUS_REMOVED) { + if (usbd_disable(usbd_ctx)) { + LOG_ERR("Failed to disable device support"); + } + } + } +} + +int main(void) +{ + struct usbd_context *sample_usbd; + int ret; + + sample_usbd = sample_usbd_setup_device(msg_cb); + if (sample_usbd == NULL) { + LOG_ERR("Failed to setup USB device"); + return -ENODEV; + } + + ret = usbd_add_descriptor(sample_usbd, &bos_vreq_msosv2); + if (ret) { + LOG_ERR("Failed to add MSOSv2 capability descriptor"); + return ret; + } + + ret = usbd_add_descriptor(sample_usbd, &bos_vreq_webusb); + if (ret) { + LOG_ERR("Failed to add WebUSB capability descriptor"); + return ret; + } + + ret = usbd_init(sample_usbd); + if (ret) { + LOG_ERR("Failed to initialize device support"); + return ret; + } + + if (!usbd_can_detect_vbus(sample_usbd)) { + ret = usbd_enable(sample_usbd); + if (ret) { + LOG_ERR("Failed to enable device support"); + return ret; + } + } + + return 0; +} diff --git a/samples/subsys/usb/webusb-next/src/msosv2.h b/samples/subsys/usb/webusb-next/src/msosv2.h new file mode 100644 index 0000000000000..88fc592737fa4 --- /dev/null +++ b/samples/subsys/usb/webusb-next/src/msosv2.h @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2016-2019 Intel Corporation + * Copyright (c) 2023-2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_MSOSV2_DESCRIPTOR_H +#define ZEPHYR_INCLUDE_MSOSV2_DESCRIPTOR_H + +/* + * Microsoft OS 2.0 platform capability and Microsoft OS 2.0 descriptor set. + * See Microsoft OS 2.0 Descriptors Specification for reference. + */ + +#define SAMPLE_MSOS2_VENDOR_CODE 0x02U +/* Windows version (10)*/ +#define SAMPLE_MSOS2_OS_VERSION 0x0A000000UL + +/* random GUID {FA611CC3-7057-42EE-9D82-4919639562B3} */ +#define WEBUSB_DEVICE_INTERFACE_GUID \ + '{', 0x00, 'F', 0x00, 'A', 0x00, '6', 0x00, '1', 0x00, '1', 0x00, \ + 'C', 0x00, 'C', 0x00, '3', 0x00, '-', 0x00, '7', 0x00, '0', 0x00, \ + '5', 0x00, '7', 0x00, '-', 0x00, '4', 0x00, '2', 0x00, 'E', 0x00, \ + 'E', 0x00, '-', 0x00, '9', 0x00, 'D', 0x00, '8', 0x00, '2', 0x00, \ + '-', 0x00, '4', 0x00, '9', 0x00, '1', 0x00, '9', 0x00, '6', 0x00, \ + '3', 0x00, '9', 0x00, '5', 0x00, '6', 0x00, '2', 0x00, 'B', 0x00, \ + '3', 0x00, '}', 0x00, 0x00, 0x00, 0x00, 0x00 + +#define CDC_ACM_DESCRIPTOR_LENGTH 160 + +struct msosv2_descriptor { + struct msosv2_descriptor_set_header header; +#if defined(CONFIG_USBD_CDC_ACM_CLASS) + /* + * The composition of this descriptor is specific to the WebUSB example + * in its default configuration. If you use it for your application or + * change the configuration, you may need to modify this descriptor for + * your USB device. The following only covers the case where the CDC + * ACM implementation is enabled, and there is only one CDC ACM UART + * instance, and the CDC ACM communication interface is the first in + * the configuration. + */ + struct msosv2_function_subset_header subset_header; +#endif + struct msosv2_compatible_id compatible_id; + struct msosv2_guids_property guids_property; +} __packed; + +static struct msosv2_descriptor msosv2_desc = { + .header = { + .wLength = sizeof(struct msosv2_descriptor_set_header), + .wDescriptorType = MS_OS_20_SET_HEADER_DESCRIPTOR, + .dwWindowsVersion = sys_cpu_to_le32(SAMPLE_MSOS2_OS_VERSION), + .wTotalLength = sizeof(msosv2_desc), + }, +#if defined(CONFIG_USBD_CDC_ACM_CLASS) + .subset_header = { + .wLength = sizeof(struct msosv2_function_subset_header), + .wDescriptorType = MS_OS_20_SUBSET_HEADER_FUNCTION, + .bFirstInterface = 0, + .wSubsetLength = CDC_ACM_DESCRIPTOR_LENGTH, + }, +#endif + .compatible_id = { + .wLength = sizeof(struct msosv2_compatible_id), + .wDescriptorType = MS_OS_20_FEATURE_COMPATIBLE_ID, + .CompatibleID = {'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00}, + }, + .guids_property = { + .wLength = sizeof(struct msosv2_guids_property), + .wDescriptorType = MS_OS_20_FEATURE_REG_PROPERTY, + .wPropertyDataType = MS_OS_20_PROPERTY_DATA_REG_MULTI_SZ, + .wPropertyNameLength = 42, + .PropertyName = {DEVICE_INTERFACE_GUIDS_PROPERTY_NAME}, + .wPropertyDataLength = 80, + .bPropertyData = {WEBUSB_DEVICE_INTERFACE_GUID}, + }, +}; + +struct bos_msosv2_descriptor { + struct usb_bos_platform_descriptor platform; + struct usb_bos_capability_msos cap; +} __packed; + +struct bos_msosv2_descriptor bos_msosv2_desc = { + /* + * Microsoft OS 2.0 Platform Capability Descriptor, + * see Microsoft OS 2.0 Descriptors Specification + */ + .platform = { + .bLength = sizeof(struct usb_bos_platform_descriptor) + + sizeof(struct usb_bos_capability_msos), + .bDescriptorType = USB_DESC_DEVICE_CAPABILITY, + .bDevCapabilityType = USB_BOS_CAPABILITY_PLATFORM, + .bReserved = 0, + /* Microsoft OS 2.0 descriptor platform capability UUID + * D8DD60DF-4589-4CC7-9CD2-659D9E648A9F + */ + .PlatformCapabilityUUID = { + 0xDF, 0x60, 0xDD, 0xD8, + 0x89, 0x45, + 0xC7, 0x4C, + 0x9C, 0xD2, + 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F, + }, + }, + .cap = { + .dwWindowsVersion = sys_cpu_to_le32(SAMPLE_MSOS2_OS_VERSION), + .wMSOSDescriptorSetTotalLength = sys_cpu_to_le16(sizeof(msosv2_desc)), + .bMS_VendorCode = SAMPLE_MSOS2_VENDOR_CODE, + .bAltEnumCode = 0x00 + }, +}; + +static int msosv2_to_host_cb(const struct usbd_context *const ctx, + const struct usb_setup_packet *const setup, + struct net_buf *const buf) +{ + LOG_INF("Vendor callback to host"); + + if (setup->bRequest == SAMPLE_MSOS2_VENDOR_CODE && + setup->wIndex == MS_OS_20_DESCRIPTOR_INDEX) { + LOG_INF("Get MS OS 2.0 Descriptor Set"); + + net_buf_add_mem(buf, &msosv2_desc, + MIN(net_buf_tailroom(buf), sizeof(msosv2_desc))); + + return 0; + } + + return -ENOTSUP; +} + +USBD_DESC_BOS_VREQ_DEFINE(bos_vreq_msosv2, sizeof(bos_msosv2_desc), &bos_msosv2_desc, + SAMPLE_MSOS2_VENDOR_CODE, msosv2_to_host_cb, NULL); + +#endif /* ZEPHYR_INCLUDE_MSOSV2_DESCRIPTOR_H */ diff --git a/samples/subsys/usb/webusb-next/src/sfunc.c b/samples/subsys/usb/webusb-next/src/sfunc.c new file mode 100644 index 0000000000000..d20482e429ad0 --- /dev/null +++ b/samples/subsys/usb/webusb-next/src/sfunc.c @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_REGISTER(sfunc, LOG_LEVEL_INF); + +#include +#include +#include + +/* + * This file implements a simple USB function that echoes received data back to + * the host using bulk endpoints. + */ + +NET_BUF_POOL_FIXED_DEFINE(sfunc_pool, + 1, 0, sizeof(struct udc_buf_info), NULL); + +static uint8_t __aligned(sizeof(void *)) sfunc_buf[512]; + +struct sfunc_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_hs_out_ep; + struct usb_ep_descriptor if0_hs_in_ep; + struct usb_desc_header nil_desc; +}; + +#define SAMPLE_FUNCTION_ENABLED 0 + +struct sfunc_data { + struct sfunc_desc *const desc; + const struct usb_desc_header **const fs_desc; + const struct usb_desc_header **const hs_desc; + atomic_t state; +}; + +static uint8_t sfunc_get_bulk_out(struct usbd_class_data *const c_data) +{ + struct sfunc_data *data = usbd_class_get_private(c_data); + struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data); + struct sfunc_desc *desc = data->desc; + + if (usbd_bus_speed(uds_ctx) == USBD_SPEED_HS) { + return desc->if0_hs_out_ep.bEndpointAddress; + } + + return desc->if0_out_ep.bEndpointAddress; +} + +static uint8_t sfunc_get_bulk_in(struct usbd_class_data *const c_data) +{ + struct sfunc_data *data = usbd_class_get_private(c_data); + struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data); + struct sfunc_desc *desc = data->desc; + + if (usbd_bus_speed(uds_ctx) == USBD_SPEED_HS) { + return desc->if0_hs_in_ep.bEndpointAddress; + } + + return desc->if0_in_ep.bEndpointAddress; +} + +static int sfunc_request_handler(struct usbd_class_data *c_data, + struct net_buf *buf, int err) +{ + struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data); + struct sfunc_data *data = usbd_class_get_private(c_data); + struct udc_buf_info *bi = NULL; + + bi = (struct udc_buf_info *)net_buf_user_data(buf); + LOG_INF("Transfer finished %p -> ep 0x%02x, len %u, err %d", + (void *)c_data, bi->ep, buf->len, err); + + if (atomic_test_bit(&data->state, SAMPLE_FUNCTION_ENABLED) && err == 0) { + uint8_t ep = bi->ep; + + memset(bi, 0, sizeof(struct udc_buf_info)); + + if (ep == sfunc_get_bulk_in(c_data)) { + bi->ep = sfunc_get_bulk_out(c_data); + net_buf_reset(buf); + } else { + bi->ep = sfunc_get_bulk_in(c_data); + } + + if (usbd_ep_enqueue(c_data, buf)) { + LOG_ERR("Failed to enqueue buffer"); + usbd_ep_buf_free(uds_ctx, buf); + } + } else { + LOG_ERR("Function is disabled or transfer failed"); + usbd_ep_buf_free(uds_ctx, buf); + } + + return 0; +} + +static void *sfunc_get_desc(struct usbd_class_data *const c_data, + const enum usbd_speed speed) +{ + struct sfunc_data *data = usbd_class_get_private(c_data); + + if (speed == USBD_SPEED_HS) { + return data->hs_desc; + } + + return data->fs_desc; +} + +struct net_buf *sfunc_buf_alloc(struct usbd_class_data *const c_data, + const uint8_t ep) +{ + struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data); + struct net_buf *buf = NULL; + struct udc_buf_info *bi; + size_t size; + + if (usbd_bus_speed(uds_ctx) == USBD_SPEED_HS) { + size = 512U; + } else { + size = 64U; + } + + buf = net_buf_alloc_with_data(&sfunc_pool, sfunc_buf, size, K_NO_WAIT); + net_buf_reset(buf); + if (!buf) { + return NULL; + } + + bi = udc_get_buf_info(buf); + memset(bi, 0, sizeof(struct udc_buf_info)); + bi->ep = ep; + + return buf; +} + +static void sfunc_enable(struct usbd_class_data *const c_data) +{ + struct sfunc_data *data = usbd_class_get_private(c_data); + struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data); + struct net_buf *buf; + + LOG_INF("Configuration enabled"); + + if (!atomic_test_and_set_bit(&data->state, SAMPLE_FUNCTION_ENABLED)) { + buf = sfunc_buf_alloc(c_data, sfunc_get_bulk_out(c_data)); + if (buf == NULL) { + LOG_ERR("Failed to allocate buffer"); + return; + } + + if (usbd_ep_enqueue(c_data, buf)) { + LOG_ERR("Failed to enqueue buffer"); + usbd_ep_buf_free(uds_ctx, buf); + } + } +} + +static void sfunc_disable(struct usbd_class_data *const c_data) +{ + struct sfunc_data *data = usbd_class_get_private(c_data); + + atomic_clear_bit(&data->state, SAMPLE_FUNCTION_ENABLED); + LOG_INF("Configuration disabled"); +} + +static int sfunc_init(struct usbd_class_data *c_data) +{ + LOG_DBG("Init class instance %p", (void *)c_data); + + return 0; +} + +struct usbd_class_api sfunc_api = { + .request = sfunc_request_handler, + .get_desc = sfunc_get_desc, + .enable = sfunc_enable, + .disable = sfunc_disable, + .init = sfunc_init, +}; + +#define SFUNC_DESCRIPTOR_DEFINE(n, _) \ +static struct sfunc_desc sfunc_desc_##n = { \ + /* Interface descriptor 0 */ \ + .if0 = { \ + .bLength = sizeof(struct usb_if_descriptor), \ + .bDescriptorType = USB_DESC_INTERFACE, \ + .bInterfaceNumber = 0, \ + .bAlternateSetting = 0, \ + .bNumEndpoints = 2, \ + .bInterfaceClass = USB_BCC_VENDOR, \ + .bInterfaceSubClass = 0, \ + .bInterfaceProtocol = 0, \ + .iInterface = 0, \ + }, \ + \ + /* Endpoint OUT */ \ + .if0_out_ep = { \ + .bLength = sizeof(struct usb_ep_descriptor), \ + .bDescriptorType = USB_DESC_ENDPOINT, \ + .bEndpointAddress = 0x01, \ + .bmAttributes = USB_EP_TYPE_BULK, \ + .wMaxPacketSize = sys_cpu_to_le16(64U), \ + .bInterval = 0x00, \ + }, \ + \ + /* Endpoint IN */ \ + .if0_in_ep = { \ + .bLength = sizeof(struct usb_ep_descriptor), \ + .bDescriptorType = USB_DESC_ENDPOINT, \ + .bEndpointAddress = 0x81, \ + .bmAttributes = USB_EP_TYPE_BULK, \ + .wMaxPacketSize = sys_cpu_to_le16(64U), \ + .bInterval = 0x00, \ + }, \ + \ + /* High-speed Endpoint OUT */ \ + .if0_hs_out_ep = { \ + .bLength = sizeof(struct usb_ep_descriptor), \ + .bDescriptorType = USB_DESC_ENDPOINT, \ + .bEndpointAddress = 0x01, \ + .bmAttributes = USB_EP_TYPE_BULK, \ + .wMaxPacketSize = sys_cpu_to_le16(512), \ + .bInterval = 0x00, \ + }, \ + \ + /* High-speed Endpoint IN */ \ + .if0_hs_in_ep = { \ + .bLength = sizeof(struct usb_ep_descriptor), \ + .bDescriptorType = USB_DESC_ENDPOINT, \ + .bEndpointAddress = 0x81, \ + .bmAttributes = USB_EP_TYPE_BULK, \ + .wMaxPacketSize = sys_cpu_to_le16(512), \ + .bInterval = 0x00, \ + }, \ + \ + /* Termination descriptor */ \ + .nil_desc = { \ + .bLength = 0, \ + .bDescriptorType = 0, \ + }, \ +}; \ + \ +const static struct usb_desc_header *sfunc_fs_desc_##n[] = { \ + (struct usb_desc_header *) &sfunc_desc_##n.if0, \ + (struct usb_desc_header *) &sfunc_desc_##n.if0_in_ep, \ + (struct usb_desc_header *) &sfunc_desc_##n.if0_out_ep, \ + (struct usb_desc_header *) &sfunc_desc_##n.nil_desc, \ +}; \ + \ +const static struct usb_desc_header *sfunc_hs_desc_##n[] = { \ + (struct usb_desc_header *) &sfunc_desc_##n.if0, \ + (struct usb_desc_header *) &sfunc_desc_##n.if0_hs_in_ep, \ + (struct usb_desc_header *) &sfunc_desc_##n.if0_hs_out_ep, \ + (struct usb_desc_header *) &sfunc_desc_##n.nil_desc, \ +}; + + +#define SFUNC_FUNCTION_DATA_DEFINE(n, _) \ + static struct sfunc_data sfunc_data_##n = { \ + .desc = &sfunc_desc_##n, \ + .fs_desc = sfunc_fs_desc_##n, \ + .hs_desc = sfunc_hs_desc_##n, \ + }; \ + \ + USBD_DEFINE_CLASS(sfunc_##n, &sfunc_api, &sfunc_data_##n, NULL); + +LISTIFY(1, SFUNC_DESCRIPTOR_DEFINE, ()) +LISTIFY(1, SFUNC_FUNCTION_DATA_DEFINE, ()) diff --git a/samples/subsys/usb/webusb-next/src/webusb.h b/samples/subsys/usb/webusb-next/src/webusb.h new file mode 100644 index 0000000000000..329626d4587b6 --- /dev/null +++ b/samples/subsys/usb/webusb-next/src/webusb.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2016-2019 Intel Corporation + * Copyright (c) 2023-2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_WEBUSB_DESCRIPTOR_H +#define ZEPHYR_INCLUDE_WEBUSB_DESCRIPTOR_H + +/* + * WebUSB platform capability and WebUSB URL descriptor. + * See https://wicg.github.io/webusb for reference. + */ + +#define WEBUSB_REQ_GET_URL 0x02U +#define WEBUSB_DESC_TYPE_URL 0x03U +#define WEBUSB_URL_PREFIX_HTTP 0x00U +#define WEBUSB_URL_PREFIX_HTTPS 0x01U + +#define SAMPLE_WEBUSB_VENDOR_CODE 0x01U +#define SAMPLE_WEBUSB_LANDING_PAGE 0x01U + +struct usb_bos_webusb_desc { + struct usb_bos_platform_descriptor platform; + struct usb_bos_capability_webusb cap; +} __packed; + +static const struct usb_bos_webusb_desc bos_cap_webusb = { + /* WebUSB Platform Capability Descriptor: + * https://wicg.github.io/webusb/#webusb-platform-capability-descriptor + */ + .platform = { + .bLength = sizeof(struct usb_bos_platform_descriptor) + + sizeof(struct usb_bos_capability_webusb), + .bDescriptorType = USB_DESC_DEVICE_CAPABILITY, + .bDevCapabilityType = USB_BOS_CAPABILITY_PLATFORM, + .bReserved = 0, + /* WebUSB Platform Capability UUID + * 3408b638-09a9-47a0-8bfd-a0768815b665 + */ + .PlatformCapabilityUUID = { + 0x38, 0xB6, 0x08, 0x34, + 0xA9, 0x09, + 0xA0, 0x47, + 0x8B, 0xFD, + 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65, + }, + }, + .cap = { + .bcdVersion = sys_cpu_to_le16(0x0100), + .bVendorCode = SAMPLE_WEBUSB_VENDOR_CODE, + .iLandingPage = SAMPLE_WEBUSB_LANDING_PAGE + } +}; + +/* WebUSB URL Descriptor, see https://wicg.github.io/webusb/#webusb-descriptors */ +static const uint8_t webusb_origin_url[] = { + /* bLength, bDescriptorType, bScheme, UTF-8 encoded URL */ + 0x11, WEBUSB_DESC_TYPE_URL, WEBUSB_URL_PREFIX_HTTP, + 'l', 'o', 'c', 'a', 'l', 'h', 'o', 's', 't', ':', '8', '0', '0', '0' +}; + +static int webusb_to_host_cb(const struct usbd_context *const ctx, + const struct usb_setup_packet *const setup, + struct net_buf *const buf) +{ + LOG_INF("Vendor callback to host"); + + if (setup->wIndex == WEBUSB_REQ_GET_URL) { + uint8_t index = USB_GET_DESCRIPTOR_INDEX(setup->wValue); + + if (index != SAMPLE_WEBUSB_LANDING_PAGE) { + return -ENOTSUP; + } + + LOG_INF("Get URL request, index %u", index); + net_buf_add_mem(buf, &webusb_origin_url, + MIN(net_buf_tailroom(buf), sizeof(webusb_origin_url))); + + return 0; + } + + return -ENOTSUP; +} + +USBD_DESC_BOS_VREQ_DEFINE(bos_vreq_webusb, sizeof(bos_cap_webusb), &bos_cap_webusb, + SAMPLE_WEBUSB_VENDOR_CODE, webusb_to_host_cb, NULL); + +#endif /* ZEPHYR_INCLUDE_WEBUSB_DESCRIPTOR_H */ diff --git a/subsys/usb/device_next/usbd_ch9.c b/subsys/usb/device_next/usbd_ch9.c index d3eebcaccc5bd..823fc6f0d56e1 100644 --- a/subsys/usb/device_next/usbd_ch9.c +++ b/subsys/usb/device_next/usbd_ch9.c @@ -913,6 +913,34 @@ static int std_request_to_host(struct usbd_context *const uds_ctx, return ret; } +static int vendor_device_request(struct usbd_context *const uds_ctx, + struct net_buf *const buf) +{ + struct usb_setup_packet *setup = usbd_get_setup_pkt(uds_ctx); + struct usbd_vreq_node *vreq_nd; + + vreq_nd = usbd_device_get_vreq(uds_ctx, setup->bRequest); + if (vreq_nd == NULL) { + errno = -ENOTSUP; + return 0; + } + + if (reqtype_is_to_device(setup) && vreq_nd->to_dev != NULL) { + LOG_ERR("Vendor request 0x%02x to device", setup->bRequest); + errno = vreq_nd->to_dev(uds_ctx, setup, buf); + return 0; + } + + if (reqtype_is_to_host(setup) && vreq_nd->to_host != NULL) { + LOG_ERR("Vendor request 0x%02x to host", setup->bRequest); + errno = vreq_nd->to_host(uds_ctx, setup, buf); + return 0; + } + + errno = -ENOTSUP; + return 0; +} + static int nonstd_request(struct usbd_context *const uds_ctx, struct net_buf *const dbuf) { @@ -941,7 +969,7 @@ static int nonstd_request(struct usbd_context *const uds_ctx, ret = usbd_class_control_to_host(c_nd->c_data, setup, dbuf); } } else { - errno = -ENOTSUP; + return vendor_device_request(uds_ctx, dbuf); } return ret; diff --git a/subsys/usb/device_next/usbd_core.c b/subsys/usb/device_next/usbd_core.c index 4bdebd6fc6877..fd9af4c256397 100644 --- a/subsys/usb/device_next/usbd_core.c +++ b/subsys/usb/device_next/usbd_core.c @@ -249,6 +249,8 @@ int usbd_device_shutdown_core(struct usbd_context *const uds_ctx) LOG_ERR("Failed to cleanup descriptors, %d", ret); } + usbd_device_unregister_all_vreq(uds_ctx); + return udc_shutdown(uds_ctx->dev); } diff --git a/subsys/usb/device_next/usbd_desc.c b/subsys/usb/device_next/usbd_desc.c index 158cdb1ba22fc..5979b9b16ad35 100644 --- a/subsys/usb/device_next/usbd_desc.c +++ b/subsys/usb/device_next/usbd_desc.c @@ -142,6 +142,13 @@ int usbd_add_descriptor(struct usbd_context *const uds_ctx, } if (desc_nd->bDescriptorType == USB_DESC_BOS) { + if (desc_nd->bos.utype == USBD_DUT_BOS_VREQ) { + ret = usbd_device_register_vreq(uds_ctx, desc_nd->bos.vreq_nd); + if (ret) { + goto add_descriptor_error; + } + } + sys_dlist_append(&uds_ctx->descriptors, &desc_nd->node); } diff --git a/subsys/usb/device_next/usbd_device.c b/subsys/usb/device_next/usbd_device.c index b2ba1fe3cffc2..0dce57a1bb66f 100644 --- a/subsys/usb/device_next/usbd_device.c +++ b/subsys/usb/device_next/usbd_device.c @@ -310,3 +310,67 @@ bool usbd_can_detect_vbus(struct usbd_context *const uds_ctx) return caps.can_detect_vbus; } + +struct usbd_vreq_node *usbd_device_get_vreq(struct usbd_context *const uds_ctx, + const uint8_t code) +{ + struct usbd_vreq_node *vreq_nd; + + SYS_DLIST_FOR_EACH_CONTAINER(&uds_ctx->vreqs, vreq_nd, node) { + if (vreq_nd->code == code) { + return vreq_nd; + } + } + + return NULL; +} + +int usbd_device_register_vreq(struct usbd_context *const uds_ctx, + struct usbd_vreq_node *const vreq_nd) +{ + int ret = 0; + + usbd_device_lock(uds_ctx); + + if (usbd_is_initialized(uds_ctx)) { + ret = -EPERM; + goto error; + } + + if (vreq_nd->to_dev == NULL && vreq_nd->to_host == NULL) { + ret = -EINVAL; + goto error; + } + + if (!sys_dnode_is_linked(&uds_ctx->vreqs)) { + LOG_DBG("Initialize vendor request list"); + sys_dlist_init(&uds_ctx->vreqs); + } + + if (sys_dnode_is_linked(&vreq_nd->node)) { + ret = -EALREADY; + goto error; + } + + sys_dlist_append(&uds_ctx->vreqs, &vreq_nd->node); + LOG_DBG("Registered vendor request 0x%02x", vreq_nd->code); + +error: + usbd_device_unlock(uds_ctx); + return ret; +} + +void usbd_device_unregister_all_vreq(struct usbd_context *const uds_ctx) +{ + struct usbd_vreq_node *tmp; + sys_dnode_t *node; + + if (!sys_dnode_is_linked(&uds_ctx->vreqs)) { + return; + } + + while ((node = sys_dlist_get(&uds_ctx->vreqs))) { + tmp = CONTAINER_OF(node, struct usbd_vreq_node, node); + LOG_DBG("Remove vendor request 0x%02x", tmp->code); + } +} diff --git a/subsys/usb/device_next/usbd_device.h b/subsys/usb/device_next/usbd_device.h index 76c7ed7ee9aaf..e2f0d98ab8b4e 100644 --- a/subsys/usb/device_next/usbd_device.h +++ b/subsys/usb/device_next/usbd_device.h @@ -10,6 +10,26 @@ #include #include +/** + * @brief Get vendor request node + * + * Get vendor request node from internal vendor request list. + * + * @param[in] ctx Pointer to USB device support context + * @param[in] code Vendor request code + * + * @return pointer to vendor request node or NULL if not found. + */ +struct usbd_vreq_node *usbd_device_get_vreq(struct usbd_context *const uds_ctx, + const uint8_t code); + +/** + * @brief Unregister all registered vendor request + * + * @param[in] uds_ctx Pointer to a device context + */ +void usbd_device_unregister_all_vreq(struct usbd_context *const uds_ctx); + /** * @brief Get device descriptor bNumConfigurations value *