Skip to content

Commit bf944b7

Browse files
committed
Merge branch 'feature/usb_host_enum_testing' into 'master'
feature(usb_host): Added test app for edge cases in stages during enumeration process See merge request espressif/esp-idf!31421
2 parents 403cc24 + 7c118fd commit bf944b7

15 files changed

+3335
-0
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# This is the project CMakeLists.txt file for the test subproject
2+
cmake_minimum_required(VERSION 3.16)
3+
4+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
5+
6+
set(EXTRA_COMPONENT_DIRS "../common")
7+
8+
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
9+
idf_build_set_property(MINIMAL_BUILD ON)
10+
11+
project(test_app_enum)
Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
| Supported Targets | ESP32-P4 | ESP32-S2 | ESP32-S3 |
2+
| ----------------- | -------- | -------- | -------- |
3+
4+
# USB: Enumeration Driver - Local Test with emulated device based on TinyUSB
5+
6+
## Introduction
7+
8+
To verify the process how Enumeration Driver handles different scenarios the artificial approach of emulation USB device with TinyUSB is used.
9+
This test helps to verify all possible scenarios of detaching device under the enumeration process.
10+
11+
## Key Terms
12+
13+
**Mocked Device** - A esp32-cpu based board, which represents the USB device, build on TinyUSB component (**tud_teardown()** feature is required, thus the version of the component should be higher than v0.17.0). The Mocked Device is used to emulate different scenarios with ability of controlling the USB Enumeration flow and detach the Mocked Device when necessary.
14+
15+
**Host** - A esp32-cpu based board with USB Host functionality. Used to verify the part of the USB Host Library - Enumeration Driver.
16+
17+
## Test Hardware requirements
18+
19+
For testing purpose two esp32-cpu based hardware with USB-OTG support are required (One for Host, one for Device (mocked dev)):
20+
21+
```
22+
+----------------+
23+
| USB Host Board |
24+
| +------------+ | USB
25+
| | USB-to-UART|========++
26+
| +------------+ | ||
27+
| +------------+ | \/
28+
| | USB OTG | | \/
29+
| +------------+ | || +-----------------+
30+
+-------||-------+ ++==| |
31+
/\ | PC Host |
32+
/\ USB | (Test runner) |
33+
+-------||-------+ ++==| |
34+
| || | || +-----------------+
35+
| +------------+ | /\
36+
| | USB OTG | | /\
37+
| +------------+ | ||
38+
| +------------+ | ||
39+
| | USB-to-UART|========++
40+
| +------------+ | USB
41+
| USB Dev Board |
42+
| (mocked dev) |
43+
+----------------+
44+
```
45+
46+
## Mocked Device
47+
48+
Implementation could be found in [mock_dev.c](main/mock_dev.c) and [mock_dev.h](main/mock_dev.h).
49+
50+
The Mocked Device has a Final State Machine (FSM), which could be configured to stop at any stage and run the teardown process after. This flow represents the emulation of device detachment.
51+
52+
FSM is described in the **mock_dev_stage_t** structure and could be found in [mock_dev.h](main/mock_dev.h).
53+
54+
Available stages:
55+
- Request Short Device Descriptor
56+
- Request Full Device Descriptor
57+
- Request Short Configuration Descriptor
58+
- Request Full Configuration Descriptor
59+
- Request Short LANGID Table
60+
- Request Full LANDID Table
61+
- Request Short Manufacturer String Descriptor
62+
- Request Full Manufacturer String Descriptor
63+
- Request Short Product String Descriptor
64+
- Request Full Product String Descriptor
65+
- Request Short Serial String Descriptor
66+
- Request Full Serial String Descriptor
67+
- Set configuration
68+
69+
Not covered stages:
70+
- Device Reset (First and Second)
71+
- Set Address
72+
73+
These stages are handled internally in TinyUSB and there is no user callbacks available. That means, that without specific changes in the TinyUSB component code, it is impossible to achieve emulation of device detaching during these stages.
74+
75+
76+
77+
### Mocked Device - Stages
78+
79+
To specify the descriptor, which should be used on every stage, there is a possibility to configure the pointer at every stage.
80+
81+
Example of fully-defined non-detachable Mocked Device configuration:
82+
83+
``` c
84+
static const tusb_desc_device_t mock_device_desc = {
85+
.bLength = sizeof(tusb_desc_device_t),
86+
.bDescriptorType = TUSB_DESC_DEVICE,
87+
.bcdUSB = 0x0200,
88+
.bDeviceClass = TUSB_CLASS_MISC,
89+
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
90+
.bDeviceProtocol = MISC_PROTOCOL_IAD,
91+
.bMaxPacketSize0 = 64,
92+
.idVendor = 0x0001,
93+
.idProduct = 0x0002,
94+
.bcdDevice = 0x0000,
95+
.iManufacturer = 0x01,
96+
.iProduct = 0x02,
97+
.iSerialNumber = 0x03,
98+
.bNumConfigurations = 0x01
99+
};
100+
101+
static const uint8_t mock_config_desc[] = {
102+
// Config number, interface count, string index, total length, attribute, power in mA
103+
TUD_CONFIG_DESCRIPTOR(1, 0, 0, TUD_CONFIG_DESC_LEN, TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
104+
};
105+
106+
static const char *mock_langid_str_desc = {
107+
(char[]){0x09, 0x04}, // 0: LANGID
108+
};
109+
110+
static const char *mock_manu_str_desc = {
111+
"Espressif", // 1: Manufacturer
112+
};
113+
114+
static const char *mock_prod_str_desc = {
115+
"Enum Testing", // 2: Product
116+
};
117+
118+
static const char *mock_ser_str_desc = {
119+
"123456", // 3: Serials, should use chip ID
120+
};
121+
122+
TEST_CASE("enum::complete", "[mock_enum_device]")
123+
{
124+
mock_dev_cfg_t mock_dev_cfg = {
125+
.enumeration = {
126+
.stage_config[MOCK_DEV_STAGE_REQ_SHORT_DEV_DESC] = {
127+
.desc = (const uint8_t *) &mock_device_desc,
128+
},
129+
.stage_config[MOCK_DEV_STAGE_REQ_FULL_DEV_DESC] = {
130+
.desc = (const uint8_t *) &mock_device_desc,
131+
},
132+
.stage_config[MOCK_DEV_STAGE_REQ_SHORT_CONFIG_DESC] = {
133+
.desc = mock_config_desc,
134+
},
135+
.stage_config[MOCK_DEV_STAGE_REQ_FULL_CONFIG_DESC] = {
136+
.desc = mock_config_desc,
137+
},
138+
// String Descriptors
139+
.stage_config[MOCK_DEV_STAGE_REQ_SHORT_LANGID_TABLE] = {
140+
.str_desc = mock_langid_str_desc,
141+
},
142+
.stage_config[MOCK_DEV_STAGE_REQ_FULL_LANGID_TABLE] = {
143+
.str_desc = mock_langid_str_desc,
144+
},
145+
.stage_config[MOCK_DEV_STAGE_REQ_SHORT_MANU_STR_DESC] = {
146+
.str_desc = mock_manu_str_desc,
147+
},
148+
.stage_config[MOCK_DEV_STAGE_REQ_FULL_MANU_STR_DESC] = {
149+
.str_desc = mock_manu_str_desc,
150+
},
151+
.stage_config[MOCK_DEV_STAGE_REQ_SHORT_PROD_STR_DESC] = {
152+
.str_desc = mock_prod_str_desc,
153+
},
154+
.stage_config[MOCK_DEV_STAGE_REQ_FULL_PROD_STR_DESC] = {
155+
.str_desc = mock_prod_str_desc,
156+
},
157+
.stage_config[MOCK_DEV_STAGE_REQ_SHORT_SER_STR_DESC] = {
158+
.str_desc = mock_ser_str_desc,
159+
},
160+
.stage_config[MOCK_DEV_STAGE_REQ_FULL_SER_STR_DESC] = {
161+
.str_desc = mock_ser_str_desc,
162+
},
163+
.stage_config[MOCK_DEV_STAGE_SET_CONFIG] = {
164+
.desc = mock_config_desc,
165+
}
166+
},
167+
};
168+
169+
// Test remained logic
170+
}
171+
```
172+
173+
Every stage can be stopped with providing the special flag **stop_test**.
174+
175+
To specify the test, provide the following configuration to teardown the Mocked Device after getting the request:
176+
177+
```
178+
mock_dev_cfg_t mock_dev_cfg = {
179+
.enumeration = {
180+
.stage_config[MOCK_DEV_STAGE_REQ_SHORT_DEV_DESC] = {
181+
.desc = NULL,
182+
.stop_test = true,
183+
},
184+
},
185+
};
186+
187+
```
188+
189+
If we want to emulate the device detachment during the requesting of Full Device Descriptor, we can change the test configuration accordingly:
190+
191+
```
192+
mock_dev_cfg_t mock_dev_cfg = {
193+
.enumeration = {
194+
.stage_config[MOCK_DEV_STAGE_REQ_SHORT_DEV_DESC] = {
195+
.desc = (const uint8_t *) &mock_device_desc,
196+
},
197+
.stage_config[MOCK_DEV_STAGE_REQ_FULL_DEV_DESC] = {
198+
.desc = NULL,
199+
.stop_test = true,
200+
},
201+
},
202+
};
203+
204+
```
205+
206+
### USB Host Library
207+
208+
To verify the USB Host Library - Enumeration Driver, **usb_host_lib** example is used.
209+
210+
## Run Test
211+
212+
When all the hardware have been configured and prepared, the Test can be run via following commands. As the Test requires both Host and Device parts, the following description will be for Host and Device.
213+
214+
The description is provided, assuming that the test is run under Linux and Host board has a /dev/ttyACM0 path, Mocked Device has a /dev/ttyUSB0 path, and we are in the esp-idf root folder.
215+
216+
To run the pytest, esp-idf must be installed and configured with ```--enable-pytest```.
217+
218+
### Prepare Host
219+
220+
Host part is controlled by visual control of debug output from the Host.
221+
222+
The following description is based on the usb_host_lib example as the application on the Host side.
223+
224+
To run the example on the Host:
225+
226+
```bash
227+
cd examples/peripherals/usb/host/usb_host_lib
228+
idf.py set-target esp32s3
229+
idf.py -p /dev/ttyACM0 flash monitor
230+
```
231+
232+
### Run Mocked Device
233+
234+
To run the Device part:
235+
236+
```bash
237+
cd components/usb/test_apps/enum
238+
idf.py set-target esp32s3
239+
idf.py build
240+
pytest --target=esp32s3 --port=/dev/ttyUSB0
241+
```
242+
243+
After running the pytest on the device, the debug output from the Host works as the usb_host_lib example:
244+
245+
```
246+
I (270) main_task: Started on CPU0
247+
I (280) main_task: Calling app_main()
248+
I (280) USB host lib: USB host library example
249+
I (280) gpio: GPIO[0]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:2
250+
I (290) USB host lib: Installing USB Host Library
251+
I (320) CLASS: Registering Client
252+
I (700) CLASS: Opening device at address 1
253+
I (700) CLASS: Getting device information
254+
I (700) CLASS: Full speed
255+
I (700) CLASS: Parent info:
256+
I (700) CLASS: Port: ROOT
257+
I (700) CLASS: bConfigurationValue 1
258+
I (700) CLASS: Getting device descriptor
259+
*** Device descriptor ***
260+
bLength 18
261+
bDescriptorType 1
262+
bcdUSB 2.00
263+
bDeviceClass 0xef
264+
bDeviceSubClass 0x2
265+
bDeviceProtocol 0x1
266+
bMaxPacketSize0 64
267+
idVendor 0x1
268+
idProduct 0x2
269+
bcdDevice 0.00
270+
iManufacturer 1
271+
iProduct 2
272+
iSerialNumber 3
273+
bNumConfigurations 1
274+
I (90980) CLASS: Getting config descriptor
275+
*** Configuration descriptor ***
276+
bLength 9
277+
bDescriptorType 2
278+
wTotalLength 9
279+
bNumInterfaces 0
280+
bConfigurationValue 1
281+
iConfiguration 0
282+
bmAttributes 0xe0
283+
bMaxPower 100mA
284+
I (91000) CLASS: Getting Manufacturer string descriptor
285+
Espressif
286+
I (91010) CLASS: Getting Product string descriptor
287+
Enum Testing
288+
I (91020) CLASS: Getting Serial Number string descriptor
289+
123456
290+
```
291+
292+
Which means that the Mocked Device is connected to Host and the enumeration process was completed.
293+
Different scenarios are being tested during the test run, so there could be errors in the log
294+
That means, that the device emulates the specific behavior, to cause this error.
295+
296+
Important things are:
297+
- During the test there must be no KERNEL PANIC, CPU reset etc. (not to miss the reset, it is better to configure the Panic handler behaviour to: Print registers and halt)
298+
- The Host application should be responsive after the test. (In terms of usb_host_lib example, that means handle new device connection and show the descriptors from the connected device).
299+
300+
"Normal" test flow error are:
301+
302+
```
303+
E (116330) USBH: Dev 28 EP 0 STALL
304+
```
305+
306+
Or
307+
308+
```
309+
E (96200) ENUM: Short dev desc has wrong bDescriptorType
310+
E (96200) ENUM: [0:0] CHECK_SHORT_DEV_DESC FAILED
311+
E (96820) ENUM: Full dev desc has wrong bDescriptorType
312+
E (96820) ENUM: [0:0] CHECK_FULL_DEV_DESC FAILED
313+
W (97470) ENUM: Device has more than 1 configuration
314+
E (97470) USBH: Dev 16 EP 0 STALL
315+
E (97470) ENUM: Bad transfer status 4: CHECK_SHORT_CONFIG_DESC
316+
E (97470) ENUM: [0:0] CHECK_SHORT_CONFIG_DESC FAILED
317+
W (98120) ENUM: [0:0] Unexpected (8) device response length (expected 16)
318+
E (98120) ENUM: Device returned less bytes than requested
319+
E (98120) ENUM: [0:0] CHECK_SHORT_CONFIG_DESC FAILED
320+
W (98770) ENUM: [0:0] Unexpected (8) device response length (expected 17)
321+
E (98770) ENUM: Device returned less bytes than requested
322+
E (98770) ENUM: [0:0] CHECK_FULL_CONFIG_DESC FAILED
323+
E (99420) ENUM: Short config desc has wrong bDescriptorType
324+
E (99420) ENUM: [0:0] CHECK_SHORT_CONFIG_DESC FAILED
325+
E (100080) ENUM: Full config desc has wrong bDescriptorType
326+
E (100080) ENUM: [0:0] CHECK_FULL_CONFIG_DESC FAILED
327+
E (100660) USBH: Dev 0 EP 0 STALL
328+
E (100660) ENUM: Bad transfer status 4: CHECK_SHORT_DEV_DESC
329+
E (100660) ENUM: [0:0] CHECK_SHORT_DEV_DESC FAILED
330+
E (101280) USBH: Dev 16 EP 0 STALL
331+
E (101280) ENUM: Bad transfer status 4: CHECK_FULL_DEV_DESC
332+
E (101280) ENUM: [0:0] CHECK_FULL_DEV_DESC FAILED
333+
E (101930) USBH: Dev 16 EP 0 STALL
334+
E (101930) ENUM: Bad transfer status 4: CHECK_SHORT_CONFIG_DESC
335+
E (101930) ENUM: [0:0] CHECK_SHORT_CONFIG_DESC FAILED
336+
E (102580) USBH: Dev 16 EP 0 STALL
337+
E (102580) ENUM: Bad transfer status 4: CHECK_FULL_CONFIG_DESC
338+
E (102580) ENUM: [0:0] CHECK_FULL_CONFIG_DESC FAILED
339+
E (103230) USBH: Dev 16 EP 0 STALL
340+
```
341+
342+
Or
343+
344+
```
345+
E (121120) USBH: Dev 33 EP 0 STALL
346+
E (121120) ENUM: Bad transfer status 4: CHECK_CONFIG
347+
E (121120) ENUM: [0:0] CHECK_CONFIG FAILED
348+
```
349+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
2+
# the component can be registered as WHOLE_ARCHIVE
3+
idf_component_register(SRC_DIRS "."
4+
INCLUDE_DIRS "."
5+
PRIV_INCLUDE_DIRS "."
6+
REQUIRES usb unity
7+
WHOLE_ARCHIVE)
8+
9+
# Determine whether tinyusb is fetched from component registry or from local path
10+
idf_build_get_property(build_components BUILD_COMPONENTS)
11+
if(tinyusb IN_LIST build_components)
12+
set(tinyusb_name tinyusb) # Local component
13+
else()
14+
set(tinyusb_name espressif__tinyusb) # Managed component
15+
endif()
16+
17+
# Pass tusb_config.h from this component to TinyUSB
18+
idf_component_get_property(tusb_lib ${tinyusb_name} COMPONENT_LIB)
19+
target_include_directories(${tusb_lib} PRIVATE ".")
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
menu "ENUM:: test configuration"
2+
3+
config TINYUSB_DEBUG_LEVEL
4+
int "TinyUSB log level (0-3)"
5+
default 1
6+
range 0 3
7+
help
8+
Specify verbosity of TinyUSB log output.
9+
10+
endmenu
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
## IDF Component Manager Manifest File
2+
dependencies:
3+
espressif/tinyusb:
4+
version: '>=0.17.0'
5+
public: true

0 commit comments

Comments
 (0)