Merge branch 'master' into fork/ennebi/mtp

This commit is contained in:
hathach
2025-09-15 16:45:41 +07:00
600 changed files with 35320 additions and 11206 deletions

View File

@ -1,5 +1,3 @@
DEPS_SUBMODULES += lib/lwip
include ../../make.mk
# suppress warning caused by lwip

View File

@ -124,11 +124,5 @@ endif
# Log level is mapped to TUSB DEBUG option
ifneq ($(LOG),)
CMAKE_DEFSYM += -DLOG=$(LOG)
CFLAGS += -DCFG_TUSB_DEBUG=$(LOG)
endif
# Logger: default is uart, can be set to rtt or swo
ifneq ($(LOGGER),)
CMAKE_DEFSYM += -DLOGGER=$(LOGGER)
endif

View File

@ -143,7 +143,7 @@ endif
# get depenecies
.PHONY: get-deps
get-deps:
$(PYTHON) $(TOP)/tools/get_deps.py $(DEPS_SUBMODULES)
$(PYTHON) $(TOP)/tools/get_deps.py $(FAMILY)
size: $(BUILD)/$(PROJECT)
-@echo ''

View File

@ -28,14 +28,20 @@ def main():
else:
toolchain = 'arm-gcc'
if 'build' in board and 'flags_on' in board['build']:
for f in board['build']['flags_on']:
if f == '':
matrix[toolchain].append(f'-b {name}')
else:
matrix[toolchain].append(f'-b {name} -f1 {f.replace(" ", " -f1 ")}')
build_board = f'-b {name}'
if 'build' in board:
if 'args' in board['build']:
build_board += ' ' + ' '.join(f'-D{a}' for a in board['build']['args'])
if 'flags_on' in board['build']:
for f in board['build']['flags_on']:
if f == '':
matrix[toolchain].append(build_board)
else:
matrix[toolchain].append(f'{build_board} -f1 {f.replace(" ", " -f1 ")}')
else:
matrix[toolchain].append(build_board)
else:
matrix[toolchain].append(f'-b {name}')
matrix[toolchain].append(build_board)
print(json.dumps(matrix))

View File

@ -81,7 +81,7 @@ def get_serial_dev(id, vendor_str, product_str, ifnum):
return f'/dev/serial/by-id/usb-{vendor_str}_{product_str}_{id}-if{ifnum:02d}'
else:
# just use id: mostly for cp210x/ftdi flasher
pattern = f'/dev/serial/by-id/usb-*_{id}-if{ifnum:02d}*'
pattern = f'/dev/serial/by-id/usb-*_{id}-if*'
port_list = glob.glob(pattern)
return port_list[0]
@ -98,20 +98,18 @@ def get_hid_dev(id, vendor_str, product_str, event):
def open_serial_dev(port):
timeout = ENUM_TIMEOUT
ser = None
while timeout:
while timeout > 0:
if os.path.exists(port):
try:
# slight delay since kernel may occupy the port briefly
time.sleep(0.5)
timeout = timeout - 0.5
ser = serial.Serial(port, baudrate=115200, timeout=5)
break
except serial.SerialException:
print(f'serial {port} not reaady {timeout} sec')
pass
time.sleep(0.5)
timeout = timeout - 0.5
time.sleep(0.1)
timeout -= 0.1
assert timeout, f'Cannot open port f{port}' if os.path.exists(port) else f'Port {port} not existed'
assert timeout > 0, f'Cannot open port f{port}' if os.path.exists(port) else f'Port {port} not existed'
return ser
@ -119,7 +117,7 @@ def read_disk_file(uid, lun, fname):
# open_fs("fat://{dev}) require 'pip install pyfatfs'
dev = get_disk_dev(uid, 'TinyUSB', lun)
timeout = ENUM_TIMEOUT
while timeout:
while timeout > 0:
if os.path.exists(dev):
fat = fs.open_fs(f'fat://{dev}?read_only=true')
try:
@ -132,7 +130,7 @@ def read_disk_file(uid, lun, fname):
time.sleep(1)
timeout -= 1
assert timeout, f'Storage {dev} not existed'
assert timeout > 0, f'Storage {dev} not existed'
return None
@ -202,14 +200,14 @@ def reset_stflash(board):
def flash_openocd(board, firmware):
flasher = board['flasher']
ret = run_cmd(f'openocd -c "tcl_port disabled" -c "gdb_port disabled" -c "adapter serial {flasher["uid"]}" '
f'{flasher["args"]} -c init -c halt -c "program {firmware}.elf verify" -c reset -c exit')
f'{flasher["args"]} -c "init; halt; program {firmware}.elf verify; reset; exit"')
return ret
def reset_openocd(board):
flasher = board['flasher']
ret = run_cmd(f'openocd -c "tcl_port disabled" -c "gdb_port disabled" -c "adapter serial {flasher["uid"]}" '
f'{flasher["args"]} -c "reset exit"')
f'{flasher["args"]} -c "init; reset run; exit"')
return ret
@ -304,11 +302,11 @@ def test_dual_host_info_to_device_cdc(board):
ser = open_serial_dev(port)
# read from cdc, first line should contain vid/pid and serial
data = ser.read(1000)
data = ser.read(10000)
ser.close()
if len(data) == 0:
assert False, 'No data from device'
lines = data.decode('utf-8').splitlines()
lines = data.decode('utf-8', errors='ignore').splitlines()
enum_dev_sn = []
for l in lines:
@ -319,6 +317,7 @@ def test_dual_host_info_to_device_cdc(board):
if set(declared_devs) != set(enum_dev_sn):
failed_msg = f'Expected {declared_devs}, Enumerated {enum_dev_sn}'
print('\n'.join(lines))
assert False, failed_msg
return 0
@ -337,12 +336,12 @@ def test_host_device_info(board):
ret = globals()[f'reset_{flasher["name"].lower()}'](board)
assert ret.returncode == 0, 'Failed to reset device'
data = ser.read(1000)
data = ser.read(10000)
ser.close()
if len(data) == 0:
assert False, 'No data from device'
lines = data.decode('utf-8', errors='ignore').splitlines()
lines = data.decode('utf-8').splitlines()
enum_dev_sn = []
for l in lines:
vid_pid_sn = re.search(r'ID ([0-9a-fA-F]+):([0-9a-fA-F]+) SN (\w+)', l)
@ -352,8 +351,8 @@ def test_host_device_info(board):
if set(declared_devs) != set(enum_dev_sn):
failed_msg = f'Expected {declared_devs}, Enumerated {enum_dev_sn}'
print('\n'.join(lines))
assert False, failed_msg
return 0
@ -419,7 +418,7 @@ def test_device_dfu(board):
# Wait device enum
timeout = ENUM_TIMEOUT
while timeout:
while timeout > 0:
ret = run_cmd(f'dfu-util -l')
stdout = ret.stdout.decode()
if f'serial="{uid}"' in stdout and 'Found DFU: [cafe:4000]' in stdout:
@ -427,7 +426,7 @@ def test_device_dfu(board):
time.sleep(1)
timeout = timeout - 1
assert timeout, 'Device not available'
assert timeout > 0, 'Device not available'
f_dfu0 = f'dfu0_{uid}'
f_dfu1 = f'dfu1_{uid}'
@ -460,7 +459,7 @@ def test_device_dfu_runtime(board):
# Wait device enum
timeout = ENUM_TIMEOUT
while timeout:
while timeout > 0:
ret = run_cmd(f'dfu-util -l')
stdout = ret.stdout.decode()
if f'serial="{uid}"' in stdout and 'Found Runtime: [cafe:4000]' in stdout:
@ -468,7 +467,7 @@ def test_device_dfu_runtime(board):
time.sleep(1)
timeout = timeout - 1
assert timeout, 'Device not available'
assert timeout > 0, 'Device not available'
def test_device_hid_boot_interface(board):
@ -478,13 +477,13 @@ def test_device_hid_boot_interface(board):
mouse2 = get_hid_dev(uid, 'TinyUSB', 'TinyUSB_Device', 'if01-mouse')
# Wait device enum
timeout = ENUM_TIMEOUT
while timeout:
while timeout > 0:
if os.path.exists(kbd) and os.path.exists(mouse1) and os.path.exists(mouse2):
break
time.sleep(1)
timeout = timeout - 1
assert timeout, 'HID device not available'
assert timeout > 0, 'HID device not available'
def test_device_hid_composite_freertos(id):
@ -515,6 +514,64 @@ host_test = [
]
def test_example(board, f1, example):
"""
Test example firmware
:param board: board dict
:param f1: flags on
:param example: example name
:return: 0 if success/skip, 1 if failed
"""
name = board['name']
err_count = 0
f1_str = ""
if f1 != "":
f1_str = '-f1_' + f1.replace(' ', '_')
fw_dir = f'{TINYUSB_ROOT}/cmake-build/cmake-build-{name}{f1_str}/{example}'
if not os.path.exists(fw_dir):
fw_dir = f'{TINYUSB_ROOT}/examples/cmake-build-{name}{f1_str}/{example}'
fw_name = f'{fw_dir}/{os.path.basename(example)}'
print(f'{name+f1_str:40} {example:30} ...', end='')
if not os.path.exists(fw_dir) or not (os.path.exists(f'{fw_name}.elf') or os.path.exists(f'{fw_name}.bin')):
print('Skip (no binary)')
return 0
if verbose:
print(f'Flashing {fw_name}.elf')
# flash firmware. It may fail randomly, retry a few times
max_rety = 3
start_s = time.time()
for i in range(max_rety):
ret = globals()[f'flash_{board["flasher"]["name"].lower()}'](board, fw_name)
if ret.returncode == 0:
try:
globals()[f'test_{example.replace("/", "_")}'](board)
print(' OK', end='')
break
except Exception as e:
if i == max_rety - 1:
err_count += 1
print(f'{STATUS_FAILED}: {e}')
else:
print(f'\n Test failed: {e}, retry {i+2}/{max_rety}', end='')
time.sleep(0.5)
else:
print(f'\n Flash failed, retry {i+2}/{max_rety}', end='')
time.sleep(0.5)
if ret.returncode != 0:
err_count += 1
print(f' Flash {STATUS_FAILED}', end='')
print(f' in {time.time() - start_s:.1f}s')
return err_count
def test_board(board):
name = board['name']
flasher = board['flasher']
@ -538,56 +595,19 @@ def test_board(board):
test_list.remove(skip)
print(f'{name:25} {skip:30} ... Skip')
# board_test is added last to disable board's usb
test_list.append('device/board_test')
err_count = 0
flags_on_list = [""]
if 'build' in board and 'flags_on' in board['build']:
flags_on_list = board['build']['flags_on']
for f1 in flags_on_list:
f1_str = ""
if f1 != "":
f1_str = '-f1_' + f1.replace(' ', '_')
for test in test_list:
fw_dir = f'{TINYUSB_ROOT}/cmake-build/cmake-build-{name}{f1_str}/{test}'
if not os.path.exists(fw_dir):
fw_dir = f'{TINYUSB_ROOT}/examples/cmake-build-{name}{f1_str}/{test}'
fw_name = f'{fw_dir}/{os.path.basename(test)}'
print(f'{name+f1_str:40} {test:30} ... ', end='')
err_count += test_example(board, f1, test)
if not os.path.exists(fw_dir) or not (os.path.exists(f'{fw_name}.elf') or os.path.exists(f'{fw_name}.bin')):
print('Skip (no binary)')
continue
# flash board_test last to disable board's usb
test_example(board, flags_on_list[0], 'device/board_test')
# flash firmware. It may fail randomly, retry a few times
max_rety = 2
for i in range(max_rety):
ret = globals()[f'flash_{flasher["name"].lower()}'](board, fw_name)
if ret.returncode == 0:
try:
globals()[f'test_{test.replace("/", "_")}'](board)
print('OK')
break
except Exception as e:
if i == max_rety - 1:
err_count += 1
print(STATUS_FAILED)
print(f' {e}')
else:
print()
print(f' Test failed: {e}, retry {i+1}')
time.sleep(1)
else:
print(f'Flashing failed, retry {i+1}')
time.sleep(1)
if ret.returncode != 0:
err_count += 1
print(f'Flash {STATUS_FAILED}')
return err_count
return name, err_count
def main():
@ -601,11 +621,13 @@ def main():
parser = argparse.ArgumentParser()
parser.add_argument('config_file', help='Configuration JSON file')
parser.add_argument('-b', '--board', action='append', default=[], help='Boards to test, all if not specified')
parser.add_argument('-s', '--skip', action='append', default=[], help='Skip boards from test')
parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
args = parser.parse_args()
config_file = args.config_file
boards = args.board
skip_boards = args.skip
verbose = args.verbose
# if config file is not found, try to find it in the same directory as this script
@ -615,12 +637,22 @@ def main():
config = json.load(f)
if len(boards) == 0:
config_boards = config['boards']
config_boards = [e for e in config['boards'] if e['name'] not in skip_boards]
else:
config_boards = [e for e in config['boards'] if e['name'] in boards]
err_count = 0
with Pool(processes=os.cpu_count()) as pool:
err_count = sum(pool.map(test_board, config_boards))
mret = pool.map(test_board, config_boards)
err_count = sum(e[1] for e in mret)
# generate skip list for next re-run if failed
skip_fname = f'{config_file}.skip'
if err_count > 0:
skip_boards += [name for name, err in mret if err == 0]
with open(skip_fname, 'w') as f:
f.write(' '.join(f'-s {i}' for i in skip_boards))
elif os.path.exists(skip_fname):
os.remove(skip_fname)
duration = time.time() - duration
print()

View File

@ -21,16 +21,18 @@
"name": "espressif_s3_devkitm",
"uid": "84F703C084E4",
"build" : {
"flags_on": ["", "CFG_TUD_DWC2_DMA_ENABLE"]
"flags_on": ["", "CFG_TUD_DWC2_DMA_ENABLE CFG_TUH_DWC2_DMA_ENABLE"]
},
"tests": {
"only": ["device/cdc_msc_freertos", "device/hid_composite_freertos"]
"only": ["device/cdc_msc_freertos", "device/hid_composite_freertos", "host/device_info"],
"dev_attached": [{"vid_pid": "1a86_55d4", "serial": "52D2005402"}]
},
"flasher": {
"name": "esptool",
"uid": "3ea619acd1cdeb11a0a0b806e93fd3f1",
"args": "-b 1500000"
}
},
"comment": "Use TS3USB30 mux to test both device and host"
},
{
"name": "feather_nrf52840_express",
@ -59,8 +61,11 @@
{
"name": "metro_m4_express",
"uid": "9995AD485337433231202020FF100A34",
"build" : {
"args": ["MAX3421_HOST=1"]
},
"tests": {
"device": true, "host": false, "dual": false,
"device": true, "host": false, "dual": true,
"dev_attached": [{"vid_pid": "1a86_55d4", "serial": "52D2002130"}]
},
"flasher": {
@ -69,6 +74,19 @@
"args": "-device ATSAMD51J19"
}
},
{
"name": "mimxrt1064_evk",
"uid": "BAE96FB95AFA6DBB8F00005002001200",
"tests": {
"device": true, "host": true, "dual": true,
"dev_attached": [{"vid_pid": "1a86_55d4", "serial": "52D2023299"}]
},
"flasher": {
"name": "jlink",
"uid": "000725299165",
"args": "-device MIMXRT1064xxx6A"
}
},
{
"name": "lpcxpresso11u37",
"uid": "17121919",
@ -98,8 +116,11 @@
{
"name": "raspberry_pi_pico",
"uid": "E6614C311B764A37",
"build" : {
"flags_on": ["CFG_TUH_RPI_PIO_USB"]
},
"tests": {
"device": true, "host": false, "dual": false,
"device": true, "host": true, "dual": true,
"dev_attached": [{"vid_pid": "1a86_55d4", "serial": "52D2002470"}]
},
"flasher": {
@ -108,11 +129,28 @@
"args": "-f interface/cmsis-dap.cfg -f target/rp2040.cfg -c \"adapter speed 5000\""
}
},
{
"name": "raspberry_pi_pico_w",
"uid": "E6614C311B764A37",
"tests": {
"device": false, "host": true, "dual": false,
"dev_attached": [{"vid_pid": "1a86_55d4", "serial": "52D2023934"}]
},
"flasher": {
"name": "openocd",
"uid": "E6633861A3819D38",
"args": "-f interface/cmsis-dap.cfg -f target/rp2040.cfg -c \"adapter speed 5000\""
},
"comment": "Test native host"
},
{
"name": "raspberry_pi_pico2",
"uid": "560AE75E1C7152C9",
"build" : {
"flags_on": ["CFG_TUH_RPI_PIO_USB"]
},
"tests": {
"device": true, "host": false, "dual": false,
"device": true, "host": true, "dual": true,
"dev_attached": [{"vid_pid": "1a86_55d4", "serial": "533D004242"}]
},
"flasher": {
@ -157,9 +195,9 @@
"device": true, "host": false, "dual": false
},
"flasher": {
"name": "stlink",
"name": "openocd",
"uid": "004C00343137510F39383538",
"args": ""
"args": "-f interface/stlink.cfg -f target/stm32h7x.cfg"
}
},
{

View File

@ -94,19 +94,21 @@ enum
uint8_t msc_disk[DISK_BLOCK_NUM][DISK_BLOCK_SIZE];
// Invoked when received SCSI_CMD_INQUIRY
// Application fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively
void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4])
{
// Invoked when received SCSI_CMD_INQUIRY, v2 with full inquiry response
// Some inquiry_resp's fields are already filled with default values, application can update them
// Return length of inquiry response, typically sizeof(scsi_inquiry_resp_t) (36 bytes), can be longer if included vendor data.
uint32_t tud_msc_inquiry2_cb(uint8_t lun, scsi_inquiry_resp_t* inquiry_resp, uint32_t bufsize) {
(void) lun;
(void) bufsize;
const char vid[] = "TinyUSB";
const char pid[] = "Mass Storage";
const char rev[] = "1.0";
memcpy(vendor_id , vid, strlen(vid));
memcpy(product_id , pid, strlen(pid));
memcpy(product_rev, rev, strlen(rev));
memcpy(inquiry_resp->vendor_id, vid, strlen(vid));
memcpy(inquiry_resp->product_id, pid, strlen(pid));
memcpy(inquiry_resp->product_rev, rev, strlen(rev));
return sizeof(scsi_inquiry_resp_t); // 36 bytes
}
// Invoked when received Test Unit Ready command.