mirror of
https://github.com/hathach/tinyusb.git
synced 2026-08-18 11:02:16 +00:00
example(usbtest): device-side peer for the Linux kernel usbtest battery
Gadget-Zero style source/sink on a vendor interface (alt0 empty, alt1 bulk+int+iso) plus EP0 ctrl_out; tier advertised in bcdDevice. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HeF2gZ1M7GWkz6Av4BpKPg
This commit is contained in:
@ -35,6 +35,7 @@ set(EXAMPLE_LIST
|
||||
printer_to_cdc
|
||||
uac2_headset
|
||||
uac2_speaker_fb
|
||||
usbtest
|
||||
usbtmc
|
||||
video_capture
|
||||
video_capture_2ch
|
||||
|
||||
30
examples/device/usbtest/CMakeLists.txt
Normal file
30
examples/device/usbtest/CMakeLists.txt
Normal file
@ -0,0 +1,30 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake)
|
||||
|
||||
project(usbtest C CXX ASM)
|
||||
|
||||
# Checks this example is valid for the family and initializes the project
|
||||
family_initialize_project(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
# Espressif has its own cmake build system
|
||||
if(FAMILY STREQUAL "espressif")
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_executable(${PROJECT_NAME})
|
||||
|
||||
# Example source
|
||||
target_sources(${PROJECT_NAME} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/usb_descriptors.c
|
||||
)
|
||||
|
||||
# Example include
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
)
|
||||
|
||||
# Configure compilation flags and libraries for the example without RTOS.
|
||||
# See the corresponding function in hw/bsp/FAMILY/family.cmake for details.
|
||||
family_configure_device_example(${PROJECT_NAME} noos)
|
||||
6
examples/device/usbtest/CMakePresets.json
Normal file
6
examples/device/usbtest/CMakePresets.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": 6,
|
||||
"include": [
|
||||
"../../../hw/bsp/BoardPresets.json"
|
||||
]
|
||||
}
|
||||
11
examples/device/usbtest/Makefile
Normal file
11
examples/device/usbtest/Makefile
Normal file
@ -0,0 +1,11 @@
|
||||
include ../../../hw/bsp/family_support.mk
|
||||
|
||||
INC += \
|
||||
src \
|
||||
|
||||
|
||||
# Example source
|
||||
EXAMPLE_SOURCE += $(wildcard src/*.c)
|
||||
SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE))
|
||||
|
||||
include ../../../hw/bsp/family_rules.mk
|
||||
94
examples/device/usbtest/README.md
Normal file
94
examples/device/usbtest/README.md
Normal file
@ -0,0 +1,94 @@
|
||||
# usbtest
|
||||
|
||||
Device-side peer of the Linux kernel USB test pair:
|
||||
|
||||
- `usbtest.ko` — host kernel module (`drivers/usb/misc/usbtest.c`) containing ~30 numbered
|
||||
test cases over bulk/control/interrupt/isochronous transfers.
|
||||
- `testusb` — userspace dispatcher (`tools/usb/testusb.c`) that tells the module which case
|
||||
to run via usbfs ioctl.
|
||||
|
||||
This example implements the Gadget-Zero style *source/sink* protocol on a vendor-specific
|
||||
interface so the whole battery can exercise TinyUSB device controller drivers:
|
||||
|
||||
- bulk IN = infinite source (usbtest pattern 0: all zeros)
|
||||
- bulk OUT = infinite sink (data discarded)
|
||||
|
||||
## Tiers
|
||||
|
||||
The firmware advertises its capability tier in `bcdDevice` (`0x01TT`); the host script picks
|
||||
the matching test battery automatically.
|
||||
|
||||
| Tier | Capability | usbtest cases |
|
||||
|------|------------|---------------|
|
||||
| 1 | bulk source/sink | 0, 9, 10, 1–8, 11, 12, 24, 13, 29, 17–20, 27, 28 |
|
||||
| 2 | + vendor control `0x5b`/`0x5c` (ctrl_out) | + 14, 21 |
|
||||
| 3 | + interrupt source/sink | + 25, 26 |
|
||||
| 4 | + isochronous source/sink | + 15, 16, 22, 23 |
|
||||
|
||||
This example implements all four tiers using the vendor class with the interrupt
|
||||
(`CFG_TUD_VENDOR_EP_INT_OUT/IN`) and isochronous (`CFG_TUD_VENDOR_EP_ISO_OUT/IN`)
|
||||
endpoint pairs and altsetting support (`CFG_TUD_VENDOR_ALT_SETTINGS`): alt 0
|
||||
carries no endpoints, alt 1 the full source/sink set, per USB 2.0 5.6.3 (the host
|
||||
usbtest driver selects alt 1 itself).
|
||||
|
||||
## Test cases
|
||||
|
||||
Directions are from the host's point of view: *write* = host→device (OUT endpoint, device
|
||||
sinks and discards), *read* = device→host (IN endpoint, device sources zeros and the host
|
||||
verifies every byte). All checking happens host-side in `usbtest.ko`; a case fails on a data
|
||||
mismatch, an unexpected short packet/STALL, or a timeout. What each case stresses on the
|
||||
device/DCD side:
|
||||
|
||||
| # | Name | What it does / what it exercises |
|
||||
|---|------|----------------------------------|
|
||||
| 0 | NOP | ioctl round-trip sanity, no USB traffic — proves the interface bound with the right capability profile |
|
||||
| 9 | ch9 subset | chapter-9 standard control requests (GET_DESCRIPTOR, GET_STATUS, SET/CLEAR_FEATURE, SET_INTERFACE, …) — the EP0 state machine, incl. status stages and ZLPs |
|
||||
| 10 | queued control | many control URBs in flight at once — EP0 under sustained back-to-back SETUPs |
|
||||
| 1 / 2 | bulk write / read | plain OUT sink / IN source streams of whole max-size packets — FIFO handling, multi-packet transfers |
|
||||
| 3 / 4 | bulk write / read vary | same with transfer sizes varying per URB — short packets and packet-boundary edge cases |
|
||||
| 5–8 | bulk sg write/read (+vary) | scatter-gather queued URBs — continuous packet pressure with no inter-URB gap; classic overflow/babble catcher |
|
||||
| 11 / 12 | unlink reads / writes | URBs submitted then cancelled mid-flight — the device keeps streaming while the host aborts; DCD abort/cleanup paths |
|
||||
| 24 | unlink queued writes | unlink from a deep OUT queue — same, under queue pressure |
|
||||
| 13 | ep halt set/clear | SET_FEATURE(ENDPOINT_HALT), verify the endpoint really STALLs, then CLEAR_FEATURE and verify traffic resumes at DATA0 — stall must abort an armed transfer (and flush any loaded FIFO) |
|
||||
| 29 | toggle clear | CLEAR_FEATURE(HALT) on a **non-halted** endpoint mid-traffic, purely to reset the data toggle — the DCD must reset DATA0 *without* disarming the queued transfer (historically the most common per-DCD bug in this battery) |
|
||||
| 17 / 18 | bulk write / read unaligned | bulk streams from oddly-offset host buffers — host DMA-alignment path; the device sees normal traffic |
|
||||
| 19 / 20 | bulk write / read premapped | bulk streams using host pre-mapped DMA buffers — another host memory path |
|
||||
| 27 / 28 | bulk write / read perf | sustained maximum-throughput streams, reported in MB/s — real-time FIFO servicing under load |
|
||||
| 14 | ctrl_out write/read | vendor EP0 request `0x5b` stores wLength bytes, `0x5c` reads them back, sizes varying — multi-packet control-OUT data stages and buffer persistence across requests |
|
||||
| 21 | ctrl_out unaligned | same from odd host buffer offsets |
|
||||
| 25 / 26 | int write / read | interrupt OUT sink / IN source at the descriptor's polling interval — interrupt endpoint arming and completion |
|
||||
| 15 / 16 | iso write / read | isochronous OUT sink / IN source, one packet per (micro)frame with per-packet status — no handshake/retry, DATA0-only; the IN source must re-arm fast enough to make every frame deadline |
|
||||
| 22 / 23 | iso write / read unaligned | same from odd host buffer offsets |
|
||||
|
||||
Per-case iteration counts and sizes are chosen by `test/hil/usbtest.py` for the negotiated
|
||||
speed (see its `PARAMS` table); the authoritative case implementations live in the kernel's
|
||||
`drivers/usb/misc/usbtest.c`.
|
||||
|
||||
## Running
|
||||
|
||||
Use the host script (handles driver binding, per-case parameters, result parsing):
|
||||
|
||||
```bash
|
||||
python3 test/hil/usbtest.py --serial <board-uid>
|
||||
```
|
||||
|
||||
Requirements on the host: `usbtest` kernel module (`CONFIG_USB_TEST`, `modprobe usbtest`),
|
||||
the `testusb` binary built from kernel `tools/usb/testusb.c`, and sudo (usbfs ioctls +
|
||||
driver bind/unbind).
|
||||
|
||||
Manual runs are possible but beware `testusb` defaults: always pass explicit `-s`/`-v`
|
||||
values that are multiples of 512 — the device streams whole max-size packets, so a
|
||||
non-packet-aligned read length overflows (`-EOVERFLOW`), and never run bare `testusb -a`
|
||||
(the default parameter set includes cases with invalid parameters and hour-long runtimes
|
||||
at full speed).
|
||||
|
||||
```bash
|
||||
# bind: MUST use the 5-field form referencing Gadget Zero (0525:a4a0) so the
|
||||
# dynamic id inherits its capability profile. A plain "cafe 4010" id leaves
|
||||
# driver_info NULL, which usbtest_probe() dereferences -> kernel oops.
|
||||
sudo modprobe usbtest
|
||||
echo "cafe 4010 0 0525 a4a0" | sudo tee /sys/bus/usb/drivers/usbtest/new_id
|
||||
# example: bulk write/read
|
||||
sudo testusb -D /dev/bus/usb/<BBB>/<DDD> -t 1 -c 128 -s 1024 -v 512
|
||||
sudo testusb -D /dev/bus/usb/<BBB>/<DDD> -t 2 -c 128 -s 1024 -v 512
|
||||
```
|
||||
15
examples/device/usbtest/skip.txt
Normal file
15
examples/device/usbtest/skip.txt
Normal file
@ -0,0 +1,15 @@
|
||||
mcu:MSP430x5xx
|
||||
mcu:NUC121
|
||||
mcu:SAMD11
|
||||
# DCD has no isochronous support (dcd_edpt_iso_alloc refuses), tier-4 cannot enumerate:
|
||||
mcu:CXD56
|
||||
mcu:FT90X
|
||||
mcu:LPC175X_6X
|
||||
mcu:LPC40XX
|
||||
mcu:NUC100
|
||||
mcu:NUC120
|
||||
mcu:NUC505
|
||||
mcu:PIC32MZ
|
||||
mcu:SAMG
|
||||
mcu:SAMX7X
|
||||
mcu:VALENTYUSB_EPTRI
|
||||
4
examples/device/usbtest/src/CMakeLists.txt
Normal file
4
examples/device/usbtest/src/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
# This file is for ESP-IDF only
|
||||
idf_component_register(SRCS "main.c" "usb_descriptors.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES boards tinyusb_src)
|
||||
336
examples/device/usbtest/src/main.c
Normal file
336
examples/device/usbtest/src/main.c
Normal file
@ -0,0 +1,336 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2026 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
/* Device-side peer of the Linux kernel host test driver drivers/usb/misc/usbtest.c
|
||||
* (driven from userspace by tools/usb/testusb.c). Implements the Gadget-Zero
|
||||
* style source/sink protocol on a vendor interface (alt 0 = no endpoints,
|
||||
* alt 1 = full set, selected by the host usbtest driver):
|
||||
* - bulk/interrupt/isochronous IN = infinite source (pattern 0: all zeros)
|
||||
* - bulk/interrupt/isochronous OUT = infinite sink (data discarded)
|
||||
* - EP0 0x5b/0x5c = control write then read-back (ctrl_out tests)
|
||||
* See examples/device/usbtest/README.md and test/hil/usbtest.py for usage.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "bsp/board_api.h"
|
||||
#include "tusb.h"
|
||||
#include "usb_descriptors.h"
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// MACRO CONSTANT TYPEDEF PROTYPES
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
/* Blink pattern
|
||||
* - 250 ms : device not mounted
|
||||
* - 1000 ms : device mounted
|
||||
*/
|
||||
enum {
|
||||
BLINK_NOT_MOUNTED = 250,
|
||||
BLINK_MOUNTED = 1000,
|
||||
};
|
||||
|
||||
static uint32_t blink_interval_ms = BLINK_NOT_MOUNTED;
|
||||
|
||||
// Source data, all zeros = usbtest pattern 0. Sizes are a multiple of the
|
||||
// endpoint max packet size: transfers are always whole packets, never an
|
||||
// unintended short packet or ZLP.
|
||||
static uint8_t const tx_chunk[CFG_TUD_VENDOR_TX_EPSIZE];
|
||||
static uint8_t const int_tx_chunk[USBTEST_INT_EP_MPS];
|
||||
static uint8_t const iso_tx_chunk[USBTEST_ISO_EP_MPS];
|
||||
|
||||
// Interrupt/iso submit one packet per (micro)frame, sized to the NEGOTIATED speed's mps — a
|
||||
// high-speed build enumerated at full speed must submit the FS length, not the HS-capacity buffer
|
||||
// size (bulk is exempt: it streams multi-packet transfers). See usb_descriptors.h.
|
||||
static inline uint16_t usbtest_int_len(void) {
|
||||
return (tud_speed_get() == TUSB_SPEED_HIGH) ? USBTEST_INT_EP_MPS_HS : USBTEST_INT_EP_MPS_FS;
|
||||
}
|
||||
static inline uint16_t usbtest_iso_len(void) {
|
||||
return (tud_speed_get() == TUSB_SPEED_HIGH) ? USBTEST_ISO_EP_MPS_HS : USBTEST_ISO_EP_MPS_FS;
|
||||
}
|
||||
|
||||
//------------- prototypes -------------//
|
||||
void led_blinking_task(void* param);
|
||||
void usbtest_task(void* param);
|
||||
|
||||
#if CFG_TUSB_OS == OPT_OS_FREERTOS
|
||||
void freertos_init(void);
|
||||
#endif
|
||||
|
||||
/*------------- MAIN -------------*/
|
||||
int main(void) {
|
||||
board_init();
|
||||
|
||||
// If using FreeRTOS: create blinky, tinyusb device, and usbtest source/sink tasks
|
||||
#if CFG_TUSB_OS == OPT_OS_FREERTOS
|
||||
freertos_init();
|
||||
#else
|
||||
// init device stack on configured roothub port
|
||||
tusb_rhport_init_t dev_init = {
|
||||
.role = TUSB_ROLE_DEVICE,
|
||||
.speed = TUSB_SPEED_AUTO
|
||||
};
|
||||
tusb_init(BOARD_TUD_RHPORT, &dev_init);
|
||||
|
||||
board_init_after_tusb();
|
||||
|
||||
while (1) {
|
||||
tud_task(); // tinyusb device task
|
||||
usbtest_task(NULL);
|
||||
led_blinking_task(NULL);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Source/sink pumps
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
// Polling keeps all four endpoints armed and self-heals after endpoint halt
|
||||
// (set/clear feature tests): stall marks the endpoint busy so the calls fail
|
||||
// quietly until the host clears the halt, then the next tick re-arms.
|
||||
static void usbtest_pump(void) {
|
||||
if (tud_vendor_mounted()) {
|
||||
tud_vendor_read_xfer(); // bulk sink: arm/re-arm, quiet fail if armed or halted
|
||||
if (tud_vendor_write_available()) { // 0 while bulk IN is busy or halted
|
||||
tud_vendor_write(tx_chunk, sizeof(tx_chunk));
|
||||
}
|
||||
|
||||
tud_vendor_int_read_xfer(); // interrupt sink
|
||||
if (tud_vendor_int_write_available()) {
|
||||
tud_vendor_int_write(int_tx_chunk, usbtest_int_len());
|
||||
}
|
||||
|
||||
tud_vendor_iso_read_xfer(); // isochronous sink
|
||||
if (tud_vendor_iso_write_available()) {
|
||||
tud_vendor_iso_write(iso_tx_chunk, usbtest_iso_len());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void usbtest_task(void* param) {
|
||||
(void) param;
|
||||
#if CFG_TUSB_OS == OPT_OS_FREERTOS
|
||||
while (1) {
|
||||
usbtest_pump();
|
||||
vTaskDelay(1); // yield; tx/rx completion callbacks keep the pipes saturated between polls
|
||||
}
|
||||
#else
|
||||
usbtest_pump(); // called from the main loop, one tick per call
|
||||
#endif
|
||||
}
|
||||
|
||||
// Invoked when received data from host: discard and immediately re-arm
|
||||
void tud_vendor_rx_cb(uint8_t idx, const uint8_t* buffer, uint32_t bufsize) {
|
||||
(void) idx;
|
||||
(void) buffer;
|
||||
(void) bufsize;
|
||||
tud_vendor_read_xfer();
|
||||
}
|
||||
|
||||
// Invoked when last bulk tx transfer finished: keep the source saturated
|
||||
void tud_vendor_tx_cb(uint8_t idx, uint32_t sent_bytes) {
|
||||
(void) idx;
|
||||
(void) sent_bytes;
|
||||
tud_vendor_write(tx_chunk, sizeof(tx_chunk));
|
||||
}
|
||||
|
||||
// Interrupt pair: same discard/refill pumps as bulk
|
||||
void tud_vendor_int_rx_cb(uint8_t idx, const uint8_t* buffer, uint32_t bufsize) {
|
||||
(void) idx;
|
||||
(void) buffer;
|
||||
(void) bufsize;
|
||||
tud_vendor_int_read_xfer();
|
||||
}
|
||||
|
||||
void tud_vendor_int_tx_cb(uint8_t idx, uint32_t sent_bytes) {
|
||||
(void) idx;
|
||||
(void) sent_bytes;
|
||||
tud_vendor_int_write(int_tx_chunk, usbtest_int_len());
|
||||
}
|
||||
|
||||
// Isochronous pair: same discard/refill pumps; a completion may be a missed
|
||||
// frame, re-arm regardless
|
||||
void tud_vendor_iso_rx_cb(uint8_t idx, const uint8_t* buffer, uint32_t bufsize) {
|
||||
(void) idx;
|
||||
(void) buffer;
|
||||
(void) bufsize;
|
||||
tud_vendor_iso_read_xfer();
|
||||
}
|
||||
|
||||
void tud_vendor_iso_tx_cb(uint8_t idx, uint32_t sent_bytes) {
|
||||
(void) idx;
|
||||
(void) sent_bytes;
|
||||
tud_vendor_iso_write(iso_tx_chunk, usbtest_iso_len());
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Vendor control requests (EP0)
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
// Control write/read-back for the ctrl_out tests (14/21), same protocol as
|
||||
// Gadget Zero: 0x5b stores the host's wLength bytes, 0x5c returns them.
|
||||
static uint8_t ctrl_buf[1024];
|
||||
|
||||
// Invoked on vendor control transfers, and by usbd for forwarded standard
|
||||
// endpoint requests (halt set/clear) whose return value it ignores — return
|
||||
// false for anything that is not a supported vendor request.
|
||||
bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const* request) {
|
||||
if (request->bmRequestType_bit.type != TUSB_REQ_TYPE_VENDOR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (request->bRequest) {
|
||||
case 0x5b: // control WRITE: receive wLength bytes into ctrl_buf
|
||||
TU_VERIFY(request->bmRequestType_bit.direction == TUSB_DIR_OUT);
|
||||
TU_VERIFY(request->wValue == 0 && request->wIndex == 0);
|
||||
TU_VERIFY(request->wLength <= sizeof(ctrl_buf));
|
||||
if (stage == CONTROL_STAGE_SETUP) {
|
||||
return tud_control_xfer(rhport, request, ctrl_buf, request->wLength);
|
||||
}
|
||||
return true; // DATA/ACK: payload already landed in ctrl_buf
|
||||
|
||||
case 0x5c: // control READ: send back the previously written bytes
|
||||
TU_VERIFY(request->bmRequestType_bit.direction == TUSB_DIR_IN);
|
||||
TU_VERIFY(request->wValue == 0 && request->wIndex == 0);
|
||||
TU_VERIFY(request->wLength <= sizeof(ctrl_buf));
|
||||
if (stage == CONTROL_STAGE_SETUP) {
|
||||
return tud_control_xfer(rhport, request, ctrl_buf, request->wLength);
|
||||
}
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Device callbacks
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
// Invoked when device is mounted
|
||||
void tud_mount_cb(void) {
|
||||
blink_interval_ms = BLINK_MOUNTED;
|
||||
}
|
||||
|
||||
// Invoked when device is unmounted
|
||||
void tud_umount_cb(void) {
|
||||
blink_interval_ms = BLINK_NOT_MOUNTED;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// BLINKING TASK
|
||||
//--------------------------------------------------------------------+
|
||||
void led_blinking_task(void* param) {
|
||||
(void) param;
|
||||
static uint32_t start_ms = 0;
|
||||
static bool led_state = false;
|
||||
|
||||
while (1) {
|
||||
#if CFG_TUSB_OS == OPT_OS_FREERTOS
|
||||
vTaskDelay(blink_interval_ms / portTICK_PERIOD_MS);
|
||||
#else
|
||||
// Blink every interval ms
|
||||
if (tusb_time_millis_api() - start_ms < blink_interval_ms) {
|
||||
return; // not enough time
|
||||
}
|
||||
#endif
|
||||
|
||||
start_ms += blink_interval_ms;
|
||||
board_led_write(led_state);
|
||||
led_state = 1 - led_state; // toggle
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// FreeRTOS
|
||||
//--------------------------------------------------------------------+
|
||||
#if CFG_TUSB_OS == OPT_OS_FREERTOS
|
||||
|
||||
#define BLINKY_STACK_SIZE configMINIMAL_STACK_SIZE
|
||||
#define USBTEST_STACK_SIZE (configMINIMAL_STACK_SIZE*2)
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#define USBD_STACK_SIZE 4096
|
||||
int main(void);
|
||||
void app_main(void) {
|
||||
main();
|
||||
}
|
||||
#else
|
||||
// Increase stack size when debug log is enabled
|
||||
#define USBD_STACK_SIZE (3*configMINIMAL_STACK_SIZE/2) * (CFG_TUSB_DEBUG ? 2 : 1)
|
||||
#endif
|
||||
|
||||
// static task allocation
|
||||
#if configSUPPORT_STATIC_ALLOCATION
|
||||
StackType_t blinky_stack[BLINKY_STACK_SIZE];
|
||||
StaticTask_t blinky_taskdef;
|
||||
|
||||
StackType_t usb_device_stack[USBD_STACK_SIZE];
|
||||
StaticTask_t usb_device_taskdef;
|
||||
|
||||
StackType_t usbtest_stack[USBTEST_STACK_SIZE];
|
||||
StaticTask_t usbtest_taskdef;
|
||||
#endif
|
||||
|
||||
// USB Device Driver task: processes all usb events and invokes callbacks
|
||||
void usb_device_task(void* param) {
|
||||
(void) param;
|
||||
|
||||
// init device stack on configured roothub port. Must be called after the
|
||||
// scheduler starts: the USB IRQ handler uses RTOS queue APIs.
|
||||
tusb_rhport_init_t dev_init = {
|
||||
.role = TUSB_ROLE_DEVICE,
|
||||
.speed = TUSB_SPEED_AUTO
|
||||
};
|
||||
tusb_init(BOARD_TUD_RHPORT, &dev_init);
|
||||
|
||||
board_init_after_tusb();
|
||||
|
||||
// RTOS forever loop
|
||||
while (1) {
|
||||
tud_task(); // put thread to waiting state until there is a new event
|
||||
}
|
||||
}
|
||||
|
||||
void freertos_init(void) {
|
||||
#if configSUPPORT_STATIC_ALLOCATION
|
||||
xTaskCreateStatic(led_blinking_task, "blinky", BLINKY_STACK_SIZE, NULL, 1, blinky_stack, &blinky_taskdef);
|
||||
xTaskCreateStatic(usb_device_task, "usbd", USBD_STACK_SIZE, NULL, configMAX_PRIORITIES-1, usb_device_stack, &usb_device_taskdef);
|
||||
xTaskCreateStatic(usbtest_task, "usbtest", USBTEST_STACK_SIZE, NULL, configMAX_PRIORITIES-2, usbtest_stack, &usbtest_taskdef);
|
||||
#else
|
||||
xTaskCreate(led_blinking_task, "blinky", BLINKY_STACK_SIZE, NULL, 1, NULL);
|
||||
xTaskCreate(usb_device_task, "usbd", USBD_STACK_SIZE, NULL, configMAX_PRIORITIES-1, NULL);
|
||||
xTaskCreate(usbtest_task, "usbtest", USBTEST_STACK_SIZE, NULL, configMAX_PRIORITIES-2, NULL);
|
||||
#endif
|
||||
|
||||
// only start scheduler for non-espressif mcu (espressif starts it in startup code)
|
||||
#ifndef ESP_PLATFORM
|
||||
vTaskStartScheduler();
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
151
examples/device/usbtest/src/tusb_config.h
Normal file
151
examples/device/usbtest/src/tusb_config.h
Normal file
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2026 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TUSB_CONFIG_H_
|
||||
#define TUSB_CONFIG_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Board Specific Configuration
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
// RHPort number used for device can be defined by board.mk, default to port 0
|
||||
#ifndef BOARD_TUD_RHPORT
|
||||
#define BOARD_TUD_RHPORT 0
|
||||
#endif
|
||||
|
||||
// RHPort max operational speed can defined by board.mk
|
||||
#ifndef BOARD_TUD_MAX_SPEED
|
||||
#define BOARD_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Common Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// defined by compiler flags for flexibility
|
||||
#ifndef CFG_TUSB_MCU
|
||||
#error CFG_TUSB_MCU must be defined
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_OS
|
||||
#define CFG_TUSB_OS OPT_OS_NONE
|
||||
#endif
|
||||
|
||||
// Espressif IDF requires "freertos/" prefix in include path
|
||||
#ifdef ESP_PLATFORM
|
||||
#define CFG_TUSB_OS_INC_PATH freertos/
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_DEBUG
|
||||
#define CFG_TUSB_DEBUG 0
|
||||
#endif
|
||||
|
||||
// Enable Device stack
|
||||
#define CFG_TUD_ENABLED 1
|
||||
|
||||
// Default is max speed that hardware controller could support with on-chip PHY
|
||||
#define CFG_TUD_MAX_SPEED BOARD_TUD_MAX_SPEED
|
||||
|
||||
/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment.
|
||||
* Tinyusb use follows macros to declare transferring memory so that they can be put
|
||||
* into those specific section.
|
||||
* e.g
|
||||
* - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
|
||||
* - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4)))
|
||||
*/
|
||||
#ifndef CFG_TUSB_MEM_SECTION
|
||||
#define CFG_TUSB_MEM_SECTION
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_MEM_ALIGN
|
||||
#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4)))
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// DEVICE CONFIGURATION
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
#ifndef CFG_TUD_ENDPOINT0_SIZE
|
||||
#define CFG_TUD_ENDPOINT0_SIZE 64
|
||||
#endif
|
||||
|
||||
//------------- CLASS -------------//
|
||||
#define CFG_TUD_CDC 0
|
||||
#define CFG_TUD_MSC 0
|
||||
#define CFG_TUD_HID 0
|
||||
#define CFG_TUD_MIDI 0
|
||||
#define CFG_TUD_VENDOR 1
|
||||
|
||||
// Non-buffered mode: every transfer is submitted with an exact length so the
|
||||
// host never sees an unexpected short packet or ZLP mid-transfer, which the
|
||||
// usbtest data-integrity cases treat as failure.
|
||||
#define CFG_TUD_VENDOR_RX_BUFSIZE 0
|
||||
#define CFG_TUD_VENDOR_TX_BUFSIZE 0
|
||||
|
||||
// App re-arms RX itself: required to recover the sink after halt tests
|
||||
// (SET_FEATURE/CLEAR_FEATURE endpoint halt) where no completion ever fires.
|
||||
#define CFG_TUD_VENDOR_RX_MANUAL_XFER 1
|
||||
|
||||
// Multi-packet IN transfers; must be a multiple of bulk MPS at both speeds (64/512).
|
||||
// LPC11/13 (ip3511 FS) keep endpoint buffers in a dedicated 2 KB USB RAM: a 2048 B bulk epbuf
|
||||
// overflows it once the int/iso buffers join, so those parts use 512 (= 8 FS packets).
|
||||
#if TU_CHECK_MCU(OPT_MCU_LPC11UXX, OPT_MCU_LPC13XX)
|
||||
#define CFG_TUD_VENDOR_TX_EPSIZE 512
|
||||
#else
|
||||
#define CFG_TUD_VENDOR_TX_EPSIZE 2048
|
||||
#endif
|
||||
|
||||
// Interrupt IN/OUT source/sink pair (usbtest cases 25/26). Buffer sizes track the
|
||||
// per-speed endpoint max packet size (see usb_descriptors.c) so full-speed builds
|
||||
// don't over-allocate the scarce USB DMA section.
|
||||
#define CFG_TUD_VENDOR_EP_INT_OUT 1
|
||||
#define CFG_TUD_VENDOR_EP_INT_IN 1
|
||||
#define CFG_TUD_VENDOR_EP_INT_OUT_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
|
||||
#define CFG_TUD_VENDOR_EP_INT_IN_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
|
||||
|
||||
// Isochronous IN/OUT source/sink pair (usbtest cases 15/16/22/23), placed in
|
||||
// altsetting 1: alt 0 has no endpoints so no iso bandwidth is claimed by default
|
||||
#define CFG_TUD_VENDOR_EP_ISO_OUT 1
|
||||
#define CFG_TUD_VENDOR_EP_ISO_IN 1
|
||||
#define CFG_TUD_VENDOR_EP_ISO_OUT_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 128)
|
||||
#define CFG_TUD_VENDOR_EP_ISO_IN_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 128)
|
||||
#define CFG_TUD_VENDOR_ALT_SETTINGS 1
|
||||
|
||||
// CH32V20X fsdev port has only 512 B PMA and single-buffered iso can't keep the iso IN endpoint
|
||||
// fed under load. Double-buffer iso; the descriptor drops iso mps to 32 there so 2x32 = 64 B/ep
|
||||
// keeps the same PMA budget (see usb_descriptors.h). Other fsdev parts have room and stay single.
|
||||
#if CFG_TUSB_MCU == OPT_MCU_CH32V20X
|
||||
#define CFG_TUD_FSDEV_DOUBLE_BUFFERED_ISO_EP 1
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* TUSB_CONFIG_H_ */
|
||||
271
examples/device/usbtest/src/usb_descriptors.c
Normal file
271
examples/device/usbtest/src/usb_descriptors.c
Normal file
@ -0,0 +1,271 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2026 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "bsp/board_api.h"
|
||||
#include "tusb.h"
|
||||
#include "usb_descriptors.h"
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Device Descriptors
|
||||
//--------------------------------------------------------------------+
|
||||
static tusb_desc_device_t const desc_device = {
|
||||
.bLength = sizeof(tusb_desc_device_t),
|
||||
.bDescriptorType = TUSB_DESC_DEVICE,
|
||||
.bcdUSB = 0x0200,
|
||||
.bDeviceClass = 0x00, // per-interface class
|
||||
.bDeviceSubClass = 0x00,
|
||||
.bDeviceProtocol = 0x00,
|
||||
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
|
||||
|
||||
.idVendor = 0xCafe,
|
||||
.idProduct = 0x4010,
|
||||
.bcdDevice = 0x0100 | USBTEST_TIER,
|
||||
|
||||
.iManufacturer = 0x01,
|
||||
.iProduct = 0x02,
|
||||
.iSerialNumber = 0x03,
|
||||
|
||||
.bNumConfigurations = 0x01
|
||||
};
|
||||
|
||||
// Invoked when received GET DEVICE DESCRIPTOR
|
||||
uint8_t const* tud_descriptor_device_cb(void) {
|
||||
return (uint8_t const*) &desc_device;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Configuration Descriptor
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
// Interface must be number 0: testusb -D issues its ioctls against interface 0
|
||||
enum {
|
||||
ITF_NUM_VENDOR = 0,
|
||||
ITF_NUM_TOTAL
|
||||
};
|
||||
|
||||
// Vendor interface, Gadget-Zero style altsettings: alt 0 carries no endpoints (an
|
||||
// isochronous endpoint must not claim bandwidth in the default altsetting, USB 2.0
|
||||
// 5.6.3), alt 1 carries bulk + interrupt + isochronous IN/OUT. The host usbtest
|
||||
// driver skips altsettings without pipes and selects alt 1 itself. No TUD_ macro
|
||||
// covers this layout, hand-rolled.
|
||||
#define USBTEST_DESC_LEN (9 + 9 + 6*7)
|
||||
#define USBTEST_DESCRIPTOR(_itfnum, _stridx, _epout, _epin, _bulk_mps, _intout, _intin, _int_mps, _int_interval, _isoout, _isoin, _iso_mps, _iso_interval) \
|
||||
/* alt 0: zero bandwidth, no endpoints */\
|
||||
9, TUSB_DESC_INTERFACE, _itfnum, 0, 0, TUSB_CLASS_VENDOR_SPECIFIC, 0x00, 0x00, _stridx,\
|
||||
/* alt 1: full source/sink set */\
|
||||
9, TUSB_DESC_INTERFACE, _itfnum, 1, 6, TUSB_CLASS_VENDOR_SPECIFIC, 0x00, 0x00, _stridx,\
|
||||
7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_bulk_mps), 0,\
|
||||
7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_bulk_mps), 0,\
|
||||
7, TUSB_DESC_ENDPOINT, _intout, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_int_mps), _int_interval,\
|
||||
7, TUSB_DESC_ENDPOINT, _intin, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_int_mps), _int_interval,\
|
||||
7, TUSB_DESC_ENDPOINT, _isoout, (uint8_t)(TUSB_XFER_ISOCHRONOUS | (uint8_t)(TUSB_ISO_EP_ATT_ASYNCHRONOUS)), U16_TO_U8S_LE(_iso_mps), _iso_interval,\
|
||||
7, TUSB_DESC_ENDPOINT, _isoin, (uint8_t)(TUSB_XFER_ISOCHRONOUS | (uint8_t)(TUSB_ISO_EP_ATT_ASYNCHRONOUS)), U16_TO_U8S_LE(_iso_mps), _iso_interval
|
||||
|
||||
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + USBTEST_DESC_LEN)
|
||||
|
||||
#if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == OPT_MCU_LPC40XX
|
||||
// LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number
|
||||
// 0 control, 1 Interrupt, 2 Bulk, 3 Iso, 4 Interrupt etc ...
|
||||
#define EPNUM_BULK_OUT 0x02
|
||||
#define EPNUM_BULK_IN 0x85
|
||||
#define EPNUM_INT_OUT 0x01
|
||||
#define EPNUM_INT_IN 0x84
|
||||
#define EPNUM_ISO_OUT 0x03
|
||||
#define EPNUM_ISO_IN 0x86
|
||||
|
||||
#elif CFG_TUD_ENDPOINT_ONE_DIRECTION_ONLY
|
||||
// MCUs that don't support a same endpoint number with different direction IN and OUT
|
||||
// e.g EP1 OUT & EP1 IN cannot exist together
|
||||
#if TU_CHECK_MCU(OPT_MCU_MAX32650, OPT_MCU_MAX32666, OPT_MCU_MAX32690, OPT_MCU_MAX78002)
|
||||
// Put bulk on EP>=8 so the 2048/4096-byte FIFOs can back double packet buffering
|
||||
#define EPNUM_BULK_OUT 0x08
|
||||
#define EPNUM_BULK_IN 0x89
|
||||
#define EPNUM_INT_OUT 0x02
|
||||
#define EPNUM_INT_IN 0x83
|
||||
#define EPNUM_ISO_OUT 0x04
|
||||
#define EPNUM_ISO_IN 0x85
|
||||
#else
|
||||
#define EPNUM_BULK_OUT 0x01
|
||||
#define EPNUM_BULK_IN 0x82
|
||||
#define EPNUM_INT_OUT 0x03
|
||||
#define EPNUM_INT_IN 0x84
|
||||
#define EPNUM_ISO_OUT 0x05
|
||||
#define EPNUM_ISO_IN 0x86
|
||||
#endif
|
||||
|
||||
#elif CFG_TUSB_MCU == OPT_MCU_NRF5X
|
||||
// nRF5x: ISO endpoints are hardware-fixed to EP8 (ISOOUT/ISOIN)
|
||||
#define EPNUM_BULK_OUT 0x01
|
||||
#define EPNUM_BULK_IN 0x81
|
||||
#define EPNUM_INT_OUT 0x02
|
||||
#define EPNUM_INT_IN 0x82
|
||||
#define EPNUM_ISO_OUT 0x08
|
||||
#define EPNUM_ISO_IN 0x88
|
||||
|
||||
#else
|
||||
#define EPNUM_BULK_OUT 0x01
|
||||
#define EPNUM_BULK_IN 0x81
|
||||
#define EPNUM_INT_OUT 0x02
|
||||
#define EPNUM_INT_IN 0x82
|
||||
#define EPNUM_ISO_OUT 0x03
|
||||
#define EPNUM_ISO_IN 0x83
|
||||
#endif
|
||||
|
||||
static uint8_t const desc_fs_configuration[] = {
|
||||
// Config number, interface count, string index, total length, attribute, power in mA
|
||||
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100),
|
||||
|
||||
// Interface number, string index, bulk out/in + mps, int out/in + mps + interval, iso out/in + mps + interval
|
||||
USBTEST_DESCRIPTOR(ITF_NUM_VENDOR, 4, EPNUM_BULK_OUT, 0x80 | EPNUM_BULK_IN, 64,
|
||||
EPNUM_INT_OUT, 0x80 | EPNUM_INT_IN, USBTEST_INT_EP_MPS_FS, 1,
|
||||
EPNUM_ISO_OUT, 0x80 | EPNUM_ISO_IN, USBTEST_ISO_EP_MPS_FS, 1)
|
||||
};
|
||||
|
||||
#if TUD_OPT_HIGH_SPEED
|
||||
// Per USB specs: high speed capable device must report device_qualifier and other_speed_configuration
|
||||
|
||||
static uint8_t const desc_hs_configuration[] = {
|
||||
// Config number, interface count, string index, total length, attribute, power in mA
|
||||
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100),
|
||||
|
||||
// Interface number, string index, bulk out/in + mps, int out/in + mps + interval, iso out/in + mps + interval (1 ms)
|
||||
USBTEST_DESCRIPTOR(ITF_NUM_VENDOR, 4, EPNUM_BULK_OUT, 0x80 | EPNUM_BULK_IN, 512,
|
||||
EPNUM_INT_OUT, 0x80 | EPNUM_INT_IN, USBTEST_INT_EP_MPS_HS, 4,
|
||||
EPNUM_ISO_OUT, 0x80 | EPNUM_ISO_IN, USBTEST_ISO_EP_MPS_HS, 4)
|
||||
};
|
||||
|
||||
// other speed configuration
|
||||
static uint8_t desc_other_speed_config[CONFIG_TOTAL_LEN];
|
||||
|
||||
// device qualifier is mostly similar to device descriptor since we don't change configuration based on speed
|
||||
static tusb_desc_device_qualifier_t const desc_device_qualifier = {
|
||||
.bLength = sizeof(tusb_desc_device_qualifier_t),
|
||||
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
|
||||
.bcdUSB = 0x0200,
|
||||
|
||||
.bDeviceClass = 0x00,
|
||||
.bDeviceSubClass = 0x00,
|
||||
.bDeviceProtocol = 0x00,
|
||||
|
||||
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
|
||||
.bNumConfigurations = 0x01,
|
||||
.bReserved = 0x00
|
||||
};
|
||||
|
||||
// Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request
|
||||
uint8_t const* tud_descriptor_device_qualifier_cb(void) {
|
||||
return (uint8_t const*) &desc_device_qualifier;
|
||||
}
|
||||
|
||||
// Invoked when received GET OTHER SPEED CONFIGURATION DESCRIPTOR request
|
||||
// Configuration descriptor in the other speed e.g if high speed then this is for full speed and vice versa
|
||||
uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index) {
|
||||
(void) index; // for multiple configurations
|
||||
|
||||
// if link speed is high return fullspeed config, and vice versa
|
||||
// Note: the descriptor type is OTHER_SPEED_CONFIG instead of CONFIG
|
||||
memcpy(desc_other_speed_config,
|
||||
(tud_speed_get() == TUSB_SPEED_HIGH) ? desc_fs_configuration : desc_hs_configuration,
|
||||
CONFIG_TOTAL_LEN);
|
||||
|
||||
desc_other_speed_config[1] = TUSB_DESC_OTHER_SPEED_CONFIG;
|
||||
|
||||
return desc_other_speed_config;
|
||||
}
|
||||
#endif // highspeed
|
||||
|
||||
// Invoked when received GET CONFIGURATION DESCRIPTOR
|
||||
uint8_t const* tud_descriptor_configuration_cb(uint8_t index) {
|
||||
(void) index; // for multiple configurations
|
||||
|
||||
#if TUD_OPT_HIGH_SPEED
|
||||
// Although we are highspeed, host may be fullspeed.
|
||||
return (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_hs_configuration : desc_fs_configuration;
|
||||
#else
|
||||
return desc_fs_configuration;
|
||||
#endif
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// String Descriptors
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
// String Descriptor Index
|
||||
enum {
|
||||
STRID_LANGID = 0,
|
||||
STRID_MANUFACTURER,
|
||||
STRID_PRODUCT,
|
||||
STRID_SERIAL,
|
||||
};
|
||||
|
||||
// array of pointer to string descriptors
|
||||
static char const* string_desc_arr[] = {
|
||||
(const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
|
||||
"TinyUSB", // 1: Manufacturer
|
||||
"TinyUSB usbtest", // 2: Product
|
||||
NULL, // 3: Serials will use unique ID if possible
|
||||
"TinyUSB usbtest source/sink" // 4: Vendor Interface
|
||||
};
|
||||
|
||||
static uint16_t _desc_str[32 + 1];
|
||||
|
||||
// Invoked when received GET STRING DESCRIPTOR request
|
||||
// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
|
||||
uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
|
||||
(void) langid;
|
||||
size_t chr_count;
|
||||
|
||||
switch (index) {
|
||||
case STRID_LANGID:
|
||||
memcpy(&_desc_str[1], string_desc_arr[0], 2);
|
||||
chr_count = 1;
|
||||
break;
|
||||
|
||||
case STRID_SERIAL:
|
||||
chr_count = board_usb_get_serial(_desc_str + 1, 32);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (!(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0]))) return NULL;
|
||||
|
||||
const char* str = string_desc_arr[index];
|
||||
|
||||
// Cap at max char
|
||||
chr_count = strlen(str);
|
||||
size_t const max_count = sizeof(_desc_str) / sizeof(_desc_str[0]) - 1; // -1 for string type
|
||||
if (chr_count > max_count) chr_count = max_count;
|
||||
|
||||
// Convert ASCII string into UTF-16
|
||||
for (size_t i = 0; i < chr_count; i++) {
|
||||
_desc_str[1 + i] = str[i];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// first byte is length (including header), second byte is string type
|
||||
_desc_str[0] = (uint16_t) ((TUSB_DESC_STRING << 8) | (2 * chr_count + 2));
|
||||
|
||||
return _desc_str;
|
||||
}
|
||||
69
examples/device/usbtest/src/usb_descriptors.h
Normal file
69
examples/device/usbtest/src/usb_descriptors.h
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2026 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef USB_DESCRIPTORS_H_
|
||||
#define USB_DESCRIPTORS_H_
|
||||
|
||||
// Device-capability tier advertised in bcdDevice low byte (0x01TT), read by the
|
||||
// host script to select which usbtest cases to run:
|
||||
// 1: bulk source/sink
|
||||
// 2: + vendor control 0x5b/0x5c (ctrl_out)
|
||||
// 3: + interrupt source/sink
|
||||
// 4: + isochronous source/sink
|
||||
#define USBTEST_TIER 4
|
||||
|
||||
// Interrupt/isochronous endpoint max packet sizes, must match the configuration descriptor.
|
||||
// TUD_OPT_HIGH_SPEED is a compile-time capability flag, NOT the live bus speed, so the full-speed
|
||||
// config descriptor (and the OTHER_SPEED descriptor served to a HS host) must use full-speed-legal
|
||||
// sizes regardless of it: interrupt <= 64 B, isochronous <= 1023 B (and both iso EPs must fit the
|
||||
// 1023 B/frame FS periodic budget). Hence separate _FS / _HS descriptor sizes; the plain macro
|
||||
// below is the compile-time capability maximum that sizes the source buffers (runtime write
|
||||
// lengths follow the negotiated speed via tud_speed_get(), see main.c).
|
||||
//
|
||||
// The CH32 USB IPs have tiny per-endpoint buffers so tier-4's six endpoints don't fit at the usual
|
||||
// FS sizes: usbfs gives 64 B/ep (iso must drop to 64), and the CH32V20X fsdev port shares one 512 B
|
||||
// PMA across every endpoint (needs iso 32 AND a small interrupt mps to fit alongside EP0+bulk+iso).
|
||||
#if CFG_TUSB_MCU == OPT_MCU_CH32V20X && defined(CFG_TUD_WCH_USBIP_FSDEV) && CFG_TUD_WCH_USBIP_FSDEV
|
||||
#define USBTEST_INT_EP_MPS_FS 16
|
||||
#define USBTEST_ISO_EP_MPS_FS 32 // double-buffered on fsdev: 2x32=64/ep, same 512 B PMA budget
|
||||
#elif TU_CHECK_MCU(OPT_MCU_CH32V20X, OPT_MCU_CH32V103, OPT_MCU_CH32F20X, OPT_MCU_CH32V307, OPT_MCU_CH583)
|
||||
// WCH USBFS parts cap every endpoint (except EP3 IN) at 64 B. For the CH32V307 this applies to its
|
||||
// full-speed (usbfs) port; its high-speed (usbhs) port uses the _HS sizes below via desc_hs.
|
||||
#define USBTEST_INT_EP_MPS_FS 64
|
||||
#define USBTEST_ISO_EP_MPS_FS 64
|
||||
#else
|
||||
#define USBTEST_INT_EP_MPS_FS 64
|
||||
#define USBTEST_ISO_EP_MPS_FS 128
|
||||
#endif
|
||||
#define USBTEST_INT_EP_MPS_HS 512
|
||||
#define USBTEST_ISO_EP_MPS_HS 512
|
||||
|
||||
// Compile-time capability maximum: sizes the source buffers / vendor epbufs for the largest
|
||||
// packet the build can negotiate. Runtime write lengths follow tud_speed_get() (see main.c) —
|
||||
// a high-speed build enumerated at full speed submits the _FS lengths.
|
||||
#define USBTEST_INT_EP_MPS (TUD_OPT_HIGH_SPEED ? USBTEST_INT_EP_MPS_HS : USBTEST_INT_EP_MPS_FS)
|
||||
#define USBTEST_ISO_EP_MPS (TUD_OPT_HIGH_SPEED ? USBTEST_ISO_EP_MPS_HS : USBTEST_ISO_EP_MPS_FS)
|
||||
|
||||
#endif /* USB_DESCRIPTORS_H_ */
|
||||
Reference in New Issue
Block a user