samples: usb: add new WebUSB sample

Add a WebUSB sample that uses the new USB device support.

Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
This commit is contained in:
Johann Fischer 2024-07-25 18:28:28 +02:00 committed by Anas Nashif
parent 6ee1358519
commit 1687e192b5
11 changed files with 831 additions and 0 deletions

View file

@ -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})

View file

@ -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"

View file

@ -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: <board to use>
: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

View file

@ -0,0 +1,7 @@
:orphan:
WebUSB HTML Demo App
====================
.. raw:: html
:file: index.html

View file

@ -0,0 +1,126 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>WebUSB Serial Sample Application</title>
</head>
<body>
<script>
var serial = {};
(function() {
'use strict';
serial.getPorts = function() {
return navigator.usb.getDevices().then(devices => {
return devices.map(device => new serial.Port(device));
});
};
serial.requestPort = function() {
const filters = [
{ 'vendorId': 0x2fe3, 'productId': 0x0100 },
{ 'vendorId': 0x2fe3, 'productId': 0x00a },
{ 'vendorId': 0x8086, 'productId': 0xF8A1 },
];
return navigator.usb.requestDevice({ 'filters': filters }).then(
device => new serial.Port(device)
);
}
serial.Port = function(device) {
this.device_ = device;
};
serial.Port.prototype.connect = function() {
let readLoop = () => {
const {
endpointNumber
} = this.device_.configuration.interfaces[0].alternate.endpoints[0]
this.device_.transferIn(endpointNumber, 64).then(result => {
this.onReceive(result.data);
readLoop();
}, error => {
this.onReceiveError(error);
});
};
return this.device_.open()
.then(() => {
if (this.device_.configuration === null) {
return this.device_.selectConfiguration(1);
}
})
.then(() => this.device_.claimInterface(0))
.then(() => {
readLoop();
});
};
serial.Port.prototype.disconnect = function() {
return this.device_.close();
};
serial.Port.prototype.send = function(data) {
const {
endpointNumber
} = this.device_.configuration.interfaces[0].alternate.endpoints[1]
return this.device_.transferOut(endpointNumber, data);
};
})();
let port;
function connect() {
port.connect().then(() => {
port.onReceive = data => {
let textDecoder = new TextDecoder();
console.log("Received:", textDecoder.decode(data));
document.getElementById('output').value += textDecoder.decode(data);
}
port.onReceiveError = error => {
console.error(error);
document.querySelector("#connect").style = "visibility: initial";
port.disconnect();
};
});
}
function send(string) {
console.log("sending to serial:" + string.length);
if (string.length === 0)
return;
console.log("sending to serial: [" + string +"]\n");
let view = new TextEncoder('utf-8').encode(string);
console.log(view);
if (port) {
port.send(view);
}
};
window.onload = _ => {
document.querySelector("#connect").onclick = function() {
serial.requestPort().then(selectedPort => {
port = selectedPort;
this.style = "visibility: hidden";
connect();
});
}
document.querySelector("#submit").onclick = () => {
let source = document.querySelector("#input").value;
send(source);
}
}
</script>
<button id="connect" style="visibility: initial">Connect To WebUSB Device</button>
<br><br><label for="input">Sender: </label> <br>
<textarea id="input" rows="25" cols="80">WebUSB!</textarea>
<br><button id="submit">Send</button>
<br><br>
<label for="output">Receiver: </label> <br>
<textarea id="output" rows="25" cols="80"></textarea>
</body>
</html>

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,83 @@
/*
* Copyright (c) 2023-2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <sample_usbd.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/usb/usbd.h>
#include <zephyr/usb/class/usbd_hid.h>
#include <zephyr/usb/msos_desc.h>
#include <zephyr/logging/log.h>
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;
}

View file

@ -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 */

View file

@ -0,0 +1,274 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(sfunc, LOG_LEVEL_INF);
#include <zephyr/usb/usbd.h>
#include <zephyr/drivers/usb/udc.h>
#include <zephyr/sys/byteorder.h>
/*
* 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, ())

View file

@ -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 */