summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhathach <[email protected]>2026-03-17 21:38:46 +0700
committerhathach <[email protected]>2026-03-17 21:38:46 +0700
commit45e80a1042ea802b74ebe97db350f9da17cb2fe4 (patch)
treee8f9352a4e8d241a37a4125fcdf297ea49e3cc2c
parenta3b2b4217630eea3f514ee042ee399e9ee397b8a (diff)
add hil test for host msc and cdc
-rw-r--r--AGENTS.md7
-rw-r--r--LICENSE2
-rw-r--r--examples/host/msc_file_explorer/src/msc_app.c11
-rw-r--r--hw/bsp/rp2040/boards/adafruit_feather_rp2040_usb_host/board.cmake1
-rw-r--r--hw/bsp/rp2040/boards/adafruit_fruit_jam/board.cmake2
-rw-r--r--hw/bsp/rp2040/boards/adafruit_metro_rp2350/board.cmake2
-rw-r--r--hw/bsp/rp2040/family.cmake6
-rwxr-xr-xtest/hil/hil_test.py169
-rw-r--r--test/hil/tinyusb.json19
9 files changed, 195 insertions, 24 deletions
diff --git a/AGENTS.md b/AGENTS.md
index d491f94f1..eb6b737c3 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -151,6 +151,13 @@ openocd -f interface/stlink.cfg -f target/stm32h7x.cfg
openocd -f interface/jlink.cfg -f target/stm32h7x.cfg
```
+For **rp2040/rp2350** with a CMSIS-DAP probe (e.g. Picoprobe, debugprobe):
+```bash
+openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c "adapter speed 5000"
+# or for rp2350:
+openocd -f interface/cmsis-dap.cfg -f target/rp2350.cfg -c "adapter speed 5000"
+```
+
For boards that define `OPENOCD_OPTION` in `board.cmake`, use those options directly:
```bash
openocd $(cat hw/bsp/FAMILY/boards/BOARD/board.cmake | grep OPENOCD_OPTION | ...)
diff --git a/LICENSE b/LICENSE
index ddd4ab410..0680c2f05 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2018, hathach (tinyusb.org)
+Copyright (c) 2012-2026, hathach (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
diff --git a/examples/host/msc_file_explorer/src/msc_app.c b/examples/host/msc_file_explorer/src/msc_app.c
index 6ac63e937..238af5431 100644
--- a/examples/host/msc_file_explorer/src/msc_app.c
+++ b/examples/host/msc_file_explorer/src/msc_app.c
@@ -130,11 +130,16 @@ static bool inquiry_complete_cb(uint8_t dev_addr, const tuh_msc_complete_data_t
drive_path[0] += drive_num;
if (f_mount(&fatfs[drive_num], drive_path, 1) != FR_OK) {
- puts("mount failed");
+ printf("mount failed\r\n");
+ return true;
}
// change to newly mounted drive
- f_chdir(drive_path);
+ f_chdrive(drive_path);
+ FRESULT rc = f_chdir("/");
+ if (rc != FR_OK) {
+ printf("chdir failed: %d\r\n", rc);
+ }
// print the drive label
// char label[34];
@@ -148,7 +153,7 @@ static bool inquiry_complete_cb(uint8_t dev_addr, const tuh_msc_complete_data_t
//------------- IMPLEMENTATION -------------//
void tuh_msc_mount_cb(uint8_t dev_addr) {
- printf("A MassStorage device is mounted\r\n");
+ printf("A MassStorage device (addr = %u) is mounted\r\n", dev_addr);
const uint8_t lun = 0;
tuh_msc_inquiry(dev_addr, lun, &scsi_resp.inquiry, inquiry_complete_cb, 0);
diff --git a/hw/bsp/rp2040/boards/adafruit_feather_rp2040_usb_host/board.cmake b/hw/bsp/rp2040/boards/adafruit_feather_rp2040_usb_host/board.cmake
index 41897f644..66758d7d0 100644
--- a/hw/bsp/rp2040/boards/adafruit_feather_rp2040_usb_host/board.cmake
+++ b/hw/bsp/rp2040/boards/adafruit_feather_rp2040_usb_host/board.cmake
@@ -1,2 +1,3 @@
set(PICO_PLATFORM rp2040)
set(PICO_BOARD adafruit_feather_rp2040_usb_host)
+set(CFG_TUH_RPI_PIO_USB 1)
diff --git a/hw/bsp/rp2040/boards/adafruit_fruit_jam/board.cmake b/hw/bsp/rp2040/boards/adafruit_fruit_jam/board.cmake
index 4ab8a5477..d3535fa36 100644
--- a/hw/bsp/rp2040/boards/adafruit_fruit_jam/board.cmake
+++ b/hw/bsp/rp2040/boards/adafruit_fruit_jam/board.cmake
@@ -2,3 +2,5 @@ set(PICO_PLATFORM rp2350-arm-s)
set(PICO_BOARD adafruit_fruit_jam)
set(PICO_BOARD_HEADER_DIRS ${CMAKE_CURRENT_LIST_DIR})
#set(OPENOCD_SERIAL E6614103E78E8324)
+
+set(CFG_TUH_RPI_PIO_USB 1)
diff --git a/hw/bsp/rp2040/boards/adafruit_metro_rp2350/board.cmake b/hw/bsp/rp2040/boards/adafruit_metro_rp2350/board.cmake
index 9a58821a5..f1f54d217 100644
--- a/hw/bsp/rp2040/boards/adafruit_metro_rp2350/board.cmake
+++ b/hw/bsp/rp2040/boards/adafruit_metro_rp2350/board.cmake
@@ -2,3 +2,5 @@ set(PICO_PLATFORM rp2350-arm-s)
set(PICO_BOARD adafruit_metro_rp2350)
set(PICO_BOARD_HEADER_DIRS ${CMAKE_CURRENT_LIST_DIR})
#set(OPENOCD_SERIAL E6614103E78E8324)
+
+set(CFG_TUH_RPI_PIO_USB 1)
diff --git a/hw/bsp/rp2040/family.cmake b/hw/bsp/rp2040/family.cmake
index e31abe50b..2e2cd436a 100644
--- a/hw/bsp/rp2040/family.cmake
+++ b/hw/bsp/rp2040/family.cmake
@@ -72,6 +72,12 @@ target_compile_definitions(tinyusb_common_base INTERFACE
CFG_TUSB_DEBUG=${TINYUSB_DEBUG_LEVEL}
)
+if (CFG_TUH_RPI_PIO_USB)
+ target_compile_definitions(tinyusb_common_base INTERFACE
+ CFG_TUH_RPI_PIO_USB=1
+ )
+endif()
+
target_link_libraries(tinyusb_common_base INTERFACE
hardware_structs
hardware_irq
diff --git a/test/hil/hil_test.py b/test/hil/hil_test.py
index 65585d929..a2c054e61 100755
--- a/test/hil/hil_test.py
+++ b/test/hil/hil_test.py
@@ -80,6 +80,11 @@ flash bank $_FLASHNAME wch_riscv 0x00000000 0 0 0 $_TARGETNAME.0
echo "Ready for Remote Connections"
"""
+MSC_README_TXT = \
+b"This is tinyusb's MassStorage Class demo.\r\n\r\n\
+If you find any bugs or get any questions, feel free to file an\r\n\
+issue at github.com/hathach/tinyusb"
+
# -------------------------------------------------------------
# Path
# -------------------------------------------------------------
@@ -360,10 +365,27 @@ def test_dual_host_info_to_device_cdc(board):
declared_devs = [f'{d["vid_pid"]}_{d["serial"]}' for d in board['tests']['dev_attached']]
port = get_serial_dev(uid, 'TinyUSB', "TinyUSB_Device", 0)
ser = open_serial_dev(port)
+ ser.timeout = 0.1
- # read from cdc, first line should contain vid/pid and serial
- data = ser.read(10000)
+ # read until all expected devices are enumerated
+ data = b''
+ timeout = ENUM_TIMEOUT
+ while timeout > 0:
+ new_data = ser.read(ser.in_waiting or 1)
+ if new_data:
+ data += new_data
+ # check if all devices found
+ enum_dev_sn = []
+ for l in data.decode('utf-8', errors='ignore').splitlines():
+ vid_pid_sn = re.search(r'ID ([0-9a-fA-F]+):([0-9a-fA-F]+) SN (\w+)', l)
+ if vid_pid_sn:
+ enum_dev_sn.append(f'{vid_pid_sn.group(1)}_{vid_pid_sn.group(2)}_{vid_pid_sn.group(3)}')
+ if set(declared_devs).issubset(set(enum_dev_sn)):
+ break
+ time.sleep(0.1)
+ timeout -= 0.1
ser.close()
+
if len(data) == 0:
assert False, 'No data from device'
lines = data.decode('utf-8', errors='ignore').splitlines()
@@ -391,13 +413,31 @@ def test_host_device_info(board):
port = get_serial_dev(flasher["uid"], None, None, 0)
ser = open_serial_dev(port)
+ ser.timeout = 0.1
# reset device since we can miss the first line
ret = globals()[f'reset_{flasher["name"].lower()}'](board)
- assert ret.returncode == 0, 'Failed to reset device'
+ assert ret.returncode == 0, 'Failed to reset device'
- data = ser.read(10000)
+ # read until all expected devices are enumerated
+ data = b''
+ timeout = ENUM_TIMEOUT
+ while timeout > 0:
+ new_data = ser.read(ser.in_waiting or 1)
+ if new_data:
+ data += new_data
+ # check if all devices found
+ enum_dev_sn = []
+ for l in data.decode('utf-8', errors='ignore').splitlines():
+ vid_pid_sn = re.search(r'ID ([0-9a-fA-F]+):([0-9a-fA-F]+) SN (\w+)', l)
+ if vid_pid_sn:
+ enum_dev_sn.append(f'{vid_pid_sn.group(1)}_{vid_pid_sn.group(2)}_{vid_pid_sn.group(3)}')
+ if set(declared_devs).issubset(set(enum_dev_sn)):
+ break
+ time.sleep(0.1)
+ timeout -= 0.1
ser.close()
+
if len(data) == 0:
assert False, 'No data from device'
lines = data.decode('utf-8', errors='ignore').splitlines()
@@ -416,10 +456,25 @@ def test_host_device_info(board):
return 0
+def print_msc_info(lines):
+ """Print MSC inquiry and disk size on a single line"""
+ inquiry = ''
+ disk_size = ''
+ for l in lines:
+ if re.match(r'^[A-Za-z].*\s+rev\s+', l):
+ inquiry = l.strip()
+ if 'Disk Size' in l:
+ disk_size = l.strip()
+ if inquiry or disk_size:
+ print(f'\r\n {inquiry} {disk_size} ', end='')
+
+
def test_host_cdc_msc_hid(board):
flasher = board['flasher']
- cdc_devs = [d for d in board['tests'].get('dev_attached', []) if d.get('is_cdc')]
- if not cdc_devs:
+ dev_attached = board['tests'].get('dev_attached', [])
+ cdc_devs = [d for d in dev_attached if d.get('is_cdc')]
+ msc_devs = [d for d in dev_attached if d.get('is_msc')]
+ if not cdc_devs and not msc_devs:
return
port = get_serial_dev(flasher["uid"], None, None, 0)
@@ -430,18 +485,21 @@ def test_host_cdc_msc_hid(board):
ret = globals()[f'reset_{flasher["name"].lower()}'](board)
assert ret.returncode == 0, 'Failed to reset device'
- # Wait for CDC mounted message
+ # Wait for all expected mount messages
data = b''
timeout = ENUM_TIMEOUT
+ wait_cdc = len(cdc_devs) > 0
+ wait_msc = len(msc_devs) > 0
while timeout > 0:
new_data = ser.read(ser.in_waiting or 1)
if new_data:
data += new_data
- if b'CDC Interface is mounted' in data:
+ cdc_ok = (not wait_cdc) or (b'CDC Interface is mounted' in data)
+ msc_ok = (not wait_msc) or (b'Disk Size' in data)
+ if cdc_ok and msc_ok:
break
time.sleep(0.1)
timeout -= 0.1
- assert b'CDC Interface is mounted' in data, 'CDC device not mounted on host'
# Lookup serial chip name from vid_pid
vid_pid_name = {
@@ -451,13 +509,29 @@ def test_host_cdc_msc_hid(board):
'1a86_7523': 'CH340', '1a86_7522': 'CH340',
'1a86_55d3': 'CH9102', '1a86_55d4': 'CH9102',
}
- dev = cdc_devs[0]
- chip_name = vid_pid_name.get(dev['vid_pid'], dev['vid_pid'])
- for l in data.decode('utf-8', errors='ignore').splitlines():
- if 'CDC Interface is mounted' in l:
- print(f'\r\n {chip_name}: {l} ', end='')
+
+ lines = data.decode('utf-8', errors='ignore').splitlines()
+
+ # Verify and print CDC mount
+ if cdc_devs:
+ assert b'CDC Interface is mounted' in data, 'CDC device not mounted on host'
+ dev = cdc_devs[0]
+ chip_name = vid_pid_name.get(dev['vid_pid'], dev['vid_pid'])
+ for l in lines:
+ if 'CDC Interface is mounted' in l:
+ print(f'\r\n {chip_name}: {l} ', end='')
+
+ # Verify and print MSC mount (inquiry + disk size)
+ if msc_devs:
+ assert b'MassStorage device is mounted' in data, 'MSC device not mounted on host'
+ assert b'Disk Size' in data, 'MSC Disk Size not reported'
+ print_msc_info(lines)
# CDC echo test via flasher serial
+ if not cdc_devs:
+ ser.close()
+ return
+
time.sleep(2)
ser.reset_input_buffer()
@@ -490,6 +564,65 @@ def test_host_cdc_msc_hid(board):
ser.close()
+def test_host_msc_file_explorer(board):
+ flasher = board['flasher']
+ msc_devs = [d for d in board['tests'].get('dev_attached', []) if d.get('is_msc')]
+ if not msc_devs:
+ return
+
+ port = get_serial_dev(flasher["uid"], None, None, 0)
+ ser = open_serial_dev(port)
+ ser.timeout = 0.1
+
+ # reset device to catch mount messages
+ ret = globals()[f'reset_{flasher["name"].lower()}'](board)
+ assert ret.returncode == 0, 'Failed to reset device'
+
+ # Wait for MSC mount (Disk Size message)
+ data = b''
+ timeout = ENUM_TIMEOUT
+ while timeout > 0:
+ new_data = ser.read(ser.in_waiting or 1)
+ if new_data:
+ data += new_data
+ if b'Disk Size' in data:
+ break
+ time.sleep(0.1)
+ timeout -= 0.1
+ assert b'Disk Size' in data, 'MSC device not mounted'
+ lines = data.decode('utf-8', errors='ignore').splitlines()
+ print_msc_info(lines)
+
+ # Send "cat README.TXT" and read response
+ time.sleep(1)
+ ser.reset_input_buffer()
+ for ch in 'cat README.TXT\r':
+ ser.write(ch.encode())
+ ser.flush()
+ time.sleep(0.002)
+
+ # Read response
+ resp = b''
+ t = 10.0
+ while t > 0:
+ rd = ser.read(max(1, ser.in_waiting))
+ if rd:
+ resp += rd
+ # wait for prompt after command output
+ if b'>' in resp and resp.rstrip().endswith(b'>'):
+ break
+ time.sleep(0.05)
+ t -= 0.05
+
+ # Verify response contains README content
+ resp_text = resp.decode('utf-8', errors='ignore')
+ assert MSC_README_TXT.decode() in resp_text, (f'MSC README.TXT not found in response:\n'
+ f' received: {resp_text}')
+ print('README.TXT matched ', end='')
+
+ ser.close()
+
+
# -------------------------------------------------------------
# Tests: device
# -------------------------------------------------------------
@@ -565,12 +698,7 @@ def test_device_cdc_msc(board):
# MSC Block test
data = read_disk_file(uid, 0, 'README.TXT')
- readme = \
- b"This is tinyusb's MassStorage Class demo.\r\n\r\n\
-If you find any bugs or get any questions, feel free to file an\r\n\
-issue at github.com/hathach/tinyusb"
-
- assert data == readme, f'MSC wrong data in README.TXT\n expected: {readme.decode()}\n received: {data.decode()}'
+ assert data == MSC_README_TXT, f'MSC wrong data in README.TXT\n expected: {MSC_README_TXT.decode()}\n received: {data.decode()}'
def test_device_cdc_msc_freertos(board):
@@ -838,6 +966,7 @@ dual_tests = [
host_test = [
'host/cdc_msc_hid',
+ 'host/msc_file_explorer',
'host/device_info',
]
diff --git a/test/hil/tinyusb.json b/test/hil/tinyusb.json
index eaf60c6ce..e84e9672b 100644
--- a/test/hil/tinyusb.json
+++ b/test/hil/tinyusb.json
@@ -176,6 +176,25 @@
}
},
{
+ "name": "adafruit_fruit_jam",
+ "uid": "2B0DC7A45781189E",
+ "tests": {
+ "device": false,
+ "host": true,
+ "dual": true,
+ "dev_attached": [
+ {"vid_pid": "0403_6001", "serial": "0", "is_cdc": true},
+ {"vid_pid": "058f_6387", "serial": "A8BEE062633D", "is_msc": true,
+ "msc_disk_size": 3730, "msc_inquiry": "Generic Flash Disk rev 8.07"}
+ ]
+ },
+ "flasher": {
+ "name": "openocd",
+ "uid": "E6614103E78E8324",
+ "args": "-f interface/cmsis-dap.cfg -f target/rp2350.cfg -c \"adapter speed 5000\""
+ }
+ },
+ {
"name": "stm32f072disco",
"uid": "3A001A001357364230353532",
"flasher": {