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:
parent
6ee1358519
commit
1687e192b5
11 changed files with 831 additions and 0 deletions
9
samples/subsys/usb/webusb-next/CMakeLists.txt
Normal file
9
samples/subsys/usb/webusb-next/CMakeLists.txt
Normal 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})
|
||||
9
samples/subsys/usb/webusb-next/Kconfig
Normal file
9
samples/subsys/usb/webusb-next/Kconfig
Normal 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"
|
||||
73
samples/subsys/usb/webusb-next/README.rst
Normal file
73
samples/subsys/usb/webusb-next/README.rst
Normal 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
|
||||
7
samples/subsys/usb/webusb-next/demo.rst
Normal file
7
samples/subsys/usb/webusb-next/demo.rst
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
:orphan:
|
||||
|
||||
WebUSB HTML Demo App
|
||||
====================
|
||||
|
||||
.. raw:: html
|
||||
:file: index.html
|
||||
126
samples/subsys/usb/webusb-next/index.html
Normal file
126
samples/subsys/usb/webusb-next/index.html
Normal 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>
|
||||
7
samples/subsys/usb/webusb-next/prj.conf
Normal file
7
samples/subsys/usb/webusb-next/prj.conf
Normal 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
|
||||
15
samples/subsys/usb/webusb-next/sample.yaml
Normal file
15
samples/subsys/usb/webusb-next/sample.yaml
Normal 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
|
||||
83
samples/subsys/usb/webusb-next/src/main.c
Normal file
83
samples/subsys/usb/webusb-next/src/main.c
Normal 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;
|
||||
}
|
||||
138
samples/subsys/usb/webusb-next/src/msosv2.h
Normal file
138
samples/subsys/usb/webusb-next/src/msosv2.h
Normal 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 */
|
||||
274
samples/subsys/usb/webusb-next/src/sfunc.c
Normal file
274
samples/subsys/usb/webusb-next/src/sfunc.c
Normal 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, ())
|
||||
90
samples/subsys/usb/webusb-next/src/webusb.h
Normal file
90
samples/subsys/usb/webusb-next/src/webusb.h
Normal 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 */
|
||||
Loading…
Reference in a new issue