From 152644c4ca70c0c460031d9a1f536f3b6e10aee1 Mon Sep 17 00:00:00 2001 From: Johann Fischer Date: Thu, 25 Jul 2024 18:25:10 +0200 Subject: [PATCH 1/4] samples: usb: add samples function to setup USB device only Add a function similar to sample_usbd_init_device(), but one that does not initialize the device. It allows the application to set additional features, such as additional descriptors. Signed-off-by: Johann Fischer --- samples/subsys/usb/common/sample_usbd.h | 7 +++++++ samples/subsys/usb/common/sample_usbd_init.c | 13 ++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) 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) { From b00b5cace4cb238634429b0afd71a86c5b47b986 Mon Sep 17 00:00:00 2001 From: Johann Fischer Date: Thu, 25 Jul 2024 10:56:18 +0200 Subject: [PATCH 2/4] usb: device_next: support vendor request with recipient device Allow the user to register a vendor request node identified by the vendor code (bRequest) and containing two callbacks to handle the vendor request. The device stack uses the vendor request node to call the vendor request callbacks when it receives a request of type Vendor, recipient Device, and bRequest value equal to the vendor code. Signed-off-by: Johann Fischer --- include/zephyr/usb/usbd.h | 83 +++++++++++++++++++++++++++- subsys/usb/device_next/usbd_ch9.c | 30 +++++++++- subsys/usb/device_next/usbd_core.c | 2 + subsys/usb/device_next/usbd_device.c | 64 +++++++++++++++++++++ subsys/usb/device_next/usbd_device.h | 20 +++++++ 5 files changed, 196 insertions(+), 3 deletions(-) diff --git a/include/zephyr/usb/usbd.h b/include/zephyr/usb/usbd.h index 77784ac198963..3cf5c8942bf79 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 */ @@ -89,6 +91,54 @@ 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 */ @@ -199,8 +249,6 @@ struct usbd_status { enum usbd_speed speed : 2; }; -struct usbd_context; - /** * @brief Callback type definition for USB device message delivery * @@ -238,6 +286,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 +647,21 @@ 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 USB device support class data * @@ -1056,6 +1121,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/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_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 * From c9521e6cf9cde16dcff941e7c0a5fd470f157960 Mon Sep 17 00:00:00 2001 From: Johann Fischer Date: Sat, 19 Oct 2024 00:04:08 +0200 Subject: [PATCH 3/4] usb: device_next: support BOS descriptor with vendor request code Platform capability descriptors such as MSOSv2 or WebUSB BOS have a vendor request code that is used by the host to perform vendor-specific requests. Add a convenient way to define and register a platform capability descriptor with a vendor request node. Signed-off-by: Johann Fischer --- include/zephyr/usb/usbd.h | 32 ++++++++++++++++++++++++++++++ subsys/usb/device_next/usbd_desc.c | 7 +++++++ 2 files changed, 39 insertions(+) diff --git a/include/zephyr/usb/usbd.h b/include/zephyr/usb/usbd.h index 3cf5c8942bf79..fde10e5e6b818 100644 --- a/include/zephyr/usb/usbd.h +++ b/include/zephyr/usb/usbd.h @@ -74,6 +74,7 @@ enum usbd_str_desc_utype { enum usbd_bos_desc_utype { USBD_DUT_BOS_NONE, + USBD_DUT_BOS_VREQ, }; /** @endcond */ @@ -145,6 +146,9 @@ struct usbd_vreq_node { 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; + }; }; /** @@ -662,6 +666,34 @@ static inline void *usbd_class_get_private(const struct usbd_class_data *const c .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 * 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); } From 210cda7f570249df1c55f21779a686c5f93a37e9 Mon Sep 17 00:00:00 2001 From: Johann Fischer Date: Thu, 25 Jul 2024 18:28:28 +0200 Subject: [PATCH 4/4] samples: usb: add new WebUSB sample Add a WebUSB sample that uses the new USB device support. Signed-off-by: Johann Fischer --- samples/subsys/usb/webusb-next/CMakeLists.txt | 9 + samples/subsys/usb/webusb-next/Kconfig | 9 + samples/subsys/usb/webusb-next/README.rst | 73 +++++ samples/subsys/usb/webusb-next/demo.rst | 7 + samples/subsys/usb/webusb-next/index.html | 126 ++++++++ samples/subsys/usb/webusb-next/prj.conf | 7 + samples/subsys/usb/webusb-next/sample.yaml | 15 + samples/subsys/usb/webusb-next/src/main.c | 83 ++++++ samples/subsys/usb/webusb-next/src/msosv2.h | 138 +++++++++ samples/subsys/usb/webusb-next/src/sfunc.c | 274 ++++++++++++++++++ samples/subsys/usb/webusb-next/src/webusb.h | 90 ++++++ 11 files changed, 831 insertions(+) create mode 100644 samples/subsys/usb/webusb-next/CMakeLists.txt create mode 100644 samples/subsys/usb/webusb-next/Kconfig create mode 100644 samples/subsys/usb/webusb-next/README.rst create mode 100644 samples/subsys/usb/webusb-next/demo.rst create mode 100644 samples/subsys/usb/webusb-next/index.html create mode 100644 samples/subsys/usb/webusb-next/prj.conf create mode 100644 samples/subsys/usb/webusb-next/sample.yaml create mode 100644 samples/subsys/usb/webusb-next/src/main.c create mode 100644 samples/subsys/usb/webusb-next/src/msosv2.h create mode 100644 samples/subsys/usb/webusb-next/src/sfunc.c create mode 100644 samples/subsys/usb/webusb-next/src/webusb.h 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 */