mirror of
https://github.com/hathach/tinyusb.git
synced 2026-08-18 11:02:16 +00:00
Merge pull request #3632 from hathach/msc_os
hcd/dwc2: fix txfifo full check
This commit is contained in:
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@ -326,13 +326,15 @@ jobs:
|
||||
github.repository_owner == 'hathach' &&
|
||||
!(github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true)
|
||||
runs-on: [ self-hosted, Linux, X64, hifiphile ]
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
IAR_LMS_BEARER_TOKEN: ${{ secrets.IAR_LMS_BEARER_TOKEN }}
|
||||
PYTHONUNBUFFERED: '1'
|
||||
steps:
|
||||
- name: Clean workspace
|
||||
run: |
|
||||
echo "Cleaning up previous run"
|
||||
rm -rf "${{ github.workspace }}"3
|
||||
rm -rf "${{ github.workspace }}"
|
||||
mkdir -p "${{ github.workspace }}"
|
||||
|
||||
- name: Toolchain version
|
||||
@ -356,4 +358,5 @@ jobs:
|
||||
run: python3 tools/build.py --toolchain iar $BUILD_ARGS
|
||||
|
||||
- name: Test on actual hardware (hardware in the loop)
|
||||
run: python3 test/hil/hil_test.py hfp.json
|
||||
run: |
|
||||
python3 test/hil/hil_test.py hfp.json
|
||||
|
||||
@ -14,6 +14,7 @@ set(EXAMPLE_LIST
|
||||
hid_controller
|
||||
midi_rx
|
||||
msc_file_explorer
|
||||
msc_file_explorer_freertos
|
||||
)
|
||||
|
||||
foreach (example ${EXAMPLE_LIST})
|
||||
|
||||
42
examples/host/msc_file_explorer_freertos/CMakeLists.txt
Normal file
42
examples/host/msc_file_explorer_freertos/CMakeLists.txt
Normal file
@ -0,0 +1,42 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/../../../hw/bsp/family_support.cmake)
|
||||
|
||||
project(msc_file_explorer_freertos 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/msc_app.c
|
||||
${TOP}/lib/fatfs/source/ff.c
|
||||
${TOP}/lib/fatfs/source/ffsystem.c
|
||||
${TOP}/lib/fatfs/source/ffunicode.c
|
||||
)
|
||||
|
||||
# Example include
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
${TOP}/lib/fatfs/source
|
||||
${TOP}/lib/embedded-cli
|
||||
)
|
||||
|
||||
# Configure compilation flags and libraries for the example with FreeRTOS.
|
||||
# See the corresponding function in hw/bsp/FAMILY/family.cmake for details.
|
||||
family_configure_host_example(${PROJECT_NAME} freertos)
|
||||
|
||||
# Suppress warnings on fatfs
|
||||
if (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang")
|
||||
set_source_files_properties(${TOP}/lib/fatfs/source/ff.c PROPERTIES
|
||||
COMPILE_OPTIONS "-Wno-conversion;-Wno-cast-qual"
|
||||
)
|
||||
endif ()
|
||||
@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": 6,
|
||||
"include": [
|
||||
"../../../hw/bsp/BoardPresets.json"
|
||||
]
|
||||
}
|
||||
27
examples/host/msc_file_explorer_freertos/Makefile
Normal file
27
examples/host/msc_file_explorer_freertos/Makefile
Normal file
@ -0,0 +1,27 @@
|
||||
RTOS = freertos
|
||||
include ../../../hw/bsp/family_support.mk
|
||||
|
||||
FATFS_PATH = lib/fatfs/source
|
||||
|
||||
INC += \
|
||||
src \
|
||||
$(TOP)/$(FATFS_PATH) \
|
||||
$(TOP)/lib/embedded-cli \
|
||||
|
||||
# Example source
|
||||
EXAMPLE_SOURCE = \
|
||||
src/main.c \
|
||||
src/msc_app.c \
|
||||
|
||||
SRC_C += $(addprefix $(EXAMPLE_PATH)/, $(EXAMPLE_SOURCE))
|
||||
|
||||
# FatFS source
|
||||
SRC_C += \
|
||||
$(FATFS_PATH)/ff.c \
|
||||
$(FATFS_PATH)/ffsystem.c \
|
||||
$(FATFS_PATH)/ffunicode.c \
|
||||
|
||||
# suppress warning caused by fatfs
|
||||
CFLAGS += -Wno-error=cast-qual
|
||||
|
||||
include ../../../hw/bsp/family_rules.mk
|
||||
105
examples/host/msc_file_explorer_freertos/README.md
Normal file
105
examples/host/msc_file_explorer_freertos/README.md
Normal file
@ -0,0 +1,105 @@
|
||||
# MSC File Explorer (FreeRTOS)
|
||||
|
||||
This host example implements an interactive command-line file browser for USB Mass Storage devices.
|
||||
When a USB flash drive is connected, the device is automatically mounted using FatFS and a shell-like
|
||||
CLI is presented over the board's serial console.
|
||||
|
||||
## Features
|
||||
|
||||
- Automatic mount/unmount of USB storage devices
|
||||
- FAT12/16/32 filesystem support via FatFS
|
||||
- Interactive CLI with command history
|
||||
- Read speed benchmarking with `dd`
|
||||
- Support for up to 4 simultaneous USB storage devices (via hub)
|
||||
|
||||
## Supported Commands
|
||||
|
||||
| Command | Usage | Description |
|
||||
|---------|--------------------|------------------------------------------------------|
|
||||
| help | `help` | Print list of available commands |
|
||||
| cat | `cat <file>` | Print file contents to the console |
|
||||
| cd | `cd <dir>` | Change current working directory |
|
||||
| cp | `cp <src> <dest>` | Copy a file |
|
||||
| dd | `dd [count]` | Read sectors and report speed (default 1024 sectors) |
|
||||
| ls | `ls [dir]` | List directory contents |
|
||||
| pwd | `pwd` | Print current working directory |
|
||||
| mkdir | `mkdir <dir>` | Create a directory |
|
||||
| mv | `mv <src> <dest>` | Rename/move a file or directory |
|
||||
| rm | `rm <file>` | Remove a file |
|
||||
|
||||
## Build
|
||||
|
||||
Build for a specific board using CMake (see [Getting Started](https://docs.tinyusb.org/en/latest/getting_started.html)):
|
||||
|
||||
```bash
|
||||
# Example: build for STM32F407 Discovery board
|
||||
cmake -B build -DBOARD=stm32f407disco -GNinja examples/host/msc_file_explorer_freertos
|
||||
cmake --build build
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
1. Flash the firmware to your board.
|
||||
2. Open a serial terminal (e.g. `minicom`, `screen`, `PuTTY`) at 115200 baud.
|
||||
3. Plug a USB flash drive into the board's USB host port.
|
||||
4. The device is auto-mounted and the prompt appears:
|
||||
|
||||
```
|
||||
TinyUSB MSC File Explorer Example
|
||||
|
||||
Device connected
|
||||
Vendor : Kingston
|
||||
Product : DataTraveler 2.0
|
||||
Rev : 1.0
|
||||
Capacity: 1.9 GB
|
||||
|
||||
0:/> _
|
||||
```
|
||||
|
||||
### Browsing Files
|
||||
|
||||
```
|
||||
0:/> ls
|
||||
----a 1234 readme.txt
|
||||
d---- 0 photos
|
||||
d---- 0 docs
|
||||
|
||||
0:/> cd photos
|
||||
0:/photos> ls
|
||||
----a 520432 vacation.jpg
|
||||
----a 312088 family.png
|
||||
|
||||
0:/> cat readme.txt
|
||||
Hello from USB drive!
|
||||
```
|
||||
|
||||
### Copying and Moving Files
|
||||
|
||||
```
|
||||
0:/> cp readme.txt backup.txt
|
||||
0:/> mv backup.txt docs/backup.txt
|
||||
```
|
||||
|
||||
### Measuring Read Speed
|
||||
|
||||
```
|
||||
0:/> dd
|
||||
Reading 1024 sectors...
|
||||
Data speed: 823 KB/s
|
||||
```
|
||||
|
||||
### Multiple Devices
|
||||
|
||||
When using a USB hub, multiple drives are mounted as `0:`, `1:`, etc. Use the drive prefix to
|
||||
navigate between them:
|
||||
|
||||
```
|
||||
0:/> cd 1:
|
||||
1:/> ls
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Build-time validation follows the standard TinyUSB host example flow. Runtime behavior should be
|
||||
verified on hardware by attaching an MSC device and exercising CLI commands such as `ls`, `pwd`,
|
||||
and `dd`.
|
||||
28
examples/host/msc_file_explorer_freertos/only.txt
Normal file
28
examples/host/msc_file_explorer_freertos/only.txt
Normal file
@ -0,0 +1,28 @@
|
||||
family:espressif
|
||||
family:samd21
|
||||
family:samd5x_e5x
|
||||
mcu:LPC175X_6X
|
||||
mcu:LPC177X_8X
|
||||
mcu:LPC18XX
|
||||
mcu:LPC40XX
|
||||
mcu:LPC43XX
|
||||
mcu:LPC54
|
||||
mcu:LPC55
|
||||
mcu:MAX3421
|
||||
mcu:MIMXRT10XX
|
||||
mcu:MIMXRT11XX
|
||||
mcu:MIMXRT1XXX
|
||||
mcu:MSP432E4
|
||||
mcu:RP2040
|
||||
mcu:RW61X
|
||||
mcu:RX65X
|
||||
mcu:STM32C0
|
||||
mcu:STM32F4
|
||||
mcu:STM32F7
|
||||
mcu:STM32G0
|
||||
mcu:STM32H5
|
||||
mcu:STM32H7
|
||||
mcu:STM32H7RS
|
||||
mcu:STM32N6
|
||||
mcu:STM32U3
|
||||
mcu:STM32U5
|
||||
3
examples/host/msc_file_explorer_freertos/skip.txt
Normal file
3
examples/host/msc_file_explorer_freertos/skip.txt
Normal file
@ -0,0 +1,3 @@
|
||||
mcu:CH32F20X
|
||||
board:lpcxpresso54114
|
||||
mcu:FT90X
|
||||
13
examples/host/msc_file_explorer_freertos/src/CMakeLists.txt
Normal file
13
examples/host/msc_file_explorer_freertos/src/CMakeLists.txt
Normal file
@ -0,0 +1,13 @@
|
||||
# This file is for ESP-IDF only
|
||||
set(FATFS_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../../lib/fatfs/source)
|
||||
set(EMBEDDED_CLI_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../../lib/embedded-cli)
|
||||
|
||||
idf_component_register(
|
||||
SRCS "main.c" "msc_app.c"
|
||||
${FATFS_DIR}/ff.c
|
||||
${FATFS_DIR}/ffsystem.c
|
||||
${FATFS_DIR}/ffunicode.c
|
||||
INCLUDE_DIRS "." ${FATFS_DIR} ${EMBEDDED_CLI_DIR}
|
||||
REQUIRES boards tinyusb_src)
|
||||
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-error=format)
|
||||
313
examples/host/msc_file_explorer_freertos/src/ffconf.h
Normal file
313
examples/host/msc_file_explorer_freertos/src/ffconf.h
Normal file
@ -0,0 +1,313 @@
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ Configurations of FatFs Module
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FFCONF_DEF 80386 /* Revision ID */
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ Function Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FF_FS_READONLY 0
|
||||
/* This option switches read-only configuration. (0:Read/Write or 1:Read-only)
|
||||
/ Read-only configuration removes writing API functions, f_write(), f_sync(),
|
||||
/ f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()
|
||||
/ and optional writing functions as well. */
|
||||
|
||||
|
||||
#define FF_FS_MINIMIZE 0
|
||||
/* This option defines minimization level to remove some basic API functions.
|
||||
/
|
||||
/ 0: Basic functions are fully enabled.
|
||||
/ 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename()
|
||||
/ are removed.
|
||||
/ 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1.
|
||||
/ 3: f_lseek() function is removed in addition to 2. */
|
||||
|
||||
|
||||
#define FF_USE_FIND 0
|
||||
/* This option switches filtered directory read functions, f_findfirst() and
|
||||
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
|
||||
|
||||
|
||||
#define FF_USE_MKFS 0
|
||||
/* This option switches f_mkfs(). (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_FASTSEEK 0
|
||||
/* This option switches fast seek feature. (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_EXPAND 0
|
||||
/* This option switches f_expand(). (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_CHMOD 0
|
||||
/* This option switches attribute control API functions, f_chmod() and f_utime().
|
||||
/ (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */
|
||||
|
||||
|
||||
#define FF_USE_LABEL 0
|
||||
/* This option switches volume label API functions, f_getlabel() and f_setlabel().
|
||||
/ (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_FORWARD 0
|
||||
/* This option switches f_forward(). (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_STRFUNC 0
|
||||
#define FF_PRINT_LLI 0
|
||||
#define FF_PRINT_FLOAT 0
|
||||
#define FF_STRF_ENCODE 0
|
||||
/* FF_USE_STRFUNC switches string API functions, f_gets(), f_putc(), f_puts() and
|
||||
/ f_printf().
|
||||
/
|
||||
/ 0: Disable. FF_PRINT_LLI, FF_PRINT_FLOAT and FF_STRF_ENCODE have no effect.
|
||||
/ 1: Enable without LF-CRLF conversion.
|
||||
/ 2: Enable with LF-CRLF conversion.
|
||||
/
|
||||
/ FF_PRINT_LLI = 1 makes f_printf() support long long argument and FF_PRINT_FLOAT = 1/2
|
||||
/ makes f_printf() support floating point argument. These features want C99 or later.
|
||||
/ When FF_LFN_UNICODE >= 1 with LFN enabled, string API functions convert the character
|
||||
/ encoding in it. FF_STRF_ENCODE selects assumption of character encoding ON THE FILE
|
||||
/ to be read/written via those functions.
|
||||
/
|
||||
/ 0: ANSI/OEM in current CP
|
||||
/ 1: Unicode in UTF-16LE
|
||||
/ 2: Unicode in UTF-16BE
|
||||
/ 3: Unicode in UTF-8
|
||||
*/
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ Locale and Namespace Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FF_CODE_PAGE 437
|
||||
/* This option specifies the OEM code page to be used on the target system.
|
||||
/ Incorrect code page setting can cause a file open failure.
|
||||
/
|
||||
/ 437 - U.S.
|
||||
/ 720 - Arabic
|
||||
/ 737 - Greek
|
||||
/ 771 - KBL
|
||||
/ 775 - Baltic
|
||||
/ 850 - Latin 1
|
||||
/ 852 - Latin 2
|
||||
/ 855 - Cyrillic
|
||||
/ 857 - Turkish
|
||||
/ 860 - Portuguese
|
||||
/ 861 - Icelandic
|
||||
/ 862 - Hebrew
|
||||
/ 863 - Canadian French
|
||||
/ 864 - Arabic
|
||||
/ 865 - Nordic
|
||||
/ 866 - Russian
|
||||
/ 869 - Greek 2
|
||||
/ 932 - Japanese (DBCS)
|
||||
/ 936 - Simplified Chinese (DBCS)
|
||||
/ 949 - Korean (DBCS)
|
||||
/ 950 - Traditional Chinese (DBCS)
|
||||
/ 0 - Include all code pages above and configured by f_setcp()
|
||||
*/
|
||||
|
||||
|
||||
#define FF_USE_LFN 1
|
||||
#define FF_MAX_LFN 255
|
||||
/* The FF_USE_LFN switches the support for LFN (long file name).
|
||||
/
|
||||
/ 0: Disable LFN. FF_MAX_LFN has no effect.
|
||||
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
|
||||
/ 2: Enable LFN with dynamic working buffer on the STACK.
|
||||
/ 3: Enable LFN with dynamic working buffer on the HEAP.
|
||||
/
|
||||
/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN feature
|
||||
/ requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and
|
||||
/ additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled.
|
||||
/ The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can
|
||||
/ be in range of 12 to 255. It is recommended to be set 255 to fully support the LFN
|
||||
/ specification.
|
||||
/ When use stack for the working buffer, take care on stack overflow. When use heap
|
||||
/ memory for the working buffer, memory management functions, ff_memalloc() and
|
||||
/ ff_memfree() exemplified in ffsystem.c, need to be added to the project. */
|
||||
|
||||
|
||||
#define FF_LFN_UNICODE 0
|
||||
/* This option switches the character encoding on the API when LFN is enabled.
|
||||
/
|
||||
/ 0: ANSI/OEM in current CP (TCHAR = char)
|
||||
/ 1: Unicode in UTF-16 (TCHAR = WCHAR)
|
||||
/ 2: Unicode in UTF-8 (TCHAR = char)
|
||||
/ 3: Unicode in UTF-32 (TCHAR = DWORD)
|
||||
/
|
||||
/ Also behavior of string I/O functions will be affected by this option.
|
||||
/ When LFN is not enabled, this option has no effect. */
|
||||
|
||||
|
||||
#define FF_LFN_BUF 255
|
||||
#define FF_SFN_BUF 12
|
||||
/* This set of options defines size of file name members in the FILINFO structure
|
||||
/ which is used to read out directory items. These values should be sufficient for
|
||||
/ the file names to read. The maximum possible length of the read file name depends
|
||||
/ on character encoding. When LFN is not enabled, these options have no effect. */
|
||||
|
||||
|
||||
#define FF_FS_RPATH 2
|
||||
/* This option configures support for relative path feature.
|
||||
/
|
||||
/ 0: Disable relative path and remove related API functions.
|
||||
/ 1: Enable relative path and dot names. f_chdir() and f_chdrive() are available.
|
||||
/ 2: f_getcwd() is available in addition to 1.
|
||||
*/
|
||||
|
||||
|
||||
#define FF_PATH_DEPTH 10
|
||||
/* This option defines maximum depth of directory in the exFAT volume. It is NOT
|
||||
/ relevant to FAT/FAT32 volume.
|
||||
/ For example, FF_PATH_DEPTH = 3 will able to follow a path "/dir1/dir2/dir3/file"
|
||||
/ but a sub-directory in the dir3 will not able to be followed and set current
|
||||
/ directory.
|
||||
/ The size of filesystem object (FATFS) increases FF_PATH_DEPTH * 24 bytes.
|
||||
/ When FF_FS_EXFAT == 0 or FF_FS_RPATH == 0, this option has no effect.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ Drive/Volume Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FF_VOLUMES 4
|
||||
/* Number of volumes (logical drives) to be used. (1-10) */
|
||||
|
||||
|
||||
#define FF_STR_VOLUME_ID 0
|
||||
#define FF_VOLUME_STRS "RAM","NAND","CF","SD","SD2","USB","USB2","USB3"
|
||||
/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings.
|
||||
/ When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive
|
||||
/ number in the path name. FF_VOLUME_STRS defines the volume ID strings for each
|
||||
/ logical drive. Number of items must not be less than FF_VOLUMES. Valid
|
||||
/ characters for the volume ID strings are A-Z, a-z and 0-9, however, they are
|
||||
/ compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is
|
||||
/ not defined, a user defined volume string table is needed as:
|
||||
/
|
||||
/ const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",...
|
||||
*/
|
||||
|
||||
|
||||
#define FF_MULTI_PARTITION 0
|
||||
/* This option switches support for multiple volumes on the physical drive.
|
||||
/ By default (0), each logical drive number is bound to the same physical drive
|
||||
/ number and only an FAT volume found on the physical drive will be mounted.
|
||||
/ When this feature is enabled (1), each logical drive number can be bound to
|
||||
/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
|
||||
/ will be available. */
|
||||
|
||||
|
||||
#define FF_MIN_SS 512
|
||||
#define FF_MAX_SS 512
|
||||
/* This set of options configures the range of sector size to be supported. (512,
|
||||
/ 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and
|
||||
/ harddisk, but a larger value may be required for on-board flash memory and some
|
||||
/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is
|
||||
/ configured for variable sector size mode and disk_ioctl() needs to implement
|
||||
/ GET_SECTOR_SIZE command. */
|
||||
|
||||
|
||||
#define FF_LBA64 0
|
||||
/* This option switches support for 64-bit LBA. (0:Disable or 1:Enable)
|
||||
/ To enable the 64-bit LBA, also exFAT needs to be enabled. (FF_FS_EXFAT == 1) */
|
||||
|
||||
|
||||
#define FF_MIN_GPT 0x10000000
|
||||
/* Minimum number of sectors to switch GPT as partitioning format in f_mkfs() and
|
||||
/ f_fdisk(). 2^32 sectors maximum. This option has no effect when FF_LBA64 == 0. */
|
||||
|
||||
|
||||
#define FF_USE_TRIM 0
|
||||
/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable)
|
||||
/ To enable this feature, also CTRL_TRIM command should be implemented to
|
||||
/ the disk_ioctl(). */
|
||||
|
||||
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ System Configurations
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FF_FS_TINY 0
|
||||
/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
|
||||
/ At the tiny configuration, size of file object (FIL) is reduced FF_MAX_SS bytes.
|
||||
/ Instead of private sector buffer eliminated from the file object, common sector
|
||||
/ buffer in the filesystem object (FATFS) is used for the file data transfer. */
|
||||
|
||||
|
||||
#define FF_FS_EXFAT 0
|
||||
/* This option switches support for exFAT filesystem. (0:Disable or 1:Enable)
|
||||
/ To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1)
|
||||
/ Note that enabling exFAT discards ANSI C (C89) compatibility. */
|
||||
|
||||
|
||||
#define FF_FS_NORTC 1
|
||||
#define FF_NORTC_MON 1
|
||||
#define FF_NORTC_MDAY 1
|
||||
#define FF_NORTC_YEAR 2025
|
||||
/* The option FF_FS_NORTC switches timestamp feature. If the system does not have
|
||||
/ an RTC or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable the
|
||||
/ timestamp feature. Every object modified by FatFs will have a fixed timestamp
|
||||
/ defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time.
|
||||
/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() need to be added
|
||||
/ to the project to read current time form real-time clock. FF_NORTC_MON,
|
||||
/ FF_NORTC_MDAY and FF_NORTC_YEAR have no effect.
|
||||
/ These options have no effect in read-only configuration (FF_FS_READONLY = 1). */
|
||||
|
||||
|
||||
#define FF_FS_CRTIME 0
|
||||
/* This option enables(1)/disables(0) the timestamp of the file created. When
|
||||
/ set 1, the file created time is available in FILINFO structure. */
|
||||
|
||||
|
||||
#define FF_FS_NOFSINFO 0
|
||||
/* If you need to know the correct free space on the FAT32 volume, set bit 0 of
|
||||
/ this option, and f_getfree() on the first time after volume mount will force
|
||||
/ a full FAT scan. Bit 1 controls the use of last allocated cluster number.
|
||||
/
|
||||
/ bit0=0: Use free cluster count in the FSINFO if available.
|
||||
/ bit0=1: Do not trust free cluster count in the FSINFO.
|
||||
/ bit1=0: Use last allocated cluster number in the FSINFO if available.
|
||||
/ bit1=1: Do not trust last allocated cluster number in the FSINFO.
|
||||
*/
|
||||
|
||||
|
||||
#define FF_FS_LOCK 0
|
||||
/* The option FF_FS_LOCK switches file lock function to control duplicated file open
|
||||
/ and illegal operation to open objects. This option must be 0 when FF_FS_READONLY
|
||||
/ is 1.
|
||||
/
|
||||
/ 0: Disable file lock function. To avoid volume corruption, application program
|
||||
/ should avoid illegal open, remove and rename to the open objects.
|
||||
/ >0: Enable file lock function. The value defines how many files/sub-directories
|
||||
/ can be opened simultaneously under file lock control. Note that the file
|
||||
/ lock control is independent of re-entrancy. */
|
||||
|
||||
|
||||
#define FF_FS_REENTRANT 0
|
||||
#define FF_FS_TIMEOUT 1000
|
||||
/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
|
||||
/ module itself. Note that regardless of this option, file access to different
|
||||
/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
|
||||
/ and f_fdisk(), are always not re-entrant. Only file/directory access to
|
||||
/ the same volume is under control of this featuer.
|
||||
/
|
||||
/ 0: Disable re-entrancy. FF_FS_TIMEOUT have no effect.
|
||||
/ 1: Enable re-entrancy. Also user provided synchronization handlers,
|
||||
/ ff_mutex_create(), ff_mutex_delete(), ff_mutex_take() and ff_mutex_give(),
|
||||
/ must be added to the project. Samples are available in ffsystem.c.
|
||||
/
|
||||
/ The FF_FS_TIMEOUT defines timeout period in unit of O/S time tick.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*--- End of configuration options ---*/
|
||||
158
examples/host/msc_file_explorer_freertos/src/main.c
Normal file
158
examples/host/msc_file_explorer_freertos/src/main.c
Normal file
@ -0,0 +1,158 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 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 <string.h>
|
||||
|
||||
#include "bsp/board_api.h"
|
||||
#include "tusb.h"
|
||||
#ifdef ESP_PLATFORM
|
||||
// ESP-IDF need "freertos/" prefix in include path.
|
||||
// CFG_TUSB_OS_INC_PATH should be defined accordingly.
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/timers.h"
|
||||
#else
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
#include "timers.h"
|
||||
#endif
|
||||
|
||||
#include "msc_app.h"
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// MACRO CONSTANT TYPEDEF PROTYPES
|
||||
//--------------------------------------------------------------------+
|
||||
#ifdef ESP_PLATFORM
|
||||
#define USBH_STACK_SIZE 4096
|
||||
#else
|
||||
// Increase stack size when debug log is enabled.
|
||||
#define USBH_STACK_SIZE (configMINIMAL_STACK_SIZE * (CFG_TUSB_DEBUG ? 4 : 3))
|
||||
#endif
|
||||
|
||||
enum {
|
||||
BLINK_MOUNTED = 1000,
|
||||
};
|
||||
|
||||
#if configSUPPORT_STATIC_ALLOCATION
|
||||
StaticTimer_t blinky_tmdef;
|
||||
|
||||
StackType_t usb_host_stack[USBH_STACK_SIZE];
|
||||
StaticTask_t usb_host_taskdef;
|
||||
#endif
|
||||
|
||||
TimerHandle_t blinky_tm;
|
||||
|
||||
static void led_blinky_cb(TimerHandle_t xTimer);
|
||||
static void usb_host_task(void* param);
|
||||
|
||||
/*------------- MAIN -------------*/
|
||||
int main(void) {
|
||||
board_init();
|
||||
|
||||
printf("TinyUSB Host MassStorage Explorer FreeRTOS Example\r\n");
|
||||
|
||||
// Create soft timer for blinky and task for TinyUSB host stack.
|
||||
#if configSUPPORT_STATIC_ALLOCATION
|
||||
blinky_tm = xTimerCreateStatic(NULL, pdMS_TO_TICKS(BLINK_MOUNTED), true, NULL, led_blinky_cb, &blinky_tmdef);
|
||||
xTaskCreateStatic(usb_host_task, "usbh", USBH_STACK_SIZE, NULL, configMAX_PRIORITIES - 1, usb_host_stack,
|
||||
&usb_host_taskdef);
|
||||
#else
|
||||
blinky_tm = xTimerCreate(NULL, pdMS_TO_TICKS(BLINK_MOUNTED), true, NULL, led_blinky_cb);
|
||||
xTaskCreate(usb_host_task, "usbh", USBH_STACK_SIZE, NULL, configMAX_PRIORITIES - 1, NULL);
|
||||
#endif
|
||||
|
||||
xTimerStart(blinky_tm, 0);
|
||||
|
||||
// only start scheduler for non-espressif mcu
|
||||
#ifndef ESP_PLATFORM
|
||||
vTaskStartScheduler();
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
void app_main(void) {
|
||||
main();
|
||||
}
|
||||
#endif
|
||||
|
||||
// USB Host task
|
||||
// This top-level thread processes all USB events and invokes callbacks.
|
||||
static void usb_host_task(void* param) {
|
||||
(void) param;
|
||||
|
||||
// init host stack on configured roothub port
|
||||
tusb_rhport_init_t host_init = {
|
||||
.role = TUSB_ROLE_HOST,
|
||||
.speed = TUSB_SPEED_AUTO
|
||||
};
|
||||
|
||||
if (!tusb_init(BOARD_TUH_RHPORT, &host_init)) {
|
||||
printf("Failed to init USB Host Stack\r\n");
|
||||
vTaskSuspend(NULL);
|
||||
}
|
||||
|
||||
board_init_after_tusb();
|
||||
|
||||
#if CFG_TUH_ENABLED && CFG_TUH_MAX3421
|
||||
// FeatherWing MAX3421E uses MAX3421E GPIO0 for VBUS enable.
|
||||
enum { IOPINS1_ADDR = 20u << 3 };
|
||||
tuh_max3421_reg_write(BOARD_TUH_RHPORT, IOPINS1_ADDR, 0x01, false);
|
||||
#endif
|
||||
|
||||
if (!msc_app_init()) {
|
||||
printf("Failed to init MSC app\r\n");
|
||||
vTaskSuspend(NULL);
|
||||
}
|
||||
|
||||
while (1) {
|
||||
// TinyUSB host task.
|
||||
tuh_task();
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// TinyUSB Callbacks
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
void tuh_mount_cb(uint8_t dev_addr) {
|
||||
(void) dev_addr;
|
||||
}
|
||||
|
||||
void tuh_umount_cb(uint8_t dev_addr) {
|
||||
(void) dev_addr;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Blinking Task
|
||||
//--------------------------------------------------------------------+
|
||||
static void led_blinky_cb(TimerHandle_t xTimer) {
|
||||
(void) xTimer;
|
||||
static bool led_state = false;
|
||||
|
||||
board_led_write(led_state);
|
||||
led_state = 1 - led_state; // toggle
|
||||
}
|
||||
709
examples/host/msc_file_explorer_freertos/src/msc_app.c
Normal file
709
examples/host/msc_file_explorer_freertos/src/msc_app.c
Normal file
@ -0,0 +1,709 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 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 <ctype.h>
|
||||
#include "tusb.h"
|
||||
#include "bsp/board_api.h"
|
||||
#ifdef ESP_PLATFORM
|
||||
// ESP-IDF need "freertos/" prefix in include path.
|
||||
// CFG_TUSB_OS_INC_PATH should be defined accordingly.
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/timers.h"
|
||||
#else
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
#include "timers.h"
|
||||
#endif
|
||||
|
||||
#include "ff.h"
|
||||
#include "diskio.h"
|
||||
|
||||
// lib/embedded-cli
|
||||
#define EMBEDDED_CLI_IMPL
|
||||
#include "embedded_cli.h"
|
||||
|
||||
#include "msc_app.h"
|
||||
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// MACRO TYPEDEF CONSTANT ENUM DECLARATION
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
//------------- embedded-cli -------------//
|
||||
#define CLI_BUFFER_SIZE 512
|
||||
#define CLI_RX_BUFFER_SIZE 16
|
||||
#define CLI_CMD_BUFFER_SIZE 64
|
||||
#define CLI_HISTORY_SIZE 32
|
||||
#define CLI_BINDING_COUNT 9
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#define MSC_APP_STACK_SIZE 4096
|
||||
#else
|
||||
#define MSC_APP_STACK_SIZE (configMINIMAL_STACK_SIZE * (CFG_TUSB_DEBUG ? 3 : 2))
|
||||
#endif
|
||||
|
||||
static EmbeddedCli *_cli;
|
||||
static CLI_UINT cli_buffer[BYTES_TO_CLI_UINTS(CLI_BUFFER_SIZE)];
|
||||
|
||||
#if configSUPPORT_STATIC_ALLOCATION
|
||||
StackType_t msc_app_stack[MSC_APP_STACK_SIZE];
|
||||
StaticTask_t msc_app_taskdef;
|
||||
#endif
|
||||
|
||||
//------------- Elm Chan FatFS -------------//
|
||||
static CFG_TUH_MEM_SECTION FATFS fatfs[CFG_TUH_DEVICE_MAX]; // for simplicity only support 1 LUN per device
|
||||
static volatile bool _disk_busy[CFG_TUH_DEVICE_MAX];
|
||||
static volatile bool _mount_pending[CFG_TUH_DEVICE_MAX];
|
||||
|
||||
static CFG_TUH_MEM_SECTION FIL file1, file2;
|
||||
|
||||
#ifndef CFG_EXAMPLE_MSC_FILE_EXPLORER_RW_BUFSIZE
|
||||
#define CFG_EXAMPLE_MSC_FILE_EXPLORER_RW_BUFSIZE 4096
|
||||
#endif
|
||||
static CFG_TUH_MEM_SECTION uint8_t rw_buf[CFG_EXAMPLE_MSC_FILE_EXPLORER_RW_BUFSIZE];
|
||||
|
||||
// define the buffer to be place in USB/DMA memory with correct alignment/cache line size
|
||||
CFG_TUH_MEM_SECTION static struct {
|
||||
TUH_EPBUF_TYPE_DEF(scsi_inquiry_resp_t, inquiry);
|
||||
} scsi_resp;
|
||||
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
//
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
static bool cli_init(void);
|
||||
static void msc_app_task(void* param);
|
||||
static void process_pending_mount(void);
|
||||
|
||||
bool msc_app_init(void) {
|
||||
for (size_t i = 0; i < CFG_TUH_DEVICE_MAX; i++) {
|
||||
_disk_busy[i] = false;
|
||||
_mount_pending[i] = false;
|
||||
}
|
||||
|
||||
// disable stdout buffered for echoing typing command
|
||||
#ifndef __ICCARM__ // TODO IAR doesn't support stream control ?
|
||||
setbuf(stdout, NULL);
|
||||
#endif
|
||||
|
||||
cli_init();
|
||||
|
||||
#if configSUPPORT_STATIC_ALLOCATION
|
||||
TaskHandle_t task_hdl = xTaskCreateStatic(msc_app_task, "msc", MSC_APP_STACK_SIZE, NULL,
|
||||
configMAX_PRIORITIES - 2, msc_app_stack, &msc_app_taskdef);
|
||||
TU_ASSERT(task_hdl != NULL);
|
||||
#else
|
||||
TU_ASSERT(xTaskCreate(msc_app_task, "msc", MSC_APP_STACK_SIZE, NULL, configMAX_PRIORITIES - 2, NULL) == pdPASS);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void msc_app_task(void* param) {
|
||||
(void) param;
|
||||
|
||||
while (1) {
|
||||
process_pending_mount();
|
||||
|
||||
if (!_cli) {
|
||||
vTaskDelay(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
int ch = board_getchar();
|
||||
if (ch > 0) {
|
||||
while (ch > 0) {
|
||||
embeddedCliReceiveChar(_cli, (char) ch);
|
||||
ch = board_getchar();
|
||||
}
|
||||
embeddedCliProcess(_cli);
|
||||
}
|
||||
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void process_pending_mount(void) {
|
||||
for (uint8_t drive_num = 0; drive_num < CFG_TUH_DEVICE_MAX; drive_num++) {
|
||||
if (!_mount_pending[drive_num]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
_mount_pending[drive_num] = false;
|
||||
|
||||
const uint8_t dev_addr = drive_num + 1;
|
||||
if (!tuh_msc_mounted(dev_addr)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char drive_path[3] = "0:";
|
||||
drive_path[0] += drive_num;
|
||||
|
||||
if (f_mount(&fatfs[drive_num], drive_path, 1) != FR_OK) {
|
||||
printf("mount failed\r\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
f_chdrive(drive_path);
|
||||
FRESULT rc = f_chdir("/");
|
||||
if (rc != FR_OK) {
|
||||
printf("chdir failed: %d\r\n", rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
//
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
static bool inquiry_complete_cb(uint8_t dev_addr, const tuh_msc_complete_data_t *cb_data) {
|
||||
const msc_cbw_t *cbw = cb_data->cbw;
|
||||
const msc_csw_t *csw = cb_data->csw;
|
||||
|
||||
if (csw->status != 0) {
|
||||
printf("Inquiry failed\r\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Print out Vendor ID, Product ID and Rev
|
||||
printf("%.8s %.16s %.4s\r\n", scsi_resp.inquiry.vendor_id, scsi_resp.inquiry.product_id,
|
||||
scsi_resp.inquiry.product_rev);
|
||||
|
||||
// Get capacity of device
|
||||
const uint32_t block_count = tuh_msc_get_block_count(dev_addr, cbw->lun);
|
||||
const uint32_t block_size = tuh_msc_get_block_size(dev_addr, cbw->lun);
|
||||
|
||||
printf("Disk Size: %" PRIu32 " %" PRIu32 "-byte blocks: %" PRIu32 " MB\r\n",
|
||||
block_count, block_size, block_count / ((1024 * 1024) / block_size));
|
||||
|
||||
// For simplicity: we only mount 1 LUN per device
|
||||
const uint8_t drive_num = dev_addr - 1;
|
||||
_mount_pending[drive_num] = true;
|
||||
|
||||
// print the drive label
|
||||
// char label[34];
|
||||
// if ( FR_OK == f_getlabel(drive_path, label, NULL) )
|
||||
// {
|
||||
// puts(label);
|
||||
// }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------- IMPLEMENTATION -------------//
|
||||
void tuh_msc_mount_cb(uint8_t dev_addr) {
|
||||
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);
|
||||
}
|
||||
|
||||
void tuh_msc_umount_cb(uint8_t dev_addr) {
|
||||
printf("A MassStorage device is unmounted\r\n");
|
||||
|
||||
const uint8_t drive_num = dev_addr - 1;
|
||||
char drive_path[3] = "0:";
|
||||
drive_path[0] += drive_num;
|
||||
|
||||
_mount_pending[drive_num] = false;
|
||||
|
||||
f_unmount(drive_path);
|
||||
|
||||
// if ( phy_disk == f_get_current_drive() )
|
||||
// { // active drive is unplugged --> change to other drive
|
||||
// for(uint8_t i=0; i<CFG_TUH_DEVICE_MAX; i++)
|
||||
// {
|
||||
// if ( disk_is_ready(i) )
|
||||
// {
|
||||
// f_chdrive(i);
|
||||
// cli_init(); // refractor, rename
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// DiskIO
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
static void wait_for_disk_io(BYTE pdrv) {
|
||||
while (_disk_busy[pdrv]) {
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
static bool disk_io_complete(uint8_t dev_addr, const tuh_msc_complete_data_t *cb_data) {
|
||||
(void)dev_addr;
|
||||
(void)cb_data;
|
||||
_disk_busy[dev_addr - 1] = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
DSTATUS disk_status(BYTE pdrv /* Physical drive nmuber to identify the drive */
|
||||
) {
|
||||
uint8_t dev_addr = pdrv + 1;
|
||||
return tuh_msc_mounted(dev_addr) ? 0 : STA_NODISK;
|
||||
}
|
||||
|
||||
DSTATUS disk_initialize(BYTE pdrv /* Physical drive nmuber to identify the drive */
|
||||
) {
|
||||
(void)pdrv;
|
||||
return 0; // nothing to do
|
||||
}
|
||||
|
||||
DRESULT disk_read(BYTE pdrv, /* Physical drive nmuber to identify the drive */
|
||||
BYTE *buff, /* Data buffer to store read data */
|
||||
LBA_t sector, /* Start sector in LBA */
|
||||
UINT count /* Number of sectors to read */
|
||||
) {
|
||||
const uint8_t dev_addr = pdrv + 1;
|
||||
const uint8_t lun = 0;
|
||||
|
||||
_disk_busy[pdrv] = true;
|
||||
tuh_msc_read10(dev_addr, lun, buff, sector, (uint16_t)count, disk_io_complete, 0);
|
||||
wait_for_disk_io(pdrv);
|
||||
|
||||
return RES_OK;
|
||||
}
|
||||
|
||||
#if FF_FS_READONLY == 0
|
||||
|
||||
DRESULT disk_write(BYTE pdrv, /* Physical drive nmuber to identify the drive */
|
||||
const BYTE *buff, /* Data to be written */
|
||||
LBA_t sector, /* Start sector in LBA */
|
||||
UINT count /* Number of sectors to write */
|
||||
) {
|
||||
const uint8_t dev_addr = pdrv + 1;
|
||||
const uint8_t lun = 0;
|
||||
|
||||
_disk_busy[pdrv] = true;
|
||||
tuh_msc_write10(dev_addr, lun, buff, sector, (uint16_t)count, disk_io_complete, 0);
|
||||
wait_for_disk_io(pdrv);
|
||||
|
||||
return RES_OK;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
DRESULT disk_ioctl(BYTE pdrv, /* Physical drive nmuber (0..) */
|
||||
BYTE cmd, /* Control code */
|
||||
void *buff /* Buffer to send/receive control data */
|
||||
) {
|
||||
const uint8_t dev_addr = pdrv + 1;
|
||||
const uint8_t lun = 0;
|
||||
switch (cmd) {
|
||||
case CTRL_SYNC:
|
||||
// nothing to do since we do blocking
|
||||
return RES_OK;
|
||||
|
||||
case GET_SECTOR_COUNT:
|
||||
*((DWORD *)buff) = (DWORD)tuh_msc_get_block_count(dev_addr, lun);
|
||||
return RES_OK;
|
||||
|
||||
case GET_SECTOR_SIZE:
|
||||
*((WORD *)buff) = (WORD)tuh_msc_get_block_size(dev_addr, lun);
|
||||
return RES_OK;
|
||||
|
||||
case GET_BLOCK_SIZE:
|
||||
*((DWORD *)buff) = 1; // erase block size in units of sector size
|
||||
return RES_OK;
|
||||
|
||||
default:
|
||||
return RES_PARERR;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// CLI Commands
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
void cli_cmd_cat(EmbeddedCli *cli, char *args, void *context);
|
||||
void cli_cmd_cd(EmbeddedCli *cli, char *args, void *context);
|
||||
void cli_cmd_cp(EmbeddedCli *cli, char *args, void *context);
|
||||
void cli_cmd_dd(EmbeddedCli *cli, char *args, void *context);
|
||||
void cli_cmd_ls(EmbeddedCli *cli, char *args, void *context);
|
||||
void cli_cmd_pwd(EmbeddedCli *cli, char *args, void *context);
|
||||
void cli_cmd_mkdir(EmbeddedCli *cli, char *args, void *context);
|
||||
void cli_cmd_mv(EmbeddedCli *cli, char *args, void *context);
|
||||
void cli_cmd_rm(EmbeddedCli *cli, char *args, void *context);
|
||||
|
||||
static void cli_write_char(EmbeddedCli *cli, char c) {
|
||||
(void)cli;
|
||||
putchar((int)c);
|
||||
}
|
||||
|
||||
bool cli_init(void) {
|
||||
EmbeddedCliConfig *config = embeddedCliDefaultConfig();
|
||||
config->cliBuffer = cli_buffer;
|
||||
config->cliBufferSize = CLI_BUFFER_SIZE;
|
||||
config->rxBufferSize = CLI_RX_BUFFER_SIZE;
|
||||
config->cmdBufferSize = CLI_CMD_BUFFER_SIZE;
|
||||
config->historyBufferSize = CLI_HISTORY_SIZE;
|
||||
config->maxBindingCount = CLI_BINDING_COUNT;
|
||||
|
||||
TU_ASSERT(embeddedCliRequiredSize(config) <= CLI_BUFFER_SIZE);
|
||||
|
||||
_cli = embeddedCliNew(config);
|
||||
TU_ASSERT(_cli != NULL);
|
||||
|
||||
_cli->writeChar = cli_write_char;
|
||||
|
||||
embeddedCliAddBinding(_cli,
|
||||
(CliCommandBinding){"cat", "Usage: cat [FILE]...\r\n\tConcatenate FILE(s) to standard output..",
|
||||
true, NULL, cli_cmd_cat});
|
||||
|
||||
embeddedCliAddBinding(_cli, (CliCommandBinding){"cd", "Usage: cd [DIR]...\r\n\tChange the current directory to DIR.",
|
||||
true, NULL, cli_cmd_cd});
|
||||
|
||||
embeddedCliAddBinding(_cli, (CliCommandBinding){"cp", "Usage: cp SOURCE DEST\r\n\tCopy SOURCE to DEST.", true, NULL,
|
||||
cli_cmd_cp});
|
||||
|
||||
embeddedCliAddBinding(_cli, (CliCommandBinding){"dd", "Usage: dd [COUNT]\r\n\t" "Read COUNT sectors (default 1024) and report speed.", true, NULL,
|
||||
cli_cmd_dd});
|
||||
|
||||
embeddedCliAddBinding(_cli, (CliCommandBinding){"ls",
|
||||
"Usage: ls [DIR]...\r\n\tList information about the FILEs (the "
|
||||
"current directory by default).",
|
||||
true, NULL, cli_cmd_ls});
|
||||
|
||||
embeddedCliAddBinding(_cli,
|
||||
(CliCommandBinding){"pwd", "Usage: pwd\r\n\tPrint the name of the current working directory.",
|
||||
true, NULL, cli_cmd_pwd});
|
||||
|
||||
embeddedCliAddBinding(_cli, (CliCommandBinding){"mkdir",
|
||||
"Usage: mkdir DIR...\r\n\tCreate the DIRECTORY(ies), if they do not "
|
||||
"already exist..",
|
||||
true, NULL, cli_cmd_mkdir});
|
||||
|
||||
embeddedCliAddBinding(_cli, (CliCommandBinding){"mv", "Usage: mv SOURCE DEST...\r\n\tRename SOURCE to DEST.", true,
|
||||
NULL, cli_cmd_mv});
|
||||
|
||||
embeddedCliAddBinding(_cli, (CliCommandBinding){"rm", "Usage: rm [FILE]...\r\n\tRemove (unlink) the FILE(s).", true,
|
||||
NULL, cli_cmd_rm});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void cli_cmd_dd(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
|
||||
uint32_t count = 1024; // default sectors to read
|
||||
if (embeddedCliGetTokenCount(args) >= 1) {
|
||||
count = (uint32_t)atoi(embeddedCliGetToken(args, 1));
|
||||
if (count == 0) {
|
||||
count = 1024;
|
||||
}
|
||||
}
|
||||
|
||||
// find first mounted MSC device
|
||||
uint8_t dev_addr = 0;
|
||||
for (uint8_t i = 1; i <= CFG_TUH_DEVICE_MAX; i++) {
|
||||
if (tuh_msc_mounted(i)) {
|
||||
dev_addr = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dev_addr == 0) {
|
||||
printf("no MSC device mounted\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t lun = 0;
|
||||
const uint32_t block_size = tuh_msc_get_block_size(dev_addr, lun);
|
||||
const uint32_t block_count = tuh_msc_get_block_count(dev_addr, lun);
|
||||
if (count > block_count) {
|
||||
count = block_count;
|
||||
}
|
||||
|
||||
const uint16_t sectors_per_xfer = (uint16_t)(sizeof(rw_buf) / block_size);
|
||||
const uint32_t xfer_count = (count + sectors_per_xfer - 1) / sectors_per_xfer;
|
||||
|
||||
printf("dd: reading %" PRIu32 " sectors (%" PRIu32 " bytes), %u sectors/xfer ...\r\n",
|
||||
count, count * block_size, sectors_per_xfer);
|
||||
|
||||
const uint32_t start_ms = tusb_time_millis_api();
|
||||
const uint8_t pdrv = dev_addr - 1;
|
||||
bool submit_failed = false;
|
||||
|
||||
for (uint32_t i = 0; i < count; i += sectors_per_xfer) {
|
||||
const uint16_t n = (uint16_t)((count - i < sectors_per_xfer) ? (count - i) : sectors_per_xfer);
|
||||
_disk_busy[pdrv] = true;
|
||||
|
||||
if (!tuh_msc_read10(dev_addr, lun, rw_buf, i, n, disk_io_complete, 0)) {
|
||||
_disk_busy[pdrv] = false;
|
||||
printf("dd: failed to submit read at sector %" PRIu32 " (%u sectors)\r\n", i, n);
|
||||
submit_failed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
wait_for_disk_io(pdrv);
|
||||
}
|
||||
|
||||
if (submit_failed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t elapsed_ms = tusb_time_millis_api() - start_ms;
|
||||
const uint32_t total_data = count * block_size;
|
||||
// each SCSI transaction has 31-byte CBW + data + 13-byte CSW
|
||||
const uint32_t total_bus = total_data + xfer_count * (31 + 13);
|
||||
|
||||
if (elapsed_ms > 0) {
|
||||
const uint32_t data_kbs = total_data / elapsed_ms; // KB/s (bytes/ms = KB/s)
|
||||
const uint32_t bus_kbs = total_bus / elapsed_ms;
|
||||
printf("dd: %" PRIu32 " bytes in %" PRIu32 " ms = %" PRIu32 " KB/s (bus %" PRIu32 " KB/s)\r\n",
|
||||
total_data, elapsed_ms, data_kbs, bus_kbs);
|
||||
} else {
|
||||
printf("dd: %" PRIu32 " bytes in <1 ms\r\n", total_data);
|
||||
}
|
||||
}
|
||||
|
||||
void cli_cmd_cat(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
|
||||
uint16_t argc = embeddedCliGetTokenCount(args);
|
||||
|
||||
// need at least 1 argument
|
||||
if (argc == 0) {
|
||||
printf("invalid arguments\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint16_t i = 0; i < argc; i++) {
|
||||
FIL *fi = &file1;
|
||||
const char *fpath = embeddedCliGetToken(args, i + 1); // token count from 1
|
||||
|
||||
if (FR_OK != f_open(fi, fpath, FA_READ)) {
|
||||
printf("%s: No such file or directory\r\n", fpath);
|
||||
} else {
|
||||
UINT count = 0;
|
||||
while ((FR_OK == f_read(fi, rw_buf, sizeof(rw_buf), &count)) && (count > 0)) {
|
||||
for (UINT c = 0; c < count; c++) {
|
||||
const uint8_t ch = rw_buf[c];
|
||||
if (isprint(ch) || iscntrl(ch)) {
|
||||
putchar(ch);
|
||||
} else {
|
||||
putchar('.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f_close(fi);
|
||||
}
|
||||
}
|
||||
|
||||
void cli_cmd_cd(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
|
||||
uint16_t argc = embeddedCliGetTokenCount(args);
|
||||
|
||||
// only support 1 argument
|
||||
if (argc != 1) {
|
||||
printf("invalid arguments\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// default is current directory
|
||||
const char *dpath = args;
|
||||
|
||||
if (FR_OK != f_chdir(dpath)) {
|
||||
printf("%s: No such file or directory\r\n", dpath);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void cli_cmd_cp(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
|
||||
uint16_t argc = embeddedCliGetTokenCount(args);
|
||||
if (argc != 2) {
|
||||
printf("invalid arguments\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// default is current directory
|
||||
const char *src = embeddedCliGetToken(args, 1);
|
||||
const char *dst = embeddedCliGetToken(args, 2);
|
||||
|
||||
FIL *f_src = &file1;
|
||||
FIL *f_dst = &file2;
|
||||
|
||||
if (FR_OK != f_open(f_src, src, FA_READ)) {
|
||||
printf("cannot stat '%s': No such file or directory\r\n", src);
|
||||
return;
|
||||
}
|
||||
|
||||
if (FR_OK != f_open(f_dst, dst, FA_WRITE | FA_CREATE_ALWAYS)) {
|
||||
printf("cannot create '%s'\r\n", dst);
|
||||
f_close(f_src);
|
||||
return;
|
||||
} else {
|
||||
UINT rd_count = 0;
|
||||
while ((FR_OK == f_read(f_src, rw_buf, sizeof(rw_buf), &rd_count)) && (rd_count > 0)) {
|
||||
UINT wr_count = 0;
|
||||
|
||||
if (FR_OK != f_write(f_dst, rw_buf, rd_count, &wr_count)) {
|
||||
printf("cannot write to '%s'\r\n", dst);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f_close(f_src);
|
||||
f_close(f_dst);
|
||||
}
|
||||
|
||||
void cli_cmd_ls(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
|
||||
uint16_t argc = embeddedCliGetTokenCount(args);
|
||||
|
||||
// only support 1 argument
|
||||
if (argc > 1) {
|
||||
printf("invalid arguments\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// default is current directory
|
||||
const char *dpath = ".";
|
||||
if (argc) {
|
||||
dpath = args;
|
||||
}
|
||||
|
||||
DIR dir;
|
||||
if (FR_OK != f_opendir(&dir, dpath)) {
|
||||
printf("cannot access '%s': No such file or directory\r\n", dpath);
|
||||
return;
|
||||
}
|
||||
|
||||
FILINFO fno;
|
||||
while ((f_readdir(&dir, &fno) == FR_OK) && (fno.fname[0] != 0)) {
|
||||
if (fno.fname[0] != '.') // ignore . and .. entry
|
||||
{
|
||||
if (fno.fattrib & AM_DIR) {
|
||||
// directory
|
||||
printf("/%s\r\n", fno.fname);
|
||||
} else {
|
||||
printf("%-40s", fno.fname);
|
||||
if (fno.fsize < 1024) {
|
||||
printf("%" PRIu32 " B\r\n", fno.fsize);
|
||||
} else {
|
||||
printf("%" PRIu32 " KB\r\n", fno.fsize / 1024);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f_closedir(&dir);
|
||||
}
|
||||
|
||||
void cli_cmd_pwd(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
uint16_t argc = embeddedCliGetTokenCount(args);
|
||||
|
||||
if (argc != 0) {
|
||||
printf("invalid arguments\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
char path[256];
|
||||
if (FR_OK != f_getcwd(path, sizeof(path))) {
|
||||
printf("cannot get current working directory\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
puts(path);
|
||||
}
|
||||
|
||||
void cli_cmd_mkdir(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
|
||||
uint16_t argc = embeddedCliGetTokenCount(args);
|
||||
|
||||
// only support 1 argument
|
||||
if (argc != 1) {
|
||||
printf("invalid arguments\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// default is current directory
|
||||
const char *dpath = args;
|
||||
|
||||
if (FR_OK != f_mkdir(dpath)) {
|
||||
printf("%s: cannot create this directory\r\n", dpath);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void cli_cmd_mv(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
|
||||
uint16_t argc = embeddedCliGetTokenCount(args);
|
||||
if (argc != 2) {
|
||||
printf("invalid arguments\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// default is current directory
|
||||
const char *src = embeddedCliGetToken(args, 1);
|
||||
const char *dst = embeddedCliGetToken(args, 2);
|
||||
|
||||
if (FR_OK != f_rename(src, dst)) {
|
||||
printf("cannot mv %s to %s\r\n", src, dst);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void cli_cmd_rm(EmbeddedCli *cli, char *args, void *context) {
|
||||
(void)cli;
|
||||
(void)context;
|
||||
|
||||
uint16_t argc = embeddedCliGetTokenCount(args);
|
||||
|
||||
// need at least 1 argument
|
||||
if (argc == 0) {
|
||||
printf("invalid arguments\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint16_t i = 0; i < argc; i++) {
|
||||
const char *fpath = embeddedCliGetToken(args, i + 1); // token count from 1
|
||||
|
||||
if (FR_OK != f_unlink(fpath)) {
|
||||
printf("cannot remove '%s': No such file or directory\r\n", fpath);
|
||||
}
|
||||
}
|
||||
}
|
||||
34
examples/host/msc_file_explorer_freertos/src/msc_app.h
Normal file
34
examples/host/msc_file_explorer_freertos/src/msc_app.h
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2025 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.
|
||||
*
|
||||
* This file is part of the TinyUSB stack.
|
||||
*/
|
||||
#ifndef MSC_APP_H
|
||||
#define MSC_APP_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
bool msc_app_init(void);
|
||||
|
||||
#endif
|
||||
126
examples/host/msc_file_explorer_freertos/src/tusb_config.h
Normal file
126
examples/host/msc_file_explorer_freertos/src/tusb_config.h
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 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
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// 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_FREERTOS
|
||||
#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
|
||||
|
||||
/* 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_TUH_MEM_SECTION
|
||||
#define CFG_TUH_MEM_SECTION
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUH_MEM_ALIGN
|
||||
#define CFG_TUH_MEM_ALIGN __attribute__ ((aligned(4)))
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Host Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// Enable Host stack
|
||||
#define CFG_TUH_ENABLED 1
|
||||
|
||||
// #define CFG_TUH_MAX3421 1 // use max3421 as host controller
|
||||
|
||||
#if CFG_TUSB_MCU == OPT_MCU_RP2040
|
||||
// #define CFG_TUH_RPI_PIO_USB 1 // use pio-usb as host controller
|
||||
|
||||
// host roothub port is 1 if using either pio-usb or max3421
|
||||
#if (defined(CFG_TUH_RPI_PIO_USB) && CFG_TUH_RPI_PIO_USB) || (defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421)
|
||||
#define BOARD_TUH_RHPORT 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Default is max speed that hardware controller could support with on-chip PHY
|
||||
#define CFG_TUH_MAX_SPEED BOARD_TUH_MAX_SPEED
|
||||
|
||||
//------------------------- Board Specific --------------------------
|
||||
|
||||
// RHPort number used for host can be defined by board.mk, default to port 0
|
||||
#ifndef BOARD_TUH_RHPORT
|
||||
#define BOARD_TUH_RHPORT 0
|
||||
#endif
|
||||
|
||||
// RHPort max operational speed can defined by board.mk
|
||||
#ifndef BOARD_TUH_MAX_SPEED
|
||||
#define BOARD_TUH_MAX_SPEED OPT_MODE_DEFAULT_SPEED
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Driver Configuration
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// Size of buffer to hold descriptors and other data used for enumeration
|
||||
#define CFG_TUH_ENUMERATION_BUFSIZE 256
|
||||
|
||||
#define CFG_TUH_HUB 1 // number of supported hubs
|
||||
#define CFG_TUH_MSC 1
|
||||
#define CFG_TUH_CDC 0
|
||||
#define CFG_TUH_HID 0 // typical keyboard + mouse device can have 3-4 HID interfaces
|
||||
#define CFG_TUH_VENDOR 0
|
||||
|
||||
// max device support (excluding hub device): 1 hub typically has 4 ports
|
||||
#define CFG_TUH_DEVICE_MAX (3*CFG_TUH_HUB + 1)
|
||||
|
||||
//------------- MSC -------------//
|
||||
#define CFG_TUH_MSC_MAXLUN 4 // typical for most card reader
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* TUSB_CONFIG_H_ */
|
||||
@ -903,9 +903,6 @@ static void handle_rxflvl_irq(uint8_t rhport) {
|
||||
|
||||
// return true if there is still pending data and need more ISR
|
||||
static bool handle_txfifo_empty(dwc2_regs_t* dwc2, bool is_periodic) {
|
||||
// Use period txsts for both p/np to get request queue space available (1-bit difference, it is small enough)
|
||||
const dwc2_hptxsts_t txsts = {.value = (is_periodic ? dwc2->hptxsts : dwc2->hnptxsts)};
|
||||
|
||||
const uint8_t max_channel = dwc2_channel_count(dwc2);
|
||||
for (uint8_t ch_id = 0; ch_id < max_channel; ch_id++) {
|
||||
dwc2_channel_t* channel = &dwc2->channel[ch_id];
|
||||
@ -923,6 +920,8 @@ static bool handle_txfifo_empty(dwc2_regs_t* dwc2, bool is_periodic) {
|
||||
|
||||
// skip if there is not enough space in FIFO and RequestQueue.
|
||||
// Packet's last word written to FIFO will trigger a request queue
|
||||
// Use period txsts for both p/np to get request queue space available (1-bit difference, it is small enough)
|
||||
const dwc2_hptxsts_t txsts = {.value = (is_periodic ? dwc2->hptxsts : dwc2->hnptxsts)};
|
||||
if ((xact_bytes > (txsts.fifo_available << 2)) || (txsts.req_queue_available == 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -34,6 +34,9 @@ import select
|
||||
import sys
|
||||
import time
|
||||
import warnings
|
||||
import signal
|
||||
from pathlib import Path
|
||||
from typing import Any, TypedDict, NotRequired, cast
|
||||
|
||||
# Suppress pkg_resources deprecation warning from fs module
|
||||
warnings.filterwarnings("ignore", message="pkg_resources is deprecated")
|
||||
@ -45,6 +48,7 @@ import subprocess
|
||||
import json
|
||||
import glob
|
||||
from multiprocessing import Pool
|
||||
from multiprocessing import TimeoutError as MpTimeoutError
|
||||
import fs
|
||||
import hashlib
|
||||
import ctypes
|
||||
@ -63,6 +67,57 @@ board_test = {}
|
||||
build_dir = 'cmake-build'
|
||||
skip_flash = False
|
||||
|
||||
class FlasherCfg(TypedDict):
|
||||
name: str
|
||||
uid: str
|
||||
args: str
|
||||
|
||||
|
||||
class AttachedDevCfg(TypedDict, total=False):
|
||||
vid_pid: str
|
||||
serial: str
|
||||
is_cdc: bool
|
||||
is_msc: bool
|
||||
block_count: int
|
||||
block_size: int
|
||||
|
||||
|
||||
class TestsCfg(TypedDict, total=False):
|
||||
device: bool
|
||||
dual: bool
|
||||
host: bool
|
||||
only: list[str]
|
||||
skip: list[str]
|
||||
dev_attached: list[AttachedDevCfg]
|
||||
|
||||
|
||||
class BuildCfg(TypedDict, total=False):
|
||||
flags_on: list[str]
|
||||
args: list[str]
|
||||
|
||||
|
||||
class Board(TypedDict):
|
||||
name: str
|
||||
uid: str
|
||||
tests: TestsCfg
|
||||
flasher: FlasherCfg
|
||||
build: NotRequired[BuildCfg]
|
||||
|
||||
|
||||
class HilConfig(TypedDict):
|
||||
boards: list[Board]
|
||||
|
||||
CMD_TIMEOUT = int(os.getenv('HIL_CMD_TIMEOUT', '180'))
|
||||
POOL_TIMEOUT = int(os.getenv('HIL_POOL_TIMEOUT', '3000'))
|
||||
|
||||
|
||||
def cmd_stdout_text(out: Any) -> str:
|
||||
if out is None:
|
||||
return ''
|
||||
if isinstance(out, bytes):
|
||||
return out.decode('utf-8', errors='ignore')
|
||||
return str(out)
|
||||
|
||||
WCH_RISCV_CONTENT = """
|
||||
adapter driver wlinke
|
||||
adapter speed 6000
|
||||
@ -91,8 +146,8 @@ issue at github.com/hathach/tinyusb"
|
||||
# -------------------------------------------------------------
|
||||
# Path
|
||||
# -------------------------------------------------------------
|
||||
OPENCOD_ADI_PATH = f'{os.getenv("HOME")}/app/openocd_adi'
|
||||
TINYUSB_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
OPENCOD_ADI_PATH = Path.home() / 'app' / 'openocd_adi'
|
||||
TINYUSB_ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
# get usb serial by id
|
||||
def get_serial_dev(id, vendor_str, product_str, ifnum):
|
||||
@ -119,7 +174,7 @@ def get_hid_dev(id, vendor_str, product_str, event):
|
||||
return f'/dev/input/by-id/usb-{vendor_str}_{product_str}_{id}-{event}'
|
||||
|
||||
|
||||
def open_serial_dev(port):
|
||||
def open_serial_dev(port: str):
|
||||
timeout = ENUM_TIMEOUT
|
||||
ser = None
|
||||
while timeout > 0:
|
||||
@ -134,10 +189,11 @@ def open_serial_dev(port):
|
||||
timeout -= 0.1
|
||||
|
||||
assert timeout > 0, f'Cannot open port f{port}' if os.path.exists(port) else f'Port {port} not existed'
|
||||
assert ser is not None
|
||||
return ser
|
||||
|
||||
|
||||
def read_disk_file(uid, lun, fname):
|
||||
def read_disk_file(uid: str, lun: int, fname: str) -> bytes:
|
||||
# open_fs("fat://{dev}) require 'pip install pyfatfs'
|
||||
dev = get_disk_dev(uid, 'TinyUSB', lun)
|
||||
timeout = ENUM_TIMEOUT
|
||||
@ -154,8 +210,7 @@ def read_disk_file(uid, lun, fname):
|
||||
time.sleep(1)
|
||||
timeout -= 1
|
||||
|
||||
assert timeout > 0, f'Storage {dev} not existed'
|
||||
return None
|
||||
raise AssertionError(f'Storage {dev} not existed')
|
||||
|
||||
|
||||
def open_mtp_dev(uid):
|
||||
@ -177,7 +232,7 @@ def open_mtp_dev(uid):
|
||||
return None
|
||||
|
||||
|
||||
def get_printer_dev(id, vendor_str, product_str, ifnum):
|
||||
def get_printer_dev(id: str, vendor_str, product_str, ifnum: int):
|
||||
"""Find /dev/usb/lpX by matching USB serial, vendor, product, and interface number via sysfs"""
|
||||
vendor_str = vendor_str.replace(' ', '_') if vendor_str else ''
|
||||
product_str = product_str.replace(' ', '_') if product_str else ''
|
||||
@ -191,7 +246,7 @@ def get_printer_dev(id, vendor_str, product_str, ifnum):
|
||||
return None
|
||||
|
||||
|
||||
def open_printer_dev(id, vendor_str, product_str, ifnum):
|
||||
def open_printer_dev(id: str, vendor_str, product_str, ifnum: int) -> str:
|
||||
"""Wait for printer device to enumerate and return its path"""
|
||||
timeout = ENUM_TIMEOUT
|
||||
while timeout > 0:
|
||||
@ -206,41 +261,77 @@ def open_printer_dev(id, vendor_str, product_str, ifnum):
|
||||
# -------------------------------------------------------------
|
||||
# Flashing firmware
|
||||
# -------------------------------------------------------------
|
||||
def run_cmd(cmd, cwd=None):
|
||||
r = subprocess.run(cmd, cwd=cwd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
def run_cmd(cmd: str, cwd: str | None = None, timeout: int = CMD_TIMEOUT) -> subprocess.CompletedProcess:
|
||||
popen_kwargs = {
|
||||
'cwd': cwd,
|
||||
'shell': True,
|
||||
'stdout': subprocess.PIPE,
|
||||
'stderr': subprocess.STDOUT,
|
||||
'text': True,
|
||||
'encoding': 'utf-8',
|
||||
'errors': 'replace',
|
||||
}
|
||||
if os.name != 'nt':
|
||||
popen_kwargs['preexec_fn'] = os.setsid
|
||||
|
||||
p = subprocess.Popen(cmd, **popen_kwargs)
|
||||
try:
|
||||
out, _ = p.communicate(timeout=timeout)
|
||||
r = subprocess.CompletedProcess(args=cmd, returncode=p.returncode, stdout=out)
|
||||
except subprocess.TimeoutExpired as ex:
|
||||
if os.name != 'nt':
|
||||
try:
|
||||
os.killpg(p.pid, signal.SIGKILL)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
else:
|
||||
p.kill()
|
||||
out, _ = p.communicate()
|
||||
timeout_out = ex.stdout or out or b''
|
||||
title = f'COMMAND TIMEOUT ({timeout}s): {cmd}'
|
||||
print()
|
||||
if os.getenv('CI'):
|
||||
print(f"::group::{title}")
|
||||
print(cmd_stdout_text(timeout_out))
|
||||
print(f"::endgroup::")
|
||||
else:
|
||||
print(title)
|
||||
print(cmd_stdout_text(timeout_out))
|
||||
return subprocess.CompletedProcess(args=cmd, returncode=124, stdout=timeout_out)
|
||||
|
||||
if r.returncode != 0:
|
||||
title = f'COMMAND FAILED: {cmd}'
|
||||
print()
|
||||
if os.getenv('CI'):
|
||||
print(f"::group::{title}")
|
||||
print(r.stdout.decode("utf-8"))
|
||||
print(cmd_stdout_text(r.stdout))
|
||||
print(f"::endgroup::")
|
||||
else:
|
||||
print(title)
|
||||
print(r.stdout.decode("utf-8"))
|
||||
print(cmd_stdout_text(r.stdout))
|
||||
elif verbose:
|
||||
print(cmd)
|
||||
print(r.stdout.decode("utf-8"))
|
||||
print(cmd_stdout_text(r.stdout))
|
||||
return r
|
||||
|
||||
|
||||
def flash_jlink(board, firmware):
|
||||
def flash_jlink(board: Board, firmware: str) -> subprocess.CompletedProcess:
|
||||
flasher = board['flasher']
|
||||
script = ['halt', 'r', f'loadfile {firmware}.elf', 'r', 'go', 'exit']
|
||||
f_jlink = f'{board["name"]}_{os.path.basename(firmware)}.jlink'
|
||||
with open(f_jlink, 'w') as f:
|
||||
f_jlink = Path(f'{board["name"]}_{Path(firmware).name}.jlink')
|
||||
with f_jlink.open('w') as f:
|
||||
f.writelines(f'{s}\n' for s in script)
|
||||
ret = run_cmd(f'JLinkExe -USB {flasher["uid"]} {flasher["args"]} -if swd -JTAGConf -1,-1 -speed auto -NoGui 1 -ExitOnError 1 -CommandFile {f_jlink}')
|
||||
os.remove(f_jlink)
|
||||
f_jlink.unlink(missing_ok=True)
|
||||
return ret
|
||||
|
||||
|
||||
def reset_jlink(board):
|
||||
def reset_jlink(board: Board) -> subprocess.CompletedProcess:
|
||||
flasher = board['flasher']
|
||||
script = ['halt', 'r', 'go', 'exit']
|
||||
f_jlink = f'{board["name"]}_reset.jlink'
|
||||
if not os.path.exists(f_jlink):
|
||||
with open(f_jlink, 'w') as f:
|
||||
f_jlink = Path(f'{board["name"]}_reset.jlink')
|
||||
if not f_jlink.exists():
|
||||
with f_jlink.open('w') as f:
|
||||
f.writelines(f'{s}\n' for s in script)
|
||||
ret = run_cmd(f'JLinkExe -USB {flasher["uid"]} {flasher["args"]} -if swd -JTAGConf -1,-1 -speed auto -NoGui 1 -ExitOnError 1 -CommandFile {f_jlink}')
|
||||
return ret
|
||||
@ -303,16 +394,20 @@ def reset_openocd_wch(board):
|
||||
return ret
|
||||
|
||||
|
||||
def flash_openocd_adi(board, firmware):
|
||||
def flash_openocd_adi(board: Board, firmware: str) -> subprocess.CompletedProcess:
|
||||
flasher = board['flasher']
|
||||
ret = run_cmd(f'{OPENCOD_ADI_PATH}/src/openocd -c "adapter serial {flasher["uid"]}" -s {OPENCOD_ADI_PATH}/tcl '
|
||||
openocd = OPENCOD_ADI_PATH / 'src' / 'openocd'
|
||||
tcl_dir = OPENCOD_ADI_PATH / 'tcl'
|
||||
ret = run_cmd(f'{openocd} -c "adapter serial {flasher["uid"]}" -s {tcl_dir} '
|
||||
f'{flasher["args"]} -c "program {firmware}.elf reset exit"')
|
||||
return ret
|
||||
|
||||
|
||||
def reset_openocd_adi(board):
|
||||
def reset_openocd_adi(board: Board) -> subprocess.CompletedProcess:
|
||||
flasher = board['flasher']
|
||||
ret = run_cmd(f'{OPENCOD_ADI_PATH}/src/openocd -c "adapter serial {flasher["uid"]}" -s {OPENCOD_ADI_PATH}/tcl '
|
||||
openocd = OPENCOD_ADI_PATH / 'src' / 'openocd'
|
||||
tcl_dir = OPENCOD_ADI_PATH / 'tcl'
|
||||
ret = run_cmd(f'{openocd} -c "adapter serial {flasher["uid"]}" -s {tcl_dir} '
|
||||
f'{flasher["args"]} -c "program reset exit"')
|
||||
return ret
|
||||
|
||||
@ -331,17 +426,17 @@ def reset_wlink_rs(board):
|
||||
return ret
|
||||
|
||||
|
||||
def flash_esptool(board, firmware):
|
||||
def flash_esptool(board: Board, firmware: str) -> subprocess.CompletedProcess:
|
||||
flasher = board['flasher']
|
||||
port = get_serial_dev(flasher["uid"], None, None, 0)
|
||||
fw_dir = os.path.dirname(f'{firmware}.bin')
|
||||
with open(f'{fw_dir}/config.env') as f:
|
||||
fw_dir = Path(f'{firmware}.bin').parent
|
||||
with (fw_dir / 'config.env').open() as f:
|
||||
idf_target = json.load(f)['IDF_TARGET']
|
||||
with open(f'{fw_dir}/flash_args') as f:
|
||||
with (fw_dir / 'flash_args').open() as f:
|
||||
flash_args = f.read().strip().replace('\n', ' ')
|
||||
command = (f'esptool --chip {idf_target} -p {port} {flasher["args"]} '
|
||||
f'--before=default_reset --after=hard_reset write_flash {flash_args}')
|
||||
ret = run_cmd(command, cwd=fw_dir)
|
||||
ret = run_cmd(command, cwd=str(fw_dir))
|
||||
return ret
|
||||
|
||||
|
||||
@ -684,7 +779,7 @@ def test_device_cdc_dual_ports(board):
|
||||
|
||||
sizes = [32, 64, 128, 256, 512, random.randint(2000, 5000)]
|
||||
|
||||
def write_and_check(writer, payload):
|
||||
def write_and_check(writer, payload : bytes):
|
||||
payload_len = len(payload)
|
||||
for s in ser:
|
||||
s.reset_input_buffer()
|
||||
@ -785,7 +880,7 @@ def test_device_cdc_msc_throughput(board):
|
||||
|
||||
# Put tty in raw mode so dd sees pure binary throughput.
|
||||
rs = run_cmd(f'timeout 30 stty -F {tty} raw -echo')
|
||||
assert rs.returncode == 0, f'stty failed: {rs.stdout.decode()}'
|
||||
assert rs.returncode == 0, f'stty failed: {cmd_stdout_text(rs.stdout)}'
|
||||
|
||||
# Payload aim: ~5 s per direction at FS (~830 kB/s), much less at HS.
|
||||
msc_count = 2 if is_fs else 16 # bs=1M
|
||||
@ -794,20 +889,20 @@ def test_device_cdc_msc_throughput(board):
|
||||
tmp_file = f'/tmp/cdc_msc_tp_{uid}.bin'
|
||||
|
||||
rw = run_cmd(f'timeout 30 dd if=/dev/zero of={tty} bs=64K count={cdc_count} 2>&1')
|
||||
assert rw.returncode == 0, f'CDC dd write failed: {rw.stdout.decode()}'
|
||||
cdc_w = parse_speed(rw.stdout.decode())
|
||||
assert rw.returncode == 0, f'CDC dd write failed: {cmd_stdout_text(rw.stdout)}'
|
||||
cdc_w = parse_speed(cmd_stdout_text(rw.stdout))
|
||||
|
||||
rr = run_cmd(f'timeout 30 dd if={tty} of=/dev/null bs=64K count={cdc_count} iflag=fullblock 2>&1')
|
||||
assert rr.returncode == 0, f'CDC dd read failed: {rr.stdout.decode()}'
|
||||
cdc_r = parse_speed(rr.stdout.decode())
|
||||
assert rr.returncode == 0, f'CDC dd read failed: {cmd_stdout_text(rr.stdout)}'
|
||||
cdc_r = parse_speed(cmd_stdout_text(rr.stdout))
|
||||
|
||||
rmr = run_cmd(f'dd if={dev} of={tmp_file} bs=1M count={msc_count} iflag=direct 2>&1')
|
||||
assert rmr.returncode == 0, f'MSC dd read failed: {rmr.stdout.decode()}'
|
||||
msc_r = parse_speed(rmr.stdout.decode())
|
||||
assert rmr.returncode == 0, f'MSC dd read failed: {cmd_stdout_text(rmr.stdout)}'
|
||||
msc_r = parse_speed(cmd_stdout_text(rmr.stdout))
|
||||
|
||||
rmw = run_cmd(f'dd if={tmp_file} of={dev} bs=1M count={msc_count} oflag=direct 2>&1')
|
||||
assert rmw.returncode == 0, f'MSC dd write failed: {rmw.stdout.decode()}'
|
||||
msc_w = parse_speed(rmw.stdout.decode())
|
||||
assert rmw.returncode == 0, f'MSC dd write failed: {cmd_stdout_text(rmw.stdout)}'
|
||||
msc_w = parse_speed(cmd_stdout_text(rmw.stdout))
|
||||
|
||||
try:
|
||||
os.remove(tmp_file)
|
||||
@ -824,7 +919,7 @@ def test_device_dfu(board):
|
||||
timeout = ENUM_TIMEOUT
|
||||
while timeout > 0:
|
||||
ret = run_cmd(f'dfu-util -l')
|
||||
stdout = ret.stdout.decode()
|
||||
stdout = cmd_stdout_text(ret.stdout)
|
||||
if f'serial="{uid}"' in stdout and 'Found DFU: [cafe:4000]' in stdout:
|
||||
break
|
||||
time.sleep(1)
|
||||
@ -864,7 +959,7 @@ def test_device_dfu_runtime(board):
|
||||
timeout = ENUM_TIMEOUT
|
||||
while timeout > 0:
|
||||
ret = run_cmd(f'dfu-util -l')
|
||||
stdout = ret.stdout.decode()
|
||||
stdout = cmd_stdout_text(ret.stdout)
|
||||
if f'serial="{uid}"' in stdout and 'Found Runtime: [cafe:4000]' in stdout:
|
||||
break
|
||||
time.sleep(1)
|
||||
@ -1262,7 +1357,7 @@ host_test = [
|
||||
]
|
||||
|
||||
|
||||
def test_example(board, f1, example):
|
||||
def test_example(board: Board, f1: str, example: str) -> int:
|
||||
"""
|
||||
Test example firmware
|
||||
:param board: board dict
|
||||
@ -1277,11 +1372,11 @@ def test_example(board, f1, example):
|
||||
if f1 != "":
|
||||
f1_str = '-f1_' + f1.replace(' ', '_')
|
||||
|
||||
fw_dir = f'{TINYUSB_ROOT}/{build_dir}/cmake-build-{name}{f1_str}/{example}'
|
||||
fw_name = f'{fw_dir}/{os.path.basename(example)}'
|
||||
fw_dir = TINYUSB_ROOT / build_dir / f'cmake-build-{name}{f1_str}' / example
|
||||
fw_name = fw_dir / Path(example).name
|
||||
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')):
|
||||
if not fw_dir.exists() or not ((fw_name.with_suffix('.elf')).exists() or (fw_name.with_suffix('.bin')).exists()):
|
||||
print('Skip (no binary)')
|
||||
return 0
|
||||
|
||||
@ -1294,7 +1389,7 @@ def test_example(board, f1, example):
|
||||
flash_ok = True
|
||||
for i in range(max_retry):
|
||||
if not skip_flash:
|
||||
ret = globals()[f'flash_{board["flasher"]["name"].lower()}'](board, fw_name)
|
||||
ret = globals()[f'flash_{board["flasher"]["name"].lower()}'](board, str(fw_name))
|
||||
flash_ok = (ret.returncode == 0)
|
||||
if flash_ok:
|
||||
try:
|
||||
@ -1324,18 +1419,18 @@ def test_example(board, f1, example):
|
||||
return err_count
|
||||
|
||||
|
||||
def build_board(board):
|
||||
def build_board(board: Board) -> tuple[str, int]:
|
||||
"""Build firmware for this board via tools/build.py.
|
||||
Honors board config's build.flags_on variants and build.args defines.
|
||||
Output goes to cmake-build/cmake-build-BOARD[-f1_...]/ (tools/build.py layout)."""
|
||||
name = board['name']
|
||||
bcfg = board.get('build', {})
|
||||
bcfg = cast(BuildCfg, board.get('build', {}))
|
||||
flags_on_list = bcfg.get('flags_on', [''])
|
||||
extra_defs = bcfg.get('args', [])
|
||||
|
||||
failed = 0
|
||||
for f1 in flags_on_list:
|
||||
cmd = [sys.executable, f'{TINYUSB_ROOT}/tools/build.py', '-b', name]
|
||||
cmd = [sys.executable, str(TINYUSB_ROOT / 'tools' / 'build.py'), '-b', name]
|
||||
for d in extra_defs:
|
||||
cmd += ['-D', d]
|
||||
if f1:
|
||||
@ -1350,7 +1445,7 @@ def build_board(board):
|
||||
return name, failed
|
||||
|
||||
|
||||
def test_board(board):
|
||||
def test_board(board: Board) -> tuple[str, int, list[str]]:
|
||||
name = board['name']
|
||||
flasher = board['flasher']
|
||||
|
||||
@ -1364,11 +1459,11 @@ def test_board(board):
|
||||
else:
|
||||
if 'tests' in board:
|
||||
board_tests = board['tests']
|
||||
if 'device' in board_tests and board_tests['device'] == True:
|
||||
if board_tests.get('device') is True:
|
||||
test_list += list(device_tests)
|
||||
if 'dual' in board_tests and board_tests['dual'] == True:
|
||||
if board_tests.get('dual') is True:
|
||||
test_list += dual_tests
|
||||
if 'host' in board_tests and board_tests['host'] == True:
|
||||
if board_tests.get('host') is True:
|
||||
test_list += host_test
|
||||
if 'only' in board_tests:
|
||||
test_list = board_tests['only']
|
||||
@ -1398,7 +1493,7 @@ def test_board(board):
|
||||
return name, err_count, sorted(set(failed_tests))
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
"""
|
||||
Hardware test on specified boards
|
||||
"""
|
||||
@ -1425,7 +1520,7 @@ def main():
|
||||
parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
|
||||
args = parser.parse_args()
|
||||
|
||||
config_file = args.config_file
|
||||
config_file = Path(args.config_file)
|
||||
boards = args.board
|
||||
skip_boards = args.skip_board
|
||||
verbose = args.verbose
|
||||
@ -1440,10 +1535,10 @@ def main():
|
||||
skip_flash = args.skip_flash
|
||||
|
||||
# if config file is not found, try to find it in the same directory as this script
|
||||
if not os.path.exists(config_file):
|
||||
config_file = os.path.join(os.path.dirname(__file__), config_file)
|
||||
with open(config_file) as f:
|
||||
config = json.load(f)
|
||||
if not config_file.exists():
|
||||
config_file = Path(__file__).resolve().parent / config_file
|
||||
with config_file.open() as f:
|
||||
config = cast(HilConfig, json.load(f))
|
||||
|
||||
if len(boards) == 0:
|
||||
config_boards = [e for e in config['boards'] if e['name'] not in skip_boards]
|
||||
@ -1465,20 +1560,26 @@ def main():
|
||||
print(f'Build phase done: {build_err} failed')
|
||||
print('-' * 30)
|
||||
|
||||
with Pool(processes=os.cpu_count()) as pool:
|
||||
mret = pool.map(test_board, config_boards)
|
||||
with Pool(processes=os.cpu_count() or 1) as pool:
|
||||
async_ret = pool.map_async(test_board, config_boards)
|
||||
try:
|
||||
mret = async_ret.get(timeout=POOL_TIMEOUT)
|
||||
except MpTimeoutError:
|
||||
pool.terminate()
|
||||
pool.join()
|
||||
raise RuntimeError(f'HIL worker pool timed out after {POOL_TIMEOUT}s')
|
||||
err_count = build_err + sum(e[1] for e in mret)
|
||||
# generate skip list for next re-run if failed: skip boards that fully passed,
|
||||
# and emit -bt BOARD:t1,t2 so each failed board only re-runs its own failed tests.
|
||||
skip_fname = f'{config_file}.skip'
|
||||
skip_fname = config_file.with_suffix(config_file.suffix + '.skip')
|
||||
if err_count > 0:
|
||||
skip_boards += [name for name, err, _ in mret if err == 0]
|
||||
parts = [f'--skip-board {i}' for i in skip_boards]
|
||||
parts += [f'-bt {name}:{",".join(fts)}' for name, err, fts in mret if err > 0 and fts]
|
||||
with open(skip_fname, 'w') as f:
|
||||
with skip_fname.open('w') as f:
|
||||
f.write(' '.join(parts))
|
||||
elif os.path.exists(skip_fname):
|
||||
os.remove(skip_fname)
|
||||
elif skip_fname.exists():
|
||||
skip_fname.unlink()
|
||||
|
||||
duration = time.time() - duration
|
||||
print()
|
||||
|
||||
Reference in New Issue
Block a user