Initial project setup

This commit is contained in:
2025-12-13 11:59:11 +02:00
commit 3218e6039f
2176 changed files with 355321 additions and 0 deletions

View File

@@ -0,0 +1 @@
6a50305bc61c7a361da8c0833642be824e92dacb0a6001719a832a4e96e471bf

View File

@@ -0,0 +1,107 @@
## 1.7.6~2
- esp_tinyusb: Added support for IDF 6.0 after removal of the USB component
## 1.7.6~1
- esp_tinyusb: Added documentation to README.md
## 1.7.6
- MSC: Fixed the possibility to use SD/MMC storage with large capacity (more than 4 GB)
## 1.7.5
- esp_tinyusb: Provide forward compatibility with IDF 6.0
## 1.7.4
- MSC: WL Sector runtime check during spiflash init (fix for build time error check)
## 1.7.3 [yanked]
- MSC: Improved transfer speed to SD cards and SPI flash
## 1.7.2
- esp_tinyusb: Fixed crash on logging from ISR
- PHY: Fixed crash with external_phy=true configuration
## 1.7.1
- NCM: Changed default NTB config to decrease DRAM memory usage (fix for DRAM overflow on ESP32S2)
## 1.7.0 [yanked]
- NCM: Added possibility to configure NCM Transfer Blocks (NTB) via menuconfig
- esp_tinyusb: Added option to select TinyUSB peripheral on esp32p4 via menuconfig (USB_PHY_SUPPORTS_P4_OTG11 in esp-idf is required)
- esp_tinyusb: Fixed uninstall tinyusb driver with not default task configuration
## 1.6.0
- CDC-ACM: Fixed memory leak on deinit
- esp_tinyusb: Added Teardown
## 1.5.0
- esp_tinyusb: Added DMA mode option to tinyusb DCD DWC2 configuration
- esp_tinyusb: Changed the default affinity mask of the task to CPU1
## 1.4.5
- CDC-ACM: Fixed memory leak at VFS unregister
- Vendor specific: Provided default configuration
## 1.4.4
- esp_tinyusb: Added HighSpeed and Qualifier device descriptors in tinyusb configuration
- CDC-ACM: Removed MIN() definition if already defined
- MSC: Fixed EP size selecting in default configuration descriptor
## 1.4.3
- esp_tinyusb: Added ESP32P4 support (HS only)
## 1.4.2
- MSC: Fixed maximum files open
- Added uninstall function
## 1.4.0
- MSC: Fixed integer overflows
- CDC-ACM: Removed intermediate RX ringbuffer
- CDC-ACM: Increased default FIFO size to 512 bytes
- CDC-ACM: Fixed Virtual File System binding
## 1.3.0
- Added NCM extension
## 1.2.1 - 1.2.2
- Minor bugfixes
## 1.2.0
- Added MSC extension for accessing SPI Flash on memory card https://github.com/espressif/idf-extra-components/commit/a8c00d7707ba4ceeb0970c023d702c7768dba3dc
## 1.1.0
- Added support for NCM, ECM/RNDIS, DFU and Bluetooth TinyUSB drivers https://github.com/espressif/idf-extra-components/commit/79f35c9b047b583080f93a63310e2ee7d82ef17b
## 1.0.4
- Cleaned up string descriptors handling https://github.com/espressif/idf-extra-components/commit/046cc4b02f524d5c7e3e56480a473cfe844dc3d6
## 1.0.2 - 1.0.3
- Minor bugfixes
## 1.0.1
- CDC-ACM: Return ESP_OK if there is nothing to flush https://github.com/espressif/idf-extra-components/commit/388ff32eb09aa572d98c54cb355f1912ce42707c
## 1.0.0
- Initial version based on [esp-idf v4.4.3](https://github.com/espressif/esp-idf/tree/v4.4.3/components/tinyusb)

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,59 @@
set(srcs
"descriptors_control.c"
"tinyusb.c"
"usb_descriptors.c"
)
set(priv_req "")
if(${IDF_VERSION_MAJOR} LESS 6)
list(APPEND priv_req "usb")
endif()
if(NOT CONFIG_TINYUSB_NO_DEFAULT_TASK)
list(APPEND srcs "tusb_tasks.c")
endif() # CONFIG_TINYUSB_NO_DEFAULT_TASK
if(CONFIG_TINYUSB_CDC_ENABLED)
list(APPEND srcs
"cdc.c"
"tusb_cdc_acm.c"
)
if(CONFIG_VFS_SUPPORT_IO)
list(APPEND srcs
"tusb_console.c"
"vfs_tinyusb.c"
)
endif() # CONFIG_VFS_SUPPORT_IO
endif() # CONFIG_TINYUSB_CDC_ENABLED
if(CONFIG_TINYUSB_MSC_ENABLED)
list(APPEND srcs
tusb_msc_storage.c
)
endif() # CONFIG_TINYUSB_MSC_ENABLED
if(CONFIG_TINYUSB_NET_MODE_NCM)
list(APPEND srcs
tinyusb_net.c
)
endif() # CONFIG_TINYUSB_NET_MODE_NCM
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "include_private"
PRIV_REQUIRES ${priv_req}
REQUIRES fatfs vfs
)
# Determine whether tinyusb is fetched from component registry or from local path
idf_build_get_property(build_components BUILD_COMPONENTS)
if(tinyusb IN_LIST build_components)
set(tinyusb_name tinyusb) # Local component
else()
set(tinyusb_name espressif__tinyusb) # Managed component
endif()
# Pass tusb_config.h from this component to TinyUSB
idf_component_get_property(tusb_lib ${tinyusb_name} COMPONENT_LIB)
target_include_directories(${tusb_lib} PRIVATE "include")

View File

@@ -0,0 +1,379 @@
menu "TinyUSB Stack"
config TINYUSB_DEBUG_LEVEL
int "TinyUSB log level (0-3)"
default 1
range 0 3
help
Specify verbosity of TinyUSB log output.
choice TINYUSB_RHPORT
prompt "USB Peripheral"
default TINYUSB_RHPORT_HS if IDF_TARGET_ESP32P4
default TINYUSB_RHPORT_FS
help
Allows set the USB Peripheral Controller for TinyUSB.
- High-speed (USB OTG2.0 Peripheral for High-, Full- and Low-speed)
- Full-speed (USB OTG1.1 Peripheral for Full- and Low-speed)
config TINYUSB_RHPORT_HS
bool "OTG2.0"
depends on IDF_TARGET_ESP32P4
config TINYUSB_RHPORT_FS
bool "OTG1.1"
endchoice
menu "TinyUSB DCD"
choice TINYUSB_MODE
prompt "DCD Mode"
default TINYUSB_MODE_DMA
help
TinyUSB DCD DWC2 Driver supports two modes: Slave mode (based on IRQ) and Buffer DMA mode.
config TINYUSB_MODE_SLAVE
bool "Slave/IRQ"
config TINYUSB_MODE_DMA
bool "Buffer DMA"
endchoice
endmenu # "TinyUSB DCD"
menu "TinyUSB task configuration"
config TINYUSB_NO_DEFAULT_TASK
bool "Do not create a TinyUSB task"
default n
help
This option allows to not create the FreeRTOS task during the driver initialization.
User will have to handle TinyUSB events manually.
config TINYUSB_TASK_PRIORITY
int "TinyUSB task priority"
default 5
depends on !TINYUSB_NO_DEFAULT_TASK
help
Set the priority of the default TinyUSB main task.
config TINYUSB_TASK_STACK_SIZE
int "TinyUSB task stack size (bytes)"
default 4096
depends on !TINYUSB_NO_DEFAULT_TASK
help
Set the stack size of the default TinyUSB main task.
choice TINYUSB_TASK_AFFINITY
prompt "TinyUSB task affinity"
default TINYUSB_TASK_AFFINITY_CPU1 if !FREERTOS_UNICORE
default TINYUSB_TASK_AFFINITY_NO_AFFINITY
depends on !TINYUSB_NO_DEFAULT_TASK
help
Allows setting TinyUSB tasks affinity, i.e. whether the task is pinned to
CPU0, pinned to CPU1, or allowed to run on any CPU.
config TINYUSB_TASK_AFFINITY_NO_AFFINITY
bool "No affinity"
config TINYUSB_TASK_AFFINITY_CPU0
bool "CPU0"
config TINYUSB_TASK_AFFINITY_CPU1
bool "CPU1"
depends on !FREERTOS_UNICORE
endchoice
config TINYUSB_TASK_AFFINITY
hex
default FREERTOS_NO_AFFINITY if TINYUSB_TASK_AFFINITY_NO_AFFINITY
default 0x0 if TINYUSB_TASK_AFFINITY_CPU0
default 0x1 if TINYUSB_TASK_AFFINITY_CPU1
config TINYUSB_INIT_IN_DEFAULT_TASK
bool "Initialize TinyUSB stack within the default TinyUSB task"
default n
depends on !TINYUSB_NO_DEFAULT_TASK
help
Run TinyUSB stack initialization just after starting the default TinyUSB task.
This is especially useful in multicore scenarios, when we need to pin the task
to a specific core and, at the same time initialize TinyUSB stack
(i.e. install interrupts) on the same core.
endmenu # "TinyUSB task configuration"
menu "Descriptor configuration"
comment "You can provide your custom descriptors via tinyusb_driver_install()"
config TINYUSB_DESC_USE_ESPRESSIF_VID
bool "VID: Use Espressif's vendor ID"
default y
help
Enable this option, USB device will use Espressif's vendor ID as its VID.
This is helpful at product develop stage.
config TINYUSB_DESC_CUSTOM_VID
hex "VID: Custom vendor ID"
default 0x1234
depends on !TINYUSB_DESC_USE_ESPRESSIF_VID
help
Custom Vendor ID.
config TINYUSB_DESC_USE_DEFAULT_PID
bool "PID: Use a default PID assigned to TinyUSB"
default y
help
Default TinyUSB PID assigning uses values 0x4000...0x4007.
config TINYUSB_DESC_CUSTOM_PID
hex "PID: Custom product ID"
default 0x5678
depends on !TINYUSB_DESC_USE_DEFAULT_PID
help
Custom Product ID.
config TINYUSB_DESC_BCD_DEVICE
hex "bcdDevice"
default 0x0100
help
Version of the firmware of the USB device.
config TINYUSB_DESC_MANUFACTURER_STRING
string "Manufacturer name"
default "Espressif Systems"
help
Name of the manufacturer of the USB device.
config TINYUSB_DESC_PRODUCT_STRING
string "Product name"
default "Espressif Device"
help
Name of the USB device.
config TINYUSB_DESC_SERIAL_STRING
string "Serial string"
default "123456"
help
Serial number of the USB device.
config TINYUSB_DESC_CDC_STRING
depends on TINYUSB_CDC_ENABLED
string "CDC Device String"
default "Espressif CDC Device"
help
Name of the CDC device.
config TINYUSB_DESC_MSC_STRING
depends on TINYUSB_MSC_ENABLED
string "MSC Device String"
default "Espressif MSC Device"
help
Name of the MSC device.
endmenu # "Descriptor configuration"
menu "Massive Storage Class (MSC)"
config TINYUSB_MSC_ENABLED
bool "Enable TinyUSB MSC feature"
default n
help
Enable TinyUSB MSC feature.
config TINYUSB_MSC_BUFSIZE
depends on TINYUSB_MSC_ENABLED
int "MSC FIFO size"
default 512 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
default 8192 if IDF_TARGET_ESP32P4
range 64 8192 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
range 64 32768 if IDF_TARGET_ESP32P4
help
MSC FIFO size, in bytes.
config TINYUSB_MSC_MOUNT_PATH
depends on TINYUSB_MSC_ENABLED
string "Mount Path"
default "/data"
help
MSC Mount Path of storage.
menu "TinyUSB FAT Format Options"
choice TINYUSB_FAT_FORMAT_TYPE
prompt "FatFS Format Type"
default TINYUSB_FAT_FORMAT_ANY
help
Select the FAT filesystem type used when formatting storage.
config TINYUSB_FAT_FORMAT_ANY
bool "FM_ANY - Automatically select from FAT12/FAT16/FAT32"
config TINYUSB_FAT_FORMAT_FAT
bool "FM_FAT - Allow only FAT12/FAT16"
config TINYUSB_FAT_FORMAT_FAT32
bool "FM_FAT32 - Force FAT32 only"
config TINYUSB_FAT_FORMAT_EXFAT
bool "FM_EXFAT - Force exFAT (requires exFAT enabled)"
endchoice
config TINYUSB_FAT_FORMAT_SFD
bool "FM_SFD - Use SFD (no partition table)"
default n
help
Format as a Super Floppy Disk (no partition table).
This is typical for USB flash drives and small volumes.
endmenu
endmenu # "Massive Storage Class"
menu "Communication Device Class (CDC)"
config TINYUSB_CDC_ENABLED
bool "Enable TinyUSB CDC feature"
default n
help
Enable TinyUSB CDC feature.
config TINYUSB_CDC_COUNT
int "CDC Channel Count"
default 1
range 1 2
depends on TINYUSB_CDC_ENABLED
help
Number of independent serial ports.
config TINYUSB_CDC_RX_BUFSIZE
depends on TINYUSB_CDC_ENABLED
int "CDC FIFO size of RX channel"
default 512
range 64 10000
help
CDC FIFO size of RX channel.
config TINYUSB_CDC_TX_BUFSIZE
depends on TINYUSB_CDC_ENABLED
int "CDC FIFO size of TX channel"
default 512
help
CDC FIFO size of TX channel.
endmenu # "Communication Device Class"
menu "Musical Instrument Digital Interface (MIDI)"
config TINYUSB_MIDI_COUNT
int "TinyUSB MIDI interfaces count"
default 0
range 0 2
help
Setting value greater than 0 will enable TinyUSB MIDI feature.
endmenu # "Musical Instrument Digital Interface (MIDI)"
menu "Human Interface Device Class (HID)"
config TINYUSB_HID_COUNT
int "TinyUSB HID interfaces count"
default 0
range 0 4
help
Setting value greater than 0 will enable TinyUSB HID feature.
endmenu # "HID Device Class (HID)"
menu "Device Firmware Upgrade (DFU)"
choice TINYUSB_DFU_MODE
prompt "DFU mode"
default TINYUSB_DFU_MODE_NONE
help
Select which DFU driver you want to use.
config TINYUSB_DFU_MODE_DFU
bool "DFU"
config TINYUSB_DFU_MODE_DFU_RUNTIME
bool "DFU Runtime"
config TINYUSB_DFU_MODE_NONE
bool "None"
endchoice
config TINYUSB_DFU_BUFSIZE
depends on TINYUSB_DFU_MODE_DFU
int "DFU XFER BUFFSIZE"
default 512
help
DFU XFER BUFFSIZE.
endmenu # Device Firmware Upgrade (DFU)
menu "Bluetooth Host Class (BTH)"
config TINYUSB_BTH_ENABLED
bool "Enable TinyUSB BTH feature"
default n
help
Enable TinyUSB BTH feature.
config TINYUSB_BTH_ISO_ALT_COUNT
depends on TINYUSB_BTH_ENABLED
int "BTH ISO ALT COUNT"
default 0
help
BTH ISO ALT COUNT.
endmenu # "Bluetooth Host Device Class"
menu "Network driver (ECM/NCM/RNDIS)"
choice TINYUSB_NET_MODE
prompt "Network mode"
default TINYUSB_NET_MODE_NONE
help
Select network driver you want to use.
config TINYUSB_NET_MODE_ECM_RNDIS
bool "ECM/RNDIS"
config TINYUSB_NET_MODE_NCM
bool "NCM"
config TINYUSB_NET_MODE_NONE
bool "None"
endchoice
config TINYUSB_NCM_OUT_NTB_BUFFS_COUNT
int "Number of NCM NTB buffers for reception side"
depends on TINYUSB_NET_MODE_NCM
default 3
range 1 6
help
Number of NTB buffers for reception side.
Can be increased to improve performance and stability with the cost of additional RAM requirements.
Helps to mitigate "tud_network_can_xmit: request blocked" warning message when running NCM device.
config TINYUSB_NCM_IN_NTB_BUFFS_COUNT
int "Number of NCM NTB buffers for transmission side"
depends on TINYUSB_NET_MODE_NCM
default 3
range 1 6
help
Number of NTB buffers for transmission side.
Can be increased to improve performance and stability with the cost of additional RAM requirements.
Helps to mitigate "tud_network_can_xmit: request blocked" warning message when running NCM device.
config TINYUSB_NCM_OUT_NTB_BUFF_MAX_SIZE
int "NCM NTB Buffer size for reception size"
depends on TINYUSB_NET_MODE_NCM
default 3200
range 1600 10240
help
Size of NTB buffers on the reception side. The minimum size used by Linux is 2048 bytes.
NTB buffer size must be significantly larger than the MTU (Maximum Transmission Unit).
The typical default MTU size for Ethernet is 1500 bytes, plus an additional packet overhead.
To improve performance, the NTB buffer size should be large enough to fit multiple MTU-sized
frames in a single NTB buffer and it's length should be multiple of 4.
config TINYUSB_NCM_IN_NTB_BUFF_MAX_SIZE
int "NCM NTB Buffer size for transmission size"
depends on TINYUSB_NET_MODE_NCM
default 3200
range 1600 10240
help
Size of NTB buffers on the transmission side. The minimum size used by Linux is 2048 bytes.
NTB buffer size must be significantly larger than the MTU (Maximum Transmission Unit).
The typical default MTU size for Ethernet is 1500 bytes, plus an additional packet overhead.
To improve performance, the NTB buffer size should be large enough to fit multiple MTU-sized
frames in a single NTB buffer and it's length should be multiple of 4.
endmenu # "Network driver (ECM/NCM/RNDIS)"
menu "Vendor Specific Interface"
config TINYUSB_VENDOR_COUNT
int "TinyUSB Vendor specific interfaces count"
default 0
range 0 2
help
Setting value greater than 0 will enable TinyUSB Vendor specific feature.
endmenu # "Vendor Specific Interface"
endmenu # "TinyUSB Stack"

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,166 @@
# Espressif's additions to TinyUSB
[![Component Registry](https://components.espressif.com/components/espressif/esp_tinyusb/badge.svg)](https://components.espressif.com/components/espressif/esp_tinyusb)
This component adds features to TinyUSB that help users with integrating TinyUSB with their ESP-IDF application.
It contains:
* Configuration of USB device and string descriptors
* USB Serial Device (CDC-ACM) with optional Virtual File System support
* Input and output streams through USB Serial Device. This feature is available only when Virtual File System support is enabled.
* Other USB classes (MIDI, MSC, HID…) support directly via TinyUSB
* VBUS monitoring for self-powered devices
* SPI Flash or sd-card access via MSC USB device Class.
## How to use?
This component is distributed via [IDF component manager](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-component-manager.html). Just add `idf_component.yml` file to your main component with the following content:
``` yaml
## IDF Component Manager Manifest File
dependencies:
esp_tinyusb: "~1.0.0"
```
Or simply run:
```
idf.py add-dependency esp_tinyusb~1.0.0
```
## Documentation
Hardware-related documentation could be found in [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/usb_device.html).
### Device Stack Structure
The Device Stack is built on top of TinyUSB and provides:
- Custom USB descriptor support
- Serial device (CDC-ACM) support
- Standard stream redirection through the serial device
- Storage media support (SPI-Flash and SD-Card) for USB MSC Class
- A dedicated task for TinyUSB servicing
### Configuration Options
Configure the Device Stack using `menuconfig`:
- TinyUSB log verbosity
- Device Stack task options
- Default device/string descriptor options
- Class-specific options
### Descriptor Configuration
Configure USB descriptors using the `tinyusb_config_t` structure:
- `device_descriptor`
- `string_descriptor`
- `configuration_descriptor` (full-speed)
- For high-speed devices: `fs_configuration_descriptor`, `hs_configuration_descriptor`, `qualifier_descriptor`
If any descriptor field is set to `NULL`, default descriptors (based on menuconfig) are used.
### Installation
Install the Device Stack by calling `tinyusb_driver_install` with a `tinyusb_config_t` structure. Members set to `0` or `NULL` use default values.
```c
const tinyusb_config_t partial_init = {
.device_descriptor = NULL,
.string_descriptor = NULL,
.external_phy = false,
#if (TUD_OPT_HIGH_SPEED)
.fs_configuration_descriptor = NULL,
.hs_configuration_descriptor = NULL,
.qualifier_descriptor = NULL,
#else
.configuration_descriptor = NULL,
#endif
};
```
### Self-Powered Device
Self-powered devices must monitor VBUS voltage. Use a GPIO pin with a voltage divider or comparator to detect VBUS state. Set `self_powered = true` and assign the VBUS monitor GPIO in `tinyusb_config_t`.
### USB Serial Device (CDC-ACM)
If enabled, initialize the USB Serial Device with `tusb_cdc_acm_init` and a `tinyusb_config_cdcacm_t` structure:
```c
const tinyusb_config_cdcacm_t acm_cfg = {
.usb_dev = TINYUSB_USBDEV_0,
.cdc_port = TINYUSB_CDC_ACM_0,
.rx_unread_buf_sz = 64,
.callback_rx = NULL,
.callback_rx_wanted_char = NULL,
.callback_line_state_changed = NULL,
.callback_line_coding_changed = NULL
};
tusb_cdc_acm_init(&acm_cfg);
```
Redirect standard I/O streams to USB with `esp_tusb_init_console` and revert with `esp_tusb_deinit_console`.
### USB Mass Storage Device (MSC)
If enabled, initialize storage media for MSC:
**SPI-Flash Example:**
```c
static esp_err_t storage_init_spiflash(wl_handle_t *wl_handle) {
// ... partition and mount logic ...
}
storage_init_spiflash(&wl_handle);
const tinyusb_msc_spiflash_config_t config_spi = {
.wl_handle = wl_handle
};
tinyusb_msc_storage_init_spiflash(&config_spi);
```
**SD-Card Example:**
```c
static esp_err_t storage_init_sdmmc(sdmmc_card_t **card) {
// ... SDMMC host and slot config ...
}
storage_init_sdmmc(&card);
const tinyusb_msc_sdmmc_config_t config_sdmmc = {
.card = card
};
tinyusb_msc_storage_init_sdmmc(&config_sdmmc);
```
### MSC Performance Optimization
- **Single-buffer approach:** Buffer size is set via `CONFIG_TINYUSB_MSC_BUFSIZE`.
- **Performance:** SD cards offer higher throughput than internal SPI flash due to architectural constraints.
**Performance Table (ESP32-S3):**
| FIFO Size | Read Speed | Write Speed |
|-----------|------------|-------------|
| 512 B | 0.566 MB/s | 0.236 MB/s |
| 8192 B | 0.925 MB/s | 0.928 MB/s |
**Performance Table (ESP32-P4):**
| FIFO Size | Read Speed | Write Speed |
|-----------|------------|-------------|
| 512 B | 1.174 MB/s | 0.238 MB/s |
| 8192 B | 4.744 MB/s | 2.157 MB/s |
| 32768 B | 5.998 MB/s | 4.485 MB/s |
**Performance Table (ESP32-S2, SPI Flash):**
| FIFO Size | Write Speed |
|-----------|-------------|
| 512 B | 5.59 KB/s |
| 8192 B | 21.54 KB/s |
**Note:** Internal SPI flash is for demonstration only; use SD cards or external flash for higher performance.
## Examples
You can find examples in [ESP-IDF on GitHub](https://github.com/espressif/esp-idf/tree/master/examples/peripherals/usb/device).

View File

@@ -0,0 +1,109 @@
/*
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "esp_check.h"
#include "esp_err.h"
#include "esp_log.h"
#include "tusb.h"
#include "cdc.h"
#define CDC_INTF_NUM CFG_TUD_CDC // number of cdc blocks
static esp_tusb_cdc_t *cdc_obj[CDC_INTF_NUM] = {};
static const char *TAG = "tusb_cdc";
esp_tusb_cdc_t *tinyusb_cdc_get_intf(int itf_num)
{
if (itf_num >= CDC_INTF_NUM || itf_num < 0) {
return NULL;
}
return cdc_obj[itf_num];
}
static esp_err_t cdc_obj_check(int itf, bool expected_inited, tusb_class_code_t expected_type)
{
esp_tusb_cdc_t *this_itf = tinyusb_cdc_get_intf(itf);
bool inited = (this_itf != NULL);
ESP_RETURN_ON_FALSE(expected_inited == inited, ESP_ERR_INVALID_STATE, TAG, "Wrong state of the interface. Expected state: %s", expected_inited ? "initialized" : "not initialized");
ESP_RETURN_ON_FALSE(!(inited && (expected_type != -1) && !(this_itf->type == expected_type)), ESP_ERR_INVALID_STATE, TAG, "Wrong type of the interface. Should be : 0x%x (tusb_class_code_t)", expected_type);
return ESP_OK;
}
static esp_err_t tusb_cdc_comm_init(int itf)
{
ESP_RETURN_ON_ERROR(cdc_obj_check(itf, false, -1), TAG, "cdc_obj_check failed");
cdc_obj[itf] = calloc(1, sizeof(esp_tusb_cdc_t));
if (cdc_obj[itf] != NULL) {
cdc_obj[itf]->type = TUSB_CLASS_CDC;
ESP_LOGD(TAG, "CDC Comm class initialized");
return ESP_OK;
} else {
ESP_LOGE(TAG, "CDC Comm initialization error");
return ESP_FAIL;
}
}
static esp_err_t tusb_cdc_deinit_comm(int itf)
{
ESP_RETURN_ON_ERROR(cdc_obj_check(itf, true, TUSB_CLASS_CDC), TAG, "cdc_obj_check failed");
free(cdc_obj[itf]);
cdc_obj[itf] = NULL;
return ESP_OK;
}
static esp_err_t tusb_cdc_data_init(int itf)
{
ESP_RETURN_ON_ERROR(cdc_obj_check(itf, false, TUSB_CLASS_CDC_DATA), TAG, "cdc_obj_check failed");
cdc_obj[itf] = calloc(1, sizeof(esp_tusb_cdc_t));
if (cdc_obj[itf] != NULL) {
cdc_obj[itf]->type = TUSB_CLASS_CDC_DATA;
ESP_LOGD(TAG, "CDC Data class initialized");
return ESP_OK;
} else {
ESP_LOGE(TAG, "CDC Data initialization error");
return ESP_FAIL;
}
}
static esp_err_t tusb_cdc_deinit_data(int itf)
{
ESP_RETURN_ON_ERROR(cdc_obj_check(itf, true, TUSB_CLASS_CDC_DATA), TAG, "cdc_obj_check failed");
free(cdc_obj[itf]);
cdc_obj[itf] = NULL;
return ESP_OK;
}
esp_err_t tinyusb_cdc_init(int itf, const tinyusb_config_cdc_t *cfg)
{
ESP_RETURN_ON_ERROR(cdc_obj_check(itf, false, -1), TAG, "cdc_obj_check failed");
ESP_LOGD(TAG, "Init CDC %d", itf);
if (cfg->cdc_class == TUSB_CLASS_CDC) {
ESP_RETURN_ON_ERROR(tusb_cdc_comm_init(itf), TAG, "tusb_cdc_comm_init failed");
cdc_obj[itf]->cdc_subclass.comm_subclass = cfg->cdc_subclass.comm_subclass;
} else {
ESP_RETURN_ON_ERROR(tusb_cdc_data_init(itf), TAG, "tusb_cdc_data_init failed");
cdc_obj[itf]->cdc_subclass.data_subclass = cfg->cdc_subclass.data_subclass;
}
cdc_obj[itf]->usb_dev = cfg->usb_dev;
return ESP_OK;
}
esp_err_t tinyusb_cdc_deinit(int itf)
{
ESP_RETURN_ON_ERROR(cdc_obj_check(itf, true, -1), TAG, "cdc_obj_check failed");
ESP_LOGD(TAG, "Deinit CDC %d", itf);
if (cdc_obj[itf]->type == TUSB_CLASS_CDC) {
ESP_RETURN_ON_ERROR(tusb_cdc_deinit_comm(itf), TAG, "tusb_cdc_deinit_comm failed");
} else if (cdc_obj[itf]->type == TUSB_CLASS_CDC_DATA) {
ESP_RETURN_ON_ERROR(tusb_cdc_deinit_data(itf), TAG, "tusb_cdc_deinit_data failed");
} else {
return ESP_ERR_INVALID_ARG;
}
return ESP_OK;
}

View File

@@ -0,0 +1,294 @@
/*
* SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "esp_log.h"
#include "esp_check.h"
#include "esp_err.h"
#include "descriptors_control.h"
#include "usb_descriptors.h"
#define MAX_DESC_BUF_SIZE 32 // Max length of string descriptor (can be extended, USB supports lengths up to 255 bytes)
static const char *TAG = "tusb_desc";
// =============================================================================
// STRUCTS
// =============================================================================
/**
* @brief Descriptor pointers for tinyusb descriptor requests callbacks
*
*/
typedef struct {
const tusb_desc_device_t *dev; /*!< Pointer to device descriptor */
union {
const uint8_t *cfg; /*!< Pointer to FullSpeed configuration descriptor when device one-speed only */
const uint8_t *fs_cfg; /*!< Pointer to FullSpeed configuration descriptor when device support HighSpeed */
};
#if (TUD_OPT_HIGH_SPEED)
const uint8_t *hs_cfg; /*!< Pointer to HighSpeed configuration descriptor */
const tusb_desc_device_qualifier_t *qualifier; /*!< Pointer to Qualifier descriptor */
uint8_t *other_speed; /*!< Pointer for other speed configuration descriptor */
#endif // TUD_OPT_HIGH_SPEED
const char *str[USB_STRING_DESCRIPTOR_ARRAY_SIZE]; /*!< Pointer to array of UTF-8 strings */
int str_count; /*!< Number of descriptors in str */
} tinyusb_descriptor_config_t;
static tinyusb_descriptor_config_t s_desc_cfg;
// =============================================================================
// CALLBACKS
// =============================================================================
/**
* @brief Invoked when received GET DEVICE DESCRIPTOR.
* Descriptor contents must exist long enough for transfer to complete
*
* @return Pointer to device descriptor
*/
uint8_t const *tud_descriptor_device_cb(void)
{
assert(s_desc_cfg.dev);
return (uint8_t const *)s_desc_cfg.dev;
}
/**
* @brief Invoked when received GET CONFIGURATION DESCRIPTOR.
* Descriptor contents must exist long enough for transfer to complete
*
* @param[in] index Index of required configuration
* @return Pointer to configuration descriptor
*/
uint8_t const *tud_descriptor_configuration_cb(uint8_t index)
{
(void)index; // Unused, this driver supports only 1 configuration
assert(s_desc_cfg.cfg);
#if (TUD_OPT_HIGH_SPEED)
// HINT: cfg and fs_cfg are union, no need to assert(fs_cfg)
assert(s_desc_cfg.hs_cfg);
// Return configuration descriptor based on Host speed
return (TUSB_SPEED_HIGH == tud_speed_get())
? s_desc_cfg.hs_cfg
: s_desc_cfg.fs_cfg;
#else
return s_desc_cfg.cfg;
#endif // TUD_OPT_HIGH_SPEED
}
#if (TUD_OPT_HIGH_SPEED)
/**
* @brief Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request
* Descriptor contents must exist long enough for transfer to complete
* If not highspeed capable stall this request
*/
uint8_t const *tud_descriptor_device_qualifier_cb(void)
{
assert(s_desc_cfg.qualifier);
return (uint8_t const *)s_desc_cfg.qualifier;
}
/**
* @brief Invoked when received GET OTHER SPEED CONFIGURATION DESCRIPTOR request
* Descriptor contents must exist long enough for transfer to complete
* Configuration descriptor in the other speed e.g if high speed then this is for full speed and vice versa
*/
uint8_t const *tud_descriptor_other_speed_configuration_cb(uint8_t index)
{
assert(s_desc_cfg.other_speed);
const uint8_t *other_speed = (TUSB_SPEED_HIGH == tud_speed_get())
? s_desc_cfg.fs_cfg
: s_desc_cfg.hs_cfg;
memcpy(s_desc_cfg.other_speed,
other_speed,
((tusb_desc_configuration_t *)other_speed)->wTotalLength);
((tusb_desc_configuration_t *)s_desc_cfg.other_speed)->bDescriptorType = TUSB_DESC_OTHER_SPEED_CONFIG;
return s_desc_cfg.other_speed;
}
#endif // TUD_OPT_HIGH_SPEED
/**
* @brief Invoked when received GET STRING DESCRIPTOR request
*
* @param[in] index Index of required descriptor
* @param[in] langid Language of the descriptor
* @return Pointer to UTF-16 string descriptor
*/
uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid)
{
(void) langid; // Unused, this driver supports only one language in string descriptors
assert(s_desc_cfg.str);
uint8_t chr_count;
static uint16_t _desc_str[MAX_DESC_BUF_SIZE];
if (index == 0) {
memcpy(&_desc_str[1], s_desc_cfg.str[0], 2);
chr_count = 1;
} else {
if (index >= USB_STRING_DESCRIPTOR_ARRAY_SIZE) {
ESP_LOGW(TAG, "String index (%u) is out of bounds, check your string descriptor", index);
return NULL;
}
if (s_desc_cfg.str[index] == NULL) {
ESP_LOGW(TAG, "String index (%u) points to NULL, check your string descriptor", index);
return NULL;
}
const char *str = s_desc_cfg.str[index];
chr_count = strnlen(str, MAX_DESC_BUF_SIZE - 1); // Buffer len - header
// Convert ASCII string into UTF-16
for (uint8_t i = 0; i < chr_count; i++) {
_desc_str[1 + i] = str[i];
}
}
// First byte is length in bytes (including header), second byte is descriptor type (TUSB_DESC_STRING)
_desc_str[0] = (TUSB_DESC_STRING << 8 ) | (2 * chr_count + 2);
return _desc_str;
}
// =============================================================================
// Driver functions
// =============================================================================
esp_err_t tinyusb_set_descriptors(const tinyusb_config_t *config)
{
esp_err_t ret = ESP_FAIL;
assert(config);
const char **pstr_desc;
// Flush descriptors control struct
memset(&s_desc_cfg, 0x00, sizeof(tinyusb_descriptor_config_t));
// Parse configuration and save descriptors's pointer
// Select Device Descriptor
if (config->device_descriptor == NULL) {
ESP_LOGW(TAG, "No Device descriptor provided, using default.");
s_desc_cfg.dev = &descriptor_dev_default;
} else {
s_desc_cfg.dev = config->device_descriptor;
}
// Select FullSpeed configuration descriptor
if (config->configuration_descriptor == NULL) {
// Default configuration descriptor must be provided for the following classes
#if (CFG_TUD_HID > 0 || CFG_TUD_MIDI > 0 || CFG_TUD_ECM_RNDIS > 0 || CFG_TUD_DFU > 0 || CFG_TUD_DFU_RUNTIME > 0 || CFG_TUD_BTH > 0)
ESP_GOTO_ON_FALSE(config->configuration_descriptor, ESP_ERR_INVALID_ARG, fail, TAG, "Configuration descriptor must be provided for this device");
#else
ESP_LOGW(TAG, "No FullSpeed configuration descriptor provided, using default.");
s_desc_cfg.cfg = descriptor_fs_cfg_default;
#endif
} else {
s_desc_cfg.cfg = config->configuration_descriptor;
}
#if (TUD_OPT_HIGH_SPEED)
// High Speed
if (config->hs_configuration_descriptor == NULL) {
// Default configuration descriptor must be provided for the following classes
#if (CFG_TUD_HID > 0 || CFG_TUD_MIDI > 0 || CFG_TUD_ECM_RNDIS > 0 || CFG_TUD_DFU > 0 || CFG_TUD_DFU_RUNTIME > 0 || CFG_TUD_BTH > 0)
ESP_GOTO_ON_FALSE(config->hs_configuration_descriptor, ESP_ERR_INVALID_ARG, fail, TAG, "HighSpeed configuration descriptor must be provided for this device");
#else
ESP_LOGW(TAG, "No HighSpeed configuration descriptor provided, using default.");
s_desc_cfg.hs_cfg = descriptor_hs_cfg_default;
#endif
} else {
s_desc_cfg.hs_cfg = config->hs_configuration_descriptor;
}
// HS and FS cfg desc should be equal length
ESP_GOTO_ON_FALSE(((tusb_desc_configuration_t *)s_desc_cfg.hs_cfg)->wTotalLength ==
((tusb_desc_configuration_t *)s_desc_cfg.fs_cfg)->wTotalLength,
ESP_ERR_INVALID_ARG, fail, TAG, "HighSpeed and FullSpeed configuration descriptors must be same length");
// Qualifier Descriptor
if (config->qualifier_descriptor == NULL) {
ESP_GOTO_ON_FALSE((s_desc_cfg.dev == &descriptor_dev_default), ESP_ERR_INVALID_ARG, fail, TAG, "Qualifier descriptor must be present (Device Descriptor not default).");
// Get default qualifier if device descriptor is default
ESP_LOGW(TAG, "No Qulifier descriptor provided, using default.");
s_desc_cfg.qualifier = &descriptor_qualifier_default;
} else {
s_desc_cfg.qualifier = config->qualifier_descriptor;
}
// Other Speed buffer allocate
s_desc_cfg.other_speed = calloc(1, ((tusb_desc_configuration_t *)s_desc_cfg.hs_cfg)->wTotalLength);
ESP_GOTO_ON_FALSE(s_desc_cfg.other_speed, ESP_ERR_NO_MEM, fail, TAG, "Other speed memory allocation error");
#endif // TUD_OPT_HIGH_SPEED
// Select String Descriptors and count them
if (config->string_descriptor == NULL) {
ESP_LOGW(TAG, "No String descriptors provided, using default.");
pstr_desc = descriptor_str_default;
while (descriptor_str_default[++s_desc_cfg.str_count] != NULL);
} else {
pstr_desc = config->string_descriptor;
s_desc_cfg.str_count = (config->string_descriptor_count != 0)
? config->string_descriptor_count
: 8; // '8' is for backward compatibility with esp_tinyusb v1.0.0. Do NOT remove!
}
ESP_GOTO_ON_FALSE(s_desc_cfg.str_count <= USB_STRING_DESCRIPTOR_ARRAY_SIZE, ESP_ERR_NOT_SUPPORTED, fail, TAG, "String descriptors exceed limit");
memcpy(s_desc_cfg.str, pstr_desc, s_desc_cfg.str_count * sizeof(pstr_desc[0]));
ESP_LOGI(TAG, "\n"
"┌─────────────────────────────────┐\n"
"│ USB Device Descriptor Summary │\n"
"├───────────────────┬─────────────┤\n"
"│bDeviceClass │ %-4u │\n"
"├───────────────────┼─────────────┤\n"
"│bDeviceSubClass │ %-4u │\n"
"├───────────────────┼─────────────┤\n"
"│bDeviceProtocol │ %-4u │\n"
"├───────────────────┼─────────────┤\n"
"│bMaxPacketSize0 │ %-4u │\n"
"├───────────────────┼─────────────┤\n"
"│idVendor │ %-#10x │\n"
"├───────────────────┼─────────────┤\n"
"│idProduct │ %-#10x │\n"
"├───────────────────┼─────────────┤\n"
"│bcdDevice │ %-#10x │\n"
"├───────────────────┼─────────────┤\n"
"│iManufacturer │ %-#10x │\n"
"├───────────────────┼─────────────┤\n"
"│iProduct │ %-#10x │\n"
"├───────────────────┼─────────────┤\n"
"│iSerialNumber │ %-#10x │\n"
"├───────────────────┼─────────────┤\n"
"│bNumConfigurations │ %-#10x │\n"
"└───────────────────┴─────────────┘",
s_desc_cfg.dev->bDeviceClass, s_desc_cfg.dev->bDeviceSubClass,
s_desc_cfg.dev->bDeviceProtocol, s_desc_cfg.dev->bMaxPacketSize0,
s_desc_cfg.dev->idVendor, s_desc_cfg.dev->idProduct, s_desc_cfg.dev->bcdDevice,
s_desc_cfg.dev->iManufacturer, s_desc_cfg.dev->iProduct, s_desc_cfg.dev->iSerialNumber,
s_desc_cfg.dev->bNumConfigurations);
return ESP_OK;
fail:
#if (TUD_OPT_HIGH_SPEED)
free(s_desc_cfg.other_speed);
#endif // TUD_OPT_HIGH_SPEED
return ret;
}
void tinyusb_set_str_descriptor(const char *str, int str_idx)
{
assert(str_idx < USB_STRING_DESCRIPTOR_ARRAY_SIZE);
s_desc_cfg.str[str_idx] = str;
}
void tinyusb_free_descriptors(void)
{
#if (TUD_OPT_HIGH_SPEED)
assert(s_desc_cfg.other_speed);
free(s_desc_cfg.other_speed);
#endif // TUD_OPT_HIGH_SPEED
}

View File

@@ -0,0 +1,13 @@
dependencies:
idf: '>=5.0'
tinyusb:
public: true
version: '>=0.14.2'
description: Espressif's additions to TinyUSB
documentation: https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/usb_device.html
repository: git://github.com/espressif/esp-usb.git
repository_info:
commit_sha: c0be948c1acc6ee1f7ef00ab183c571971fccefd
path: device/esp_tinyusb
url: https://github.com/espressif/esp-usb/tree/master/device/esp_tinyusb
version: 1.7.6~2

View File

@@ -0,0 +1,85 @@
/*
* SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include "esp_err.h"
#include "tusb.h"
#include "tinyusb_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Configuration structure of the TinyUSB core
*
* USB specification mandates self-powered devices to monitor USB VBUS to detect connection/disconnection events.
* If you want to use this feature, connected VBUS to any free GPIO through a voltage divider or voltage comparator.
* The voltage divider output should be (0.75 * Vdd) if VBUS is 4.4V (lowest valid voltage at device port).
* The comparator thresholds should be set with hysteresis: 4.35V (falling edge) and 4.75V (raising edge).
*/
typedef struct {
union {
const tusb_desc_device_t *device_descriptor; /*!< Pointer to a device descriptor. If set to NULL, the TinyUSB device will use a default device descriptor whose values are set in Kconfig */
const tusb_desc_device_t *descriptor __attribute__((deprecated)); /*!< Alias to `device_descriptor` for backward compatibility */
};
const char **string_descriptor; /*!< Pointer to array of string descriptors. If set to NULL, TinyUSB device will use a default string descriptors whose values are set in Kconfig */
int string_descriptor_count; /*!< Number of descriptors in above array */
bool external_phy; /*!< Should USB use an external PHY */
union {
struct {
const uint8_t *configuration_descriptor; /*!< Pointer to a configuration descriptor. If set to NULL, TinyUSB device will use a default configuration descriptor whose values are set in Kconfig */
};
#if (TUD_OPT_HIGH_SPEED)
struct {
const uint8_t *fs_configuration_descriptor; /*!< Pointer to a FullSpeed configuration descriptor. If set to NULL, TinyUSB device will use a default configuration descriptor whose values are set in Kconfig */
};
};
const uint8_t *hs_configuration_descriptor; /*!< Pointer to a HighSpeed configuration descriptor. If set to NULL, TinyUSB device will use a default configuration descriptor whose values are set in Kconfig */
const tusb_desc_device_qualifier_t *qualifier_descriptor; /*!< Pointer to a qualifier descriptor */
#else
};
#endif // TUD_OPT_HIGH_SPEED
bool self_powered; /*!< This is a self-powered USB device. USB VBUS must be monitored. */
int vbus_monitor_io; /*!< GPIO for VBUS monitoring. Ignored if not self_powered. */
} tinyusb_config_t;
/**
* @brief This is an all-in-one helper function, including:
* 1. USB device driver initialization
* 2. Descriptors preparation
* 3. TinyUSB stack initialization
* 4. Creates and start a task to handle usb events
*
* @note Don't change Custom descriptor, but if it has to be done,
* Suggest to define as follows in order to match the Interface Association Descriptor (IAD):
* bDeviceClass = TUSB_CLASS_MISC,
* bDeviceSubClass = MISC_SUBCLASS_COMMON,
*
* @param config tinyusb stack specific configuration
* @retval ESP_ERR_INVALID_ARG Install driver and tinyusb stack failed because of invalid argument
* @retval ESP_FAIL Install driver and tinyusb stack failed because of internal error
* @retval ESP_OK Install driver and tinyusb stack successfully
*/
esp_err_t tinyusb_driver_install(const tinyusb_config_t *config);
/**
* @brief This is an all-in-one helper function, including:
* 1. Stops the task to handle usb events
* 2. TinyUSB stack tearing down
* 2. Freeing resources after descriptors preparation
* 3. Deletes USB PHY
*
* @retval ESP_FAIL Uninstall driver or tinyusb stack failed because of internal error
* @retval ESP_OK Uninstall driver, tinyusb stack and USB PHY successfully
*/
esp_err_t tinyusb_driver_uninstall(void);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,99 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "tinyusb_types.h"
#include "esp_err.h"
#include "sdkconfig.h"
#if (CONFIG_TINYUSB_NET_MODE_NONE != 1)
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief On receive callback type
*/
typedef esp_err_t (*tusb_net_rx_cb_t)(void *buffer, uint16_t len, void *ctx);
/**
* @brief Free Tx buffer callback type
*/
typedef void (*tusb_net_free_tx_cb_t)(void *buffer, void *ctx);
/**
* @brief On init callback type
*/
typedef void (*tusb_net_init_cb_t)(void *ctx);
/**
* @brief ESP TinyUSB NCM driver configuration structure
*/
typedef struct {
uint8_t mac_addr[6]; /*!< MAC address. Must be 6 bytes long. */
tusb_net_rx_cb_t on_recv_callback; /*!< TinyUSB receive data callbeck */
tusb_net_free_tx_cb_t free_tx_buffer; /*!< User function for freeing the Tx buffer.
* - could be NULL, if user app is responsible for freeing the buffer
* - must be used in asynchronous send mode
* - is only called if the used tinyusb_net_send...() function returns ESP_OK
* - in sync mode means that the packet was accepted by TinyUSB
* - in async mode means that the packet was queued to be processed in TinyUSB task
*/
tusb_net_init_cb_t on_init_callback; /*!< TinyUSB init network callback */
void *user_context; /*!< User context to be passed to any of the callback */
} tinyusb_net_config_t;
/**
* @brief Initialize TinyUSB NET driver
*
* @param[in] usb_dev USB device to use
* @param[in] cfg Configuration of the driver
* @return esp_err_t
*/
esp_err_t tinyusb_net_init(tinyusb_usbdev_t usb_dev, const tinyusb_net_config_t *cfg);
/**
* @brief TinyUSB NET driver send data synchronously
*
* @note It is possible to use sync and async send interchangeably.
* This function needs some synchronization primitives, so using sync mode (even once) uses more heap
*
* @param[in] buffer USB send data
* @param[in] len Send data len
* @param[in] buff_free_arg Pointer to be passed to the free_tx_buffer() callback
* @param[in] timeout Send data len
* @return ESP_OK on success == packet has been consumed by tusb and would be eventually freed
* by free_tx_buffer() callback (if non null)
* ESP_ERR_TIMEOUT on timeout
* ESP_ERR_INVALID_STATE if tusb not initialized, ESP_ERR_NO_MEM on alloc failure
*/
esp_err_t tinyusb_net_send_sync(void *buffer, uint16_t len, void *buff_free_arg, TickType_t timeout);
/**
* @brief TinyUSB NET driver send data asynchronously
*
* @note If using asynchronous sends, you must free the buffer using free_tx_buffer() callback.
* @note It is possible to use sync and async send interchangeably.
* @note Async flavor of the send is useful when the USB stack runs faster than the caller,
* since we have no control over the transmitted packets, if they get accepted or discarded.
*
* @param[in] buffer USB send data
* @param[in] len Send data len
* @param[in] buff_free_arg Pointer to be passed to the free_tx_buffer() callback
* @return ESP_OK on success == packet has been consumed by tusb and will be freed
* by free_tx_buffer() callback (if non null)
* ESP_ERR_INVALID_STATE if tusb not initialized
*/
esp_err_t tinyusb_net_send_async(void *buffer, uint16_t len, void *buff_free_arg);
#endif // (CONFIG_TINYUSB_NET_MODE_NONE != 1)
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,21 @@
/*
* SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#define USB_ESPRESSIF_VID 0x303A
typedef enum {
TINYUSB_USBDEV_0,
} tinyusb_usbdev_t;
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,206 @@
/*
* SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include "sdkconfig.h"
#include "esp_err.h"
#include "tinyusb_types.h"
#include "class/cdc/cdc.h"
#if (CONFIG_TINYUSB_CDC_ENABLED != 1)
#error "TinyUSB CDC driver must be enabled in menuconfig"
#endif
/**
* @brief CDC ports available to setup
*/
typedef enum {
TINYUSB_CDC_ACM_0 = 0x0,
TINYUSB_CDC_ACM_1,
TINYUSB_CDC_ACM_MAX
} tinyusb_cdcacm_itf_t;
/* Callbacks and events
********************************************************************* */
/**
* @brief Data provided to the input of the `callback_rx_wanted_char` callback
*/
typedef struct {
char wanted_char; /*!< Wanted character */
} cdcacm_event_rx_wanted_char_data_t;
/**
* @brief Data provided to the input of the `callback_line_state_changed` callback
*/
typedef struct {
bool dtr; /*!< Data Terminal Ready (DTR) line state */
bool rts; /*!< Request To Send (RTS) line state */
} cdcacm_event_line_state_changed_data_t;
/**
* @brief Data provided to the input of the `line_coding_changed` callback
*/
typedef struct {
cdc_line_coding_t const *p_line_coding; /*!< New line coding value */
} cdcacm_event_line_coding_changed_data_t;
/**
* @brief Types of CDC ACM events
*/
typedef enum {
CDC_EVENT_RX,
CDC_EVENT_RX_WANTED_CHAR,
CDC_EVENT_LINE_STATE_CHANGED,
CDC_EVENT_LINE_CODING_CHANGED
} cdcacm_event_type_t;
/**
* @brief Describes an event passing to the input of a callbacks
*/
typedef struct {
cdcacm_event_type_t type; /*!< Event type */
union {
cdcacm_event_rx_wanted_char_data_t rx_wanted_char_data; /*!< Data input of the `callback_rx_wanted_char` callback */
cdcacm_event_line_state_changed_data_t line_state_changed_data; /*!< Data input of the `callback_line_state_changed` callback */
cdcacm_event_line_coding_changed_data_t line_coding_changed_data; /*!< Data input of the `line_coding_changed` callback */
};
} cdcacm_event_t;
/**
* @brief CDC-ACM callback type
*/
typedef void(*tusb_cdcacm_callback_t)(int itf, cdcacm_event_t *event);
/*********************************************************************** Callbacks and events*/
/* Other structs
********************************************************************* */
/**
* @brief Configuration structure for CDC-ACM
*/
typedef struct {
tinyusb_usbdev_t usb_dev; /*!< Usb device to set up */
tinyusb_cdcacm_itf_t cdc_port; /*!< CDC port */
size_t rx_unread_buf_sz __attribute__((deprecated("This parameter is not used any more. Configure RX buffer in menuconfig.")));
tusb_cdcacm_callback_t callback_rx; /*!< Pointer to the function with the `tusb_cdcacm_callback_t` type that will be handled as a callback */
tusb_cdcacm_callback_t callback_rx_wanted_char; /*!< Pointer to the function with the `tusb_cdcacm_callback_t` type that will be handled as a callback */
tusb_cdcacm_callback_t callback_line_state_changed; /*!< Pointer to the function with the `tusb_cdcacm_callback_t` type that will be handled as a callback */
tusb_cdcacm_callback_t callback_line_coding_changed; /*!< Pointer to the function with the `tusb_cdcacm_callback_t` type that will be handled as a callback */
} tinyusb_config_cdcacm_t;
/*********************************************************************** Other structs*/
/* Public functions
********************************************************************* */
/**
* @brief Initialize CDC ACM. Initialization will be finished with
* the `tud_cdc_line_state_cb` callback
*
* @param[in] cfg Configuration structure
* @return esp_err_t
*/
esp_err_t tusb_cdc_acm_init(const tinyusb_config_cdcacm_t *cfg);
/**
* @brief De-initialize CDC ACM.
*
* @param[in] itf Index of CDC interface
* @return esp_err_t
*/
esp_err_t tusb_cdc_acm_deinit(int itf);
/**
* @brief Register a callback invoking on CDC event. If the callback had been
* already registered, it will be overwritten
*
* @param[in] itf Index of CDC interface
* @param[in] event_type Type of registered event for a callback
* @param[in] callback Callback function
* @return esp_err_t - ESP_OK or ESP_ERR_INVALID_ARG
*/
esp_err_t tinyusb_cdcacm_register_callback(tinyusb_cdcacm_itf_t itf,
cdcacm_event_type_t event_type,
tusb_cdcacm_callback_t callback);
/**
* @brief Unregister a callback invoking on CDC event
*
* @param[in] itf Index of CDC interface
* @param[in] event_type Type of registered event for a callback
* @return esp_err_t - ESP_OK or ESP_ERR_INVALID_ARG
*/
esp_err_t tinyusb_cdcacm_unregister_callback(tinyusb_cdcacm_itf_t itf, cdcacm_event_type_t event_type);
/**
* @brief Sent one character to a write buffer
*
* @param[in] itf Index of CDC interface
* @param[in] ch Character to send
* @return size_t - amount of queued bytes
*/
size_t tinyusb_cdcacm_write_queue_char(tinyusb_cdcacm_itf_t itf, char ch);
/**
* @brief Write data to write buffer
*
* @param[in] itf Index of CDC interface
* @param[in] in_buf Data
* @param[in] in_size Data size in bytes
* @return size_t - amount of queued bytes
*/
size_t tinyusb_cdcacm_write_queue(tinyusb_cdcacm_itf_t itf, const uint8_t *in_buf, size_t in_size);
/**
* @brief Flush data in write buffer of CDC interface
*
* Use `tinyusb_cdcacm_write_queue` to add data to the buffer
*
* WARNING! TinyUSB can block output Endpoint for several RX callbacks, after will do additional flush
* after the each transfer. That can leads to the situation when you requested a flush, but it will fail until
* one of the next callbacks ends.
* SO USING OF THE FLUSH WITH TIMEOUTS IN CALLBACKS IS NOT RECOMMENDED - YOU CAN GET A LOCK FOR THE TIMEOUT
*
* @param[in] itf Index of CDC interface
* @param[in] timeout_ticks Transfer timeout. Set to zero for non-blocking mode
* @return - ESP_OK All data flushed
* - ESP_ERR_TIMEOUT Time out occurred in blocking mode
* - ESP_NOT_FINISHED The transfer is still in progress in non-blocking mode
*/
esp_err_t tinyusb_cdcacm_write_flush(tinyusb_cdcacm_itf_t itf, uint32_t timeout_ticks);
/**
* @brief Receive data from CDC interface
*
* @param[in] itf Index of CDC interface
* @param[out] out_buf Data buffer
* @param[in] out_buf_sz Data buffer size in bytes
* @param[out] rx_data_size Number of bytes written to out_buf
* @return esp_err_t ESP_OK, ESP_FAIL or ESP_ERR_INVALID_STATE
*/
esp_err_t tinyusb_cdcacm_read(tinyusb_cdcacm_itf_t itf, uint8_t *out_buf, size_t out_buf_sz, size_t *rx_data_size);
/**
* @brief Check if the CDC interface is initialized
*
* @param[in] itf Index of CDC interface
* @return - true Initialized
* - false Not Initialized
*/
bool tusb_cdc_acm_initialized(tinyusb_cdcacm_itf_t itf);
/*********************************************************************** Public functions*/
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,185 @@
/*
* SPDX-FileCopyrightText: 2019 Ha Thach (tinyusb.org),
* SPDX-FileContributor: 2020-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2019 Ha Thach (tinyusb.org),
* Additions Copyright (c) 2020, Espressif Systems (Shanghai) PTE LTD
*
* 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.
*
*/
#pragma once
#include "tusb_option.h"
#include "sdkconfig.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifndef CONFIG_TINYUSB_CDC_ENABLED
# define CONFIG_TINYUSB_CDC_ENABLED 0
#endif
#ifndef CONFIG_TINYUSB_CDC_COUNT
# define CONFIG_TINYUSB_CDC_COUNT 0
#endif
#ifndef CONFIG_TINYUSB_MSC_ENABLED
# define CONFIG_TINYUSB_MSC_ENABLED 0
#endif
#ifndef CONFIG_TINYUSB_HID_COUNT
# define CONFIG_TINYUSB_HID_COUNT 0
#endif
#ifndef CONFIG_TINYUSB_MIDI_COUNT
# define CONFIG_TINYUSB_MIDI_COUNT 0
#endif
#ifndef CONFIG_TINYUSB_VENDOR_COUNT
# define CONFIG_TINYUSB_VENDOR_COUNT 0
#endif
#ifndef CONFIG_TINYUSB_NET_MODE_ECM_RNDIS
# define CONFIG_TINYUSB_NET_MODE_ECM_RNDIS 0
#endif
#ifndef CONFIG_TINYUSB_NET_MODE_NCM
# define CONFIG_TINYUSB_NET_MODE_NCM 0
#endif
#ifndef CONFIG_TINYUSB_DFU_MODE_DFU
# define CONFIG_TINYUSB_DFU_MODE_DFU 0
#endif
#ifndef CONFIG_TINYUSB_DFU_MODE_DFU_RUNTIME
# define CONFIG_TINYUSB_DFU_MODE_DFU_RUNTIME 0
#endif
#ifndef CONFIG_TINYUSB_BTH_ENABLED
# define CONFIG_TINYUSB_BTH_ENABLED 0
# define CONFIG_TINYUSB_BTH_ISO_ALT_COUNT 0
#endif
#ifndef CONFIG_TINYUSB_DEBUG_LEVEL
# define CONFIG_TINYUSB_DEBUG_LEVEL 0
#endif
#ifdef CONFIG_TINYUSB_RHPORT_HS
# define CFG_TUSB_RHPORT1_MODE OPT_MODE_DEVICE | OPT_MODE_HIGH_SPEED
#else
# define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE | OPT_MODE_FULL_SPEED
#endif
// ------------------------------------------------------------------------
// DCD DWC2 Mode
// ------------------------------------------------------------------------
#define CFG_TUD_DWC2_SLAVE_ENABLE 1 // Enable Slave/IRQ by default
// ------------------------------------------------------------------------
// DMA & Cache
// ------------------------------------------------------------------------
#ifdef CONFIG_TINYUSB_MODE_DMA
// DMA Mode has a priority over Slave/IRQ mode and will be used if hardware supports it
#define CFG_TUD_DWC2_DMA_ENABLE 1 // Enable DMA
#if CONFIG_CACHE_L1_CACHE_LINE_SIZE
// To enable the dcd_dcache clean/invalidate/clean_invalidate calls
# define CFG_TUD_MEM_DCACHE_ENABLE 1
#define CFG_TUD_MEM_DCACHE_LINE_SIZE CONFIG_CACHE_L1_CACHE_LINE_SIZE
// NOTE: starting with esp-idf v5.3 there is specific attribute present: DRAM_DMA_ALIGNED_ATTR
# define CFG_TUSB_MEM_SECTION __attribute__((aligned(CONFIG_CACHE_L1_CACHE_LINE_SIZE))) DRAM_ATTR
#else
# define CFG_TUD_MEM_CACHE_ENABLE 0
# define CFG_TUSB_MEM_SECTION TU_ATTR_ALIGNED(4) DRAM_ATTR
#endif // CONFIG_CACHE_L1_CACHE_LINE_SIZE
#endif // CONFIG_TINYUSB_MODE_DMA
#define CFG_TUSB_OS OPT_OS_FREERTOS
/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment.
* Tinyusb use follows macros to declare transferring memory so that they can be put
* into those specific section.
* e.g
* - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
* - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4)))
*/
#ifndef CFG_TUSB_MEM_SECTION
# define CFG_TUSB_MEM_SECTION
#endif
#ifndef CFG_TUSB_MEM_ALIGN
# define CFG_TUSB_MEM_ALIGN TU_ATTR_ALIGNED(4)
#endif
#ifndef CFG_TUD_ENDPOINT0_SIZE
#define CFG_TUD_ENDPOINT0_SIZE 64
#endif
// Debug Level
#define CFG_TUSB_DEBUG CONFIG_TINYUSB_DEBUG_LEVEL
#define CFG_TUSB_DEBUG_PRINTF esp_rom_printf // TinyUSB can print logs from ISR, so we must use esp_rom_printf()
// CDC FIFO size of TX and RX
#define CFG_TUD_CDC_RX_BUFSIZE CONFIG_TINYUSB_CDC_RX_BUFSIZE
#define CFG_TUD_CDC_TX_BUFSIZE CONFIG_TINYUSB_CDC_TX_BUFSIZE
// MSC Buffer size of Device Mass storage
#define CFG_TUD_MSC_BUFSIZE CONFIG_TINYUSB_MSC_BUFSIZE
// MIDI macros
#define CFG_TUD_MIDI_EP_BUFSIZE 64
#define CFG_TUD_MIDI_EPSIZE CFG_TUD_MIDI_EP_BUFSIZE
#define CFG_TUD_MIDI_RX_BUFSIZE 64
#define CFG_TUD_MIDI_TX_BUFSIZE 64
// Vendor FIFO size of TX and RX
#define CFG_TUD_VENDOR_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
#define CFG_TUD_VENDOR_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64)
// DFU macros
#define CFG_TUD_DFU_XFER_BUFSIZE CONFIG_TINYUSB_DFU_BUFSIZE
// Number of BTH ISO alternatives
#define CFG_TUD_BTH_ISO_ALT_COUNT CONFIG_TINYUSB_BTH_ISO_ALT_COUNT
// Enabled device class driver
#define CFG_TUD_CDC CONFIG_TINYUSB_CDC_COUNT
#define CFG_TUD_MSC CONFIG_TINYUSB_MSC_ENABLED
#define CFG_TUD_HID CONFIG_TINYUSB_HID_COUNT
#define CFG_TUD_MIDI CONFIG_TINYUSB_MIDI_COUNT
#define CFG_TUD_VENDOR CONFIG_TINYUSB_VENDOR_COUNT
#define CFG_TUD_ECM_RNDIS CONFIG_TINYUSB_NET_MODE_ECM_RNDIS
#define CFG_TUD_NCM CONFIG_TINYUSB_NET_MODE_NCM
#define CFG_TUD_DFU CONFIG_TINYUSB_DFU_MODE_DFU
#define CFG_TUD_DFU_RUNTIME CONFIG_TINYUSB_DFU_MODE_DFU_RUNTIME
#define CFG_TUD_BTH CONFIG_TINYUSB_BTH_ENABLED
// NCM NET Mode NTB buffers configuration
#define CFG_TUD_NCM_OUT_NTB_N CONFIG_TINYUSB_NCM_OUT_NTB_BUFFS_COUNT
#define CFG_TUD_NCM_IN_NTB_N CONFIG_TINYUSB_NCM_IN_NTB_BUFFS_COUNT
#define CFG_TUD_NCM_OUT_NTB_MAX_SIZE CONFIG_TINYUSB_NCM_OUT_NTB_BUFF_MAX_SIZE
#define CFG_TUD_NCM_IN_NTB_MAX_SIZE CONFIG_TINYUSB_NCM_IN_NTB_BUFF_MAX_SIZE
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "esp_err.h"
/**
* @brief Redirect output to the USB serial
* @param cdc_intf - interface number of TinyUSB's CDC
*
* @return esp_err_t - ESP_OK, ESP_FAIL or an error code
*/
esp_err_t esp_tusb_init_console(int cdc_intf);
/**
* @brief Switch log to the default output
* @param cdc_intf - interface number of TinyUSB's CDC
*
* @return esp_err_t
*/
esp_err_t esp_tusb_deinit_console(int cdc_intf);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,190 @@
/*
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
#include "esp_err.h"
#include "wear_levelling.h"
#include "esp_vfs_fat.h"
#if SOC_SDMMC_HOST_SUPPORTED
#include "driver/sdmmc_host.h"
#endif
/**
* @brief Data provided to the input of the `callback_mount_changed` and `callback_premount_changed` callback
*/
typedef struct {
bool is_mounted; /*!< Flag if storage is mounted or not */
} tinyusb_msc_event_mount_changed_data_t;
/**
* @brief Types of MSC events
*/
typedef enum {
TINYUSB_MSC_EVENT_MOUNT_CHANGED, /*!< Event type AFTER mount/unmount operation is successfully finished */
TINYUSB_MSC_EVENT_PREMOUNT_CHANGED /*!< Event type BEFORE mount/unmount operation is started */
} tinyusb_msc_event_type_t;
/**
* @brief Describes an event passing to the input of a callbacks
*/
typedef struct {
tinyusb_msc_event_type_t type; /*!< Event type */
union {
tinyusb_msc_event_mount_changed_data_t mount_changed_data; /*!< Data input of the callback */
};
} tinyusb_msc_event_t;
/**
* @brief MSC callback that is delivered whenever a specific event occurs.
*/
typedef void(*tusb_msc_callback_t)(tinyusb_msc_event_t *event);
#if SOC_SDMMC_HOST_SUPPORTED
/**
* @brief Configuration structure for sdmmc initialization
*
* User configurable parameters that are used while
* initializing the sdmmc media.
*/
typedef struct {
sdmmc_card_t *card; /*!< Pointer to sdmmc card configuration structure */
tusb_msc_callback_t callback_mount_changed; /*!< Pointer to the function callback that will be delivered AFTER mount/unmount operation is successfully finished */
tusb_msc_callback_t callback_premount_changed; /*!< Pointer to the function callback that will be delivered BEFORE mount/unmount operation is started */
const esp_vfs_fat_mount_config_t mount_config; /*!< FATFS mount config */
} tinyusb_msc_sdmmc_config_t;
#endif
/**
* @brief Configuration structure for spiflash initialization
*
* User configurable parameters that are used while
* initializing the SPI Flash media.
*/
typedef struct {
wl_handle_t wl_handle; /*!< Pointer to spiflash wera-levelling handle */
tusb_msc_callback_t callback_mount_changed; /*!< Pointer to the function callback that will be delivered AFTER mount/unmount operation is successfully finished */
tusb_msc_callback_t callback_premount_changed; /*!< Pointer to the function callback that will be delivered BEFORE mount/unmount operation is started */
const esp_vfs_fat_mount_config_t mount_config; /*!< FATFS mount config */
} tinyusb_msc_spiflash_config_t;
/**
* @brief Register storage type spiflash with tinyusb driver
*
* @param config pointer to the spiflash configuration
* @return esp_err_t
* - ESP_OK, if success;
* - ESP_ERR_NO_MEM, if there was no memory to allocate storage components;
* - ESP_ERR_NOT_SUPPORTED, if wear leveling sector size CONFIG_WL_SECTOR_SIZE is bigger than
* the tinyusb MSC buffer size CONFIG_TINYUSB_MSC_BUFSIZE
*/
esp_err_t tinyusb_msc_storage_init_spiflash(const tinyusb_msc_spiflash_config_t *config);
#if SOC_SDMMC_HOST_SUPPORTED
/**
* @brief Register storage type sd-card with tinyusb driver
*
* @param config pointer to the sd card configuration
* @return esp_err_t
* - ESP_OK, if success;
* - ESP_ERR_NO_MEM, if there was no memory to allocate storage components;
*/
esp_err_t tinyusb_msc_storage_init_sdmmc(const tinyusb_msc_sdmmc_config_t *config);
#endif
/**
* @brief Deregister storage with tinyusb driver and frees the memory
*
*/
void tinyusb_msc_storage_deinit(void);
/**
* @brief Register a callback invoking on MSC event. If the callback had been
* already registered, it will be overwritten
*
* @param event_type - type of registered event for a callback
* @param callback - callback function
* @return esp_err_t - ESP_OK or ESP_ERR_INVALID_ARG
*/
esp_err_t tinyusb_msc_register_callback(tinyusb_msc_event_type_t event_type,
tusb_msc_callback_t callback);
/**
* @brief Unregister a callback invoking on MSC event.
*
* @param event_type - type of registered event for a callback
* @return esp_err_t - ESP_OK or ESP_ERR_INVALID_ARG
*/
esp_err_t tinyusb_msc_unregister_callback(tinyusb_msc_event_type_t event_type);
/**
* @brief Mount the storage partition locally on the firmware application.
*
* Get the available drive number. Register spi flash partition.
* Connect POSIX and C standard library IO function with FATFS.
* Mounts the partition.
* This API is used by the firmware application. If the storage partition is
* mounted by this API, host (PC) can't access the storage via MSC.
* When this function is called from the tinyusb callback functions, care must be taken
* so as to make sure that user callbacks must be completed within a
* specific time. Otherwise, MSC device may re-appear again on Host.
*
* @param base_path path prefix where FATFS should be registered
* @return esp_err_t
* - ESP_OK, if success;
* - ESP_ERR_NOT_FOUND if the maximum count of volumes is already mounted
* - ESP_ERR_NO_MEM if not enough memory or too many VFSes already registered;
*/
esp_err_t tinyusb_msc_storage_mount(const char *base_path);
/**
* @brief Unmount the storage partition from the firmware application.
*
* Unmount the partition. Unregister diskio driver.
* Unregister the SPI flash partition.
* Finally, Un-register FATFS from VFS.
* After this function is called, storage device can be seen (recognized) by host (PC).
* When this function is called from the tinyusb callback functions, care must be taken
* so as to make sure that user callbacks must be completed within a specific time.
* Otherwise, MSC device may not appear on Host.
*
* @return esp_err_t
* - ESP_OK on success
* - ESP_ERR_INVALID_STATE if FATFS is not registered in VFS
*/
esp_err_t tinyusb_msc_storage_unmount(void);
/**
* @brief Get number of sectors in storage media
*
* @return usable size, in bytes
*/
uint32_t tinyusb_msc_storage_get_sector_count(void);
/**
* @brief Get sector size of storage media
*
* @return sector count
*/
uint32_t tinyusb_msc_storage_get_sector_size(void);
/**
* @brief Get status if storage media is exposed over USB to Host
*
* @return bool
* - true, if the storage media is exposed to Host
* - false, if the stoarge media is mounted on application (not exposed to Host)
*/
bool tinyusb_msc_storage_in_use_by_usb_host(void);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,39 @@
/*
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief This helper function creates and starts a task which wraps `tud_task()`.
*
* The wrapper function basically wraps tud_task and some log.
* Default parameters: stack size and priority as configured, argument = NULL, not pinned to any core.
* If you have more requirements for this task, you can create your own task which calls tud_task as the last step.
*
* @retval ESP_OK run tinyusb main task successfully
* @retval ESP_FAIL run tinyusb main task failed of internal error or initialization within the task failed when TINYUSB_INIT_IN_DEFAULT_TASK=y
* @retval ESP_FAIL initialization within the task failed if CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK is enabled
* @retval ESP_ERR_INVALID_STATE tinyusb main task has been created before
*/
esp_err_t tusb_run_task(void);
/**
* @brief This helper function stops and destroys the task created by `tusb_run_task()`
*
* @retval ESP_OK stop and destroy tinyusb main task successfully
* @retval ESP_ERR_INVALID_STATE tinyusb main task hasn't been created yet
*/
esp_err_t tusb_stop_task(void);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,69 @@
/*
* SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_err.h"
#include "esp_vfs_common.h" // For esp_line_endings_t definitions
#ifdef __cplusplus
extern "C" {
#endif
#define VFS_TUSB_MAX_PATH 16
#define VFS_TUSB_PATH_DEFAULT "/dev/tusb_cdc"
/**
* @brief Register TinyUSB CDC at VFS with path
*
* Know limitation:
* In case there are multiple CDC interfaces in the system, only one of them can be registered to VFS.
*
* @param[in] cdc_intf Interface number of TinyUSB's CDC
* @param[in] path Path where the CDC will be registered, `/dev/tusb_cdc` will be used if left NULL.
* @return esp_err_t ESP_OK or ESP_FAIL
*/
esp_err_t esp_vfs_tusb_cdc_register(int cdc_intf, char const *path);
/**
* @brief Unregister TinyUSB CDC from VFS
*
* @param[in] path Path where the CDC will be unregistered if NULL will be used `/dev/tusb_cdc`
* @return esp_err_t ESP_OK or ESP_FAIL
*/
esp_err_t esp_vfs_tusb_cdc_unregister(char const *path);
/**
* @brief Set the line endings to sent
*
* This specifies the conversion between newlines ('\n', LF) on stdout and line
* endings sent:
*
* - ESP_LINE_ENDINGS_CRLF: convert LF to CRLF
* - ESP_LINE_ENDINGS_CR: convert LF to CR
* - ESP_LINE_ENDINGS_LF: no modification
*
* @param[in] mode line endings to send
*/
void esp_vfs_tusb_cdc_set_tx_line_endings(esp_line_endings_t mode);
/**
* @brief Set the line endings expected to be received
*
* This specifies the conversion between line endings received and
* newlines ('\n', LF) passed into stdin:
*
* - ESP_LINE_ENDINGS_CRLF: convert CRLF to LF
* - ESP_LINE_ENDINGS_CR: convert CR to LF
* - ESP_LINE_ENDINGS_LF: no modification
*
* @param[in] mode line endings expected
*/
void esp_vfs_tusb_cdc_set_rx_line_endings(esp_line_endings_t mode);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,82 @@
/*
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/timers.h"
#include "tusb.h"
#include "tinyusb_types.h"
/* CDC classification
********************************************************************* */
typedef enum {
TINYUSB_CDC_DATA = 0x00,
} cdc_data_sublcass_type_t; // CDC120 specification
/* Note:other classification is represented in the file components\tinyusb\tinyusb\src\class\cdc\cdc.h */
/*********************************************************************** CDC classification*/
/* Structs
********************************************************************* */
typedef struct {
tinyusb_usbdev_t usb_dev; /*!< USB device to set up */
tusb_class_code_t cdc_class; /*!< CDC device class : Communications or Data device */
union {
cdc_comm_sublcass_type_t comm_subclass; /*!< Communications device subclasses: ACM, ECM, etc. */
cdc_data_sublcass_type_t data_subclass; /*!< Data device has only one subclass.*/
} cdc_subclass; /*!< CDC device subclass according to Class Definitions for Communications Devices the CDC v.1.20 */
} tinyusb_config_cdc_t; /*!< Main configuration structure of a CDC device */
typedef struct {
tinyusb_usbdev_t usb_dev; /*!< USB device used for the instance */
tusb_class_code_t type;
union {
cdc_comm_sublcass_type_t comm_subclass; /*!< Communications device subclasses: ACM, ECM, etc. */
cdc_data_sublcass_type_t data_subclass; /*!< Data device has only one subclass.*/
} cdc_subclass; /*!< CDC device subclass according to Class Definitions for Communications Devices the CDC v.1.20 */
void *subclass_obj; /*!< Dynamically allocated subclass specific object */
} esp_tusb_cdc_t;
/*********************************************************************** Structs*/
/* Functions
********************************************************************* */
/**
* @brief Initializing CDC basic object
* @param itf - number of a CDC object
* @param cfg - CDC configuration structure
*
* @return esp_err_t ESP_OK or ESP_FAIL
*/
esp_err_t tinyusb_cdc_init(int itf, const tinyusb_config_cdc_t *cfg);
/**
* @brief De-initializing CDC. Clean its objects
* @param itf - number of a CDC object
* @return esp_err_t ESP_OK, ESP_ERR_INVALID_ARG, ESP_ERR_INVALID_STATE
*
*/
esp_err_t tinyusb_cdc_deinit(int itf);
/**
* @brief Return interface of a CDC device
*
* @param itf_num
* @return esp_tusb_cdc_t* pointer to the interface or (NULL) on error
*/
esp_tusb_cdc_t *tinyusb_cdc_get_intf(int itf_num);
/*********************************************************************** Functions*/
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "tinyusb.h"
#ifdef __cplusplus
extern "C" {
#endif
#define USB_STRING_DESCRIPTOR_ARRAY_SIZE 8 // Max 8 string descriptors for a device. LANGID, Manufacturer, Product, Serial number + 4 user defined
/**
* @brief Parse tinyusb configuration and prepare the device configuration pointer list to configure tinyusb driver
*
* @attention All descriptors passed to this function must exist for the duration of USB device lifetime
*
* @param[in] config tinyusb stack specific configuration
* @retval ESP_ERR_INVALID_ARG Default configuration descriptor is provided only for CDC, MSC and NCM classes
* @retval ESP_ERR_NO_MEM Memory allocation error
* @retval ESP_OK Descriptors configured without error
*/
esp_err_t tinyusb_set_descriptors(const tinyusb_config_t *config);
/**
* @brief Set specific string descriptor
*
* @attention The descriptor passed to this function must exist for the duration of USB device lifetime
*
* @param[in] str UTF-8 string
* @param[in] str_idx String descriptor index
*/
void tinyusb_set_str_descriptor(const char *str, int str_idx);
/**
* @brief Free memory allocated during tinyusb_set_descriptors
*
*/
void tinyusb_free_descriptors(void);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "tusb.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Device descriptor generated from Kconfig
*
* This descriptor is used by default.
* The user can provide their own device descriptor via tinyusb_driver_install() call
*/
extern const tusb_desc_device_t descriptor_dev_default;
#if (TUD_OPT_HIGH_SPEED)
/**
* @brief Qualifier Device descriptor generated from Kconfig
*
* This descriptor is used by default.
* The user can provide their own descriptor via tinyusb_driver_install() call
*/
extern const tusb_desc_device_qualifier_t descriptor_qualifier_default;
#endif // TUD_OPT_HIGH_SPEED
/**
* @brief Array of string descriptors generated from Kconfig
*
* This descriptor is used by default.
* The user can provide their own descriptor via tinyusb_driver_install() call
*/
extern const char *descriptor_str_default[];
/**
* @brief FullSpeed configuration descriptor generated from Kconfig
* This descriptor is used by default.
* The user can provide their own FullSpeed configuration descriptor via tinyusb_driver_install() call
*/
extern const uint8_t descriptor_fs_cfg_default[];
#if (TUD_OPT_HIGH_SPEED)
/**
* @brief HighSpeed Configuration descriptor generated from Kconfig
*
* This descriptor is used by default.
* The user can provide their own HighSpeed configuration descriptor via tinyusb_driver_install() call
*/
extern const uint8_t descriptor_hs_cfg_default[];
#endif // TUD_OPT_HIGH_SPEED
uint8_t tusb_get_mac_string_id(void);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,2 @@
supplier: 'Organization: Espressif Systems (Shanghai) CO LTD'
originator: 'Organization: Espressif Systems (Shanghai) CO LTD'

View File

@@ -0,0 +1,7 @@
cmake_minimum_required(VERSION 3.22)
project(libusb_test
LANGUAGES C
)
add_executable(libusb_test libusb_test.c)
target_link_libraries(libusb_test -lusb-1.0)

View File

@@ -0,0 +1,121 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <assert.h>
#include <stdio.h>
#include <libusb-1.0/libusb.h>
#define TINYUSB_VENDOR 0x303A
#define TINYUSB_PRODUCT 0x4002
#define DESC_TYPE_DEVICE_QUALIFIER 0x06
#define DESC_TYOE_OTHER_SPEED_CONFIG 0x07
// Buffer for descriptor data
unsigned char buffer[512] = { 0 };
// USB Other Speed Configuration Descriptor
typedef struct __attribute__ ((packed))
{
uint8_t bLength ; ///< Size of descriptor
uint8_t bDescriptorType ; ///< Other_speed_Configuration Type
uint16_t wTotalLength ; ///< Total length of data returned
uint8_t bNumInterfaces ; ///< Number of interfaces supported by this speed configuration
uint8_t bConfigurationValue ; ///< Value to use to select configuration
uint8_t iConfiguration ; ///< Index of string descriptor
uint8_t bmAttributes ; ///< Same as Configuration descriptor
uint8_t bMaxPower ; ///< Same as Configuration descriptor
} desc_other_speed_t;
// USB Device Qualifier Descriptor
typedef struct __attribute__ ((packed))
{
uint8_t bLength ; ///< Size of descriptor
uint8_t bDescriptorType ; ///< Device Qualifier Type
uint16_t bcdUSB ; ///< USB specification version number (e.g., 0200H for V2.00)
uint8_t bDeviceClass ; ///< Class Code
uint8_t bDeviceSubClass ; ///< SubClass Code
uint8_t bDeviceProtocol ; ///< Protocol Code
uint8_t bMaxPacketSize0 ; ///< Maximum packet size for other speed
uint8_t bNumConfigurations ; ///< Number of Other-speed Configurations
uint8_t bReserved ; ///< Reserved for future use, must be zero
} desc_device_qualifier_t;
// printf helpers
static void _print_device_qulifier_desc(unsigned char *buffer, int length);
static void _print_other_speed_desc(unsigned char *buffer, int length);
//
// MAIN
//
int main()
{
libusb_context *context = NULL;
int rc = 0;
rc = libusb_init(&context);
assert(rc == 0);
libusb_device_handle *dev_handle = libusb_open_device_with_vid_pid(context,
TINYUSB_VENDOR,
TINYUSB_PRODUCT);
if (dev_handle != NULL) {
printf("TinyUSB Device has been found\n");
// Test Qualifier Descriprtor
// 1. Get Qualifier Descriptor
// 2. print descriptor data
rc = libusb_get_descriptor(dev_handle, DESC_TYPE_DEVICE_QUALIFIER, 0, buffer, 512);
_print_device_qulifier_desc(buffer, rc);
// Test Other Speed Descriptor
// 1. Get Other Speed Descriptor
// 2. print descriptor data
rc = libusb_get_descriptor(dev_handle, DESC_TYOE_OTHER_SPEED_CONFIG, 0, buffer, 512);
_print_other_speed_desc(buffer, rc);
libusb_close(dev_handle);
} else {
printf("TinyUSB Device has NOT been found\n");
}
libusb_exit(context);
}
// =============================================================================
static void _print_device_qulifier_desc(unsigned char *buffer, int length)
{
assert(buffer);
desc_device_qualifier_t *qualifier_desc = (desc_device_qualifier_t *) buffer;
printf("========= Device Qualifier ========== \n");
printf("\t bLength: %d \n", qualifier_desc->bLength);
printf("\t bDescriptorType: %d (%#x)\n", qualifier_desc->bDescriptorType, qualifier_desc->bDescriptorType);
printf("\t bcdUSB: %d (%#x) \n", qualifier_desc->bcdUSB, qualifier_desc->bcdUSB);
printf("\t bDeviceClass: %d (%#x) \n", qualifier_desc->bDeviceClass, qualifier_desc->bDeviceClass);
printf("\t bDeviceSubClass: %d \n", qualifier_desc->bDeviceSubClass);
printf("\t bDeviceProtocol: %d \n", qualifier_desc->bDeviceProtocol);
printf("\t bMaxPacketSize0: %d \n", qualifier_desc->bMaxPacketSize0);
printf("\t bNumConfigurations: %d \n", qualifier_desc->bNumConfigurations);
}
static void _print_other_speed_desc(unsigned char *buffer, int length)
{
assert(buffer);
desc_other_speed_t *other_speed = (desc_other_speed_t *) buffer;
printf("============ Other Speed ============ \n");
printf("\t bLength: %d \n", other_speed->bLength);
printf("\t bDescriptorType: %d (%#x) \n", other_speed->bDescriptorType, other_speed->bDescriptorType);
printf("\t wTotalLength: %d \n", other_speed->wTotalLength);
printf("\t bNumInterfaces: %d \n", other_speed->bNumInterfaces);
printf("\t bConfigurationValue: %d \n", other_speed->bConfigurationValue);
printf("\t iConfiguration: %d \n", other_speed->iConfiguration);
printf("\t bmAttributes: %d (%#x) \n", other_speed->bmAttributes, other_speed->bmAttributes);
printf("\t bMaxPower: %d (%#x) \n", other_speed->bMaxPower, other_speed->bMaxPower);
}

View File

@@ -0,0 +1,131 @@
# CI target runner setup
To allow a Docker container, running on a CI target runner, to access USB devices connected to the CI target runner, some modifications must be made.
In our case, it's an `RPI` target runner.
The main idea comes from this response on [stackoverflow](https://stackoverflow.com/a/66427245/19840830). The same approach is also recommended in the official Docker [documentation](https://docs.docker.com/reference/cli/docker/container/run/#device-cgroup-rule)
### Following changes shall be made on a CI target runner
- [`UDEV rules`](#udev-rules)
- [`Docker tty script`](#docker-tty-script)
- [`Logging`](#logging)
- [`Running a docker container`](#running-a-docker-container)
- [`GitHub CI target runner setup`](#github-ci-target-runner-setup)
- [`GitLab CI target runner setup`](#gitlab-ci-target-runner-setup)
## UDEV rules
- This UDEV rule will trigger a `docker_tty.sh` script every time a USB device is connected, disconnected, or enumerated by the host machine (CI target runner)
- Location: `/etc/udev/rules.d/99-docker-tty.rules`
- `99-docker-tty.rules` file content:
``` sh
ACTION=="add", SUBSYSTEM=="tty", RUN+="/usr/local/bin/docker_tty.sh 'added' '%E{DEVNAME}' '%M' '%m'"
ACTION=="remove", SUBSYSTEM=="tty", RUN+="/usr/local/bin/docker_tty.sh 'removed' '%E{DEVNAME}' '%M' '%m'"
```
## Docker tty script
- This `.sh` script, triggered by the UDEV rule above, will propagate USB devices to a running Docker container.
- Location: `/usr/local/bin/docker_tty.sh`
- `docker_tty.sh` file content:
``` sh
#!/usr/bin/env bash
# Log the USB event with parameters
echo "USB event: $1 $2 $3 $4" >> /tmp/docker_tty.log
# Find a running Docker container (using the first one found)
docker_name=$(docker ps --format "{{.Names}}" | head -n 1)
# Check if a container was found
if [ ! -z "$docker_name" ]; then
if [ "$1" == "added" ]; then
docker exec -u 0 "$docker_name" mknod $2 c $3 $4
docker exec -u 0 "$docker_name" chmod -R 777 $2
echo "Adding $2 to Docker container $docker_name" >> /tmp/docker_tty.log
else
docker exec -u 0 "$docker_name" rm $2
echo "Removing $2 from Docker container $docker_name" >> /tmp/docker_tty.log
fi
else
echo "No running Docker containers found." >> /tmp/docker_tty.log
fi
```
### Making the script executable
Don't forget to make the created script executable:
``` sh
root@~$ chmod +x /usr/local/bin/docker_tty.sh
```
## Logging
- The `docker_tty.sh` script logs information about the USB devices it processes.
- Location: `/tmp/docker_tty.log`
- Example of a log from the `docker_tty.log` file, showing a flow of the `pytest_usb_device.py` test
```
USB event: added /dev/ttyACM0 166 0
USB event: added /dev/ttyACM1 166 1
Adding /dev/ttyACM0 to Docker container d5e5c774174b435b8befea864f8fcb7f_python311bookworm_6a975d
Adding /dev/ttyACM1 to Docker container d5e5c774174b435b8befea864f8fcb7f_python311bookworm_6a975d
USB event: removed /dev/ttyACM0 166 0
USB event: removed /dev/ttyACM1 166 1
```
## Running a docker container
### Check Major and Minor numbers of connected devices
Check the Major and Minor numbers assigned by the Linux kernel to devices that you want the Docker container to access.
In our case, we want to access `/dev/ttyUSB0`, `/dev/ttyACM0` and `/dev/ttyACM1`
`/dev/ttyUSB0`: Major 188, Minor 0
``` sh
peter@BrnoRPIG007:~ $ ls -l /dev/ttyUSB0
crw-rw-rw- 1 root dialout 188, 0 Nov 12 11:08 /dev/ttyUSB0
```
`/dev/ttyACM0` and `/dev/ttyACM1`: Major 166, Minor 0 (1)
``` sh
peter@BrnoRPIG007:~ $ ls -l /dev/ttyACM0
crw-rw---- 1 root dialout 166, 0 Nov 13 10:26 /dev/ttyACM0
peter@BrnoRPIG007:~ $ ls -l /dev/ttyACM1
crw-rw---- 1 root dialout 166, 1 Nov 13 10:26 /dev/ttyACM1
```
### Run a docker container
Run a Docker container with the following extra options:
``` sh
docker run --device-cgroup-rule='c 188:* rmw' --device-cgroup-rule='c 166:* rmw' --privileged ..
```
- `--device-cgroup-rule='c 188:* rmw'`: allow access to `ttyUSBx` (Major 188, all Minors)
- `--device-cgroup-rule='c 166:* rmw'`: allow access to `ttyACMx` (Major 166, all Minors)
## GitHub CI target runner setup
To apply these changes to a GitHub target runner a `.yml` file used to run a Docker container for pytest must be modified. The Docker container is then run with the following options:
``` yaml
container:
image: python:3.11-bookworm
options: --privileged --device-cgroup-rule="c 188:* rmw" --device-cgroup-rule="c 166:* rmw"
```
## GitLab CI target runner setup
To apply these changes to a GitLab runner the `config.toml` file located at `/etc/gitlab-runner/config.toml` on each GitLab target runner must be modified.
According to GitLab's [documentation](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runnersdocker-section) the `[runners.docker]` section of the `config.toml` file should include the `device_cgroup_rules` parameter:
``` toml
[runners.docker]
...
device_cgroup_rules = ["c 188:* rmw", "c 166:* rmw"]
```

View File

@@ -0,0 +1,9 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
set(COMPONENTS main)
project(test_app_cdc)

View File

@@ -0,0 +1,4 @@
idf_component_register(SRC_DIRS .
INCLUDE_DIRS .
REQUIRES unity
WHOLE_ARCHIVE)

View File

@@ -0,0 +1,5 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_tinyusb:
version: "*"
override_path: "../../../"

View File

@@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "unity_test_runner.h"
#include "unity_test_utils_memory.h"
void app_main(void)
{
/*
_ _ _
| | (_) | |
___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__
/ _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \
| __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) |
\___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/
| |______ __/ |
|_|______| |___/
_____ _____ _____ _____
|_ _| ___/ ___|_ _|
| | | |__ \ `--. | |
| | | __| `--. \ | |
| | | |___/\__/ / | |
\_/ \____/\____/ \_/
*/
printf(" _ _ _ \n");
printf(" | | (_) | | \n");
printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n");
printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n");
printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n");
printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n");
printf(" | |______ __/ | \n");
printf(" |_|______| |___/ \n");
printf(" _____ _____ _____ _____ \n");
printf("|_ _| ___/ ___|_ _| \n");
printf(" | | | |__ \\ `--. | | \n");
printf(" | | | __| `--. \\ | | \n");
printf(" | | | |___/\\__/ / | | \n");
printf(" \\_/ \\____/\\____/ \\_/ \n");
unity_utils_setup_heap_record(80);
unity_utils_set_leak_level(128);
unity_run_menu();
}
/* setUp runs before every test */
void setUp(void)
{
unity_utils_record_free_mem();
}
/* tearDown runs after every test */
void tearDown(void)
{
unity_utils_evaluate_leaks();
}

View File

@@ -0,0 +1,152 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_USB_OTG_SUPPORTED
#include <stdio.h>
#include <string.h>
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_err.h"
#include "unity.h"
#include "tinyusb.h"
#include "tusb_cdc_acm.h"
#include "vfs_tinyusb.h"
#define VFS_PATH "/dev/usb-cdc1"
static const tusb_desc_device_t cdc_device_descriptor = {
.bLength = sizeof(cdc_device_descriptor),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = USB_ESPRESSIF_VID,
.idProduct = 0x4002,
.bcdDevice = 0x0100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
};
static const uint16_t cdc_desc_config_len = TUD_CONFIG_DESC_LEN + CFG_TUD_CDC * TUD_CDC_DESC_LEN;
static const uint8_t cdc_desc_configuration[] = {
TUD_CONFIG_DESCRIPTOR(1, 4, 0, cdc_desc_config_len, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
TUD_CDC_DESCRIPTOR(0, 4, 0x81, 8, 0x02, 0x82, (TUD_OPT_HIGH_SPEED ? 512 : 64)),
TUD_CDC_DESCRIPTOR(2, 4, 0x83, 8, 0x04, 0x84, (TUD_OPT_HIGH_SPEED ? 512 : 64)),
};
#if (TUD_OPT_HIGH_SPEED)
static const tusb_desc_device_qualifier_t device_qualifier = {
.bLength = sizeof(tusb_desc_device_qualifier_t),
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.bNumConfigurations = 0x01,
.bReserved = 0
};
#endif // TUD_OPT_HIGH_SPEED
static void tinyusb_cdc_rx_callback(int itf, cdcacm_event_t *event)
{
}
/**
* @brief TinyUSB CDC testcase
*
* This is not a 'standard' testcase, as it never exits. The testcase runs in a loop where it echoes back all the data received.
*
* - Init TinyUSB with standard CDC device and configuration descriptors
* - Init 2 CDC-ACM interfaces
* - Map CDC1 to Virtual File System
* - In a loop: Read data from CDC0 and CDC1. Echo received data back
*
* Note: CDC0 appends 'novfs' to echoed data, so the host (test runner) can easily determine which port is which.
*/
TEST_CASE("tinyusb_cdc", "[esp_tinyusb][cdc]")
{
// Install TinyUSB driver
const tinyusb_config_t tusb_cfg = {
.device_descriptor = &cdc_device_descriptor,
.string_descriptor = NULL,
.string_descriptor_count = 0,
.external_phy = false,
#if (TUD_OPT_HIGH_SPEED)
.fs_configuration_descriptor = cdc_desc_configuration,
.hs_configuration_descriptor = cdc_desc_configuration,
.qualifier_descriptor = &device_qualifier,
#else
.configuration_descriptor = cdc_desc_configuration,
#endif // TUD_OPT_HIGH_SPEED
};
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg));
tinyusb_config_cdcacm_t acm_cfg = {
.usb_dev = TINYUSB_USBDEV_0,
.cdc_port = TINYUSB_CDC_ACM_0,
.rx_unread_buf_sz = 64,
.callback_rx = &tinyusb_cdc_rx_callback,
.callback_rx_wanted_char = NULL,
.callback_line_state_changed = NULL,
.callback_line_coding_changed = NULL
};
// Init CDC 0
TEST_ASSERT_FALSE(tusb_cdc_acm_initialized(TINYUSB_CDC_ACM_0));
TEST_ASSERT_EQUAL(ESP_OK, tusb_cdc_acm_init(&acm_cfg));
TEST_ASSERT_TRUE(tusb_cdc_acm_initialized(TINYUSB_CDC_ACM_0));
// Init CDC 1
acm_cfg.cdc_port = TINYUSB_CDC_ACM_1;
acm_cfg.callback_rx = NULL;
TEST_ASSERT_FALSE(tusb_cdc_acm_initialized(TINYUSB_CDC_ACM_1));
TEST_ASSERT_EQUAL(ESP_OK, tusb_cdc_acm_init(&acm_cfg));
TEST_ASSERT_TRUE(tusb_cdc_acm_initialized(TINYUSB_CDC_ACM_1));
// Install VFS to CDC 1
TEST_ASSERT_EQUAL(ESP_OK, esp_vfs_tusb_cdc_register(TINYUSB_CDC_ACM_1, VFS_PATH));
esp_vfs_tusb_cdc_set_rx_line_endings(ESP_LINE_ENDINGS_CRLF);
esp_vfs_tusb_cdc_set_tx_line_endings(ESP_LINE_ENDINGS_LF);
FILE *cdc = fopen(VFS_PATH, "r+");
TEST_ASSERT_NOT_NULL(cdc);
uint8_t buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE + 1];
while (true) {
size_t b = fread(buf, 1, sizeof(buf), cdc);
if (b > 0) {
printf("Intf VFS, RX %d bytes\n", b);
//ESP_LOG_BUFFER_HEXDUMP("test", buf, b, ESP_LOG_INFO);
fwrite(buf, 1, b, cdc);
}
vTaskDelay(1);
size_t rx_size = 0;
int itf = 0;
ESP_ERROR_CHECK(tinyusb_cdcacm_read(itf, buf, CONFIG_TINYUSB_CDC_RX_BUFSIZE, &rx_size));
if (rx_size > 0) {
printf("Intf %d, RX %d bytes\n", itf, rx_size);
// Add 'novfs' to reply so the host can identify the port
strcpy((char *)&buf[rx_size - 2], "novfs\r\n");
tinyusb_cdcacm_write_queue(itf, buf, rx_size + sizeof("novfs") - 1);
tinyusb_cdcacm_write_flush(itf, 0);
}
}
}
#endif

View File

@@ -0,0 +1,84 @@
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import pytest
from pytest_embedded_idf.dut import IdfDut
from time import sleep
from serial import Serial, SerialException
from serial.tools.list_ports import comports
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.esp32p4
@pytest.mark.usb_device
def test_usb_device_cdc(dut) -> None:
'''
Running the test locally:
1. Build the testa app for your DUT (ESP32-S2 or S3)
2. Connect you DUT to your test runner (local machine) with USB port and flashing port
3. Run `pytest --target esp32s3`
Test procedure:
1. Run the test on the DUT
2. Expect 2 Virtual COM Ports in the system
3. Open both comports and send some data. Expect echoed data
'''
dut.expect_exact('Press ENTER to see the list of tests.')
dut.write('[cdc]')
dut.expect_exact('TinyUSB: TinyUSB Driver installed')
sleep(2) # Some time for the OS to enumerate our USB device
# Find devices with Espressif TinyUSB VID/PID
s = []
ports = comports()
for port, _, hwid in ports:
if '303A:4002' in hwid:
s.append(port)
if len(s) != 2:
raise Exception('TinyUSB COM port not found')
try:
with Serial(s[0]) as cdc0:
with Serial(s[1]) as cdc1:
# Write dummy string and check for echo
cdc0.write('text\r\n'.encode())
res = cdc0.readline()
assert b'text' in res
if b'novfs' in res:
novfs_cdc = cdc0
vfs_cdc = cdc1
cdc1.write('text\r\n'.encode())
res = cdc1.readline()
assert b'text' in res
if b'novfs' in res:
novfs_cdc = cdc1
vfs_cdc = cdc0
# Write more than MPS, check that the transfer is not divided
novfs_cdc.write(bytes(100))
dut.expect_exact("Intf 0, RX 100 bytes")
# Write more than RX buffer, check correct reception
novfs_cdc.write(bytes(600))
transfer_len1 = int(dut.expect(r'Intf 0, RX (\d+) bytes')[1].decode())
transfer_len2 = int(dut.expect(r'Intf 0, RX (\d+) bytes')[1].decode())
assert transfer_len1 + transfer_len2 == 600
# The VFS is setup for CRLF RX and LF TX
vfs_cdc.write('text\r\n'.encode())
res = vfs_cdc.readline()
assert b'text\n' in res
return
except SerialException as e:
print(f"SerialException occurred: {e}")
raise
except Exception as e:
print(f"An unexpected error occurred: {e}")
raise

View File

@@ -0,0 +1,18 @@
# Configure TinyUSB, it will be used to mock USB devices
CONFIG_TINYUSB_MSC_ENABLED=n
CONFIG_TINYUSB_CDC_ENABLED=y
CONFIG_TINYUSB_CDC_COUNT=2
CONFIG_TINYUSB_HID_COUNT=0
# Disable watchdogs, they'd get triggered during unity interactive menu
CONFIG_ESP_INT_WDT=n
CONFIG_ESP_TASK_WDT=n
# Run-time checks of Heap and Stack
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
CONFIG_COMPILER_STACK_CHECK=y
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y
CONFIG_COMPILER_CXX_EXCEPTIONS=y

View File

@@ -0,0 +1,9 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
set(COMPONENTS main)
project(test_app_configuration_descriptor)

View File

@@ -0,0 +1,4 @@
idf_component_register(SRC_DIRS .
INCLUDE_DIRS .
REQUIRES unity
WHOLE_ARCHIVE)

View File

@@ -0,0 +1,5 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_tinyusb:
version: "*"
override_path: "../../../"

View File

@@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "unity_test_runner.h"
#include "unity_test_utils_memory.h"
void app_main(void)
{
/*
_ _ _
| | (_) | |
___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__
/ _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \
| __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) |
\___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/
| |______ __/ |
|_|______| |___/
_____ _____ _____ _____
|_ _| ___/ ___|_ _|
| | | |__ \ `--. | |
| | | __| `--. \ | |
| | | |___/\__/ / | |
\_/ \____/\____/ \_/
*/
printf(" _ _ _ \n");
printf(" | | (_) | | \n");
printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n");
printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n");
printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n");
printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n");
printf(" | |______ __/ | \n");
printf(" |_|______| |___/ \n");
printf(" _____ _____ _____ _____ \n");
printf("|_ _| ___/ ___|_ _| \n");
printf(" | | | |__ \\ `--. | | \n");
printf(" | | | __| `--. \\ | | \n");
printf(" | | | |___/\\__/ / | | \n");
printf(" \\_/ \\____/\\____/ \\_/ \n");
unity_utils_setup_heap_record(80);
unity_utils_set_leak_level(128);
unity_run_menu();
}
/* setUp runs before every test */
void setUp(void)
{
unity_utils_record_free_mem();
}
/* tearDown runs after every test */
void tearDown(void)
{
unity_utils_evaluate_leaks();
}

View File

@@ -0,0 +1,277 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_USB_OTG_SUPPORTED
#include <stdio.h>
#include <string.h>
#include "esp_system.h"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "esp_err.h"
#include "unity.h"
#include "tinyusb.h"
static const char *TAG = "config_test";
#define TEARDOWN_DEVICE_ATTACH_TIMEOUT_MS 1000
#define TEARDOWN_DEVICE_DETACH_DELAY_MS 1000
// ========================= TinyUSB descriptors ===============================
// Here we need to create dual CDC device, to match the CONFIG_TINYUSB_CDC_COUNT from sdkconfig.defaults
static const uint16_t cdc_desc_config_len = TUD_CONFIG_DESC_LEN + CFG_TUD_CDC * TUD_CDC_DESC_LEN;
static const uint8_t test_fs_configuration_descriptor[] = {
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, CFG_TUD_CDC * 2, 0, cdc_desc_config_len, TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
TUD_CDC_DESCRIPTOR(0, 4, 0x81, 8, 0x02, 0x82, 64),
#if CFG_TUD_CDC > 1
TUD_CDC_DESCRIPTOR(2, 4, 0x83, 8, 0x04, 0x84, 64),
#endif
};
#if (TUD_OPT_HIGH_SPEED)
static const uint8_t test_hs_configuration_descriptor[] = {
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, 4, 0, cdc_desc_config_len, TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
TUD_CDC_DESCRIPTOR(0, 4, 0x81, 8, 0x02, 0x82, 512),
TUD_CDC_DESCRIPTOR(2, 4, 0x83, 8, 0x04, 0x84, 512),
};
static const tusb_desc_device_qualifier_t device_qualifier = {
.bLength = sizeof(tusb_desc_device_qualifier_t),
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.bNumConfigurations = 0x01,
.bReserved = 0
};
#endif // TUD_OPT_HIGH_SPEED
static const tusb_desc_device_t test_device_descriptor = {
.bLength = sizeof(test_device_descriptor),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = 0x303A, // This is Espressif VID. This needs to be changed according to Users / Customers
.idProduct = 0x4002,
.bcdDevice = 0x100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
};
// ========================== Private logic ====================================
SemaphoreHandle_t wait_mount = NULL;
/**
* @brief Creates test additional resources.
*
* Is called before start test to create/init internal resources.
*/
static bool __test_init(void)
{
wait_mount = xSemaphoreCreateBinary();
return (wait_mount != NULL);
}
/**
* @brief Indicates device connection event.
*
* Is called in tud_mount callback.
*/
static void __test_conn(void)
{
if (wait_mount) {
xSemaphoreGive(wait_mount);
}
}
/**
* @brief Awaits device connection event.
*
* Is used for waiting the event in test logic.
* Timeout could be configured via TEARDOWN_DEVICE_ATTACH_TIMEOUT_MS define.
*/
static esp_err_t __test_wait_conn(void)
{
if (!wait_mount) {
return ESP_ERR_INVALID_STATE;
}
return ( xSemaphoreTake(wait_mount, pdMS_TO_TICKS(TEARDOWN_DEVICE_ATTACH_TIMEOUT_MS))
? ESP_OK
: ESP_ERR_TIMEOUT );
}
/**
* @brief Releases test resources.
*
* Is called in the end of the test to release internal resources.
*/
static void __test_release(void)
{
if (wait_mount) {
vSemaphoreDelete(wait_mount);
}
}
/**
* @brief One round of setup configuration for TinyUSB.
*
* Steps:
* 1. Init internal resources
* 2. Installs TinyUSB with provided configuration
* 3. Waits for the connection event from the Host
* 4. Gives some extra time for Host to configure the device (configures via TEARDOWN_DEVICE_DETACH_DELAY_MS)
* 5. Uninstall TinyUSB
* 6. Release internal resources
*
* Is called in the end of the test to release internal resources.
*/
static void __test_tinyusb_set_config(const tinyusb_config_t *tusb_cfg)
{
TEST_ASSERT_EQUAL(true, __test_init());
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(tusb_cfg));
TEST_ASSERT_EQUAL(ESP_OK, __test_wait_conn());
vTaskDelay(pdMS_TO_TICKS(TEARDOWN_DEVICE_DETACH_DELAY_MS));
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
__test_release();
}
// ========================== Callbacks ========================================
/**
* @brief TinyUSB callback for device mount.
*
* @note
* For Linux-based Hosts: Reflects the SetConfiguration() request from the Host Driver.
* For Win-based Hosts: SetConfiguration() request is present only with available Class in device descriptor.
*/
void tud_mount_cb(void)
{
ESP_LOGD(TAG, "%s", __FUNCTION__);
__test_conn();
}
// ============================= Tests =========================================
/**
* @brief TinyUSB Configuration test case.
*
* Verifies:
* Configuration without specifying any parameters.
* Configuration & descriptors are provided by esp_tinyusb wrapper.
*/
TEST_CASE("descriptors_config_all_default", "[esp_tinyusb][usb_device][config]")
{
const tinyusb_config_t tusb_cfg = {
.external_phy = false,
.device_descriptor = NULL,
.configuration_descriptor = NULL,
#if (CONFIG_TINYUSB_RHPORT_HS)
.hs_configuration_descriptor = NULL,
#endif // CONFIG_TINYUSB_RHPORT_HS
};
// Install TinyUSB driver
__test_tinyusb_set_config(&tusb_cfg);
}
/**
* @brief TinyUSB Configuration test case.
*
* Verifies:
* Configuration with specifying only device descriptor.
* For High-speed qualifier descriptor as well.
*/
TEST_CASE("descriptors_config_device", "[esp_tinyusb][usb_device][config]")
{
const tinyusb_config_t tusb_cfg = {
.external_phy = false,
.device_descriptor = &test_device_descriptor,
.configuration_descriptor = NULL,
#if (CONFIG_TINYUSB_RHPORT_HS)
.hs_configuration_descriptor = NULL,
.qualifier_descriptor = &device_qualifier,
#endif // CONFIG_TINYUSB_RHPORT_HS
};
__test_tinyusb_set_config(&tusb_cfg);
}
/**
* @brief TinyUSB Configuration test case.
*
* Verifies:
* Configuration with specifying device & configuration descriptors.
*
* For High-speed:
* - HS configuration descriptor is not provided by user (legacy compatibility) and default HS config descriptor is using when possible.
* - Qualifier descriptor.
*/
TEST_CASE("descriptors_config_device_and_config", "[esp_tinyusb][usb_device][config]")
{
const tinyusb_config_t tusb_cfg = {
.external_phy = false,
.device_descriptor = &test_device_descriptor,
.configuration_descriptor = test_fs_configuration_descriptor,
#if (CONFIG_TINYUSB_RHPORT_HS)
.hs_configuration_descriptor = NULL,
.qualifier_descriptor = &device_qualifier,
#endif // CONFIG_TINYUSB_RHPORT_HS
};
__test_tinyusb_set_config(&tusb_cfg);
}
#if (CONFIG_IDF_TARGET_ESP32P4)
/**
* @brief TinyUSB High-speed Configuration test case.
*
* Verifies:
* Configuration with specifying device & HS configuration descriptor only.
* FS configuration descriptor is not provided by user (legacy compatibility) and default configuration descriptor for FS is using when possible.
*/
TEST_CASE("descriptors_config_device_and_hs_config_only", "[esp_tinyusb][usb_device][config]")
{
const tinyusb_config_t tusb_cfg = {
.external_phy = false,
.device_descriptor = &test_device_descriptor,
.configuration_descriptor = NULL,
.hs_configuration_descriptor = test_hs_configuration_descriptor,
.qualifier_descriptor = &device_qualifier,
};
__test_tinyusb_set_config(&tusb_cfg);
}
/**
* @brief TinyUSB High-speed Configuration test case.
*
* Verifies:
* Configuration with specifying all descriptors by user.
*/
TEST_CASE("descriptors_config_all_configured", "[esp_tinyusb][usb_device][config]")
{
const tinyusb_config_t tusb_cfg = {
.external_phy = false,
.device_descriptor = &test_device_descriptor,
.fs_configuration_descriptor = test_fs_configuration_descriptor,
.hs_configuration_descriptor = test_hs_configuration_descriptor,
.qualifier_descriptor = &device_qualifier,
};
__test_tinyusb_set_config(&tusb_cfg);
}
#endif // CONFIG_IDF_TARGET_ESP32P4
#endif // SOC_USB_OTG_SUPPORTED

View File

@@ -0,0 +1,13 @@
# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import pytest
from pytest_embedded_idf.dut import IdfDut
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.esp32p4
@pytest.mark.usb_device
def test_usb_device_configuration(dut: IdfDut) -> None:
dut.run_all_single_board_cases(group='config')

View File

@@ -0,0 +1,16 @@
# Configure TinyUSB, it will be used to mock USB devices
CONFIG_TINYUSB_CDC_ENABLED=y
CONFIG_TINYUSB_CDC_COUNT=2
# Disable watchdogs, they'd get triggered during unity interactive menu
CONFIG_ESP_INT_WDT=n
CONFIG_ESP_TASK_WDT=n
# Run-time checks of Heap and Stack
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
CONFIG_COMPILER_STACK_CHECK=y
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y
CONFIG_COMPILER_CXX_EXCEPTIONS=y

View File

@@ -0,0 +1,9 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
set(COMPONENTS main)
project(test_app_dconn_detection)

View File

@@ -0,0 +1,4 @@
idf_component_register(SRC_DIRS .
INCLUDE_DIRS .
REQUIRES unity
WHOLE_ARCHIVE)

View File

@@ -0,0 +1,5 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_tinyusb:
version: "*"
override_path: "../../../"

View File

@@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "unity_test_runner.h"
#include "unity_test_utils_memory.h"
void app_main(void)
{
/*
_ _ _
| | (_) | |
___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__
/ _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \
| __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) |
\___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/
| |______ __/ |
|_|______| |___/
_____ _____ _____ _____
|_ _| ___/ ___|_ _|
| | | |__ \ `--. | |
| | | __| `--. \ | |
| | | |___/\__/ / | |
\_/ \____/\____/ \_/
*/
printf(" _ _ _ \n");
printf(" | | (_) | | \n");
printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n");
printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n");
printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n");
printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n");
printf(" | |______ __/ | \n");
printf(" |_|______| |___/ \n");
printf(" _____ _____ _____ _____ \n");
printf("|_ _| ___/ ___|_ _| \n");
printf(" | | | |__ \\ `--. | | \n");
printf(" | | | __| `--. \\ | | \n");
printf(" | | | |___/\\__/ / | | \n");
printf(" \\_/ \\____/\\____/ \\_/ \n");
unity_utils_setup_heap_record(80);
unity_utils_set_leak_level(128);
unity_run_menu();
}
/* setUp runs before every test */
void setUp(void)
{
unity_utils_record_free_mem();
}
/* tearDown runs after every test */
void tearDown(void)
{
unity_utils_evaluate_leaks();
}

View File

@@ -0,0 +1,155 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_USB_OTG_SUPPORTED
#include <stdio.h>
#include <string.h>
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_err.h"
#include "driver/gpio.h"
#include "esp_rom_gpio.h"
#include "soc/gpio_sig_map.h"
#include "unity.h"
#include "tinyusb.h"
#include "tusb_tasks.h"
#define DEVICE_DETACH_TEST_ROUNDS 10
#define DEVICE_DETACH_ROUND_DELAY_MS 1000
#if (CONFIG_IDF_TARGET_ESP32P4)
#define USB_SRP_BVALID_IN_IDX USB_SRP_BVALID_PAD_IN_IDX
#endif // CONFIG_IDF_TARGET_ESP32P4
/* TinyUSB descriptors
********************************************************************* */
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN)
static unsigned int dev_mounted = 0;
static unsigned int dev_umounted = 0;
static uint8_t const test_configuration_descriptor[] = {
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, 0, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
};
static const tusb_desc_device_t test_device_descriptor = {
.bLength = sizeof(test_device_descriptor),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = 0x303A, // This is Espressif VID. This needs to be changed according to Users / Customers
.idProduct = 0x4002,
.bcdDevice = 0x100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
};
#if (TUD_OPT_HIGH_SPEED)
static const tusb_desc_device_qualifier_t device_qualifier = {
.bLength = sizeof(tusb_desc_device_qualifier_t),
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.bNumConfigurations = 0x01,
.bReserved = 0
};
#endif // TUD_OPT_HIGH_SPEED
// Invoked when device is mounted
void tud_mount_cb(void)
{
/**
* @attention Tests relying on this callback only pass on Linux USB Host!
*
* This callback is issued after SetConfiguration command from USB Host.
* However, Windows issues SetConfiguration only after a USB driver was assigned to the device.
* So in case you are implementing a Vendor Specific class, or your device has 0 interfaces, this callback is not issued on Windows host.
*/
printf("%s\n", __FUNCTION__);
dev_mounted++;
}
// Invoked when device is unmounted
void tud_umount_cb(void)
{
printf("%s\n", __FUNCTION__);
dev_umounted++;
}
/**
* @brief TinyUSB Disconnect Detection test case
*
* This is specific artificial test for verifying the disconnection detection event.
* Normally, this event comes as a result of detaching USB device from the port and disappearing the VBUS voltage.
* In this test case, we use GPIO matrix and connect the signal to the ZERO or ONE constant inputs.
* Connection to constant ONE input emulates the attachment to the USB Host port (appearing VBUS).
* Connection to constant ZERO input emulates the detachment from the USB Host port (removing VBUS).
*
* Test logic:
* - Install TinyUSB Device stack without any class
* - In cycle:
* - Emulate the detachment, get the tud_umount_cb(), increase the dev_umounted value
* - Emulate the attachment, get the tud_mount_cb(), increase the dev_mounted value
* - Verify that dev_umounted == dev_mounted
* - Verify that dev_mounted == DEVICE_DETACH_TEST_ROUNDS, where DEVICE_DETACH_TEST_ROUNDS - amount of rounds
* - Uninstall TinyUSB Device stack
*
*/
TEST_CASE("dconn_detection", "[esp_tinyusb][dconn]")
{
unsigned int rounds = DEVICE_DETACH_TEST_ROUNDS;
// Install TinyUSB driver
const tinyusb_config_t tusb_cfg = {
.device_descriptor = &test_device_descriptor,
.string_descriptor = NULL,
.string_descriptor_count = 0,
.external_phy = false,
#if (TUD_OPT_HIGH_SPEED)
.fs_configuration_descriptor = test_configuration_descriptor,
.hs_configuration_descriptor = test_configuration_descriptor,
.qualifier_descriptor = &device_qualifier,
#else
.configuration_descriptor = test_configuration_descriptor,
#endif // TUD_OPT_HIGH_SPEED
};
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg));
dev_mounted = 0;
dev_umounted = 0;
while (rounds--) {
// LOW to emulate disconnect USB device
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ZERO_INPUT, USB_SRP_BVALID_IN_IDX, false);
vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS));
// HIGH to emulate connect USB device
esp_rom_gpio_connect_in_signal(GPIO_MATRIX_CONST_ONE_INPUT, USB_SRP_BVALID_IN_IDX, false);
vTaskDelay(pdMS_TO_TICKS(DEVICE_DETACH_ROUND_DELAY_MS));
}
// Verify
TEST_ASSERT_EQUAL(dev_umounted, dev_mounted);
TEST_ASSERT_EQUAL(DEVICE_DETACH_TEST_ROUNDS, dev_mounted);
// Cleanup
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
}
#endif // SOC_USB_OTG_SUPPORTED

View File

@@ -0,0 +1,13 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import pytest
from pytest_embedded_idf.dut import IdfDut
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.esp32p4
#@pytest.mark.usb_device Disable in CI: unavailable teardown for P4
def test_usb_device_dconn_detection(dut: IdfDut) -> None:
dut.run_all_single_board_cases(group='dconn')

View File

@@ -0,0 +1,18 @@
# Configure TinyUSB, it will be used to mock USB devices
CONFIG_TINYUSB_MSC_ENABLED=n
CONFIG_TINYUSB_CDC_ENABLED=n
CONFIG_TINYUSB_CDC_COUNT=0
CONFIG_TINYUSB_HID_COUNT=0
# Disable watchdogs, they'd get triggered during unity interactive menu
CONFIG_ESP_INT_WDT=n
CONFIG_ESP_TASK_WDT=n
# Run-time checks of Heap and Stack
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
CONFIG_COMPILER_STACK_CHECK=y
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y
CONFIG_COMPILER_CXX_EXCEPTIONS=y

View File

@@ -0,0 +1,9 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
set(COMPONENTS main)
project(test_app_default_task_without_init)

View File

@@ -0,0 +1,4 @@
idf_component_register(SRC_DIRS .
INCLUDE_DIRS .
REQUIRES unity
WHOLE_ARCHIVE)

View File

@@ -0,0 +1,5 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_tinyusb:
version: "*"
override_path: "../../../"

View File

@@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "unity_test_runner.h"
#include "unity_test_utils_memory.h"
void app_main(void)
{
/*
_ _ _
| | (_) | |
___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__
/ _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \
| __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) |
\___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/
| |______ __/ |
|_|______| |___/
_____ _____ _____ _____
|_ _| ___/ ___|_ _|
| | | |__ \ `--. | |
| | | __| `--. \ | |
| | | |___/\__/ / | |
\_/ \____/\____/ \_/
*/
printf(" _ _ _ \n");
printf(" | | (_) | | \n");
printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n");
printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n");
printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n");
printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n");
printf(" | |______ __/ | \n");
printf(" |_|______| |___/ \n");
printf(" _____ _____ _____ _____ \n");
printf("|_ _| ___/ ___|_ _| \n");
printf(" | | | |__ \\ `--. | | \n");
printf(" | | | __| `--. \\ | | \n");
printf(" | | | |___/\\__/ / | | \n");
printf(" \\_/ \\____/\\____/ \\_/ \n");
unity_utils_setup_heap_record(80);
unity_utils_set_leak_level(128);
unity_run_menu();
}
/* setUp runs before every test */
void setUp(void)
{
unity_utils_record_free_mem();
}
/* tearDown runs after every test */
void tearDown(void)
{
unity_utils_evaluate_leaks();
}

View File

@@ -0,0 +1,117 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_USB_OTG_SUPPORTED
//
#include <stdio.h>
#include <string.h>
//
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
//
#include "esp_system.h"
#include "esp_log.h"
#include "esp_err.h"
//
#include "unity.h"
#include "tinyusb.h"
static const char *TAG = "default_task";
SemaphoreHandle_t wait_mount = NULL;
#define TEARDOWN_DEVICE_DELAY_MS 1000
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN)
static uint8_t const test_configuration_descriptor[] = {
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, 0, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
};
static const tusb_desc_device_t test_device_descriptor = {
.bLength = sizeof(test_device_descriptor),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = 0x303A, // This is Espressif VID. This needs to be changed according to Users / Customers
.idProduct = 0x4002,
.bcdDevice = 0x100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
};
#if (TUD_OPT_HIGH_SPEED)
static const tusb_desc_device_qualifier_t device_qualifier = {
.bLength = sizeof(tusb_desc_device_qualifier_t),
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.bNumConfigurations = 0x01,
.bReserved = 0
};
#endif // TUD_OPT_HIGH_SPEED
// Invoked when device is mounted
void tud_mount_cb(void)
{
xSemaphoreGive(wait_mount);
}
/**
* @brief TinyUSB Task specific testcase
*
* Scenario:
* 1. Install TinyUSB driver
* 2. Wait tud_mount_cb() until TUSB_DEVICE_DELAY_MS
* 3. Wait TUSB_DEVICE_DELAY_MS
* 4. Teardown TinyUSB
* 5. Release resources
*/
TEST_CASE("tinyusb_default_task_with_init", "[esp_tinyusb][tusb_task]")
{
wait_mount = xSemaphoreCreateBinary();
TEST_ASSERT_NOT_EQUAL(NULL, wait_mount);
// TinyUSB driver configuration
const tinyusb_config_t tusb_cfg = {
.device_descriptor = &test_device_descriptor,
.string_descriptor = NULL,
.string_descriptor_count = 0,
.external_phy = false,
#if (TUD_OPT_HIGH_SPEED)
.fs_configuration_descriptor = test_configuration_descriptor,
.hs_configuration_descriptor = test_configuration_descriptor,
.qualifier_descriptor = &device_qualifier,
#else
.configuration_descriptor = test_configuration_descriptor,
#endif // TUD_OPT_HIGH_SPEED
};
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg));
// Wait for the usb event
ESP_LOGD(TAG, "wait mount...");
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(wait_mount, pdMS_TO_TICKS(TEARDOWN_DEVICE_DELAY_MS)));
ESP_LOGD(TAG, "mounted");
// Teardown
vTaskDelay(pdMS_TO_TICKS(TEARDOWN_DEVICE_DELAY_MS));
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
// Remove primitives
vSemaphoreDelete(wait_mount);
}
#endif

View File

@@ -0,0 +1,12 @@
# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import pytest
from pytest_embedded_idf.dut import IdfDut
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.esp32p4
@pytest.mark.usb_device
def test_usb_default_task_with_init(dut: IdfDut) -> None:
dut.run_all_single_board_cases(group='tusb_task')

View File

@@ -0,0 +1,16 @@
# Configure TinyUSB
CONFIG_TINYUSB_NO_DEFAULT_TASK=n
CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK=n
# Disable watchdogs, they'd get triggered during unity interactive menu
CONFIG_ESP_INT_WDT=n
CONFIG_ESP_TASK_WDT=n
# Run-time checks of Heap and Stack
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
CONFIG_COMPILER_STACK_CHECK=y
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y
CONFIG_COMPILER_CXX_EXCEPTIONS=y

View File

@@ -0,0 +1,9 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
set(COMPONENTS main)
project(test_app_default_task_with_init)

View File

@@ -0,0 +1,4 @@
idf_component_register(SRC_DIRS .
INCLUDE_DIRS .
REQUIRES unity
WHOLE_ARCHIVE)

View File

@@ -0,0 +1,5 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_tinyusb:
version: "*"
override_path: "../../../"

View File

@@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "unity_test_runner.h"
#include "unity_test_utils_memory.h"
void app_main(void)
{
/*
_ _ _
| | (_) | |
___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__
/ _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \
| __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) |
\___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/
| |______ __/ |
|_|______| |___/
_____ _____ _____ _____
|_ _| ___/ ___|_ _|
| | | |__ \ `--. | |
| | | __| `--. \ | |
| | | |___/\__/ / | |
\_/ \____/\____/ \_/
*/
printf(" _ _ _ \n");
printf(" | | (_) | | \n");
printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n");
printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n");
printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n");
printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n");
printf(" | |______ __/ | \n");
printf(" |_|______| |___/ \n");
printf(" _____ _____ _____ _____ \n");
printf("|_ _| ___/ ___|_ _| \n");
printf(" | | | |__ \\ `--. | | \n");
printf(" | | | __| `--. \\ | | \n");
printf(" | | | |___/\\__/ / | | \n");
printf(" \\_/ \\____/\\____/ \\_/ \n");
unity_utils_setup_heap_record(80);
unity_utils_set_leak_level(128);
unity_run_menu();
}
/* setUp runs before every test */
void setUp(void)
{
unity_utils_record_free_mem();
}
/* tearDown runs after every test */
void tearDown(void)
{
unity_utils_evaluate_leaks();
}

View File

@@ -0,0 +1,117 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_USB_OTG_SUPPORTED
//
#include <stdio.h>
#include <string.h>
//
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
//
#include "esp_system.h"
#include "esp_log.h"
#include "esp_err.h"
//
#include "unity.h"
#include "tinyusb.h"
static const char *TAG = "default_task_with_init";
SemaphoreHandle_t wait_mount = NULL;
#define TEARDOWN_DEVICE_DELAY_MS 1000
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN)
static uint8_t const test_configuration_descriptor[] = {
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, 0, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
};
static const tusb_desc_device_t test_device_descriptor = {
.bLength = sizeof(test_device_descriptor),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = 0x303A, // This is Espressif VID. This needs to be changed according to Users / Customers
.idProduct = 0x4002,
.bcdDevice = 0x100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
};
#if (TUD_OPT_HIGH_SPEED)
static const tusb_desc_device_qualifier_t device_qualifier = {
.bLength = sizeof(tusb_desc_device_qualifier_t),
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.bNumConfigurations = 0x01,
.bReserved = 0
};
#endif // TUD_OPT_HIGH_SPEED
// Invoked when device is mounted
void tud_mount_cb(void)
{
xSemaphoreGive(wait_mount);
}
/**
* @brief TinyUSB Task specific testcase
*
* Scenario:
* 1. Install TinyUSB driver
* 2. Wait tud_mount_cb() until TUSB_DEVICE_DELAY_MS
* 3. Wait TUSB_DEVICE_DELAY_MS
* 4. Teardown TinyUSB
* 5. Release resources
*/
TEST_CASE("tinyusb_default_task_with_init", "[esp_tinyusb][tusb_task]")
{
wait_mount = xSemaphoreCreateBinary();
TEST_ASSERT_NOT_EQUAL(NULL, wait_mount);
// TinyUSB driver configuration
const tinyusb_config_t tusb_cfg = {
.device_descriptor = &test_device_descriptor,
.string_descriptor = NULL,
.string_descriptor_count = 0,
.external_phy = false,
#if (TUD_OPT_HIGH_SPEED)
.fs_configuration_descriptor = test_configuration_descriptor,
.hs_configuration_descriptor = test_configuration_descriptor,
.qualifier_descriptor = &device_qualifier,
#else
.configuration_descriptor = test_configuration_descriptor,
#endif // TUD_OPT_HIGH_SPEED
};
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg));
// Wait for the usb event
ESP_LOGD(TAG, "wait mount...");
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(wait_mount, pdMS_TO_TICKS(TEARDOWN_DEVICE_DELAY_MS)));
ESP_LOGD(TAG, "mounted");
// Teardown
vTaskDelay(pdMS_TO_TICKS(TEARDOWN_DEVICE_DELAY_MS));
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
// Remove primitives
vSemaphoreDelete(wait_mount);
}
#endif

View File

@@ -0,0 +1,12 @@
# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import pytest
from pytest_embedded_idf.dut import IdfDut
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.esp32p4
@pytest.mark.usb_device
def test_usb_default_task_with_init(dut: IdfDut) -> None:
dut.run_all_single_board_cases(group='tusb_task')

View File

@@ -0,0 +1,16 @@
# Configure TinyUSB
CONFIG_TINYUSB_NO_DEFAULT_TASK=n
CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK=y
# Disable watchdogs, they'd get triggered during unity interactive menu
CONFIG_ESP_INT_WDT=n
CONFIG_ESP_TASK_WDT=n
# Run-time checks of Heap and Stack
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
CONFIG_COMPILER_STACK_CHECK=y
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y
CONFIG_COMPILER_CXX_EXCEPTIONS=y

View File

@@ -0,0 +1,9 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
set(COMPONENTS main)
project(test_app_external_task_without_init)

View File

@@ -0,0 +1,4 @@
idf_component_register(SRC_DIRS .
INCLUDE_DIRS .
REQUIRES unity
WHOLE_ARCHIVE)

View File

@@ -0,0 +1,5 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_tinyusb:
version: "*"
override_path: "../../../"

View File

@@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "unity_test_runner.h"
#include "unity_test_utils_memory.h"
void app_main(void)
{
/*
_ _ _
| | (_) | |
___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__
/ _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \
| __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) |
\___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/
| |______ __/ |
|_|______| |___/
_____ _____ _____ _____
|_ _| ___/ ___|_ _|
| | | |__ \ `--. | |
| | | __| `--. \ | |
| | | |___/\__/ / | |
\_/ \____/\____/ \_/
*/
printf(" _ _ _ \n");
printf(" | | (_) | | \n");
printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n");
printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n");
printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n");
printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n");
printf(" | |______ __/ | \n");
printf(" |_|______| |___/ \n");
printf(" _____ _____ _____ _____ \n");
printf("|_ _| ___/ ___|_ _| \n");
printf(" | | | |__ \\ `--. | | \n");
printf(" | | | __| `--. \\ | | \n");
printf(" | | | |___/\\__/ / | | \n");
printf(" \\_/ \\____/\\____/ \\_/ \n");
unity_utils_setup_heap_record(80);
unity_utils_set_leak_level(128);
unity_run_menu();
}
/* setUp runs before every test */
void setUp(void)
{
unity_utils_record_free_mem();
}
/* tearDown runs after every test */
void tearDown(void)
{
unity_utils_evaluate_leaks();
}

View File

@@ -0,0 +1,144 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_USB_OTG_SUPPORTED
//
#include <stdio.h>
#include <string.h>
//
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
//
#include "esp_system.h"
#include "esp_log.h"
#include "esp_err.h"
//
#include "unity.h"
#include "tinyusb.h"
static const char *TAG = "external_task";
static SemaphoreHandle_t wait_mount = NULL;
static TaskHandle_t s_test_tusb_tskh;
#define TUSB_DEVICE_DELAY_MS 1000
#define TUSB_EXTERNAL_TASK_SIZE 4096
#define TUSB_EXTERNAL_TASK_PRIO 5
#define TUSB_EXTERNAL_TASK_AFFINITY 0x7FFFFFFF /* FREERTOS_NO_AFFINITY */
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN)
static uint8_t const test_configuration_descriptor[] = {
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, 0, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
};
static const tusb_desc_device_t test_device_descriptor = {
.bLength = sizeof(test_device_descriptor),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = 0x303A, // This is Espressif VID. This needs to be changed according to Users / Customers
.idProduct = 0x4002,
.bcdDevice = 0x100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
};
#if (TUD_OPT_HIGH_SPEED)
static const tusb_desc_device_qualifier_t device_qualifier = {
.bLength = sizeof(tusb_desc_device_qualifier_t),
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.bNumConfigurations = 0x01,
.bReserved = 0
};
#endif // TUD_OPT_HIGH_SPEED
// Invoked when device is mounted
void tud_mount_cb(void)
{
xSemaphoreGive(wait_mount);
}
/**
* @brief This top level thread processes all usb events and invokes callbacks
*/
static void test_tusb_external_task(void *arg)
{
ESP_LOGD(TAG, "External TinyUSB task started");
while (1) { // RTOS forever loop
tud_task();
}
}
/**
* @brief TinyUSB Task specific testcase
*
* Scenario:
* 1. Install TinyUSB driver
* 2. Create external TinyUSB task for tud_task()
* 3. Wait tud_mount_cb() until TUSB_DEVICE_DELAY_MS
* 4. Wait TUSB_DEVICE_DELAY_MS
* 5. Teardown TinyUSB
* 6. Release resources
*
* @note If run the task before installing the tinyusb driver, the external task will lead to cpu starvation.
*/
TEST_CASE("tinyusb_external_task", "[esp_tinyusb][tusb_task]")
{
wait_mount = xSemaphoreCreateBinary();
TEST_ASSERT_NOT_EQUAL(NULL, wait_mount);
// TinyUSB driver configuration
const tinyusb_config_t tusb_cfg = {
.device_descriptor = &test_device_descriptor,
.string_descriptor = NULL,
.string_descriptor_count = 0,
.external_phy = false,
#if (TUD_OPT_HIGH_SPEED)
.fs_configuration_descriptor = test_configuration_descriptor,
.hs_configuration_descriptor = test_configuration_descriptor,
.qualifier_descriptor = &device_qualifier,
#else
.configuration_descriptor = test_configuration_descriptor,
#endif // TUD_OPT_HIGH_SPEED
};
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg));
// Create an external task for tinyusb device stack
xTaskCreate(test_tusb_external_task,
"TinyUSB",
TUSB_EXTERNAL_TASK_SIZE,
NULL,
TUSB_EXTERNAL_TASK_PRIO,
&s_test_tusb_tskh);
TEST_ASSERT_NOT_NULL(s_test_tusb_tskh);
// Wait for the usb event
ESP_LOGD(TAG, "wait mount...");
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(wait_mount, pdMS_TO_TICKS(TUSB_DEVICE_DELAY_MS)));
ESP_LOGD(TAG, "mounted");
// Teardown
vTaskDelay(pdMS_TO_TICKS(TUSB_DEVICE_DELAY_MS));
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
// Remove primitives
vTaskDelete(s_test_tusb_tskh);
vSemaphoreDelete(wait_mount);
}
#endif

View File

@@ -0,0 +1,12 @@
# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import pytest
from pytest_embedded_idf.dut import IdfDut
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.esp32p4
@pytest.mark.usb_device
def test_usb_external_task_internal_init(dut: IdfDut) -> None:
dut.run_all_single_board_cases(group='tusb_task')

View File

@@ -0,0 +1,16 @@
# Configure TinyUSB
CONFIG_TINYUSB_NO_DEFAULT_TASK=y
CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK=n
# Disable watchdogs, they'd get triggered during unity interactive menu
CONFIG_ESP_INT_WDT=n
CONFIG_ESP_TASK_WDT=n
# Run-time checks of Heap and Stack
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
CONFIG_COMPILER_STACK_CHECK=y
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y
CONFIG_COMPILER_CXX_EXCEPTIONS=y

View File

@@ -0,0 +1,9 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
set(COMPONENTS main)
project(test_app_teardown_device)

View File

@@ -0,0 +1,4 @@
idf_component_register(SRC_DIRS .
INCLUDE_DIRS .
REQUIRES unity
WHOLE_ARCHIVE)

View File

@@ -0,0 +1,5 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_tinyusb:
version: "*"
override_path: "../../../"

View File

@@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "unity_test_runner.h"
#include "unity_test_utils_memory.h"
void app_main(void)
{
/*
_ _ _
| | (_) | |
___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__
/ _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \
| __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) |
\___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/
| |______ __/ |
|_|______| |___/
_____ _____ _____ _____
|_ _| ___/ ___|_ _|
| | | |__ \ `--. | |
| | | __| `--. \ | |
| | | |___/\__/ / | |
\_/ \____/\____/ \_/
*/
printf(" _ _ _ \n");
printf(" | | (_) | | \n");
printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n");
printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n");
printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n");
printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n");
printf(" | |______ __/ | \n");
printf(" |_|______| |___/ \n");
printf(" _____ _____ _____ _____ \n");
printf("|_ _| ___/ ___|_ _| \n");
printf(" | | | |__ \\ `--. | | \n");
printf(" | | | __| `--. \\ | | \n");
printf(" | | | |___/\\__/ / | | \n");
printf(" \\_/ \\____/\\____/ \\_/ \n");
unity_utils_setup_heap_record(80);
unity_utils_set_leak_level(128);
unity_run_menu();
}
/* setUp runs before every test */
void setUp(void)
{
unity_utils_record_free_mem();
}
/* tearDown runs after every test */
void tearDown(void)
{
unity_utils_evaluate_leaks();
}

View File

@@ -0,0 +1,142 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_USB_OTG_SUPPORTED
//
#include <stdio.h>
#include <string.h>
//
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
//
#include "esp_system.h"
#include "esp_log.h"
#include "esp_err.h"
//
#include "unity.h"
#include "tinyusb.h"
#include "tusb_cdc_acm.h"
static const char *TAG = "teardown";
SemaphoreHandle_t wait_mount = NULL;
#define TEARDOWN_DEVICE_INIT_DELAY_MS 1000
#define TEARDOWN_DEVICE_ATTACH_TIMEOUT_MS 1000
#define TEARDOWN_DEVICE_DETACH_DELAY_MS 1000
#define TEARDOWN_AMOUNT 10
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN)
static uint8_t const test_configuration_descriptor[] = {
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, 0, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_SELF_POWERED | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
};
static const tusb_desc_device_t test_device_descriptor = {
.bLength = sizeof(test_device_descriptor),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = 0x303A, // This is Espressif VID. This needs to be changed according to Users / Customers
.idProduct = 0x4002,
.bcdDevice = 0x100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
};
#if (TUD_OPT_HIGH_SPEED)
static const tusb_desc_device_qualifier_t device_qualifier = {
.bLength = sizeof(tusb_desc_device_qualifier_t),
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.bNumConfigurations = 0x01,
.bReserved = 0
};
#endif // TUD_OPT_HIGH_SPEED
// Invoked when device is mounted
void tud_mount_cb(void)
{
xSemaphoreGive(wait_mount);
}
/**
* @brief TinyUSB Teardown specific testcase
*
* Scenario:
* 1. Install TinyUSB device without any class
* 2. Wait SetConfiguration() (tud_mount_cb)
* 3. If attempts == 0 goto step 8
* 4. Wait TEARDOWN_DEVICE_DETACH_DELAY_MS
* 5. Uninstall TinyUSB device
* 6. Wait TEARDOWN_DEVICE_INIT_DELAY_MS
* 7. Decrease attempts by 1, goto step 3
* 8. Wait TEARDOWN_DEVICE_DETACH_DELAY_MS
* 9. Uninstall TinyUSB device
*/
TEST_CASE("tinyusb_teardown", "[esp_tinyusb][teardown]")
{
wait_mount = xSemaphoreCreateBinary();
TEST_ASSERT_NOT_EQUAL(NULL, wait_mount);
// TinyUSB driver configuration
const tinyusb_config_t tusb_cfg = {
.device_descriptor = &test_device_descriptor,
.string_descriptor = NULL,
.string_descriptor_count = 0,
.external_phy = false,
#if (TUD_OPT_HIGH_SPEED)
.fs_configuration_descriptor = test_configuration_descriptor,
.hs_configuration_descriptor = test_configuration_descriptor,
.qualifier_descriptor = &device_qualifier,
#else
.configuration_descriptor = test_configuration_descriptor,
#endif // TUD_OPT_HIGH_SPEED
};
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg));
// Wait for the usb event
ESP_LOGD(TAG, "wait mount...");
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(wait_mount, pdMS_TO_TICKS(TEARDOWN_DEVICE_ATTACH_TIMEOUT_MS)));
ESP_LOGD(TAG, "mounted");
// Teardown routine
int attempts = TEARDOWN_AMOUNT;
while (attempts--) {
// Keep device attached
vTaskDelay(pdMS_TO_TICKS(TEARDOWN_DEVICE_DETACH_DELAY_MS));
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
// Teardown
vTaskDelay(pdMS_TO_TICKS(TEARDOWN_DEVICE_INIT_DELAY_MS));
// Reconnect
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg));
// Wait for the usb event
ESP_LOGD(TAG, "wait mount...");
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(wait_mount, pdMS_TO_TICKS(TEARDOWN_DEVICE_ATTACH_TIMEOUT_MS)));
ESP_LOGD(TAG, "mounted");
}
// Teardown
vTaskDelay(pdMS_TO_TICKS(TEARDOWN_DEVICE_DETACH_DELAY_MS));
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_uninstall());
// Remove primitives
vSemaphoreDelete(wait_mount);
}
#endif

View File

@@ -0,0 +1,75 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import pytest
from pytest_embedded_idf.dut import IdfDut
import subprocess
from time import sleep, time
class DeviceNotFoundError(Exception):
"""Custom exception for device not found within the timeout period."""
pass
def tusb_dev_in_list(vid, pid):
try:
output = subprocess.check_output(["lsusb"], text=True)
search_string = f"{vid}:{pid}"
return search_string in output
except Exception as e:
print(f"Error while executing lsusb: {e}")
raise
def wait_tusb_dev_appeared(vid, pid, timeout):
start_time = time()
while True:
if tusb_dev_in_list(vid, pid):
return True
if time() - start_time > timeout:
raise DeviceNotFoundError(f"Device with VID: 0x{vid:04x}, PID: 0x{pid:04x} not found within {timeout} seconds.")
sleep(0.5)
def wait_tusb_dev_removed(vid, pid, timeout):
start_time = time()
while True:
if not tusb_dev_in_list(vid, pid):
return True
if time() - start_time > timeout:
raise DeviceNotFoundError(f"Device with VID: 0x{vid:04x}, PID: 0x{pid:04x} wasn't removed within {timeout} seconds.")
sleep(0.5)
def tusb_device_teardown(iterations, timeout):
TUSB_VID = "303a" # Espressif TinyUSB VID
TUSB_PID = "4002" # Espressif TinyUSB VID
for i in range(iterations):
# Wait until the device is present
print(f"Waiting for device ...")
wait_tusb_dev_appeared(TUSB_VID, TUSB_PID, timeout)
print("Device detected.")
# Wait until the device is removed
print("Waiting for the device to be removed...")
wait_tusb_dev_removed(TUSB_VID, TUSB_PID, timeout)
print("Device removed.")
print("Monitoring completed.")
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.esp32p4
@pytest.mark.usb_device
def test_usb_teardown_device(dut: IdfDut) -> None:
dut.expect_exact('Press ENTER to see the list of tests.')
dut.write('[teardown]')
dut.expect_exact('TinyUSB: TinyUSB Driver installed')
sleep(2) # Some time for the OS to enumerate our USB device
try:
tusb_device_teardown(10, 10) # Teardown tusb device: amount, timeout
except DeviceNotFoundError as e:
print(f"Error: {e}")
raise
except Exception as e:
print(f"An unexpected error occurred: {e}")
raise

View File

@@ -0,0 +1,16 @@
# Configure TinyUSB, it will be used to mock USB devices
CONFIG_TINYUSB_CDC_ENABLED=y
CONFIG_TINYUSB_CDC_COUNT=1
# Disable watchdogs, they'd get triggered during unity interactive menu
CONFIG_ESP_INT_WDT=n
CONFIG_ESP_TASK_WDT=n
# Run-time checks of Heap and Stack
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
CONFIG_COMPILER_STACK_CHECK=y
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y
CONFIG_COMPILER_CXX_EXCEPTIONS=y

View File

@@ -0,0 +1,9 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
set(COMPONENTS main)
project(test_app_vendor_specific)

View File

@@ -0,0 +1,4 @@
idf_component_register(SRC_DIRS .
INCLUDE_DIRS .
REQUIRES unity
WHOLE_ARCHIVE)

View File

@@ -0,0 +1,5 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_tinyusb:
version: "*"
override_path: "../../../"

View File

@@ -0,0 +1,48 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "unity_test_runner.h"
void app_main(void)
{
/*
_ _ _
| | (_) | |
___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__
/ _ \/ __| '_ \| __| | '_ \| | | | | | / __| '_ \
| __/\__ \ |_) | |_| | | | | |_| | |_| \__ \ |_) |
\___||___/ .__/ \__|_|_| |_|\__, |\__,_|___/_.__/
| |______ __/ |
|_|______| |___/
_____ _____ _____ _____
|_ _| ___/ ___|_ _|
| | | |__ \ `--. | |
| | | __| `--. \ | |
| | | |___/\__/ / | |
\_/ \____/\____/ \_/
*/
printf(" _ _ _ \n");
printf(" | | (_) | | \n");
printf(" ___ ___ _ __ | |_ _ _ __ _ _ _ _ ___| |__ \n");
printf(" / _ \\/ __| '_ \\| __| | '_ \\| | | | | | / __| '_ \\ \n");
printf("| __/\\__ \\ |_) | |_| | | | | |_| | |_| \\__ \\ |_) |\n");
printf(" \\___||___/ .__/ \\__|_|_| |_|\\__, |\\__,_|___/_.__/ \n");
printf(" | |______ __/ | \n");
printf(" |_|______| |___/ \n");
printf(" _____ _____ _____ _____ \n");
printf("|_ _| ___/ ___|_ _| \n");
printf(" | | | |__ \\ `--. | | \n");
printf(" | | | __| `--. \\ | | \n");
printf(" | | | |___/\\__/ / | | \n");
printf(" \\_/ \\____/\\____/ \\_/ \n");
// We don't check memory leaks here because we cannot uninstall TinyUSB yet
unity_run_menu();
}

View File

@@ -0,0 +1,71 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "soc/soc_caps.h"
#if SOC_USB_OTG_SUPPORTED
#include <stdio.h>
#include <string.h>
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_err.h"
#include "unity.h"
#include "tinyusb.h"
static const char *TAG = "vendor_test";
char buffer_in[64];
#if (TUSB_VERSION_MINOR >= 17)
void tud_vendor_rx_cb(uint8_t itf, uint8_t const *buffer, uint16_t bufsize)
#else
void tud_vendor_rx_cb(uint8_t itf)
#endif // TUSB_VERSION_MINOR
{
ESP_LOGI(TAG, "tud_vendor_rx_cb(itf=%d)", itf);
int available = tud_vendor_n_available(itf);
int read = tud_vendor_n_read(itf, buffer_in, available);
ESP_LOGI(TAG, "actual read: %d. buffer message: %s", read, buffer_in);
}
// Invoked when a control transfer occurred on an interface of this class
// Driver response accordingly to the request and the transfer stage (setup/data/ack)
// return false to stall control endpoint (e.g unsupported request)
bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request)
{
// nothing to with DATA & ACK stage
if (stage != CONTROL_STAGE_SETUP) {
return true;
}
// stall unknown request
return false;
}
/**
* @brief TinyUSB Vendor specific testcase
*/
TEST_CASE("tinyusb_vendor", "[esp_tinyusb][vendor]")
{
// Install TinyUSB driver
const tinyusb_config_t tusb_cfg = {
.external_phy = false,
.device_descriptor = NULL,
#if (TUD_OPT_HIGH_SPEED)
.fs_configuration_descriptor = NULL,
.hs_configuration_descriptor = NULL,
.qualifier_descriptor = NULL,
#else
.configuration_descriptor = NULL,
#endif // TUD_OPT_HIGH_SPEED
};
TEST_ASSERT_EQUAL(ESP_OK, tinyusb_driver_install(&tusb_cfg));
}
#endif

View File

@@ -0,0 +1,96 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import pytest
from pytest_embedded_idf.dut import IdfDut
import usb.core
import usb.util
from time import sleep
def find_interface_by_index(device, interface_index):
'''
Function to find the interface by index
'''
for cfg in device:
for intf in cfg:
if intf.bInterfaceNumber == interface_index:
return intf
return None
def send_data_to_intf(VID, PID, interface_index):
'''
Find a device, its interface and dual BULK endpoints
Send some data to it
'''
# Find the USB device by VID and PID
dev = usb.core.find(idVendor=VID, idProduct=PID)
if dev is None:
raise ValueError("Device not found")
# Find the interface by index
intf = find_interface_by_index(dev, interface_index)
if intf is None:
raise ValueError(f"Interface with index {interface_index} not found")
if intf:
def ep_read(len):
try:
return ep_in.read(len, 100)
except:
return None
def ep_write(buf):
try:
ep_out.write(buf, 100)
except:
pass
maximum_packet_size = 64
ep_in = usb.util.find_descriptor(intf, custom_match = \
lambda e: usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_IN)
ep_out = usb.util.find_descriptor(intf, custom_match = \
lambda e: usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_OUT)
#print(ep_in)
#print(ep_out)
buf = "IF{}\n".format(interface_index).encode('utf-8')
ep_write(bytes(buf))
ep_read(maximum_packet_size)
else:
print("NOT found")
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.esp32p4
#@pytest.mark.usb_device Disable in CI, for now, not possible to run this test in Docker container
def test_usb_device_vendor(dut: IdfDut) -> None:
'''
Running the test locally:
1. Build the test app for your DUT
2. Connect you DUT to your test runner (local machine) with USB port and flashing port
3. Run `pytest --target esp32s3`
Important note: On Windows you must manually assign a driver the device, otherwise it will never be configured.
On Linux this is automatic
Test procedure:
1. Run the test on the DUT
2. Expect 2 Vendor specific interfaces in the system
3. Send some data to it, check log output
'''
dut.run_all_single_board_cases(group='vendor')
sleep(2) # Wait until the device is enumerated
VID = 0x303A # Replace with your device's Vendor ID
PID = 0x4040 # Replace with your device's Product ID
send_data_to_intf(VID, PID, 0)
dut.expect_exact('vendor_test: actual read: 4. buffer message: IF0')
send_data_to_intf(VID, PID, 1)
dut.expect_exact('vendor_test: actual read: 4. buffer message: IF1')

View File

@@ -0,0 +1,15 @@
# Configure TinyUSB, it will be used to mock USB devices
CONFIG_TINYUSB_VENDOR_COUNT=2
# Disable watchdogs, they'd get triggered during unity interactive menu
CONFIG_ESP_INT_WDT=n
CONFIG_ESP_TASK_WDT=n
# Run-time checks of Heap and Stack
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
CONFIG_COMPILER_STACK_CHECK=y
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y
CONFIG_COMPILER_CXX_EXCEPTIONS=y

View File

@@ -0,0 +1,112 @@
/*
* SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "sdkconfig.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_err.h"
#include "esp_private/usb_phy.h"
#include "tinyusb.h"
#include "descriptors_control.h"
#include "tusb.h"
#include "tusb_tasks.h"
const static char *TAG = "TinyUSB";
static usb_phy_handle_t phy_hdl;
// For the tinyusb component without tusb_teardown() implementation
#ifndef tusb_teardown
# define tusb_teardown() (true)
#endif // tusb_teardown
esp_err_t tinyusb_driver_install(const tinyusb_config_t *config)
{
ESP_RETURN_ON_FALSE(config, ESP_ERR_INVALID_ARG, TAG, "Config can't be NULL");
// Configure USB PHY
usb_phy_config_t phy_conf = {
.controller = USB_PHY_CTRL_OTG,
.otg_mode = USB_OTG_MODE_DEVICE,
#if (USB_PHY_SUPPORTS_P4_OTG11)
.otg_speed = (TUD_OPT_HIGH_SPEED) ? USB_PHY_SPEED_HIGH : USB_PHY_SPEED_FULL,
#else
#if (CONFIG_IDF_TARGET_ESP32P4 && CONFIG_TINYUSB_RHPORT_FS)
#error "USB PHY for OTG1.1 is not supported, please update your esp-idf."
#endif // IDF_TARGET_ESP32P4 && CONFIG_TINYUSB_RHPORT_FS
#endif // USB_PHY_SUPPORTS_P4_OTG11
};
/*
Following ext. PHY IO configuration is here to provide compatibility with IDFv5.x releases,
where ext. PHY IOs were mapped to predefined GPIOs.
In reality, ESP32-S2 and ESP32-S3 can map ext. PHY IOs to any GPIOs.
This option is implemented in esp_tinyusb v2.0.0 and later.
*/
usb_phy_ext_io_conf_t ext_io_conf;
// Use memset to be compatible with IDF < 5.4.1 where suspend_n_io_num and fs_edge_sel_io_num were added
memset(&ext_io_conf, -1, sizeof(usb_phy_ext_io_conf_t));
#if CONFIG_IDF_TARGET_ESP32S2
ext_io_conf.vp_io_num = 33;
ext_io_conf.vm_io_num = 34;
ext_io_conf.rcv_io_num = 35;
ext_io_conf.oen_io_num = 36;
ext_io_conf.vpo_io_num = 37;
ext_io_conf.vmo_io_num = 38;
#elif CONFIG_IDF_TARGET_ESP32S3
ext_io_conf.vp_io_num = 42;
ext_io_conf.vm_io_num = 41;
ext_io_conf.rcv_io_num = 21;
ext_io_conf.oen_io_num = 40;
ext_io_conf.vpo_io_num = 39;
ext_io_conf.vmo_io_num = 38;
#endif // IDF_TARGET_ESP32S3
if (config->external_phy) {
phy_conf.target = USB_PHY_TARGET_EXT;
phy_conf.ext_io_conf = &ext_io_conf;
/*
There is a bug in esp-idf that does not allow device speed selection
when External PHY is used.
Remove this when proper fix is implemented in IDF-11144
*/
phy_conf.otg_speed = USB_PHY_SPEED_UNDEFINED;
} else {
phy_conf.target = USB_PHY_TARGET_INT;
}
// OTG IOs config
const usb_phy_otg_io_conf_t otg_io_conf = USB_PHY_SELF_POWERED_DEVICE(config->vbus_monitor_io);
if (config->self_powered) {
phy_conf.otg_io_conf = &otg_io_conf;
}
ESP_RETURN_ON_ERROR(usb_new_phy(&phy_conf, &phy_hdl), TAG, "Install USB PHY failed");
// Descriptors config
ESP_RETURN_ON_ERROR(tinyusb_set_descriptors(config), TAG, "Descriptors config failed");
// Init
#if !CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK
ESP_RETURN_ON_FALSE(tusb_init(), ESP_FAIL, TAG, "Init TinyUSB stack failed");
#endif
#if !CONFIG_TINYUSB_NO_DEFAULT_TASK
ESP_RETURN_ON_ERROR(tusb_run_task(), TAG, "Run TinyUSB task failed");
#endif
ESP_LOGI(TAG, "TinyUSB Driver installed");
return ESP_OK;
}
esp_err_t tinyusb_driver_uninstall(void)
{
#if !CONFIG_TINYUSB_NO_DEFAULT_TASK
ESP_RETURN_ON_ERROR(tusb_stop_task(), TAG, "Unable to stop TinyUSB task");
#endif // !CONFIG_TINYUSB_NO_DEFAULT_TASK
ESP_RETURN_ON_FALSE(tusb_teardown(), ESP_ERR_NOT_FINISHED, TAG, "Unable to teardown TinyUSB");
tinyusb_free_descriptors();
ESP_RETURN_ON_ERROR(usb_del_phy(phy_hdl), TAG, "Unable to delete PHY");
return ESP_OK;
}

View File

@@ -0,0 +1,174 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "tinyusb_net.h"
#include "descriptors_control.h"
#include "usb_descriptors.h"
#include "device/usbd_pvt.h"
#include "esp_check.h"
#define MAC_ADDR_LEN 6
typedef struct packet {
void *buffer;
void *buff_free_arg;
uint16_t len;
esp_err_t result;
} packet_t;
struct tinyusb_net_handle {
bool initialized;
SemaphoreHandle_t buffer_sema;
EventGroupHandle_t tx_flags;
tusb_net_rx_cb_t rx_cb;
tusb_net_free_tx_cb_t tx_buff_free_cb;
tusb_net_init_cb_t init_cb;
char mac_str[2 * MAC_ADDR_LEN + 1];
void *ctx;
packet_t *packet_to_send;
};
const static int TX_FINISHED_BIT = BIT0;
static struct tinyusb_net_handle s_net_obj = { };
static const char *TAG = "tusb_net";
static void do_send_sync(void *ctx)
{
(void) ctx;
if (xSemaphoreTake(s_net_obj.buffer_sema, 0) != pdTRUE || s_net_obj.packet_to_send == NULL) {
return;
}
packet_t *packet = s_net_obj.packet_to_send;
if (tud_network_can_xmit(packet->len)) {
tud_network_xmit(packet, packet->len);
packet->result = ESP_OK;
} else {
packet->result = ESP_FAIL;
}
xSemaphoreGive(s_net_obj.buffer_sema);
xEventGroupSetBits(s_net_obj.tx_flags, TX_FINISHED_BIT);
}
static void do_send_async(void *ctx)
{
packet_t *packet = ctx;
if (tud_network_can_xmit(packet->len)) {
tud_network_xmit(packet, packet->len);
} else if (s_net_obj.tx_buff_free_cb) {
ESP_LOGW(TAG, "Packet cannot be accepted on USB interface, dropping");
s_net_obj.tx_buff_free_cb(packet->buff_free_arg, s_net_obj.ctx);
}
free(packet);
}
esp_err_t tinyusb_net_send_async(void *buffer, uint16_t len, void *buff_free_arg)
{
if (!tud_ready()) {
return ESP_ERR_INVALID_STATE;
}
packet_t *packet = calloc(1, sizeof(packet_t));
packet->len = len;
packet->buffer = buffer;
packet->buff_free_arg = buff_free_arg;
ESP_RETURN_ON_FALSE(packet, ESP_ERR_NO_MEM, TAG, "Failed to allocate packet to send");
usbd_defer_func(do_send_async, packet, false);
return ESP_OK;
}
esp_err_t tinyusb_net_send_sync(void *buffer, uint16_t len, void *buff_free_arg, TickType_t timeout)
{
if (!tud_ready()) {
return ESP_ERR_INVALID_STATE;
}
// Lazy init the flags and semaphores, as they might not be needed (if async approach is used)
if (!s_net_obj.tx_flags) {
s_net_obj.tx_flags = xEventGroupCreate();
ESP_RETURN_ON_FALSE(s_net_obj.tx_flags, ESP_ERR_NO_MEM, TAG, "Failed to allocate event flags");
}
if (!s_net_obj.buffer_sema) {
s_net_obj.buffer_sema = xSemaphoreCreateBinary();
ESP_RETURN_ON_FALSE(s_net_obj.buffer_sema, ESP_ERR_NO_MEM, TAG, "Failed to allocate buffer semaphore");
}
packet_t packet = {
.buffer = buffer,
.len = len,
.buff_free_arg = buff_free_arg
};
s_net_obj.packet_to_send = &packet;
xSemaphoreGive(s_net_obj.buffer_sema); // now the packet is ready, let's mark it available to tusb send
// to execute the send function in tinyUSB task context
usbd_defer_func(do_send_sync, NULL, false); // arg=NULL -> sync send, we keep the packet inside the object
// wait wor completion with defined timeout
EventBits_t bits = xEventGroupWaitBits(s_net_obj.tx_flags, TX_FINISHED_BIT, pdTRUE, pdTRUE, timeout);
xSemaphoreTake(s_net_obj.buffer_sema, portMAX_DELAY); // if tusb sending already started, we have wait before ditching the packet
s_net_obj.packet_to_send = NULL; // invalidate the argument
if (bits & TX_FINISHED_BIT) { // If transaction finished, return error code
return packet.result;
}
return ESP_ERR_TIMEOUT;
}
esp_err_t tinyusb_net_init(tinyusb_usbdev_t usb_dev, const tinyusb_net_config_t *cfg)
{
(void) usb_dev;
ESP_RETURN_ON_FALSE(s_net_obj.initialized == false, ESP_ERR_INVALID_STATE, TAG, "TinyUSB Net class is already initialized");
// the semaphore and event flags are initialized only if needed
s_net_obj.rx_cb = cfg->on_recv_callback;
s_net_obj.init_cb = cfg->on_init_callback;
s_net_obj.tx_buff_free_cb = cfg->free_tx_buffer;
s_net_obj.ctx = cfg->user_context;
const uint8_t *mac = &cfg->mac_addr[0];
snprintf(s_net_obj.mac_str, sizeof(s_net_obj.mac_str), "%02X%02X%02X%02X%02X%02X",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
uint8_t mac_id = tusb_get_mac_string_id();
// Pass it to Descriptor control module
tinyusb_set_str_descriptor(s_net_obj.mac_str, mac_id);
s_net_obj.initialized = true;
return ESP_OK;
}
//--------------------------------------------------------------------+
// tinyusb callbacks
//--------------------------------------------------------------------+
bool tud_network_recv_cb(const uint8_t *src, uint16_t size)
{
if (s_net_obj.rx_cb) {
s_net_obj.rx_cb((void *)src, size, s_net_obj.ctx);
}
tud_network_recv_renew();
return true;
}
uint16_t tud_network_xmit_cb(uint8_t *dst, void *ref, uint16_t arg)
{
packet_t *packet = ref;
uint16_t len = arg;
memcpy(dst, packet->buffer, packet->len);
if (s_net_obj.tx_buff_free_cb) {
s_net_obj.tx_buff_free_cb(packet->buff_free_arg, s_net_obj.ctx);
}
return len;
}
void tud_network_init_cb(void)
{
if (s_net_obj.init_cb) {
s_net_obj.init_cb(s_net_obj.ctx);
}
}

View File

@@ -0,0 +1,359 @@
/*
* SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "esp_check.h"
#include "esp_err.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "tusb.h"
#include "tusb_cdc_acm.h"
#include "cdc.h"
#include "sdkconfig.h"
#ifndef MIN
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#endif
// CDC-ACM spinlock
static portMUX_TYPE cdc_acm_lock = portMUX_INITIALIZER_UNLOCKED;
#define CDC_ACM_ENTER_CRITICAL() portENTER_CRITICAL(&cdc_acm_lock)
#define CDC_ACM_EXIT_CRITICAL() portEXIT_CRITICAL(&cdc_acm_lock)
typedef struct {
tusb_cdcacm_callback_t callback_rx;
tusb_cdcacm_callback_t callback_rx_wanted_char;
tusb_cdcacm_callback_t callback_line_state_changed;
tusb_cdcacm_callback_t callback_line_coding_changed;
} esp_tusb_cdcacm_t; /*!< CDC_ACM object */
static const char *TAG = "tusb_cdc_acm";
static inline esp_tusb_cdcacm_t *get_acm(tinyusb_cdcacm_itf_t itf)
{
esp_tusb_cdc_t *cdc_inst = tinyusb_cdc_get_intf(itf);
if (cdc_inst == NULL) {
return (esp_tusb_cdcacm_t *)NULL;
}
return (esp_tusb_cdcacm_t *)(cdc_inst->subclass_obj);
}
/* TinyUSB callbacks
********************************************************************* */
/* Invoked by cdc interface when line state changed e.g connected/disconnected */
void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts)
{
esp_tusb_cdcacm_t *acm = get_acm(itf);
if (dtr && rts) { // connected
if (acm != NULL) {
ESP_LOGV(TAG, "Host connected to CDC no.%d.", itf);
} else {
ESP_LOGW(TAG, "Host is connected to CDC no.%d, but it is not initialized. Initialize it using `tinyusb_cdc_init`.", itf);
return;
}
} else { // disconnected
if (acm != NULL) {
ESP_LOGV(TAG, "Serial device is ready to connect to CDC no.%d", itf);
} else {
return;
}
}
if (acm) {
CDC_ACM_ENTER_CRITICAL();
tusb_cdcacm_callback_t cb = acm->callback_line_state_changed;
CDC_ACM_EXIT_CRITICAL();
if (cb) {
cdcacm_event_t event = {
.type = CDC_EVENT_LINE_STATE_CHANGED,
.line_state_changed_data = {
.dtr = dtr,
.rts = rts
}
};
cb(itf, &event);
}
}
}
/* Invoked when CDC interface received data from host */
void tud_cdc_rx_cb(uint8_t itf)
{
esp_tusb_cdcacm_t *acm = get_acm(itf);
if (acm) {
CDC_ACM_ENTER_CRITICAL();
tusb_cdcacm_callback_t cb = acm->callback_rx;
CDC_ACM_EXIT_CRITICAL();
if (cb) {
cdcacm_event_t event = {
.type = CDC_EVENT_RX
};
cb(itf, &event);
}
}
}
// Invoked when line coding is change via SET_LINE_CODING
void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const *p_line_coding)
{
esp_tusb_cdcacm_t *acm = get_acm(itf);
if (acm) {
CDC_ACM_ENTER_CRITICAL();
tusb_cdcacm_callback_t cb = acm->callback_line_coding_changed;
CDC_ACM_EXIT_CRITICAL();
if (cb) {
cdcacm_event_t event = {
.type = CDC_EVENT_LINE_CODING_CHANGED,
.line_coding_changed_data = {
.p_line_coding = p_line_coding,
}
};
cb(itf, &event);
}
}
}
// Invoked when received `wanted_char`
void tud_cdc_rx_wanted_cb(uint8_t itf, char wanted_char)
{
esp_tusb_cdcacm_t *acm = get_acm(itf);
if (acm) {
CDC_ACM_ENTER_CRITICAL();
tusb_cdcacm_callback_t cb = acm->callback_rx_wanted_char;
CDC_ACM_EXIT_CRITICAL();
if (cb) {
cdcacm_event_t event = {
.type = CDC_EVENT_RX_WANTED_CHAR,
.rx_wanted_char_data = {
.wanted_char = wanted_char,
}
};
cb(itf, &event);
}
}
}
esp_err_t tinyusb_cdcacm_register_callback(tinyusb_cdcacm_itf_t itf,
cdcacm_event_type_t event_type,
tusb_cdcacm_callback_t callback)
{
esp_tusb_cdcacm_t *acm = get_acm(itf);
if (acm) {
switch (event_type) {
case CDC_EVENT_RX:
CDC_ACM_ENTER_CRITICAL();
acm->callback_rx = callback;
CDC_ACM_EXIT_CRITICAL();
return ESP_OK;
case CDC_EVENT_RX_WANTED_CHAR:
CDC_ACM_ENTER_CRITICAL();
acm->callback_rx_wanted_char = callback;
CDC_ACM_EXIT_CRITICAL();
return ESP_OK;
case CDC_EVENT_LINE_STATE_CHANGED:
CDC_ACM_ENTER_CRITICAL();
acm->callback_line_state_changed = callback;
CDC_ACM_EXIT_CRITICAL();
return ESP_OK;
case CDC_EVENT_LINE_CODING_CHANGED:
CDC_ACM_ENTER_CRITICAL();
acm->callback_line_coding_changed = callback;
CDC_ACM_EXIT_CRITICAL();
return ESP_OK;
default:
ESP_LOGE(TAG, "Wrong event type");
return ESP_ERR_INVALID_ARG;
}
} else {
ESP_LOGE(TAG, "CDC-ACM is not initialized");
return ESP_ERR_INVALID_STATE;
}
}
esp_err_t tinyusb_cdcacm_unregister_callback(tinyusb_cdcacm_itf_t itf,
cdcacm_event_type_t event_type)
{
esp_tusb_cdcacm_t *acm = get_acm(itf);
if (!acm) {
ESP_LOGE(TAG, "Interface is not initialized. Use `tinyusb_cdc_init` for initialization");
return ESP_ERR_INVALID_STATE;
}
switch (event_type) {
case CDC_EVENT_RX:
CDC_ACM_ENTER_CRITICAL();
acm->callback_rx = NULL;
CDC_ACM_EXIT_CRITICAL();
return ESP_OK;
case CDC_EVENT_RX_WANTED_CHAR:
CDC_ACM_ENTER_CRITICAL();
acm->callback_rx_wanted_char = NULL;
CDC_ACM_EXIT_CRITICAL();
return ESP_OK;
case CDC_EVENT_LINE_STATE_CHANGED:
CDC_ACM_ENTER_CRITICAL();
acm->callback_line_state_changed = NULL;
CDC_ACM_EXIT_CRITICAL();
return ESP_OK;
case CDC_EVENT_LINE_CODING_CHANGED:
CDC_ACM_ENTER_CRITICAL();
acm->callback_line_coding_changed = NULL;
CDC_ACM_EXIT_CRITICAL();
return ESP_OK;
default:
ESP_LOGE(TAG, "Wrong event type");
return ESP_ERR_INVALID_ARG;
}
}
/*********************************************************************** TinyUSB callbacks*/
/* CDC-ACM
********************************************************************* */
esp_err_t tinyusb_cdcacm_read(tinyusb_cdcacm_itf_t itf, uint8_t *out_buf, size_t out_buf_sz, size_t *rx_data_size)
{
esp_tusb_cdcacm_t *acm = get_acm(itf);
ESP_RETURN_ON_FALSE(acm, ESP_ERR_INVALID_STATE, TAG, "Interface is not initialized. Use `tinyusb_cdc_init` for initialization");
if (tud_cdc_n_available(itf) == 0) {
*rx_data_size = 0;
} else {
*rx_data_size = tud_cdc_n_read(itf, out_buf, out_buf_sz);
}
return ESP_OK;
}
size_t tinyusb_cdcacm_write_queue_char(tinyusb_cdcacm_itf_t itf, char ch)
{
if (!get_acm(itf)) { // non-initialized
return 0;
}
return tud_cdc_n_write_char(itf, ch);
}
size_t tinyusb_cdcacm_write_queue(tinyusb_cdcacm_itf_t itf, const uint8_t *in_buf, size_t in_size)
{
if (!get_acm(itf)) { // non-initialized
return 0;
}
const uint32_t size_available = tud_cdc_n_write_available(itf);
return tud_cdc_n_write(itf, in_buf, MIN(in_size, size_available));
}
static uint32_t tud_cdc_n_write_occupied(tinyusb_cdcacm_itf_t itf)
{
return CFG_TUD_CDC_TX_BUFSIZE - tud_cdc_n_write_available(itf);
}
esp_err_t tinyusb_cdcacm_write_flush(tinyusb_cdcacm_itf_t itf, uint32_t timeout_ticks)
{
if (!get_acm(itf)) { // non-initialized
return ESP_FAIL;
}
if (!timeout_ticks) { // if no timeout - nonblocking mode
// It might take some time until TinyUSB flushes the endpoint
// Since this call is non-blocking, we don't wait for flush finished,
// We only inform the user by returning ESP_ERR_NOT_FINISHED
tud_cdc_n_write_flush(itf);
if (tud_cdc_n_write_occupied(itf)) {
return ESP_ERR_NOT_FINISHED;
}
} else { // trying during the timeout
uint32_t ticks_start = xTaskGetTickCount();
uint32_t ticks_now = ticks_start;
while (1) { // loop until success or until the time runs out
ticks_now = xTaskGetTickCount();
tud_cdc_n_write_flush(itf);
if (tud_cdc_n_write_occupied(itf) == 0) {
break; // All data flushed
}
if ( (ticks_now - ticks_start) > timeout_ticks ) { // Time is up
ESP_LOGW(TAG, "Flush failed");
return ESP_ERR_TIMEOUT;
}
vTaskDelay(1);
}
}
return ESP_OK;
}
static esp_err_t alloc_obj(tinyusb_cdcacm_itf_t itf)
{
esp_tusb_cdc_t *cdc_inst = tinyusb_cdc_get_intf(itf);
if (cdc_inst == NULL) {
return ESP_FAIL;
}
cdc_inst->subclass_obj = calloc(1, sizeof(esp_tusb_cdcacm_t));
if (cdc_inst->subclass_obj == NULL) {
return ESP_FAIL;
}
return ESP_OK;
}
static esp_err_t obj_free(tinyusb_cdcacm_itf_t itf)
{
esp_tusb_cdc_t *cdc_inst = tinyusb_cdc_get_intf(itf);
if (cdc_inst == NULL || cdc_inst->subclass_obj == NULL) {
return ESP_FAIL;
}
free(cdc_inst->subclass_obj);
return ESP_OK;
}
esp_err_t tusb_cdc_acm_init(const tinyusb_config_cdcacm_t *cfg)
{
esp_err_t ret = ESP_OK;
int itf = (int)cfg->cdc_port;
/* Creating a CDC object */
const tinyusb_config_cdc_t cdc_cfg = {
.usb_dev = cfg->usb_dev,
.cdc_class = TUSB_CLASS_CDC,
.cdc_subclass.comm_subclass = CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL
};
ESP_RETURN_ON_ERROR(tinyusb_cdc_init(itf, &cdc_cfg), TAG, "tinyusb_cdc_init failed");
ESP_GOTO_ON_ERROR(alloc_obj(itf), fail, TAG, "alloc_obj failed");
/* Callbacks setting up*/
if (cfg->callback_rx) {
tinyusb_cdcacm_register_callback(itf, CDC_EVENT_RX, cfg->callback_rx);
}
if (cfg->callback_rx_wanted_char) {
tinyusb_cdcacm_register_callback(itf, CDC_EVENT_RX_WANTED_CHAR, cfg->callback_rx_wanted_char);
}
if (cfg->callback_line_state_changed) {
tinyusb_cdcacm_register_callback(itf, CDC_EVENT_LINE_STATE_CHANGED, cfg->callback_line_state_changed);
}
if (cfg->callback_line_coding_changed) {
tinyusb_cdcacm_register_callback( itf, CDC_EVENT_LINE_CODING_CHANGED, cfg->callback_line_coding_changed);
}
return ESP_OK;
fail:
tinyusb_cdc_deinit(itf);
return ret;
}
esp_err_t tusb_cdc_acm_deinit(int itf)
{
esp_err_t ret = ESP_OK;
ESP_RETURN_ON_ERROR(obj_free(itf), TAG, "obj_free failed");
ESP_RETURN_ON_ERROR(tinyusb_cdc_deinit(itf), TAG, "tinyusb_cdc_deinit failed");
return ret;
}
bool tusb_cdc_acm_initialized(tinyusb_cdcacm_itf_t itf)
{
esp_tusb_cdcacm_t *acm = get_acm(itf);
if (acm) {
return true;
} else {
return false;
}
}
/*********************************************************************** CDC-ACM*/

View File

@@ -0,0 +1,115 @@
/*
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdio_ext.h>
#include "esp_log.h"
#include "cdc.h"
#include "tusb_console.h"
#include "tinyusb.h"
#include "vfs_tinyusb.h"
#include "esp_check.h"
#define STRINGIFY(s) STRINGIFY2(s)
#define STRINGIFY2(s) #s
static const char *TAG = "tusb_console";
typedef struct {
FILE *in;
FILE *out;
FILE *err;
} console_handle_t;
static console_handle_t con;
/**
* @brief Reopen standard streams using a new path
*
* @param f_in - pointer to a pointer holding a file for in or NULL to don't change stdin
* @param f_out - pointer to a pointer holding a file for out or NULL to don't change stdout
* @param f_err - pointer to a pointer holding a file for err or NULL to don't change stderr
* @param path - mount point
* @return esp_err_t ESP_FAIL or ESP_OK
*/
static esp_err_t redirect_std_streams_to(FILE **f_in, FILE **f_out, FILE **f_err, const char *path)
{
if (f_in) {
*f_in = freopen(path, "r", stdin);
if (*f_in == NULL) {
ESP_LOGE(TAG, "Failed to reopen in!");
return ESP_FAIL;
}
}
if (f_out) {
*f_out = freopen(path, "w", stdout);
if (*f_out == NULL) {
ESP_LOGE(TAG, "Failed to reopen out!");
return ESP_FAIL;
}
}
if (f_err) {
*f_err = freopen(path, "w", stderr);
if (*f_err == NULL) {
ESP_LOGE(TAG, "Failed to reopen err!");
return ESP_FAIL;
}
}
return ESP_OK;
}
/**
* @brief Restore output to default
*
* @param f_in - pointer to a pointer of an in file updated with `redirect_std_streams_to` or NULL to don't change stdin
* @param f_out - pointer to a pointer of an out file updated with `redirect_std_streams_to` or NULL to don't change stdout
* @param f_err - pointer to a pointer of an err file updated with `redirect_std_streams_to` or NULL to don't change stderr
* @return esp_err_t ESP_FAIL or ESP_OK
*/
static esp_err_t restore_std_streams(FILE **f_in, FILE **f_out, FILE **f_err)
{
const char *default_uart_dev = "/dev/uart/" STRINGIFY(CONFIG_ESP_CONSOLE_UART_NUM);
if (f_in) {
stdin = freopen(default_uart_dev, "r", *f_in);
if (stdin == NULL) {
ESP_LOGE(TAG, "Failed to reopen stdin!");
return ESP_FAIL;
}
}
if (f_out) {
stdout = freopen(default_uart_dev, "w", *f_out);
if (stdout == NULL) {
ESP_LOGE(TAG, "Failed to reopen stdout!");
return ESP_FAIL;
}
}
if (f_err) {
stderr = freopen(default_uart_dev, "w", *f_err);
if (stderr == NULL) {
ESP_LOGE(TAG, "Failed to reopen stderr!");
return ESP_FAIL;
}
}
return ESP_OK;
}
esp_err_t esp_tusb_init_console(int cdc_intf)
{
/* Registering TUSB at VFS */
ESP_RETURN_ON_ERROR(esp_vfs_tusb_cdc_register(cdc_intf, NULL), TAG, "");
ESP_RETURN_ON_ERROR(redirect_std_streams_to(&con.in, &con.out, &con.err, VFS_TUSB_PATH_DEFAULT), TAG, "Failed to redirect STD streams");
return ESP_OK;
}
esp_err_t esp_tusb_deinit_console(int cdc_intf)
{
ESP_RETURN_ON_ERROR(restore_std_streams(&con.in, &con.out, &con.err), TAG, "Failed to restore STD streams");
esp_vfs_tusb_cdc_unregister(NULL);
return ESP_OK;
}

View File

@@ -0,0 +1,732 @@
/*
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "esp_log.h"
#include "esp_err.h"
#include "esp_check.h"
#include "esp_vfs_fat.h"
#include "diskio_impl.h"
#include "diskio_wl.h"
#include "wear_levelling.h"
#include "esp_partition.h"
#include "esp_memory_utils.h"
#include "sdkconfig.h"
#include "vfs_fat_internal.h"
#include "tinyusb.h"
#include "device/usbd_pvt.h"
#include "class/msc/msc_device.h"
#include "tusb_msc_storage.h"
#if SOC_SDMMC_HOST_SUPPORTED
#include "diskio_sdmmc.h"
#endif
static const char *TAG = "tinyusb_msc_storage";
#define MSC_STORAGE_MEM_ALIGN 4
#define MSC_STORAGE_BUFFER_SIZE CONFIG_TINYUSB_MSC_BUFSIZE /*!< Size of the buffer, configured via menuconfig (MSC FIFO size) */
#if ((MSC_STORAGE_BUFFER_SIZE) % MSC_STORAGE_MEM_ALIGN != 0)
#error "CONFIG_TINYUSB_MSC_BUFSIZE must be divisible by MSC_STORAGE_MEM_ALIGN. Adjust your configuration (MSC FIFO size) in menuconfig."
#endif
/**
* @brief Structure representing a single write buffer for MSC operations.
*/
typedef struct {
uint8_t data_buffer[MSC_STORAGE_BUFFER_SIZE]; /*!< Buffer to store write data. The size is defined by MSC_STORAGE_BUFFER_SIZE. */
uint32_t lba; /*!< Logical Block Address for the current WRITE10 operation. */
uint32_t offset; /*!< Offset within the specified LBA for the current write operation. */
uint32_t bufsize; /*!< Number of bytes to be written in this operation. */
} msc_storage_buffer_t;
/**
* @brief Handle for TinyUSB MSC storage interface.
*
* This structure holds metadata and function pointers required to
* manage the underlying storage medium (SPI flash, SDMMC).
*/
typedef struct {
msc_storage_buffer_t storage_buffer;
bool is_fat_mounted; /*!< Indicates if the FAT filesystem is currently mounted. */
const char *base_path; /*!< Base path where the filesystem is mounted. */
union {
wl_handle_t wl_handle; /*!< Handle for wear leveling on SPI flash. */
#if SOC_SDMMC_HOST_SUPPORTED
sdmmc_card_t *card; /*!< Handle for SDMMC card. */
#endif
};
esp_err_t (*mount)(BYTE pdrv); /*!< Pointer to the mount function. */
esp_err_t (*unmount)(void); /*!< Pointer to the unmount function. */
uint32_t sector_count; /*!< Total number of sectors in the storage medium. */
uint32_t sector_size; /*!< Size of a single sector in bytes. */
esp_err_t (*read)(size_t sector_size, /*!< Function pointer for reading data. */
uint32_t lba, uint32_t offset, size_t size, void *dest);
esp_err_t (*write)(size_t sector_size, /*!< Function pointer for writing data. */
size_t addr, uint32_t lba, uint32_t offset, size_t size, const void *src);
tusb_msc_callback_t callback_mount_changed; /*!< Callback for mount state change. */
tusb_msc_callback_t callback_premount_changed; /*!< Callback for pre-mount state change. */
int max_files; /*!< Maximum number of files that can be open simultaneously. */
} tinyusb_msc_storage_handle_s;
/* handle of tinyusb driver connected to application */
static tinyusb_msc_storage_handle_s *s_storage_handle;
static esp_err_t _mount_spiflash(BYTE pdrv)
{
return ff_diskio_register_wl_partition(pdrv, s_storage_handle->wl_handle);
}
static esp_err_t _unmount_spiflash(void)
{
BYTE pdrv;
pdrv = ff_diskio_get_pdrv_wl(s_storage_handle->wl_handle);
if (pdrv == 0xff) {
ESP_LOGE(TAG, "Invalid state");
return ESP_ERR_INVALID_STATE;
}
ff_diskio_clear_pdrv_wl(s_storage_handle->wl_handle);
char drv[3] = {(char)('0' + pdrv), ':', 0};
f_mount(0, drv, 0);
ff_diskio_unregister(pdrv);
return ESP_OK;
}
static uint32_t _get_sector_count_spiflash(void)
{
uint32_t result = 0;
assert(s_storage_handle->wl_handle != WL_INVALID_HANDLE);
size_t size = wl_sector_size(s_storage_handle->wl_handle);
if (size == 0) {
ESP_LOGW(TAG, "WL Sector size is zero !!!");
result = 0;
} else {
result = (uint32_t)(wl_size(s_storage_handle->wl_handle) / size);
}
return result;
}
static uint32_t _get_sector_size_spiflash(void)
{
assert(s_storage_handle->wl_handle != WL_INVALID_HANDLE);
return (uint32_t)wl_sector_size(s_storage_handle->wl_handle);
}
static esp_err_t _read_sector_spiflash(size_t sector_size,
uint32_t lba,
uint32_t offset,
size_t size,
void *dest)
{
size_t temp = 0;
size_t addr = 0; // Address of the data to be read, relative to the beginning of the partition.
ESP_RETURN_ON_FALSE(!__builtin_umul_overflow(lba, sector_size, &temp), ESP_ERR_INVALID_SIZE, TAG, "overflow lba %lu sector_size %u", lba, sector_size);
ESP_RETURN_ON_FALSE(!__builtin_uadd_overflow(temp, offset, &addr), ESP_ERR_INVALID_SIZE, TAG, "overflow addr %u offset %lu", temp, offset);
return wl_read(s_storage_handle->wl_handle, addr, dest, size);
}
static esp_err_t _write_sector_spiflash(size_t sector_size,
size_t addr,
uint32_t lba,
uint32_t offset,
size_t size,
const void *src)
{
(void) addr; // addr argument is not used in this function, we calculate it based on lba and offset.
size_t temp = 0;
size_t src_addr = 0; // Address of the data to be write, relative to the beginning of the partition.
ESP_RETURN_ON_FALSE(!__builtin_umul_overflow(lba, sector_size, &temp), ESP_ERR_INVALID_SIZE, TAG, "overflow lba %lu sector_size %u", lba, sector_size);
ESP_RETURN_ON_FALSE(!__builtin_uadd_overflow(temp, offset, &src_addr), ESP_ERR_INVALID_SIZE, TAG, "overflow addr %u offset %lu", temp, offset);
ESP_RETURN_ON_ERROR(wl_erase_range(s_storage_handle->wl_handle, src_addr, size), TAG, "Failed to erase");
return wl_write(s_storage_handle->wl_handle, src_addr, src, size);
}
#if SOC_SDMMC_HOST_SUPPORTED
static esp_err_t _mount_sdmmc(BYTE pdrv)
{
ff_diskio_register_sdmmc(pdrv, s_storage_handle->card);
ff_sdmmc_set_disk_status_check(pdrv, false);
return ESP_OK;
}
static esp_err_t _unmount_sdmmc(void)
{
BYTE pdrv;
pdrv = ff_diskio_get_pdrv_card(s_storage_handle->card);
if (pdrv == 0xff) {
ESP_LOGE(TAG, "Invalid state");
return ESP_ERR_INVALID_STATE;
}
char drv[3] = {(char)('0' + pdrv), ':', 0};
f_mount(0, drv, 0);
ff_diskio_unregister(pdrv);
return ESP_OK;
}
static uint32_t _get_sector_count_sdmmc(void)
{
assert(s_storage_handle->card);
return (uint32_t)s_storage_handle->card->csd.capacity;
}
static uint32_t _get_sector_size_sdmmc(void)
{
assert(s_storage_handle->card);
return (uint32_t)s_storage_handle->card->csd.sector_size;
}
static esp_err_t _read_sector_sdmmc(size_t sector_size,
uint32_t lba,
uint32_t offset,
size_t size,
void *dest)
{
return sdmmc_read_sectors(s_storage_handle->card, dest, lba, size / sector_size);
}
static esp_err_t _write_sector_sdmmc(size_t sector_size,
size_t addr,
uint32_t lba,
uint32_t offset,
size_t size,
const void *src)
{
(void) addr; // addr argument is not used in this function, we use lba directly
return sdmmc_write_sectors(s_storage_handle->card, src, lba, size / sector_size);
}
#endif
static esp_err_t _msc_storage_read_sector(uint32_t lba,
uint32_t offset,
size_t size,
void *dest)
{
assert(s_storage_handle);
size_t sector_size = tinyusb_msc_storage_get_sector_size();
return (s_storage_handle->read)(sector_size, lba, offset, size, dest);
}
static esp_err_t _msc_storage_write_sector(uint32_t lba,
uint32_t offset,
size_t size,
const void *src)
{
assert(s_storage_handle);
if (s_storage_handle->is_fat_mounted) {
ESP_LOGE(TAG, "can't write, FAT mounted");
return ESP_ERR_INVALID_STATE;
}
size_t sector_size = tinyusb_msc_storage_get_sector_size();
if (size % sector_size != 0) {
ESP_LOGE(TAG, "Invalid Argument lba(%lu) offset(%lu) size(%u) sector_size(%u)", lba, offset, size, sector_size);
return ESP_ERR_INVALID_ARG;
}
return (s_storage_handle->write)(sector_size, 0 /* not used */, lba, offset, size, src);
}
static esp_err_t _mount(char *drv, FATFS *fs)
{
void *workbuf = NULL;
const size_t workbuf_size = 4096;
esp_err_t ret;
// Try to mount partition
FRESULT fresult = f_mount(fs, drv, 1);
if (fresult != FR_OK) {
ESP_LOGW(TAG, "f_mount failed (%d)", fresult);
if (!((fresult == FR_NO_FILESYSTEM || fresult == FR_INT_ERR))) {
ret = ESP_FAIL;
goto fail;
}
workbuf = ff_memalloc(workbuf_size);
if (workbuf == NULL) {
ret = ESP_ERR_NO_MEM;
goto fail;
}
size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size(
CONFIG_WL_SECTOR_SIZE,
4096);
ESP_LOGW(TAG, "formatting card, allocation unit size=%d", alloc_unit_size);
BYTE format_flags;
#if defined(CONFIG_TINYUSB_FAT_FORMAT_ANY)
format_flags = FM_ANY;
#elif defined(CONFIG_TINYUSB_FAT_FORMAT_FAT)
format_flags = FM_FAT;
#elif defined(CONFIG_TINYUSB_FAT_FORMAT_FAT32)
format_flags = FM_FAT32;
#elif defined(CONFIG_TINYUSB_FAT_FORMAT_EXFAT)
format_flags = FM_EXFAT;
#else
#error "No FAT format type selected"
#endif
#ifdef CONFIG_TINYUSB_FAT_FORMAT_SFD
format_flags |= FM_SFD;
#endif
const MKFS_PARM opt = {format_flags, 0, 0, 0, alloc_unit_size};
fresult = f_mkfs("", &opt, workbuf, workbuf_size); // Use default volume
if (fresult != FR_OK) {
ret = ESP_FAIL;
ESP_LOGE(TAG, "f_mkfs failed (%d)", fresult);
goto fail;
}
free(workbuf);
workbuf = NULL;
fresult = f_mount(fs, drv, 0);
if (fresult != FR_OK) {
ret = ESP_FAIL;
ESP_LOGE(TAG, "f_mount failed after formatting (%d)", fresult);
goto fail;
}
}
return ESP_OK;
fail:
if (workbuf) {
free(workbuf);
}
return ret;
}
/**
* @brief Handles deferred USB MSC write operations.
*
* This function is invoked via TinyUSB's deferred execution mechanism to perform
* write operations to the underlying storage. It writes data from the
* `storage_buffer` stored within the `s_storage_handle`.
*
* @param param Unused. Present for compatibility with deferred function signature.
*/
static void _write_func(void *param)
{
// Process the data in storage_buffer
esp_err_t err = _msc_storage_write_sector(
s_storage_handle->storage_buffer.lba,
s_storage_handle->storage_buffer.offset,
s_storage_handle->storage_buffer.bufsize,
(const void *)s_storage_handle->storage_buffer.data_buffer
);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Write failed, error=0x%x", err);
}
}
esp_err_t tinyusb_msc_storage_mount(const char *base_path)
{
esp_err_t ret = ESP_OK;
assert(s_storage_handle);
if (s_storage_handle->is_fat_mounted) {
return ESP_OK;
}
tusb_msc_callback_t cb = s_storage_handle->callback_premount_changed;
if (cb) {
tinyusb_msc_event_t event = {
.type = TINYUSB_MSC_EVENT_PREMOUNT_CHANGED,
.mount_changed_data = {
.is_mounted = s_storage_handle->is_fat_mounted
}
};
cb(&event);
}
if (!base_path) {
base_path = CONFIG_TINYUSB_MSC_MOUNT_PATH;
}
// connect driver to FATFS
BYTE pdrv = 0xFF;
ESP_RETURN_ON_ERROR(ff_diskio_get_drive(&pdrv), TAG,
"The maximum count of volumes is already mounted");
char drv[3] = {(char)('0' + pdrv), ':', 0};
ESP_GOTO_ON_ERROR((s_storage_handle->mount)(pdrv), fail, TAG, "Failed pdrv=%d", pdrv);
FATFS *fs = NULL;
ret = esp_vfs_fat_register(base_path, drv, s_storage_handle->max_files, &fs);
if (ret == ESP_ERR_INVALID_STATE) {
ESP_LOGD(TAG, "it's okay, already registered with VFS");
} else if (ret != ESP_OK) {
ESP_LOGE(TAG, "esp_vfs_fat_register failed (0x%x)", ret);
goto fail;
}
ESP_GOTO_ON_ERROR(_mount(drv, fs), fail, TAG, "Failed _mount");
s_storage_handle->is_fat_mounted = true;
s_storage_handle->base_path = base_path;
cb = s_storage_handle->callback_mount_changed;
if (cb) {
tinyusb_msc_event_t event = {
.type = TINYUSB_MSC_EVENT_MOUNT_CHANGED,
.mount_changed_data = {
.is_mounted = s_storage_handle->is_fat_mounted
}
};
cb(&event);
}
return ret;
fail:
if (fs) {
esp_vfs_fat_unregister_path(base_path);
}
ff_diskio_unregister(pdrv);
s_storage_handle->is_fat_mounted = false;
ESP_LOGW(TAG, "Failed to mount storage (0x%x)", ret);
return ret;
}
esp_err_t tinyusb_msc_storage_unmount(void)
{
if (!s_storage_handle) {
return ESP_FAIL;
}
if (!s_storage_handle->is_fat_mounted) {
return ESP_OK;
}
tusb_msc_callback_t cb = s_storage_handle->callback_premount_changed;
if (cb) {
tinyusb_msc_event_t event = {
.type = TINYUSB_MSC_EVENT_PREMOUNT_CHANGED,
.mount_changed_data = {
.is_mounted = s_storage_handle->is_fat_mounted
}
};
cb(&event);
}
esp_err_t err = (s_storage_handle->unmount)();
if (err) {
return err;
}
err = esp_vfs_fat_unregister_path(s_storage_handle->base_path);
s_storage_handle->base_path = NULL;
s_storage_handle->is_fat_mounted = false;
cb = s_storage_handle->callback_mount_changed;
if (cb) {
tinyusb_msc_event_t event = {
.type = TINYUSB_MSC_EVENT_MOUNT_CHANGED,
.mount_changed_data = {
.is_mounted = s_storage_handle->is_fat_mounted
}
};
cb(&event);
}
return err;
}
uint32_t tinyusb_msc_storage_get_sector_count(void)
{
assert(s_storage_handle);
return (s_storage_handle->sector_count);
}
uint32_t tinyusb_msc_storage_get_sector_size(void)
{
assert(s_storage_handle);
return (s_storage_handle->sector_size);
}
esp_err_t tinyusb_msc_storage_init_spiflash(const tinyusb_msc_spiflash_config_t *config)
{
assert(!s_storage_handle);
ESP_RETURN_ON_FALSE(CONFIG_TINYUSB_MSC_BUFSIZE >= CONFIG_WL_SECTOR_SIZE,
ESP_ERR_NOT_SUPPORTED, TAG,
"CONFIG_TINYUSB_MSC_BUFSIZE (%d) must be at least the size of CONFIG_WL_SECTOR_SIZE (%d)", (int)(CONFIG_TINYUSB_MSC_BUFSIZE), (int)(CONFIG_WL_SECTOR_SIZE));
s_storage_handle = (tinyusb_msc_storage_handle_s *)heap_caps_aligned_alloc(MSC_STORAGE_MEM_ALIGN, sizeof(tinyusb_msc_storage_handle_s), MALLOC_CAP_DMA);
ESP_RETURN_ON_FALSE(s_storage_handle, ESP_ERR_NO_MEM, TAG, "Failed to allocate memory for storage handle");
s_storage_handle->mount = &_mount_spiflash;
s_storage_handle->unmount = &_unmount_spiflash;
s_storage_handle->wl_handle = config->wl_handle;
s_storage_handle->sector_count = _get_sector_count_spiflash();
s_storage_handle->sector_size = _get_sector_size_spiflash();
s_storage_handle->read = &_read_sector_spiflash;
s_storage_handle->write = &_write_sector_spiflash;
s_storage_handle->is_fat_mounted = false;
s_storage_handle->base_path = NULL;
// In case the user does not set mount_config.max_files
// and for backward compatibility with versions <1.4.2
// max_files is set to 2
const int max_files = config->mount_config.max_files;
s_storage_handle->max_files = max_files > 0 ? max_files : 2;
/* Callbacks setting up*/
if (config->callback_mount_changed) {
tinyusb_msc_register_callback(TINYUSB_MSC_EVENT_MOUNT_CHANGED, config->callback_mount_changed);
} else {
tinyusb_msc_unregister_callback(TINYUSB_MSC_EVENT_MOUNT_CHANGED);
}
if (config->callback_premount_changed) {
tinyusb_msc_register_callback(TINYUSB_MSC_EVENT_PREMOUNT_CHANGED, config->callback_premount_changed);
} else {
tinyusb_msc_unregister_callback(TINYUSB_MSC_EVENT_PREMOUNT_CHANGED);
}
if (!esp_ptr_dma_capable((const void *)s_storage_handle->storage_buffer.data_buffer)) {
ESP_LOGW(TAG, "storage buffer is not DMA capable");
}
return ESP_OK;
}
#if SOC_SDMMC_HOST_SUPPORTED
esp_err_t tinyusb_msc_storage_init_sdmmc(const tinyusb_msc_sdmmc_config_t *config)
{
assert(!s_storage_handle);
s_storage_handle = (tinyusb_msc_storage_handle_s *)heap_caps_aligned_alloc(MSC_STORAGE_MEM_ALIGN, sizeof(tinyusb_msc_storage_handle_s), MALLOC_CAP_DMA);
ESP_RETURN_ON_FALSE(s_storage_handle, ESP_ERR_NO_MEM, TAG, "Failed to allocate memory for storage handle");
s_storage_handle->mount = &_mount_sdmmc;
s_storage_handle->unmount = &_unmount_sdmmc;
s_storage_handle->card = config->card;
s_storage_handle->sector_count = _get_sector_count_sdmmc();
s_storage_handle->sector_size = _get_sector_size_sdmmc();
s_storage_handle->read = &_read_sector_sdmmc;
s_storage_handle->write = &_write_sector_sdmmc;
s_storage_handle->is_fat_mounted = false;
s_storage_handle->base_path = NULL;
// In case the user does not set mount_config.max_files
// and for backward compatibility with versions <1.4.2
// max_files is set to 2
const int max_files = config->mount_config.max_files;
s_storage_handle->max_files = max_files > 0 ? max_files : 2;
/* Callbacks setting up*/
if (config->callback_mount_changed) {
tinyusb_msc_register_callback(TINYUSB_MSC_EVENT_MOUNT_CHANGED, config->callback_mount_changed);
} else {
tinyusb_msc_unregister_callback(TINYUSB_MSC_EVENT_MOUNT_CHANGED);
}
if (config->callback_premount_changed) {
tinyusb_msc_register_callback(TINYUSB_MSC_EVENT_PREMOUNT_CHANGED, config->callback_premount_changed);
} else {
tinyusb_msc_unregister_callback(TINYUSB_MSC_EVENT_PREMOUNT_CHANGED);
}
if (!esp_ptr_dma_capable((const void *)s_storage_handle->storage_buffer.data_buffer)) {
ESP_LOGW(TAG, "storage buffer is not DMA capable");
}
return ESP_OK;
}
#endif
void tinyusb_msc_storage_deinit(void)
{
if (s_storage_handle) {
heap_caps_free(s_storage_handle);
s_storage_handle = NULL;
}
}
esp_err_t tinyusb_msc_register_callback(tinyusb_msc_event_type_t event_type,
tusb_msc_callback_t callback)
{
assert(s_storage_handle);
switch (event_type) {
case TINYUSB_MSC_EVENT_MOUNT_CHANGED:
s_storage_handle->callback_mount_changed = callback;
return ESP_OK;
case TINYUSB_MSC_EVENT_PREMOUNT_CHANGED:
s_storage_handle->callback_premount_changed = callback;
return ESP_OK;
default:
ESP_LOGE(TAG, "Wrong event type");
return ESP_ERR_INVALID_ARG;
}
}
esp_err_t tinyusb_msc_unregister_callback(tinyusb_msc_event_type_t event_type)
{
assert(s_storage_handle);
switch (event_type) {
case TINYUSB_MSC_EVENT_MOUNT_CHANGED:
s_storage_handle->callback_mount_changed = NULL;
return ESP_OK;
case TINYUSB_MSC_EVENT_PREMOUNT_CHANGED:
s_storage_handle->callback_premount_changed = NULL;
return ESP_OK;
default:
ESP_LOGE(TAG, "Wrong event type");
return ESP_ERR_INVALID_ARG;
}
}
bool tinyusb_msc_storage_in_use_by_usb_host(void)
{
assert(s_storage_handle);
return !s_storage_handle->is_fat_mounted;
}
/* TinyUSB MSC callbacks
********************************************************************* */
/** SCSI ASC/ASCQ codes. **/
/** User can add and use more codes as per the need of the application **/
#define SCSI_CODE_ASC_MEDIUM_NOT_PRESENT 0x3A /** SCSI ASC code for 'MEDIUM NOT PRESENT' **/
#define SCSI_CODE_ASC_INVALID_COMMAND_OPERATION_CODE 0x20 /** SCSI ASC code for 'INVALID COMMAND OPERATION CODE' **/
#define SCSI_CODE_ASCQ 0x00
// 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])
{
(void) lun;
const char vid[] = "TinyUSB";
const char pid[] = "Flash Storage";
const char rev[] = "0.2";
memcpy(vendor_id, vid, strlen(vid));
memcpy(product_id, pid, strlen(pid));
memcpy(product_rev, rev, strlen(rev));
}
// Invoked when received Test Unit Ready command.
// return true allowing host to read/write this LUN e.g SD card inserted
bool tud_msc_test_unit_ready_cb(uint8_t lun)
{
(void) lun;
bool result = false;
if (s_storage_handle->is_fat_mounted) {
tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, SCSI_CODE_ASC_MEDIUM_NOT_PRESENT, SCSI_CODE_ASCQ);
result = false;
} else {
if (tinyusb_msc_storage_unmount() != ESP_OK) {
ESP_LOGW(TAG, "tud_msc_test_unit_ready_cb() unmount Fails");
}
result = true;
}
return result;
}
// Invoked when received SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size
// Application update block count and block size
void tud_msc_capacity_cb(uint8_t lun, uint32_t *block_count, uint16_t *block_size)
{
(void) lun;
uint32_t sec_count = tinyusb_msc_storage_get_sector_count();
uint32_t sec_size = tinyusb_msc_storage_get_sector_size();
*block_count = sec_count;
*block_size = (uint16_t)sec_size;
}
// Invoked when received Start Stop Unit command
// - Start = 0 : stopped power mode, if load_eject = 1 : unload disk storage
// - Start = 1 : active mode, if load_eject = 1 : load disk storage
bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject)
{
(void) lun;
(void) power_condition;
if (load_eject && !start) {
if (tinyusb_msc_storage_mount(s_storage_handle->base_path) != ESP_OK) {
ESP_LOGW(TAG, "tud_msc_start_stop_cb() mount Fails");
}
}
return true;
}
// Invoked when received SCSI READ10 command
// - Address = lba * BLOCK_SIZE + offset
// - Application fill the buffer (up to bufsize) with address contents and return number of read byte.
int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void *buffer, uint32_t bufsize)
{
esp_err_t err = _msc_storage_read_sector(lba, offset, bufsize, buffer);
if (err != ESP_OK) {
ESP_LOGE(TAG, "msc_storage_read_sector failed: 0x%x", err);
return 0;
}
return bufsize;
}
// Invoked when received SCSI WRITE10 command
// - Address = lba * BLOCK_SIZE + offset
// - Application write data from buffer to address contents (up to bufsize) and return number of written byte.
int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize)
{
assert(bufsize <= MSC_STORAGE_BUFFER_SIZE);
// Copy data to the buffer
memcpy((void *)s_storage_handle->storage_buffer.data_buffer, buffer, bufsize);
s_storage_handle->storage_buffer.lba = lba;
s_storage_handle->storage_buffer.offset = offset;
s_storage_handle->storage_buffer.bufsize = bufsize;
// Defer execution of the write to the TinyUSB task
usbd_defer_func(_write_func, NULL, false);
// Return the number of bytes accepted
return bufsize;
}
/**
* Invoked when received an SCSI command not in built-in list below.
* - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, TEST_UNIT_READY, START_STOP_UNIT, MODE_SENSE6, REQUEST_SENSE
* - READ10 and WRITE10 has their own callbacks
*
* \param[in] lun Logical unit number
* \param[in] scsi_cmd SCSI command contents which application must examine to response accordingly
* \param[out] buffer Buffer for SCSI Data Stage.
* - For INPUT: application must fill this with response.
* - For OUTPUT it holds the Data from host
* \param[in] bufsize Buffer's length.
*
* \return Actual bytes processed, can be zero for no-data command.
* \retval negative Indicate error e.g unsupported command, tinyusb will \b STALL the corresponding
* endpoint and return failed status in command status wrapper phase.
*/
int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void *buffer, uint16_t bufsize)
{
int32_t ret;
switch (scsi_cmd[0]) {
case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL:
/* SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL is the Prevent/Allow Medium Removal
command (1Eh) that requests the library to enable or disable user access to
the storage media/partition. */
ret = 0;
break;
default:
ESP_LOGW(TAG, "tud_msc_scsi_cb() invoked: %d", scsi_cmd[0]);
tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_CODE_ASC_INVALID_COMMAND_OPERATION_CODE, SCSI_CODE_ASCQ);
ret = -1;
break;
}
return ret;
}
// Invoked when device is unmounted
void tud_umount_cb(void)
{
if (tinyusb_msc_storage_mount(s_storage_handle->base_path) != ESP_OK) {
ESP_LOGW(TAG, "tud_umount_cb() mount Fails");
}
}
// Invoked when device is mounted (configured)
void tud_mount_cb(void)
{
tinyusb_msc_storage_unmount();
}
/*********************************************************************** TinyUSB MSC callbacks*/

View File

@@ -0,0 +1,77 @@
/*
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_log.h"
#include "esp_check.h"
#include "tinyusb.h"
#include "tusb_tasks.h"
const static char *TAG = "tusb_tsk";
static TaskHandle_t s_tusb_tskh;
#if CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK
const static int INIT_OK = BIT0;
const static int INIT_FAILED = BIT1;
#endif
/**
* @brief This top level thread processes all usb events and invokes callbacks
*/
static void tusb_device_task(void *arg)
{
ESP_LOGD(TAG, "tinyusb task started");
#if CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK
EventGroupHandle_t *init_flags = arg;
if (!tusb_init()) {
ESP_LOGI(TAG, "Init TinyUSB stack failed");
xEventGroupSetBits(*init_flags, INIT_FAILED);
vTaskDelete(NULL);
}
ESP_LOGD(TAG, "tinyusb task has been initialized");
xEventGroupSetBits(*init_flags, INIT_OK);
#endif // CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK
while (1) { // RTOS forever loop
tud_task();
}
}
esp_err_t tusb_run_task(void)
{
// This function is not guaranteed to be thread safe, if invoked multiple times without calling `tusb_stop_task`, will cause memory leak
// doing a sanity check anyway
ESP_RETURN_ON_FALSE(!s_tusb_tskh, ESP_ERR_INVALID_STATE, TAG, "TinyUSB main task already started");
void *task_arg = NULL;
#if CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK
// need to synchronize to potentially report issue if init failed
EventGroupHandle_t init_flags = xEventGroupCreate();
ESP_RETURN_ON_FALSE(init_flags, ESP_ERR_NO_MEM, TAG, "Failed to allocate task sync flags");
task_arg = &init_flags;
#endif
// Create a task for tinyusb device stack:
xTaskCreatePinnedToCore(tusb_device_task, "TinyUSB", CONFIG_TINYUSB_TASK_STACK_SIZE, task_arg, CONFIG_TINYUSB_TASK_PRIORITY, &s_tusb_tskh, CONFIG_TINYUSB_TASK_AFFINITY);
ESP_RETURN_ON_FALSE(s_tusb_tskh, ESP_FAIL, TAG, "create TinyUSB main task failed");
#if CONFIG_TINYUSB_INIT_IN_DEFAULT_TASK
// wait until tusb initialization has completed
EventBits_t bits = xEventGroupWaitBits(init_flags, INIT_OK | INIT_FAILED, pdFALSE, pdFALSE, portMAX_DELAY);
vEventGroupDelete(init_flags);
ESP_RETURN_ON_FALSE(bits & INIT_OK, ESP_FAIL, TAG, "Init TinyUSB stack failed");
#endif
return ESP_OK;
}
esp_err_t tusb_stop_task(void)
{
ESP_RETURN_ON_FALSE(s_tusb_tskh, ESP_ERR_INVALID_STATE, TAG, "TinyUSB main task not started yet");
vTaskDelete(s_tusb_tskh);
s_tusb_tskh = NULL;
return ESP_OK;
}

View File

@@ -0,0 +1,291 @@
/*
* SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "usb_descriptors.h"
#include "sdkconfig.h"
#include "tinyusb_types.h"
/*
* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug.
* Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC.
*
* Auto ProductID layout's Bitmap:
* [MSB] HID | MSC | CDC [LSB]
*/
#define _PID_MAP(itf, n) ((CFG_TUD_##itf) << (n))
#define USB_TUSB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \
_PID_MAP(MIDI, 3) | _PID_MAP(AUDIO, 4) | _PID_MAP(VENDOR, 5) )
/**** Kconfig driven Descriptor ****/
//------------- Device Descriptor -------------//
const tusb_desc_device_t descriptor_dev_default = {
.bLength = sizeof(descriptor_dev_default),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
#if CFG_TUD_CDC
// Use Interface Association Descriptor (IAD) for CDC
// As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1)
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
#else
.bDeviceClass = 0x00,
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
#endif
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
#if CONFIG_TINYUSB_DESC_USE_ESPRESSIF_VID
.idVendor = USB_ESPRESSIF_VID,
#else
.idVendor = CONFIG_TINYUSB_DESC_CUSTOM_VID,
#endif
#if CONFIG_TINYUSB_DESC_USE_DEFAULT_PID
.idProduct = USB_TUSB_PID,
#else
.idProduct = CONFIG_TINYUSB_DESC_CUSTOM_PID,
#endif
.bcdDevice = CONFIG_TINYUSB_DESC_BCD_DEVICE,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
};
#if (TUD_OPT_HIGH_SPEED)
const tusb_desc_device_qualifier_t descriptor_qualifier_default = {
.bLength = sizeof(tusb_desc_device_qualifier_t),
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
.bcdUSB = 0x0200,
#if CFG_TUD_CDC
// Use Interface Association Descriptor (IAD) for CDC
// As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1)
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
#else
.bDeviceClass = 0x00,
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
#endif
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.bNumConfigurations = 0x01,
.bReserved = 0
};
#endif // TUD_OPT_HIGH_SPEED
//------------- Array of String Descriptors -------------//
const char *descriptor_str_default[] = {
// array of pointer to string descriptors
(char[]){0x09, 0x04}, // 0: is supported language is English (0x0409)
CONFIG_TINYUSB_DESC_MANUFACTURER_STRING, // 1: Manufacturer
CONFIG_TINYUSB_DESC_PRODUCT_STRING, // 2: Product
CONFIG_TINYUSB_DESC_SERIAL_STRING, // 3: Serials, should use chip ID
#if CONFIG_TINYUSB_CDC_ENABLED
CONFIG_TINYUSB_DESC_CDC_STRING, // 4: CDC Interface
#endif
#if CONFIG_TINYUSB_MSC_ENABLED
CONFIG_TINYUSB_DESC_MSC_STRING, // 5: MSC Interface
#endif
#if CONFIG_TINYUSB_NET_MODE_ECM_RNDIS || CONFIG_TINYUSB_NET_MODE_NCM
"USB net", // 6. NET Interface
"", // 7. MAC
#endif
#if CFG_TUD_VENDOR
"Vendor specific", // 8. Vendor specific
#endif
NULL // NULL: Must be last. Indicates end of array
};
//------------- Interfaces enumeration -------------//
enum {
#if CFG_TUD_CDC
ITF_NUM_CDC = 0,
ITF_NUM_CDC_DATA,
#endif
#if CFG_TUD_CDC > 1
ITF_NUM_CDC1,
ITF_NUM_CDC1_DATA,
#endif
#if CFG_TUD_MSC
ITF_NUM_MSC,
#endif
#if CFG_TUD_NCM
ITF_NUM_NET,
ITF_NUM_NET_DATA,
#endif
#if CFG_TUD_VENDOR
ITF_VENDOR,
#endif
#if CFG_TUD_VENDOR > 1
ITF_VENDOR1,
#endif
ITF_NUM_TOTAL
};
enum {
TUSB_DESC_TOTAL_LEN = TUD_CONFIG_DESC_LEN +
CFG_TUD_CDC * TUD_CDC_DESC_LEN +
CFG_TUD_MSC * TUD_MSC_DESC_LEN +
CFG_TUD_NCM * TUD_CDC_NCM_DESC_LEN +
CFG_TUD_VENDOR * TUD_VENDOR_DESC_LEN
};
//------------- USB Endpoint numbers -------------//
enum {
// Available USB Endpoints: 5 IN/OUT EPs and 1 IN EP
EP_EMPTY = 0,
#if CFG_TUD_CDC
EPNUM_0_CDC_NOTIF,
EPNUM_0_CDC,
#endif
#if CFG_TUD_CDC > 1
EPNUM_1_CDC_NOTIF,
EPNUM_1_CDC,
#endif
#if CFG_TUD_MSC
EPNUM_MSC,
#endif
#if CFG_TUD_NCM
EPNUM_NET_NOTIF,
EPNUM_NET_DATA,
#endif
#if CFG_TUD_VENDOR
EPNUM_0_VENDOR,
#endif
#if CFG_TUD_VENDOR > 1
EPNUM_1_VENDOR
#endif
};
//------------- STRID -------------//
enum {
STRID_LANGID = 0,
STRID_MANUFACTURER,
STRID_PRODUCT,
STRID_SERIAL,
#if CFG_TUD_CDC
STRID_CDC_INTERFACE,
#endif
#if CFG_TUD_MSC
STRID_MSC_INTERFACE,
#endif
#if CFG_TUD_NCM
STRID_NET_INTERFACE,
STRID_MAC,
#endif
#if CFG_TUD_VENDOR
STRID_VENDOR_INTERFACE,
#endif
};
//------------- Configuration Descriptor -------------//
uint8_t const descriptor_fs_cfg_default[] = {
// Configuration number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
#if CFG_TUD_CDC
// Interface number, string index, EP notification address and size, EP data address (out, in) and size.
TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, STRID_CDC_INTERFACE, 0x80 | EPNUM_0_CDC_NOTIF, 8, EPNUM_0_CDC, 0x80 | EPNUM_0_CDC, 64),
#endif
#if CFG_TUD_CDC > 1
// Interface number, string index, EP notification address and size, EP data address (out, in) and size.
TUD_CDC_DESCRIPTOR(ITF_NUM_CDC1, STRID_CDC_INTERFACE, 0x80 | EPNUM_1_CDC_NOTIF, 8, EPNUM_1_CDC, 0x80 | EPNUM_1_CDC, 64),
#endif
#if CFG_TUD_MSC
// Interface number, string index, EP Out & EP In address, EP size
TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, STRID_MSC_INTERFACE, EPNUM_MSC, 0x80 | EPNUM_MSC, 64),
#endif
#if CFG_TUD_NCM
// Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size.
TUD_CDC_NCM_DESCRIPTOR(ITF_NUM_NET, STRID_NET_INTERFACE, STRID_MAC, (0x80 | EPNUM_NET_NOTIF), 64, EPNUM_NET_DATA, (0x80 | EPNUM_NET_DATA), 64, CFG_TUD_NET_MTU),
#endif
#if CFG_TUD_VENDOR
// Interface number, string index, EP Out & IN address, EP size
TUD_VENDOR_DESCRIPTOR(ITF_VENDOR, STRID_VENDOR_INTERFACE, EPNUM_0_VENDOR, 0x80 | EPNUM_0_VENDOR, 64),
#endif
#if CFG_TUD_VENDOR > 1
// Interface number, string index, EP Out & IN address, EP size
TUD_VENDOR_DESCRIPTOR(ITF_VENDOR1, STRID_VENDOR_INTERFACE, EPNUM_1_VENDOR, 0x80 | EPNUM_1_VENDOR, 64),
#endif
};
#if (TUD_OPT_HIGH_SPEED)
uint8_t const descriptor_hs_cfg_default[] = {
// Configuration number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
#if CFG_TUD_CDC
// Interface number, string index, EP notification address and size, EP data address (out, in) and size.
TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, STRID_CDC_INTERFACE, 0x80 | EPNUM_0_CDC_NOTIF, 8, EPNUM_0_CDC, 0x80 | EPNUM_0_CDC, 512),
#endif
#if CFG_TUD_CDC > 1
// Interface number, string index, EP notification address and size, EP data address (out, in) and size.
TUD_CDC_DESCRIPTOR(ITF_NUM_CDC1, STRID_CDC_INTERFACE, 0x80 | EPNUM_1_CDC_NOTIF, 8, EPNUM_1_CDC, 0x80 | EPNUM_1_CDC, 512),
#endif
#if CFG_TUD_MSC
// Interface number, string index, EP Out & EP In address, EP size
TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, STRID_MSC_INTERFACE, EPNUM_MSC, 0x80 | EPNUM_MSC, 512),
#endif
#if CFG_TUD_NCM
// Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size.
TUD_CDC_NCM_DESCRIPTOR(ITF_NUM_NET, STRID_NET_INTERFACE, STRID_MAC, (0x80 | EPNUM_NET_NOTIF), 64, EPNUM_NET_DATA, (0x80 | EPNUM_NET_DATA), 512, CFG_TUD_NET_MTU),
#endif
#if CFG_TUD_VENDOR
// Interface number, string index, EP Out & IN address, EP size
TUD_VENDOR_DESCRIPTOR(ITF_VENDOR, STRID_VENDOR_INTERFACE, EPNUM_0_VENDOR, 0x80 | EPNUM_0_VENDOR, 512),
#endif
#if CFG_TUD_VENDOR > 1
// Interface number, string index, EP Out & IN address, EP size
TUD_VENDOR_DESCRIPTOR(ITF_VENDOR1, STRID_VENDOR_INTERFACE, EPNUM_1_VENDOR, 0x80 | EPNUM_1_VENDOR, 512),
#endif
};
#endif // TUD_OPT_HIGH_SPEED
#if CFG_TUD_NCM
uint8_t tusb_get_mac_string_id(void)
{
return STRID_MAC;
}
#endif
/* End of Kconfig driven Descriptor */

View File

@@ -0,0 +1,303 @@
/*
* SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdio_ext.h>
#include <string.h>
#include <sys/errno.h>
#include <sys/fcntl.h>
#include <sys/lock.h>
#include <sys/param.h>
#include "esp_attr.h"
#include "esp_log.h"
#include "esp_vfs.h"
#include "esp_vfs_dev.h"
#include "tinyusb.h"
#include "tusb_cdc_acm.h"
#include "vfs_tinyusb.h"
#include "sdkconfig.h"
const static char *TAG = "tusb_vfs";
// Token signifying that no character is available
#define NONE -1
#define FD_CHECK(fd, ret_val) do { \
if ((fd) != 0) { \
errno = EBADF; \
return (ret_val); \
} \
} while (0)
#if CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF
# define DEFAULT_TX_MODE ESP_LINE_ENDINGS_CRLF
#elif CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR
# define DEFAULT_TX_MODE ESP_LINE_ENDINGS_CR
#else
# define DEFAULT_TX_MODE ESP_LINE_ENDINGS_LF
#endif
#if CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF
# define DEFAULT_RX_MODE ESP_LINE_ENDINGS_CRLF
#elif CONFIG_NEWLIB_STDIN_LINE_ENDING_CR
# define DEFAULT_RX_MODE ESP_LINE_ENDINGS_CR
#else
# define DEFAULT_RX_MODE ESP_LINE_ENDINGS_LF
#endif
typedef struct {
_lock_t write_lock;
_lock_t read_lock;
esp_line_endings_t tx_mode; // Newline conversion mode when transmitting
esp_line_endings_t rx_mode; // Newline conversion mode when receiving
uint32_t flags;
char vfs_path[VFS_TUSB_MAX_PATH];
int cdc_intf;
} vfs_tinyusb_t;
static vfs_tinyusb_t s_vfstusb;
static esp_err_t apply_path(char const *path)
{
if (path == NULL) {
path = VFS_TUSB_PATH_DEFAULT;
}
size_t path_len = strlen(path) + 1;
if (path_len > VFS_TUSB_MAX_PATH) {
ESP_LOGE(TAG, "The path is too long; maximum is %d characters", VFS_TUSB_MAX_PATH);
return ESP_ERR_INVALID_ARG;
}
strncpy(s_vfstusb.vfs_path, path, (VFS_TUSB_MAX_PATH - 1));
ESP_LOGV(TAG, "Path is set to `%s`", path);
return ESP_OK;
}
/**
* @brief Fill s_vfstusb
*
* @param cdc_intf - interface of tusb for registration
* @param path - a path where the CDC will be registered
* @return esp_err_t ESP_OK or ESP_ERR_INVALID_ARG
*/
static esp_err_t vfstusb_init(int cdc_intf, char const *path)
{
s_vfstusb.cdc_intf = cdc_intf;
s_vfstusb.tx_mode = DEFAULT_TX_MODE;
s_vfstusb.rx_mode = DEFAULT_RX_MODE;
return apply_path(path);
}
/**
* @brief Clear s_vfstusb to default values
*/
static void vfstusb_deinit(void)
{
_lock_close(&(s_vfstusb.write_lock));
_lock_close(&(s_vfstusb.read_lock));
memset(&s_vfstusb, 0, sizeof(s_vfstusb));
}
static int tusb_open(const char *path, int flags, int mode)
{
(void) mode;
(void) path;
s_vfstusb.flags = flags | O_NONBLOCK; // for now only non-blocking mode is implemented
return 0;
}
static ssize_t tusb_write(int fd, const void *data, size_t size)
{
FD_CHECK(fd, -1);
size_t written_sz = 0;
const char *data_c = (const char *)data;
_lock_acquire(&(s_vfstusb.write_lock));
for (size_t i = 0; i < size; i++) {
int c = data_c[i];
if (c != '\n') {
if (!tinyusb_cdcacm_write_queue_char(s_vfstusb.cdc_intf, c)) {
break; // can't write anymore
}
} else {
if (s_vfstusb.tx_mode == ESP_LINE_ENDINGS_CRLF || s_vfstusb.tx_mode == ESP_LINE_ENDINGS_CR) {
char cr = '\r';
if (!tinyusb_cdcacm_write_queue_char(s_vfstusb.cdc_intf, cr)) {
break; // can't write anymore
}
}
if (s_vfstusb.tx_mode == ESP_LINE_ENDINGS_CRLF || s_vfstusb.tx_mode == ESP_LINE_ENDINGS_LF) {
char lf = '\n';
if (!tinyusb_cdcacm_write_queue_char(s_vfstusb.cdc_intf, lf)) {
break; // can't write anymore
}
}
}
written_sz++;
}
tud_cdc_n_write_flush(s_vfstusb.cdc_intf);
_lock_release(&(s_vfstusb.write_lock));
return written_sz;
}
static int tusb_close(int fd)
{
FD_CHECK(fd, -1);
return 0;
}
static ssize_t tusb_read(int fd, void *data, size_t size)
{
FD_CHECK(fd, -1);
char *data_c = (char *) data;
size_t received = 0;
_lock_acquire(&(s_vfstusb.read_lock));
if (tud_cdc_n_available(s_vfstusb.cdc_intf) == 0) {
goto finish;
}
while (received < size) {
int c = tud_cdc_n_read_char(s_vfstusb.cdc_intf);
if ( c == NONE) { // if data ends
break;
}
// Handle line endings. From configured mode -> LF mode
if (s_vfstusb.rx_mode == ESP_LINE_ENDINGS_CR) {
// Change CRs to newlines
if (c == '\r') {
c = '\n';
}
} else if (s_vfstusb.rx_mode == ESP_LINE_ENDINGS_CRLF) {
if (c == '\r') {
uint8_t next_char = NONE;
// Check if next char is newline. If yes, we got CRLF sequence
tud_cdc_n_peek(s_vfstusb.cdc_intf, &next_char);
if (next_char == '\n') {
c = tud_cdc_n_read_char(s_vfstusb.cdc_intf); // Remove '\n' from the fifo
}
}
}
data_c[received] = (char) c;
++received;
if (c == '\n') {
break;
}
}
finish:
_lock_release(&(s_vfstusb.read_lock));
if (received > 0) {
return received;
}
errno = EWOULDBLOCK;
return -1;
}
static int tusb_fstat(int fd, struct stat *st)
{
FD_CHECK(fd, -1);
memset(st, 0, sizeof(*st));
st->st_mode = S_IFCHR;
return 0;
}
static int tusb_fcntl(int fd, int cmd, int arg)
{
FD_CHECK(fd, -1);
int result = 0;
switch (cmd) {
case F_GETFL:
result = s_vfstusb.flags;
break;
case F_SETFL:
s_vfstusb.flags = arg;
break;
default:
result = -1;
errno = ENOSYS;
break;
}
return result;
}
esp_err_t esp_vfs_tusb_cdc_unregister(char const *path)
{
ESP_LOGD(TAG, "Unregistering CDC-VFS driver");
int res;
if (path == NULL) { // NULL means using the default path for unregistering: VFS_TUSB_PATH_DEFAULT
path = VFS_TUSB_PATH_DEFAULT;
}
res = strcmp(s_vfstusb.vfs_path, path);
if (res) {
res = ESP_ERR_INVALID_ARG;
ESP_LOGE(TAG, "There is no CDC-VFS driver registered to path '%s' (err: 0x%x)", path, res);
return res;
}
res = esp_vfs_unregister(s_vfstusb.vfs_path);
if (res != ESP_OK) {
ESP_LOGE(TAG, "Can't unregister CDC-VFS driver from '%s' (err: 0x%x)", s_vfstusb.vfs_path, res);
} else {
ESP_LOGD(TAG, "Unregistered CDC-VFS driver");
vfstusb_deinit();
}
return res;
}
esp_err_t esp_vfs_tusb_cdc_register(int cdc_intf, char const *path)
{
ESP_LOGD(TAG, "Registering CDC-VFS driver");
int res;
if (!tusb_cdc_acm_initialized(cdc_intf)) {
ESP_LOGE(TAG, "TinyUSB CDC#%d is not initialized", cdc_intf);
return ESP_ERR_INVALID_STATE;
}
res = vfstusb_init(cdc_intf, path);
if (res != ESP_OK) {
return res;
}
esp_vfs_t vfs = {
.flags = ESP_VFS_FLAG_DEFAULT,
.close = &tusb_close,
.fcntl = &tusb_fcntl,
.fstat = &tusb_fstat,
.open = &tusb_open,
.read = &tusb_read,
.write = &tusb_write,
};
res = esp_vfs_register(s_vfstusb.vfs_path, &vfs, NULL);
if (res != ESP_OK) {
ESP_LOGE(TAG, "Can't register CDC-VFS driver (err: %x)", res);
} else {
ESP_LOGD(TAG, "CDC-VFS registered (%s)", s_vfstusb.vfs_path);
}
return res;
}
void esp_vfs_tusb_cdc_set_rx_line_endings(esp_line_endings_t mode)
{
_lock_acquire(&(s_vfstusb.read_lock));
s_vfstusb.rx_mode = mode;
_lock_release(&(s_vfstusb.read_lock));
}
void esp_vfs_tusb_cdc_set_tx_line_endings(esp_line_endings_t mode)
{
_lock_acquire(&(s_vfstusb.write_lock));
s_vfstusb.tx_mode = mode;
_lock_release(&(s_vfstusb.write_lock));
}

View File

@@ -0,0 +1,91 @@
version: 2.1
setup: true
orbs:
continuation: circleci/continuation@1
jobs:
set-matrix:
executor: continuation/default
docker:
- image: cimg/base:current
resource_class: small
steps:
- checkout
- run:
name: Set matrix
command: |
MATRIX_JSON=$(python .github/workflows/ci_set_matrix.py)
echo "MATRIX_JSON=$MATRIX_JSON"
BUILDSYSTEM_TOOLCHAIN=(
"cmake aarch64-gcc"
"cmake arm-clang"
"cmake arm-gcc"
"cmake esp-idf"
"cmake msp430-gcc"
"cmake riscv-gcc"
)
# only build IAR if not forked PR, since IAR token is not shared
if [ -z $CIRCLE_PR_USERNAME ]; then
BUILDSYSTEM_TOOLCHAIN+=("cmake arm-iar")
fi
RESOURCE_LARGE='["nrf", "imxrt", "stm32f4", "stm32h7 stm32h7rs"]'
gen_build_entry() {
local build_system="$1"
local toolchain="$2"
local family="$3"
local resource_class="$4"
if [[ "$toolchain" == "esp-idf" ]]; then
echo " - build-vm:" >> .circleci/config2.yml
else
echo " - build:" >> .circleci/config2.yml
fi
echo " matrix:" >> .circleci/config2.yml
echo " parameters:" >> .circleci/config2.yml
echo " build-system: ['$build_system']" >> .circleci/config2.yml
echo " toolchain: ['$toolchain']" >> .circleci/config2.yml
echo " family: $family" >> .circleci/config2.yml
echo " resource_class: ['$resource_class']" >> .circleci/config2.yml
}
for e in "${BUILDSYSTEM_TOOLCHAIN[@]}"; do
e_arr=($e)
build_system="${e_arr[0]}"
toolchain="${e_arr[1]}"
FAMILY=$(echo $MATRIX_JSON | jq -r ".\"$toolchain\"")
echo "FAMILY_${toolchain}=$FAMILY"
# FAMILY_LARGE = FAMILY - RESOURCE_LARGE
# Separate large from medium+ resources
FAMILY_LARGE=$(jq -n --argjson family "$FAMILY" --argjson resource "$RESOURCE_LARGE" '$family | map(select(IN($resource[])))')
FAMILY=$(jq -n --argjson family "$FAMILY" --argjson resource "$RESOURCE_LARGE" '$family | map(select(IN($resource[]) | not))')
if [[ $toolchain == esp-idf || $toolchain == arm-iar ]]; then
gen_build_entry "$build_system" "$toolchain" "$FAMILY" "large"
else
gen_build_entry "$build_system" "$toolchain" "$FAMILY" "medium+"
# add large resources if available
if [ "$(echo $FAMILY_LARGE | jq 'length')" -gt 0 ]; then
gen_build_entry "$build_system" "$toolchain" "$FAMILY_LARGE" "large"
fi
fi
done
- continuation/continue:
configuration_path: .circleci/config2.yml
workflows:
set-matrix:
# Only build PR here, Push will be built by github action.
when:
and:
- not: << pipeline.git.branch.is_default >>
jobs:
- set-matrix

View File

@@ -0,0 +1,185 @@
version: 2.1
commands:
setup-toolchain:
parameters:
toolchain:
type: string
steps:
- run:
name: Set toolchain url and key
command: |
toolchain_url=$(jq -r '."<< parameters.toolchain >>"' .github/actions/setup_toolchain/toolchain.json)
# only cache if not a github link
if [[ $toolchain_url != "https://github.com"* ]]; then
echo "<< parameters.toolchain >>-$toolchain_url" > toolchain_key
fi
echo "export toolchain_url=$toolchain_url" >> $BASH_ENV
- restore_cache:
name: Restore Toolchain Cache
key: deps-{{ checksum "toolchain_key" }}
paths:
- ~/cache/<< parameters.toolchain >>
- run:
name: Install Toolchain
command: |
# download if folder does not exist (not cached)
if [ ! -d ~/cache/<< parameters.toolchain >> ]; then
mkdir -p ~/cache/<< parameters.toolchain >>
if [[ << parameters.toolchain >> == rx-gcc ]]; then
wget --progress=dot:giga $toolchain_url -O toolchain.run
chmod +x toolchain.run
./toolchain.run -p ~/cache/<< parameters.toolchain >>/gnurx -y
elif [[ << parameters.toolchain >> == arm-iar ]]; then
wget --progress=dot:giga $toolchain_url -O ~/cache/<< parameters.toolchain >>/toolchain.deb
else
wget --progress=dot:giga $toolchain_url -O toolchain.tar.gz
tar -C ~/cache/<< parameters.toolchain >> -xaf toolchain.tar.gz
fi
fi
# Add toolchain to PATH
if [[ << parameters.toolchain >> == arm-iar ]]; then
# Install IAR since we only cache deb file
sudo dpkg --ignore-depends=libusb-1.0-0 -i ~/cache/<< parameters.toolchain >>/toolchain.deb
echo "export PATH=$PATH:/opt/iar/cxarm/arm/bin" >> $BASH_ENV
else
echo "export PATH=$PATH:`echo ~/cache/<< parameters.toolchain >>/*/bin`" >> $BASH_ENV
fi
- save_cache:
name: Save Toolchain Cache
key: deps-{{ checksum "toolchain_key" }}
paths:
- ~/cache/<< parameters.toolchain >>
build:
parameters:
build-system:
type: string
toolchain:
type: string
family:
type: string
steps:
- checkout
- run:
name: Get Dependencies
command: |
python tools/get_deps.py << parameters.family >>
# Install ninja if cmake build system
if [ << parameters.build-system >> == "cmake" ]; then
NINJA_URL=https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-linux.zip
wget $NINJA_URL -O ninja-linux.zip
unzip ninja-linux.zip -d ~/bin
fi
# rx-gcc is 32-bit binary
if [[ << parameters.toolchain >> == rx-gcc ]]; then
sudo dpkg --add-architecture i386
sudo apt update
sudo apt install libc6:i386 libstdc++6:i386 zlib1g:i386
fi
# Install Pico SDK
if [ << parameters.family >> == "rp2040" ]; then
git clone --depth 1 https://github.com/raspberrypi/pico-sdk.git ~/pico-sdk
echo "export PICO_SDK_PATH=~/pico-sdk" >> $BASH_ENV
fi
- when:
condition:
not:
equal: [esp-idf, << parameters.toolchain >>]
steps:
- setup-toolchain:
toolchain: << parameters.toolchain >>
- run:
name: Build
command: |
if [ << parameters.toolchain >> == esp-idf ]; then
docker run --rm -v $PWD:/project -w /project espressif/idf:v5.3.2 python tools/build.py << parameters.family >>
else
# Toolchain option default is gcc
if [ << parameters.toolchain >> == arm-clang ]; then
TOOLCHAIN_OPTION="--toolchain clang"
elif [ << parameters.toolchain >> == arm-iar ]; then
TOOLCHAIN_OPTION="--toolchain iar"
iccarm --version
elif [ << parameters.toolchain >> == arm-gcc ]; then
TOOLCHAIN_OPTION="--toolchain gcc"
fi
python tools/build.py -s << parameters.build-system >> $TOOLCHAIN_OPTION << parameters.family >>
fi
jobs:
# Build using docker
build:
parameters:
resource_class:
type: string
default: medium+
build-system:
type: string
toolchain:
type: string
family:
type: string
docker:
- image: cimg/base:current
resource_class: << parameters.resource_class >>
steps:
- build:
build-system: << parameters.build-system >>
toolchain: << parameters.toolchain >>
family: << parameters.family >>
# Build using VM
build-vm:
parameters:
resource_class:
type: string
default: large
build-system:
type: string
toolchain:
type: string
family:
type: string
machine:
image: ubuntu-2404:current
resource_class: << parameters.resource_class >>
steps:
- build:
build-system: << parameters.build-system >>
toolchain: << parameters.toolchain >>
family: << parameters.family >>
workflows:
build:
jobs:
# - build:
# matrix:
# parameters:
# toolchain: [ 'arm-gcc' ]
# build-system: [ 'cmake' ]
# family: [ 'nrf' ]
# resource_class: ['large']
# - build-vm:
# matrix:
# parameters:
# toolchain: ['esp-idf']
# build-system: ['cmake']
# family: ['-bespressif_kaluga_1']
# resource_class: ['large']

View File

@@ -0,0 +1,66 @@
# Generated from CLion C/C++ Code Style settings
BasedOnStyle: LLVM
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: None
AlignOperands: Align
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Always
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: true
BreakBeforeBinaryOperators: None
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
ColumnLimit: 0
CompactNamespaces: false
ContinuationIndentWidth: 4
IndentCaseLabels: true
IndentPPDirectives: BeforeHash
IndentWidth: 2
KeepEmptyLinesAtTheStartOfBlocks: true
MaxEmptyLinesToKeep: 2
NamespaceIndentation: All
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PointerAlignment: Right
ReflowComments: false
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 0
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: true
SpacesInParentheses: false
SpacesInSquareBrackets: false
TabWidth: 2
UseTab: Never

View File

@@ -0,0 +1,10 @@
# See: https://github.com/codespell-project/codespell#using-a-config-file
[codespell]
# In the event of a false positive, add the problematic word, in all lowercase, to 'ignore-words.txt' (one word per line).
# Or copy & paste the whole problematic line to 'exclude-file.txt'
ignore-words = tools/codespell/ignore-words.txt
exclude-file = tools/codespell/exclude-file.txt
check-filenames =
check-hidden =
count =
skip = *.rb,.cproject,.git,./lib,./examples/*/*/_build,./examples/*/*/ses,./examples/*/*/ozone,./hw/mcu,./tests_obsolete

View File

@@ -0,0 +1 @@
5ea9d3b6d6b0734a0a0b3491967aa0e1bece2974132294dbda5dd2839b247bfa

View File

@@ -0,0 +1,25 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto
*.c text
*.cpp text
*.h text
*.icf text
*.js text
*.json text
*.ld text
*.md text
*.mk text
*.py text
*.rst text
*.s text
*.txt text
*.xml text
*.yml text
Makefile text
# Windows-only Visual Studio things
*.sln text eol=crlf
*.csproj text eol=crlf

View File

@@ -0,0 +1,55 @@
html
latex
*.a
*.d
*.o
*.P
*.axf
*.bin
*.elf
*.env
*.ind
*.log
*.map
*.obj
*.jlink
*.emSession
*.ninja*
*.eww
*.ewp
*.ewt
*.ewd
*.hex
cmake_install.cmake
CMakeCache.txt
settings/
.settings/
.vscode/
.gdb_history
/examples/*/*/build*
test_old/
tests_obsolete/
_build
/examples/*/*/ses
/examples/*/*/ozone
/examples/obsolete
hw/bsp/**/cubemx/*/
.mxproject
# coverity intermediate files
cov-int
# cppcheck build directories
*-build-dir
/_bin/
__pycache__
cmake-build-*
sdkconfig
.PVS-Studio
.vscode/
build
CMakeFiles
Debug
RelWithDebInfo
Release
BrowseInfo
.cmake_build
README_processed.rst

View File

@@ -0,0 +1,59 @@
# SPDX-FileCopyrightText: 2020 Diego Elio Pettenò
#
# SPDX-License-Identifier: Unlicense
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: check-yaml
- id: trailing-whitespace
exclude: |
(?x)^(
hw/bsp/mcx/sdk/
)
- id: end-of-file-fixer
exclude: |
(?x)^(
.idea/|
hw/bsp/mcx/sdk/|
docs/contributing/code_of_conduct.rst|
docs/info/contributors.rst
)
- id: forbid-submodules
- repo: https://github.com/codespell-project/codespell
rev: v2.2.4
hooks:
- id: codespell
args: [-w]
exclude: |
(?x)^(
lib/|
hw/bsp/mcx/sdk/
)
- repo: local
hooks:
- id: unit-test
name: unit-test
files: ^(src/|test/unit-test/)
entry: sh -c "cd test/unit-test && ceedling test:all"
pass_filenames: false
types_or: [c, header]
language: system
# - id: build-fuzzer
# name: build-fuzzer
# files: ^(src/|test/fuzz/)
# language: system
# types_or: [c, header]
# entry: |
# bash -c 'export CC=clang
# export CXX=clang++
# fuzz_harness=$(ls -d test/fuzz/device/*/)
# for h in $fuzz_harness
# do
# make -C $h get-deps
# make -C $h all
# done'

View File

@@ -0,0 +1,24 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
version: 2
# Set the version of Python and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.11"
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py
# Optionally declare the Python requirements required to build your docs
python:
install:
- requirements: docs/requirements.txt
submodules:
include: []
recursive: false

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More