├── .gitmodules ├── CMakeLists.txt ├── Kconfig ├── LICENSE ├── README.md ├── include ├── psram_allocator.h ├── tusb_config.h └── usb.h └── src ├── usb.cpp ├── usb_cdc.cpp ├── usb_hid.cpp └── usb_msc.cpp /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/tinyusb"] 2 | path = src/tinyusb 3 | url = https://github.com/hathach/tinyusb.git 4 | branch = master 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(REQUIRES esp_rom app_update spi_flash freertos soc driver 2 | SRCS 3 | "${COMPONENT_DIR}/src/tinyusb/src/tusb.c" 4 | "${COMPONENT_DIR}/src/tinyusb/src/common/tusb_fifo.c" 5 | "${COMPONENT_DIR}/src/tinyusb/src/device/usbd.c" 6 | "${COMPONENT_DIR}/src/tinyusb/src/device/usbd_control.c" 7 | "${COMPONENT_DIR}/src/tinyusb/src/class/audio/audio_device.c" 8 | "${COMPONENT_DIR}/src/tinyusb/src/class/bth/bth_device.c" 9 | "${COMPONENT_DIR}/src/tinyusb/src/class/cdc/cdc_device.c" 10 | "${COMPONENT_DIR}/src/tinyusb/src/class/cdc/cdc_host.c" 11 | "${COMPONENT_DIR}/src/tinyusb/src/class/cdc/cdc_rndis_host.c" 12 | "${COMPONENT_DIR}/src/tinyusb/src/class/dfu/dfu_rt_device.c" 13 | "${COMPONENT_DIR}/src/tinyusb/src/class/dfu/dfu_device.c" 14 | "${COMPONENT_DIR}/src/tinyusb/src/class/hid/hid_device.c" 15 | "${COMPONENT_DIR}/src/tinyusb/src/class/hid/hid_host.c" 16 | "${COMPONENT_DIR}/src/tinyusb/src/class/midi/midi_device.c" 17 | "${COMPONENT_DIR}/src/tinyusb/src/class/msc/msc_device.c" 18 | "${COMPONENT_DIR}/src/tinyusb/src/class/msc/msc_host.c" 19 | "${COMPONENT_DIR}/src/tinyusb/src/class/net/ncm_device.c" 20 | "${COMPONENT_DIR}/src/tinyusb/src/class/net/ecm_rndis_device.c" 21 | "${COMPONENT_DIR}/src/tinyusb/src/class/usbtmc/usbtmc_device.c" 22 | "${COMPONENT_DIR}/src/tinyusb/src/class/vendor/vendor_device.c" 23 | "${COMPONENT_DIR}/src/tinyusb/src/class/vendor/vendor_host.c" 24 | "${COMPONENT_DIR}/src/tinyusb/src/class/video/video_device.c" 25 | "${COMPONENT_DIR}/src/tinyusb/src/portable/espressif/esp32sx/dcd_esp32sx.c" 26 | "${COMPONENT_DIR}/src/tinyusb/src/host/hub.c" 27 | "${COMPONENT_DIR}/src/tinyusb/src/host/usbh.c" 28 | "${COMPONENT_DIR}/src/usb.cpp" 29 | "${COMPONENT_DIR}/src/usb_cdc.cpp" 30 | "${COMPONENT_DIR}/src/usb_hid.cpp" 31 | "${COMPONENT_DIR}/src/usb_msc.cpp" 32 | INCLUDE_DIRS 33 | "${COMPONENT_DIR}/include/" 34 | "${COMPONENT_DIR}/src/tinyusb/hw/bsp/" 35 | "${COMPONENT_DIR}/src/tinyusb/src/" 36 | "${COMPONENT_DIR}/src/tinyusb/src/device" 37 | "${COMPONENT_DIR}/src/tinyusb/src/class" 38 | PRIV_INCLUDE_DIRS 39 | ) 40 | 41 | git_submodule_check(${CMAKE_CURRENT_SOURCE_DIR}) 42 | 43 | 44 | idf_build_get_property(idf_target IDF_TARGET) 45 | if(${idf_target} STREQUAL "esp32s2") 46 | idf_build_set_property(COMPILE_OPTIONS "-DCFG_TUSB_MCU=OPT_MCU_ESP32S2" APPEND) 47 | elseif(${idf_target} STREQUAL "esp32s3") 48 | idf_build_set_property(COMPILE_OPTIONS "-DCFG_TUSB_MCU=OPT_MCU_ESP32S3" APPEND) 49 | endif() 50 | idf_build_set_property(COMPILE_OPTIONS "-DCFG_TUD_ENABLED=1" APPEND) -------------------------------------------------------------------------------- /Kconfig: -------------------------------------------------------------------------------- 1 | menu "TinyUSB (esp32usb)" 2 | config ESPUSB 3 | bool "Enable Esp32USB" 4 | default n 5 | depends on IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 6 | select FREERTOS_SUPPORT_STATIC_ALLOCATION 7 | select FREERTOS_USE_AUTHENTIC_INCLUDE_PATHS 8 | help 9 | Adds support for TinyUSB 10 | 11 | config ESPUSB_MAX_POWER_USAGE 12 | int "Max power usage from USB port" 13 | default 100 14 | depends on ESPUSB 15 | help 16 | Max power used by device from USB port in mA. 17 | 18 | config ESPUSB_CDC 19 | bool "Enable USB Serial (CDC) driver" 20 | default n 21 | depends on ESPUSB 22 | help 23 | Enable USB Serial (CDC) Esp32USB driver. 24 | 25 | config ESPUSB_MSC 26 | bool "Enable Mass Storage (MSC) driver" 27 | default n 28 | depends on ESPUSB 29 | help 30 | Enable MSC TinyUSB driver. It is recomended to use Menuconfig-driven descriptor (.descriptor = NULL and 31 | .string_descriptor = NULL in the tinyusb_config_t structure). 32 | 33 | config ESPUSB_HID 34 | bool "Enable HID driver" 35 | default n 36 | depends on ESPUSB 37 | help 38 | Enable HID TinyUSB driver. It is recomended to use Menuconfig-driven descriptor (.descriptor = NULL and 39 | .string_descriptor = NULL in the tinyusb_config_t structure). 40 | 41 | config ESPUSB_MIDI 42 | bool "Enable MIDI driver" 43 | default n 44 | depends on ESPUSB 45 | help 46 | Enable MIDI TinyUSB driver. It is recomended to use Menuconfig-driven descriptor (.descriptor = NULL 47 | and .string_descriptor = NULL in the tinyusb_config_t structure). 48 | 49 | config ESPUSB_VENDOR 50 | bool "Enable Vendor class driver" 51 | default n 52 | depends on ESPUSB 53 | help 54 | Enable USB vendor TinyUSB driver. Currently used by webusb driver. 55 | 56 | config ESPUSB_DFU 57 | bool "Enable DFU Runtime" 58 | default n 59 | depends on ESPUSB 60 | help 61 | Enable the DFU_RT TinyUSB driver. 62 | 63 | config ESPUSB_CUSTOM_CLASS 64 | bool "Enable a custom driver class" 65 | default n 66 | depends on ESPUSB 67 | help 68 | Enable a custom TinyUSB class. 69 | 70 | config ESPUSB_DEBUG 71 | bool "Debug mode" 72 | default n 73 | depends on ESPUSB 74 | help 75 | Debug mode 76 | 77 | menu "USB Serial (CDC) Configuration" 78 | depends on ESPUSB_CDC 79 | 80 | config ESPUSB_CDC_RX_BUFSIZE 81 | int "RX buffer size" 82 | range 64 2048 83 | default 128 84 | help 85 | CDC receive buffer size. 86 | 87 | config ESPUSB_CDC_TX_BUFSIZE 88 | int "TX buffer size" 89 | range 64 2048 90 | default 256 91 | help 92 | CDC transmit buffer size. 93 | 94 | config ESPUSB_CDC_FIFO_SIZE 95 | int 96 | default 64 97 | 98 | config ESPUSB_CDC_WRITE_FLUSH_TIMEOUT 99 | int "Write timeout (milliseconds)" 100 | default 10 101 | endmenu 102 | 103 | menu "Mass Stoarage (MSC) Configuration" 104 | depends on ESPUSB_MSC 105 | 106 | config ESPUSB_MSC_BUFSIZE 107 | int 108 | default 512 109 | 110 | config ESPUSB_MSC_FIFO_SIZE 111 | int 112 | default 64 113 | 114 | config ESPUSB_MSC_VENDOR_ID 115 | string "MSC Vendor ID" 116 | default "ESP32" 117 | 118 | config ESPUSB_MSC_PRODUCT_ID 119 | string "MSC Product ID" 120 | default "ESP32 Disk" 121 | 122 | config ESPUSB_MSC_PRODUCT_REVISION 123 | string "MSC Product revision" 124 | default "1.00" 125 | 126 | config ESPUSB_MSC_VDISK_SECTOR_SIZE 127 | int 128 | default 512 129 | 130 | config ESPUSB_MSC_VDISK_SECTOR_COUNT 131 | int 132 | default 8192 133 | 134 | config ESPUSB_MSC_VDISK_RESERVED_SECTOR_COUNT 135 | int 136 | default 1 137 | 138 | config ESPUSB_MSC_VDISK_FILE_COUNT 139 | int "Max number of files" 140 | default 64 141 | range 16 256 142 | help 143 | Maximum number of files to present on the virtual disk. This is 144 | used to calculate how many sectors to reserve for file entries. 145 | Each sector can hold up to 16 files, the first sector has one 146 | reserved file entry for the disk label. Note, if long filenames 147 | are enabled a higher value should be used here as each long 148 | filename will use at least two directory entries. 149 | 150 | config ESPUSB_MSC_LONG_FILENAMES 151 | bool "Enable long filename support" 152 | default n 153 | help 154 | Enabling this option allows the usage of long filenames up to 155 | 39 characters. The length limit of 39 characters is derrived 156 | from the usage of up to four directory entries per file with 157 | three of them for the long filename and one for the file 158 | attributes. The short filename will be generated as the first 159 | six characters of the filename (without spaces) with ~1 added 160 | and no filename extension. 161 | endmenu 162 | 163 | menu "Vendor Configuration" 164 | depends on ESPUSB_VENDOR 165 | config ESPUSB_VENDOR_RX_BUFSIZE 166 | int "RX buffer size" 167 | range 64 2048 168 | default 64 169 | help 170 | Vendor receive buffer size in bytes. 171 | 172 | config ESPUSB_VENDOR_TX_BUFSIZE 173 | int "TX buffer size" 174 | range 64 2048 175 | default 64 176 | help 177 | Vendor transmit buffer size in bytes. 178 | 179 | config ESPUSB_VENDOR_FIFO_SIZE 180 | int 181 | default 64 182 | endmenu 183 | 184 | menu "MIDI Configuration" 185 | depends on ESPUSB_MIDI 186 | 187 | config ESPUSB_MIDI_RX_BUFSIZE 188 | int "RX buffer size" 189 | range 64 2048 190 | default 128 191 | help 192 | MIDI receive buffer size in bytes. 193 | 194 | config ESPUSB_MIDI_TX_BUFSIZE 195 | int "TX buffer size" 196 | range 64 2048 197 | default 128 198 | help 199 | MIDI transmit buffer size in bytes. 200 | 201 | config ESPUSB_MIDI_FIFO_SIZE 202 | int 203 | default 64 204 | endmenu 205 | 206 | menu "HID Configuration" 207 | depends on ESPUSB_HID 208 | 209 | config ESPUSB_HID_BUFSIZE 210 | int "Buffer size" 211 | default 16 212 | help 213 | HID buffer size should be sufficient to hold ID (if any) + Data 214 | endmenu 215 | 216 | menu "DFU Runtime Configuration" 217 | depends on ESPUSB_DFU 218 | config ESPUSB_DFU_DISCONNECT_DELAY 219 | int 220 | default 1000 221 | 222 | config ESPUSB_DFU_BUFSIZE 223 | int "Buffer size" 224 | range 512 4096 225 | default 1024 226 | help 227 | The DFU buffer size is used for splitting the DFU payload into 228 | smaller pieces which may be more managable. 229 | endmenu 230 | 231 | menu "USB descriptor configuration" 232 | depends on ESPUSB 233 | config ESPUSB_USB_VENDOR_ID 234 | hex "USB Descriptor Vendor ID" 235 | default 0x303A 236 | help 237 | This is used in the USB Descriptor for the device. The default 238 | value of 0x303A is the Espressif Vendor ID. 239 | 240 | config ESPUSB_DESC_BCDDEVICE 241 | hex "USB Descriptor BCD Device" 242 | default 0x0100 243 | help 244 | Version of the firmware of the USB device 245 | endmenu 246 | 247 | menu "Task configuration" 248 | 249 | config ESPUSB_TASK_NAME 250 | string "Name" 251 | default "esp-usb" 252 | 253 | config ESPUSB_TASK_STACK_SIZE 254 | int "Stack size (bytes)" 255 | default 4096 256 | 257 | config ESPUSB_TASK_PRIORITY 258 | int "Priority" 259 | range 2 25 260 | default 5 261 | help 262 | This is the priority for the Esp32USB executor task. It must be 263 | at least one priority level higher than the app_main task. 264 | 265 | choice ESPUSB_TASK_AFFINITY 266 | bool "Core on which the EspUSB Task will run" 267 | default ESPUSB_TASK_AFFINITY_NONE 268 | help 269 | Select core on which the EspUSB Task will run 270 | 271 | config ESPUSB_TASK_AFFINITY_CORE0 272 | bool "Core 0" 273 | config ESPUSB_TASK_AFFINITY_CORE1 274 | bool "Core 1" 275 | depends on IDF_TARGET_ESP32S3 276 | config ESPUSB_TASK_AFFINITY_NONE 277 | bool "Any" 278 | endchoice 279 | 280 | config ESPUSB_TASK_AFFINITY 281 | int 282 | default 0 if ESPUSB_TASK_AFFINITY_CORE0 283 | default 1 if ESPUSB_TASK_AFFINITY_CORE1 284 | default -1 if ESPUSB_TASK_AFFINITY_NONE 285 | endmenu 286 | 287 | endmenu 288 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # What is esp32usb 2 | 3 | esp32usb is an ESP-IDF component that provides an alternative for the ESP-IDF TinyUSB component. 4 | This code uses https://github.com/hathach/tinyusb rather than https://github.com/espressif/tinyusb. 5 | 6 | This component depends on ESP-IDF v4.4 and should work for both ESP32-S2 and 7 | ESP32-S3 but does not have exhaustive testing of all functionality. Testing is 8 | generally limited to CDC and MSC. 9 | 10 | ***NOTE*** 11 | It is highly recommended to use the ESP-IDF tinyusb component instead of this component. 12 | 13 | # How to use 14 | In your project, add this as a submodule to your `components/` directory. 15 | 16 | ``` 17 | git submodule add https://github.com/atanisoft/esp32usb.git 18 | git submodule update --recursive --init -- esp32usb 19 | ``` 20 | 21 | The library can be configured via `idf.py menuconfig` under `TinyUSB (esp32usb)`. 22 | 23 | # Integrating esp32usb with your project 24 | 25 | In the `app_main()` method you should have code similar to the following: 26 | 27 | ``` 28 | void app_main() { 29 | init_usb_subsystem(); 30 | configure_usb_descriptor_str(USB_DESC_MANUFACTURER, "esp32usb"); 31 | configure_usb_descriptor_str(USB_DESC_PRODUCT, "esp32usb Device"); 32 | configure_usb_descriptor_str(USB_DESC_SERIAL_NUMBER, "1234567890"); 33 | start_usb_task(); 34 | .... rest of application code 35 | } 36 | ``` 37 | 38 | ## Integrating a virtual disk drive 39 | If you are configuring a virtual disk you will need to configure it prior to calling `start_usb_task()`: 40 | 41 | ``` 42 | static const char * const readme_txt = 43 | "This is esp32usb's MassStorage Class demo.\r\n\r\n" 44 | "If you find any bugs or get any questions, feel free to file an\r\n" 45 | "issue at github.com/atanisoft/esp32usb" 46 | 47 | void app_main() { 48 | init_usb_subsystem(); 49 | configure_usb_descriptor_str(USB_DESC_MANUFACTURER, "esp32usb"); 50 | configure_usb_descriptor_str(USB_DESC_PRODUCT, "esp32usb Device"); 51 | configure_usb_descriptor_str(USB_DESC_SERIAL_NUMBER, "1234567890"); 52 | configure_virtual_disk("esp32usb", 0x0100); 53 | add_readonly_file_to_virtual_disk("readme.txt", readme_txt, strlen(readme_txt)); 54 | add_partition_to_virtual_disk("spiffs", "spiffs.bin"); 55 | add_firmware_to_virtual_disk(); 56 | start_usb_task(); 57 | ``` 58 | 59 | ### Virtual Disk limitations 60 | 61 | 1. The virtual disk support is currently limited to around 4MiB in size but may be configurable in the future. 62 | 2. Adding the firmware to the virtual disk is currently limited to showing only two OTA partitions (current and previous/next). If more than two OTA partitions are in use it is recommended to use `add_partition_to_virtual_disk` instead of `add_firmware_to_virtual_disk` so more images can be displayed. 63 | -------------------------------------------------------------------------------- /include/psram_allocator.h: -------------------------------------------------------------------------------- 1 | /// \copyright 2 | /// Copyright 2021 Mike Dunston (https://github.com/atanisoft) 3 | /// 4 | /// Licensed under the Apache License, Version 2.0 (the "License"); 5 | /// you may not use this file except in compliance with the License. 6 | /// You may obtain a copy of the License at 7 | /// 8 | /// http://www.apache.org/licenses/LICENSE-2.0 9 | /// 10 | /// Unless required by applicable law or agreed to in writing, software 11 | /// distributed under the License is distributed on an "AS IS" BASIS, 12 | /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | /// See the License for the specific language governing permissions and 14 | /// limitations under the License. 15 | /// 16 | /// \file psram_allocator.h 17 | /// This file declares an allocator that provides memory from PSRAM rather than 18 | /// internal memory. 19 | 20 | #pragma once 21 | 22 | #include 23 | #include "sdkconfig.h" 24 | 25 | template 26 | class PSRAMAllocator 27 | { 28 | public: 29 | using value_type = T; 30 | 31 | PSRAMAllocator() noexcept 32 | { 33 | } 34 | 35 | template constexpr PSRAMAllocator(const PSRAMAllocator&) noexcept 36 | { 37 | } 38 | 39 | [[nodiscard]] value_type* allocate(std::size_t n) 40 | { 41 | #if CONFIG_SPIRAM 42 | // attempt to allocate in PSRAM first 43 | auto p = 44 | static_cast( 45 | heap_caps_malloc(n * sizeof(value_type), MALLOC_CAP_SPIRAM)); 46 | if (p) 47 | { 48 | return p; 49 | } 50 | #endif // CONFIG_SPIRAM 51 | 52 | // If the allocation in PSRAM failed (or PSRAM not enabled), try to 53 | // allocate from the default memory pool. 54 | auto p2 = 55 | static_cast( 56 | heap_caps_malloc(n * sizeof(value_type), MALLOC_CAP_DEFAULT)); 57 | if (p2) 58 | { 59 | return p2; 60 | } 61 | 62 | throw std::bad_alloc(); 63 | } 64 | 65 | void deallocate(value_type* p, std::size_t) noexcept 66 | { 67 | heap_caps_free(p); 68 | } 69 | }; 70 | template 71 | bool operator==(const PSRAMAllocator&, const PSRAMAllocator&) 72 | { 73 | return true; 74 | } 75 | template 76 | bool operator!=(const PSRAMAllocator& x, const PSRAMAllocator& y) 77 | { 78 | return !(x == y); 79 | } 80 | -------------------------------------------------------------------------------- /include/tusb_config.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Espressif Systems (Shanghai) Co. Ltd. 2 | // Copyright 2020 Mike Dunston (https://github.com/atanisoft) 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #pragma once 17 | #include "sdkconfig.h" 18 | 19 | #ifdef __cplusplus 20 | extern "C" 21 | { 22 | #endif 23 | 24 | //-------------------------------------------------------------------- 25 | // COMMON CONFIGURATION 26 | //-------------------------------------------------------------------- 27 | 28 | #define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE 29 | #define CFG_TUSB_OS OPT_OS_FREERTOS 30 | 31 | // CFG_TUSB_DEBUG is defined by compiler in DEBUG build 32 | #define CFG_TUSB_DEBUG CONFIG_ESPUSB_DEBUG 33 | 34 | /* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. 35 | * TinyUSB use follows macros to declare transferring memory so that they can be put 36 | * into those specific section. 37 | * e.g 38 | * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) 39 | * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) 40 | */ 41 | #ifndef CFG_TUSB_MEM_SECTION 42 | #define CFG_TUSB_MEM_SECTION 43 | #endif 44 | 45 | #ifndef CFG_TUSB_MEM_ALIGN 46 | #define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) 47 | #endif 48 | 49 | // Espressif IDF requires "freertos/" prefix in include path 50 | #ifndef CFG_TUSB_OS_INC_PATH 51 | #define CFG_TUSB_OS_INC_PATH freertos/ 52 | #endif 53 | 54 | //-------------------------------------------------------------------- 55 | // ENDPOINT FIFO SIZE CONFIGURATION 56 | //-------------------------------------------------------------------- 57 | #ifndef CFG_TUD_ENDPOINT0_SIZE 58 | #define CFG_TUD_ENDPOINT0_SIZE 64 59 | #endif 60 | 61 | //-------------------------------------------------------------------- 62 | // KCONFIG DEFAULT CONFIGURATION 63 | //-------------------------------------------------------------------- 64 | #ifndef CONFIG_ESPUSB_CDC 65 | #define CONFIG_ESPUSB_CDC 0 66 | #endif 67 | 68 | #ifndef CONFIG_ESPUSB_MSC 69 | #define CONFIG_ESPUSB_MSC 0 70 | #endif 71 | 72 | #ifndef CONFIG_ESPUSB_HID 73 | #define CONFIG_ESPUSB_HID 0 74 | #endif 75 | 76 | #ifndef CONFIG_ESPUSB_MIDI 77 | #define CONFIG_ESPUSB_MIDI 0 78 | #endif 79 | 80 | #ifndef CONFIG_ESPUSB_VENDOR 81 | #define CONFIG_ESPUSB_VENDOR 0 82 | #endif 83 | 84 | #ifndef CONFIG_ESPUSB_DFU 85 | #define CONFIG_ESPUSB_DFU 0 86 | #endif 87 | 88 | #ifndef CONFIG_ESPUSB_CUSTOM_CLASS 89 | #define CONFIG_ESPUSB_CUSTOM_CLASS 0 90 | #endif 91 | 92 | #ifndef CONFIG_ESPUSB_CDC_RX_BUFSIZE 93 | #define CONFIG_ESPUSB_CDC_RX_BUFSIZE 64 94 | #endif 95 | 96 | #ifndef CONFIG_ESPUSB_CDC_TX_BUFSIZE 97 | #define CONFIG_ESPUSB_CDC_TX_BUFSIZE 64 98 | #endif 99 | 100 | #ifndef CONFIG_ESPUSB_MSC_BUFSIZE 101 | #define CONFIG_ESPUSB_MSC_BUFSIZE 512 102 | #endif 103 | 104 | #ifndef CONFIG_ESPUSB_HID_BUFSIZE 105 | #define CONFIG_ESPUSB_HID_BUFSIZE 16 106 | #endif 107 | 108 | #ifndef CONFIG_ESPUSB_VENDOR_RX_BUFSIZE 109 | #define CONFIG_ESPUSB_VENDOR_RX_BUFSIZE 64 110 | #endif 111 | 112 | #ifndef CONFIG_ESPUSB_VENDOR_TX_BUFSIZE 113 | #define CONFIG_ESPUSB_VENDOR_TX_BUFSIZE 64 114 | #endif 115 | 116 | #ifndef CONFIG_ESPUSB_MIDI_RX_BUFSIZE 117 | #define CONFIG_ESPUSB_MIDI_RX_BUFSIZE 64 118 | #endif 119 | 120 | #ifndef CONFIG_ESPUSB_MIDI_TX_BUFSIZE 121 | #define CONFIG_ESPUSB_MIDI_TX_BUFSIZE 64 122 | #endif 123 | 124 | #ifndef CONFIG_ESPUSB_DFU_BUFSIZE 125 | #define CONFIG_ESPUSB_DFU_BUFSIZE 1024 126 | #endif 127 | 128 | //-------------------------------------------------------------------- 129 | // DEVICE CONFIGURATION 130 | //-------------------------------------------------------------------- 131 | #define CFG_TUD_CDC CONFIG_ESPUSB_CDC 132 | #define CFG_TUD_MSC CONFIG_ESPUSB_MSC 133 | #define CFG_TUD_HID CONFIG_ESPUSB_HID 134 | #define CFG_TUD_MIDI CONFIG_ESPUSB_MIDI 135 | #define CFG_TUD_VENDOR CONFIG_ESPUSB_VENDOR 136 | #define CFG_TUD_CUSTOM_CLASS CONFIG_ESPUSB_CUSTOM_CLASS 137 | #define CFG_TUD_DFU_RT CONFIG_ESPUSB_DFU 138 | 139 | //-------------------------------------------------------------------- 140 | // CDC FIFO CONFIGURATION 141 | //-------------------------------------------------------------------- 142 | #define CFG_TUD_CDC_RX_BUFSIZE CONFIG_ESPUSB_CDC_RX_BUFSIZE 143 | #define CFG_TUD_CDC_TX_BUFSIZE CONFIG_ESPUSB_CDC_TX_BUFSIZE 144 | 145 | //-------------------------------------------------------------------- 146 | // MSC BUFFER CONFIGURATION 147 | // 148 | // NOTE: This is the block size for read/write operations via all 149 | // defined callbacks. 150 | //-------------------------------------------------------------------- 151 | #define CFG_TUD_MSC_BUFSIZE CONFIG_ESPUSB_MSC_BUFSIZE 152 | 153 | //-------------------------------------------------------------------- 154 | // HID BUFFER CONFIGURATION 155 | // 156 | // NOTE: This should be sufficient to hold ID (if any) + Data 157 | //-------------------------------------------------------------------- 158 | #define CFG_TUD_HID_BUFSIZE CONFIG_ESPUSB_HID_BUFSIZE 159 | 160 | //-------------------------------------------------------------------- 161 | // VENDOR FIFO CONFIGURATION 162 | //-------------------------------------------------------------------- 163 | #define CFG_TUD_VENDOR_RX_BUFSIZE CONFIG_ESPUSB_VENDOR_RX_BUFSIZE 164 | #define CFG_TUD_VENDOR_TX_BUFSIZE CONFIG_ESPUSB_VENDOR_TX_BUFSIZE 165 | 166 | //-------------------------------------------------------------------- 167 | // MIDI FIFO CONFIGURATION 168 | //-------------------------------------------------------------------- 169 | #define CFG_TUD_MIDI_RX_BUFSIZE CONFIG_ESPUSB_MIDI_RX_BUFSIZE 170 | #define CFG_TUD_MIDI_TX_BUFSIZE CONFIG_ESPUSB_MIDI_TX_BUFSIZE 171 | 172 | #ifdef __cplusplus 173 | } 174 | #endif -------------------------------------------------------------------------------- /include/usb.h: -------------------------------------------------------------------------------- 1 | /// \copyright 2 | /// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd. 3 | /// Copyright 2020 Mike Dunston (https://github.com/atanisoft) 4 | /// 5 | /// Licensed under the Apache License, Version 2.0 (the "License"); 6 | /// you may not use this file except in compliance with the License. 7 | /// You may obtain a copy of the License at 8 | /// 9 | /// http://www.apache.org/licenses/LICENSE-2.0 10 | /// 11 | /// Unless required by applicable law or agreed to in writing, software 12 | /// distributed under the License is distributed on an "AS IS" BASIS, 13 | /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | /// See the License for the specific language governing permissions and 15 | /// limitations under the License. 16 | /// 17 | /// \file usb.h 18 | /// This file defines the public API for the esp32s2usb library. 19 | 20 | #pragma once 21 | 22 | #include "sdkconfig.h" 23 | #include "tusb_config.h" 24 | #include "tusb.h" 25 | 26 | #include 27 | 28 | #include 29 | 30 | /// USB Descriptor string indexes. 31 | typedef enum 32 | { 33 | /// This is used for the USB Device Manufacturer string. 34 | USB_DESC_MANUFACTURER = 1, 35 | 36 | /// This is used for the USB Product string. 37 | USB_DESC_PRODUCT, 38 | 39 | /// This is used for the USB Product string. 40 | USB_DESC_SERIAL_NUMBER, 41 | 42 | /// This is used for the USB CDC Device Description string. 43 | USB_DESC_CDC, 44 | 45 | /// This is used for the USB Mass Storage Device Description string. 46 | USB_DESC_MSC, 47 | 48 | /// This is used for the USB Human Interface Device Description string. 49 | USB_DESC_HID, 50 | 51 | /// This is used for the USB Vendor Device Description string. 52 | USB_DESC_VENDOR, 53 | 54 | /// This is used for the USB MIDI Device Description string. 55 | USB_DESC_MIDI, 56 | 57 | /// This is used for the USB DFU RT Device Description string. 58 | USB_DESC_DFU, 59 | 60 | /// This is used internally and will be ignored by callers if used. 61 | USB_DESC_MAX_COUNT 62 | } esp_usb_descriptor_index_t; 63 | 64 | /// USB HID device report types. 65 | typedef enum 66 | { 67 | /// The reported event is from a keyboard. 68 | REPORT_ID_KEYBOARD = 1, 69 | 70 | /// The reported event is from a mouse. 71 | REPORT_ID_MOUSE 72 | } esp_usb_hid_report_t; 73 | 74 | /// USB CDC line state. 75 | typedef enum 76 | { 77 | /// No device is connected. 78 | LINE_STATE_DISCONNECTED, 79 | 80 | /// A device is connected. 81 | LINE_STATE_CONNECTED, 82 | 83 | /// This state is reached by deasserting DTR and RTS asserted and is the 84 | /// first step used by esptool.py to enter download mode. 85 | LINE_STATE_MAYBE_ENTER_DOWNLOAD_DTR, 86 | 87 | /// This state is reached by asserting both DTR and RTS. This normally will 88 | /// happen when a device is connected to the USB port. It is also the 89 | /// second state used by esptool.py to enter download mode. 90 | LINE_STATE_MAYBE_CONNECTED, 91 | 92 | /// This state is reached by asserting DTR and deasserting RTS. This is the 93 | /// third step used by esptool.py to enter download mode. 94 | LINE_STATE_MAYBE_ENTER_DOWNLOAD_RTS, 95 | 96 | /// This state is used by the usb shutdown hook to trigger a restart into 97 | /// esptool binary download mode. 98 | /// 99 | /// NOTE: This is not the same as DFU download mode. 100 | LINE_STATE_REQUEST_DOWNLOAD, 101 | 102 | /// This state is used by the usb shutdown hook to trigger a restart into 103 | /// DFU download mode. 104 | LINE_STATE_REQUEST_DOWNLOAD_DFU 105 | } esp_line_state_t; 106 | 107 | /// Initializes the USB peripheral and prepares the default descriptors. 108 | /// 109 | /// @param external_phy should be left as false. 110 | void init_usb_subsystem(bool external_phy = false); 111 | 112 | /// Creates a background task for EspUSB (TinyUSB) processing of USB packets. 113 | /// 114 | /// NOTE: The task uses 4096 bytes for the stack and runs at the TCP/IP task 115 | /// priority. 116 | void start_usb_task(); 117 | 118 | /// Writes a buffer to the USB CDC if there is a device connected. 119 | /// 120 | /// @param buf is the buffer to send. 121 | /// @param size is the size of the buffer. 122 | /// 123 | /// @return the number of bytes transmitted. 124 | size_t write_to_cdc(const char *buf, size_t size); 125 | 126 | /// Configures the USB descriptor. 127 | /// 128 | /// @param desc when not null will replace the default descriptor. 129 | /// @param version will set the bcdDevice value of the default descriptor. 130 | /// 131 | /// NOTE: When desc is not null the version will be ignored. 132 | void configure_usb_descriptor(tusb_desc_device_t *desc, 133 | uint16_t version = 0x0000); 134 | 135 | /// Configures a USB descriptor string. 136 | /// 137 | /// @param index is the @ref esp_usb_descriptor_index_t for the string being 138 | /// configured. 139 | /// @param value is the value to assign to the descriptor string. 140 | /// 141 | /// NOTE: USB descriptor strings only support ASCII characters at this time and 142 | /// have a maximum length of 126 characters. 143 | void configure_usb_descriptor_str(esp_usb_descriptor_index_t index, 144 | const char *value); 145 | 146 | /// Requests that the next time the system restarts it should be started in DFU 147 | /// mode. 148 | /// 149 | /// NOTE: If a USB device connects or disconnects after this call has been made 150 | /// and the system has not restarted this request will be discarded. 151 | void request_dfu_mode(); 152 | 153 | /// Callback for CDC line state change for application code. 154 | /// 155 | /// @param status is the new @ref esp_line_state_t state. 156 | /// @param download_mode_requested will be set to true if there has been a 157 | /// request to restart the system into download mode (esptool or DFU). 158 | /// 159 | /// @return The callback function should return true if the USB code should 160 | /// make the call to @ref esp_restart() internally. If the application needs 161 | /// to prepare for restart it should return false and schedule the restart as 162 | /// soon as possible after this function returns. 163 | /// 164 | /// NOTE: The return value from this callback function is only used when there 165 | /// has been a request to restart into download mode and 166 | /// download_mode_requested is true. 167 | bool usb_line_state_changed_cb(esp_line_state_t status, 168 | bool download_mode_requested); 169 | 170 | /// Configures a 4MB virtual disk. 171 | /// 172 | /// @param label will be used as the disk label that may be displayed by the 173 | /// operating system. 174 | /// @param serial_number will be used as the disk serial number. 175 | /// 176 | /// NOTE: The disk label is limited to 11 ASCII characters and will be 177 | /// truncated if necessary. 178 | void configure_virtual_disk(std::string label, uint32_t serial_number); 179 | 180 | /// Adds a file to the virtual disk that is read-only. 181 | /// 182 | /// @param filename is the name of the file on the virtual disk. 183 | /// @param content is the raw byte content for the file. 184 | /// @param size is the number of bytes in the file. 185 | /// 186 | /// @return ESP_OK if the file was successfully added to the virtual disk or 187 | /// ESP_ERR_INVALID_STATE if there are too many files on the virtual disk. 188 | /// 189 | /// NOTE: filename is limited to 8.3 format and will be truncated if 190 | /// necessary. If the filename provided does not have a "." character then it 191 | /// will be used as-is up to 11 ASCII characters. 192 | esp_err_t add_readonly_file_to_virtual_disk(const std::string filename, 193 | const char *content, 194 | uint32_t size); 195 | 196 | /// Exposes a partition as a file on the virtual disk. 197 | /// 198 | /// @param partition_name is the name of the partition to convert to a file. 199 | /// @param filename is the name of the file on the virtual disk. 200 | /// @param writable controls if the file can be written to over USB. 201 | /// 202 | /// @return ESP_OK if the file was successfully added to the virtual disk or 203 | /// ESP_ERR_INVALID_STATE if there are too many files on the virtual disk or 204 | /// ESP_ERR_NOT_FOUND if the partition could not be found. 205 | esp_err_t add_partition_to_virtual_disk(const std::string partition_name, 206 | const std::string filename, 207 | bool writable = false); 208 | 209 | /// Adds the currently running firmware as an updatable file on the virtual disk. 210 | /// 211 | /// @param firmware_name is used as the filename for the currently running 212 | /// firmware, note that this parameter is optional and when omitted the 213 | /// filename will be "firmware.bin". 214 | /// 215 | /// @return ESP_OK if the file was successfully added to the virtual disk, 216 | /// ESP_ERR_INVALID_STATE if there are too many files on the virtual disk, or 217 | /// ESP_ERR_NOT_FOUND if there was a failure loading the currently running 218 | /// firmware. 219 | esp_err_t add_firmware_to_virtual_disk( 220 | const std::string firmware_name = "firmware.bin"); 221 | 222 | /// Callback invoked when an OTA update is about to start via the virtual disk. 223 | /// 224 | /// @param app_desc is the new application description. 225 | /// 226 | /// @return true if the update should be allowed, false if the update should be 227 | /// rejected with an error returned to the operating system. 228 | bool ota_update_start_cb(esp_app_desc_t *app_desc); 229 | 230 | /// This callback will be invoked around one second after the last data has 231 | /// been received for an OTA update via the virtual disk. It will also be 232 | /// called for other error conditions. 233 | /// 234 | /// @param received_bytes is the number of bytes received as part of the update. 235 | /// @param err is the status of the OTA update. 236 | void ota_update_end_cb(size_t received_bytes, esp_err_t err); -------------------------------------------------------------------------------- /src/usb.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Espressif Systems (Shanghai) Co. Ltd. 2 | // Copyright 2020 Mike Dunston (https://github.com/atanisoft) 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #include "sdkconfig.h" 17 | 18 | // if Esp32USB debug is enabled set the local log level higher than any of the 19 | // pre-defined log levels. 20 | #if CONFIG_ESPUSB_DEBUG 21 | #define LOG_LOCAL_LEVEL 0xFF 22 | #endif 23 | 24 | #if CONFIG_ESPUSB_HID 25 | #include 26 | #endif // CONFIG_ESPUSB_HID 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #if CONFIG_IDF_TARGET_ESP32S2 34 | #include 35 | #include 36 | #include 37 | #elif CONFIG_IDF_TARGET_ESP32S3 38 | #include 39 | #include 40 | #include 41 | #else 42 | #error Unsupported architecture. 43 | #endif 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include "usb.h" 53 | 54 | static constexpr const char * const TAG = "USB"; 55 | 56 | void init_usb_cdc(); 57 | 58 | void init_usb_subsystem(bool external_phy) 59 | { 60 | ESP_LOGI(TAG, "Initializing USB peripheral"); 61 | if ((chip_usb_get_persist_flags() & USBDC_PERSIST_ENA) == USBDC_PERSIST_ENA) 62 | { 63 | // Enable USB/IO_MUX peripheral reset on next reboot. 64 | REG_CLR_BIT(RTC_CNTL_USB_CONF_REG, RTC_CNTL_IO_MUX_RESET_DISABLE); 65 | REG_CLR_BIT(RTC_CNTL_USB_CONF_REG, RTC_CNTL_USB_RESET_DISABLE); 66 | } 67 | else 68 | { 69 | // Normal startup flow, reinitailize the USB peripheral. 70 | periph_module_reset(PERIPH_USB_MODULE); 71 | periph_module_enable(PERIPH_USB_MODULE); 72 | } 73 | 74 | usb_hal_context_t hal; 75 | hal.use_external_phy = external_phy; 76 | ESP_LOGD(TAG, "Initializing USB HAL"); 77 | usb_hal_init(&hal); 78 | 79 | if (external_phy) 80 | { 81 | gpio_output_set_high(0x10, 0, 0x1E, 0xE); 82 | } 83 | else 84 | { 85 | ESP_LOGV(TAG, "Setting GPIO %d drive to %d", USBPHY_DM_NUM, 86 | GPIO_DRIVE_CAP_3); 87 | gpio_set_drive_capability((gpio_num_t)USBPHY_DM_NUM, 88 | (gpio_drive_cap_t)GPIO_DRIVE_CAP_3); 89 | ESP_LOGV(TAG, "Setting GPIO %d drive to %d", USBPHY_DP_NUM, 90 | GPIO_DRIVE_CAP_3); 91 | gpio_set_drive_capability((gpio_num_t)USBPHY_DP_NUM, 92 | (gpio_drive_cap_t)GPIO_DRIVE_CAP_3); 93 | } 94 | 95 | for (const usb_iopin_dsc_t* iopin = usb_periph_iopins; iopin->pin != -1; 96 | ++iopin) 97 | { 98 | if (external_phy || (iopin->ext_phy_only == 0)) 99 | { 100 | gpio_pad_select_gpio(iopin->pin); 101 | if (iopin->is_output) 102 | { 103 | ESP_LOGV(TAG, "Configuring USB GPIO %d as OUTPUT", iopin->pin); 104 | gpio_matrix_out(iopin->pin, iopin->func, false, false); 105 | } 106 | else 107 | { 108 | ESP_LOGV(TAG, "Configuring USB GPIO %d as INPUT", iopin->pin); 109 | gpio_matrix_in(iopin->pin, iopin->func, false); 110 | gpio_pad_input_enable(iopin->pin); 111 | } 112 | gpio_pad_unhold(iopin->pin); 113 | } 114 | } 115 | 116 | #if CONFIG_ESPUSB_CDC 117 | init_usb_cdc(); 118 | #endif 119 | 120 | ESP_LOGI(TAG, "USB system initialized"); 121 | } 122 | 123 | static void usb_device_task(void *param) 124 | { 125 | ESP_LOGV(TAG, "Initializing TinyUSB"); 126 | if (!tusb_init()) 127 | { 128 | ESP_LOGE(TAG, "Failed to initialize TinyUSB stack!"); 129 | abort(); 130 | } 131 | 132 | ESP_LOGV(TAG, "EspUSB Task (%s) starting execution", 133 | CONFIG_ESPUSB_TASK_NAME); 134 | while (1) 135 | { 136 | tud_task(); 137 | } 138 | } 139 | 140 | // sanity check that the user did not define the task priority too low. 141 | static_assert(CONFIG_ESPUSB_TASK_PRIORITY > ESP_TASK_MAIN_PRIO, 142 | "EspUSB task must have a higher priority than the app_main task."); 143 | 144 | void start_usb_task() 145 | { 146 | BaseType_t res = 147 | xTaskCreatePinnedToCore( 148 | usb_device_task, CONFIG_ESPUSB_TASK_NAME, 149 | CONFIG_ESPUSB_TASK_STACK_SIZE, nullptr, 150 | CONFIG_ESPUSB_TASK_PRIORITY, nullptr, CONFIG_ESPUSB_TASK_AFFINITY); 151 | if (res != pdPASS) 152 | { 153 | ESP_LOGE(TAG, "Failed to create task for USB."); 154 | abort(); 155 | } 156 | ESP_LOGI(TAG, "Created EspUSB task: %s", CONFIG_ESPUSB_TASK_NAME); 157 | } 158 | 159 | // When CDC is enabled set the default descriptor to use ACM mode. 160 | #if CONFIG_ESPUSB_CDC 161 | #define USB_DEVICE_CLASS TUSB_CLASS_MISC 162 | #define USB_DEVICE_SUBCLASS MISC_SUBCLASS_COMMON 163 | #define USB_DEVICE_PROTOCOL MISC_PROTOCOL_IAD 164 | #else 165 | #define USB_DEVICE_CLASS 0x00 166 | #define USB_DEVICE_SUBCLASS 0x00 167 | #define USB_DEVICE_PROTOCOL 0x00 168 | #endif 169 | 170 | // Used to generate the USB PID based on enabled interfaces. 171 | #define _PID_MAP(itf, n) ((CFG_TUD_##itf) << (n)) 172 | 173 | /// USB Device Descriptor. 174 | static tusb_desc_device_t s_descriptor = 175 | { 176 | .bLength = sizeof(tusb_desc_device_t), 177 | .bDescriptorType = TUSB_DESC_DEVICE, 178 | .bcdUSB = 0x0200, 179 | .bDeviceClass = USB_DEVICE_CLASS, 180 | .bDeviceSubClass = USB_DEVICE_SUBCLASS, 181 | .bDeviceProtocol = USB_DEVICE_PROTOCOL, 182 | .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, 183 | .idVendor = CONFIG_ESPUSB_USB_VENDOR_ID, 184 | .idProduct = (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | 185 | _PID_MAP(HID, 2) | _PID_MAP(MIDI, 3) | 186 | _PID_MAP(VENDOR, 4) | _PID_MAP(DFU_RT, 5)), 187 | .bcdDevice = CONFIG_ESPUSB_DESC_BCDDEVICE, 188 | .iManufacturer = USB_DESC_MANUFACTURER, 189 | .iProduct = USB_DESC_PRODUCT, 190 | .iSerialNumber = USB_DESC_SERIAL_NUMBER, 191 | .bNumConfigurations = 0x01 192 | }; 193 | 194 | /// USB Device Endpoint assignments. 195 | /// 196 | /// NOTE: The ESP32-S2 has four input FIFOs available, unfortunately this will 197 | /// result in some overlap between features. The notification endpoint is not 198 | /// connected to the FIFOs. 199 | /// 200 | /// @todo switch to dynamic endpoint assignment except for CDC and NOTIF which 201 | /// require static definitions. 202 | typedef enum 203 | { 204 | /// Vendor endpoint. 205 | ENDPOINT_VENDOR_OUT = 0x01, 206 | 207 | /// Mass Storage endpoint. 208 | ENDPOINT_MSC_OUT = 0x02, 209 | 210 | /// CDC endpoint. 211 | /// 212 | /// NOTE: This matches the ESP32-S2 ROM code mapping. 213 | ENDPOINT_CDC_OUT = 0x03, 214 | 215 | /// MIDI endpoint. 216 | ENDPOINT_MIDI_OUT = 0x04, 217 | 218 | /// HID endpoint. 219 | ENDPOINT_HID_IN = 0x81, 220 | 221 | /// Mass Storage endpoint. 222 | ENDPOINT_MSC_IN = 0x82, 223 | 224 | /// Vendor endpoint. 225 | ENDPOINT_VENDOR_IN = 0x83, 226 | 227 | /// MIDI endpoint. 228 | ENDPOINT_MIDI_IN = 0x83, 229 | 230 | /// CDC endpoint. 231 | /// 232 | /// NOTE: This matches the ESP32-S2 ROM code mapping. 233 | ENDPOINT_CDC_IN = 0x84, 234 | 235 | /// Notification endpoint. 236 | /// 237 | /// NOTE: This matches the ESP32-S2 ROM code mapping. 238 | ENDPOINT_NOTIF = 0x85, 239 | } esp_usb_endpoint_t; 240 | 241 | /// USB Interface indexes. 242 | typedef enum 243 | { 244 | #if CONFIG_ESPUSB_CDC 245 | ITF_NUM_CDC = 0, 246 | ITF_NUM_CDC_DATA, 247 | #endif 248 | #if CONFIG_ESPUSB_MSC 249 | ITF_NUM_MSC, 250 | #endif 251 | #if CONFIG_ESPUSB_HID 252 | ITF_NUM_HID, 253 | #endif 254 | #if CONFIG_ESPUSB_MIDI 255 | ITF_NUM_MIDI, 256 | ITF_NUM_MIDI_STREAMING, 257 | #endif 258 | #if CONFIG_ESPUSB_VENDOR 259 | ITF_NUM_VENDOR, 260 | #endif 261 | #if CONFIG_ESPUSB_DFU 262 | ITF_NUM_DFU_RT, 263 | #endif 264 | ITF_NUM_TOTAL 265 | } esp_usb_interface_t; 266 | 267 | /// Total size of the USB device descriptor configuration data. 268 | static constexpr uint16_t USB_DESCRIPTORS_CONFIG_TOTAL_LEN = 269 | TUD_CONFIG_DESC_LEN + 270 | (CONFIG_ESPUSB_CDC * TUD_CDC_DESC_LEN) + 271 | (CONFIG_ESPUSB_MSC * TUD_MSC_DESC_LEN) + 272 | (CONFIG_ESPUSB_HID * TUD_HID_DESC_LEN) + 273 | (CONFIG_ESPUSB_VENDOR * TUD_VENDOR_DESC_LEN) + 274 | (CONFIG_ESPUSB_MIDI * TUD_MIDI_DESC_LEN) + 275 | (CONFIG_ESPUSB_DFU * TUD_DFU_RT_DESC_LEN); 276 | 277 | #if CONFIG_ESPUSB_CDC 278 | static_assert(CONFIG_ESPUSB_CDC_FIFO_SIZE == 64, "CDC FIFO size must be 64"); 279 | #endif 280 | #if CONFIG_ESPUSB_MSC 281 | static_assert(CONFIG_ESPUSB_MSC_FIFO_SIZE == 64, "MSC FIFO size must be 64"); 282 | #endif 283 | #if CONFIG_ESPUSB_VENDOR 284 | static_assert(CONFIG_ESPUSB_VENDOR_FIFO_SIZE == 64, "Vendor FIFO size must be 64"); 285 | #endif 286 | #if CONFIG_ESPUSB_MIDI 287 | static_assert(CONFIG_ESPUSB_MIDI_FIFO_SIZE == 64 288 | , "MIDI FIFO size must be 64"); 289 | #endif 290 | 291 | #if CONFIG_ESPUSB_HID 292 | // HID Report Descriptor 293 | static uint8_t const desc_hid_report[] = 294 | { 295 | TUD_HID_REPORT_DESC_KEYBOARD( HID_REPORT_ID(REPORT_ID_KEYBOARD )), 296 | TUD_HID_REPORT_DESC_MOUSE ( HID_REPORT_ID(REPORT_ID_MOUSE )), 297 | TUD_HID_REPORT_DESC_CONSUMER( HID_REPORT_ID(REPORT_ID_CONSUMER_CONTROL )), 298 | TUD_HID_REPORT_DESC_GAMEPAD ( HID_REPORT_ID(REPORT_ID_GAMEPAD )) 299 | }; 300 | 301 | // Invoked when received GET HID REPORT DESCRIPTOR request 302 | const uint8_t *tud_hid_descriptor_report_cb(void) 303 | { 304 | return desc_hid_report; 305 | } 306 | #endif // CONFIG_ESPUSB_HID 307 | 308 | /// USB device descriptor configuration data. 309 | uint8_t const desc_configuration[USB_DESCRIPTORS_CONFIG_TOTAL_LEN] = 310 | { 311 | TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, 312 | USB_DESCRIPTORS_CONFIG_TOTAL_LEN, 313 | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 314 | CONFIG_ESPUSB_MAX_POWER_USAGE), 315 | #if CONFIG_ESPUSB_CDC 316 | TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, USB_DESC_CDC, ENDPOINT_NOTIF, 8, 317 | ENDPOINT_CDC_OUT, ENDPOINT_CDC_IN, 318 | CONFIG_ESPUSB_CDC_FIFO_SIZE), 319 | #endif 320 | #if CONFIG_ESPUSB_MSC 321 | TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, USB_DESC_MSC, ENDPOINT_MSC_OUT, 322 | ENDPOINT_MSC_IN, CONFIG_ESPUSB_MSC_FIFO_SIZE), 323 | #endif 324 | #if CONFIG_ESPUSB_HID 325 | TUD_HID_DESCRIPTOR(ITF_NUM_HID, USB_DESC_HID, HID_ITF_PROTOCOL_NONE, 326 | sizeof(desc_hid_report), ENDPOINT_HID_IN, 327 | CONFIG_ESPUSB_HID_BUFSIZE, 10), 328 | #endif 329 | #if CONFIG_ESPUSB_VENDOR 330 | TUD_VENDOR_DESCRIPTOR(ITF_NUM_VENDOR, USB_DESC_VENDOR, ENDPOINT_VENDOR_OUT, 331 | ENDPOINT_VENDOR_IN, CONFIG_ESPUSB_VENDOR_FIFO_SIZE), 332 | #endif 333 | #if CONFIG_ESPUSB_MIDI 334 | TUD_MIDI_DESCRIPTOR(ITF_NUM_MIDI, USB_DESC_MIDI, ENDPOINT_MIDI_OUT, 335 | ENDPOINT_MIDI_IN, CONFIG_ESPUSB_MIDI_FIFO_SIZE), 336 | #endif 337 | #if CONFIG_ESPUSB_DFU 338 | TUD_DFU_RT_DESCRIPTOR(ITF_NUM_DFU_RT, USB_DESC_DFU, 0x0d, 339 | CONFIG_ESPUSB_DFU_DISCONNECT_DELAY, 340 | CONFIG_ESPUSB_DFU_BUFSIZE), 341 | #endif 342 | }; 343 | 344 | /// USB device descriptor strings. 345 | /// 346 | /// NOTE: Only ASCII characters are supported at this time. 347 | static std::string s_str_descriptor[USB_DESC_MAX_COUNT] = 348 | { 349 | "", // LANGUAGE (unused in tud_descriptor_string_cb) 350 | "", // USB_DESC_MANUFACTURER 351 | "", // USB_DESC_PRODUCT 352 | "", // USB_DESC_SERIAL_NUMBER 353 | "", // USB_DESC_CDC 354 | "", // USB_DESC_MSC 355 | "", // USB_DESC_HID 356 | "", // USB_DESC_VENDOR 357 | "", // USB_DESC_MIDI 358 | "", // USB_DESC_DFU 359 | }; 360 | 361 | /// Maximum length of the USB device descriptor strings. 362 | static constexpr size_t MAX_DESCRIPTOR_LEN = 126; 363 | 364 | /// Temporary holding buffer for USB device descriptor string data in UTF-16 365 | /// format. 366 | /// 367 | /// NOTE: Only ASCII characters are supported at this time. 368 | static uint16_t _desc_str[MAX_DESCRIPTOR_LEN + 1]; 369 | 370 | // ============================================================================= 371 | // Device descriptor functions 372 | // ============================================================================= 373 | void configure_usb_descriptor(tusb_desc_device_t *desc, uint16_t version) 374 | { 375 | if (desc) 376 | { 377 | memcpy(&s_descriptor, desc, sizeof(tusb_desc_device_t)); 378 | } 379 | else if (version) 380 | { 381 | s_descriptor.bcdDevice = version; 382 | } 383 | } 384 | 385 | void configure_usb_descriptor_str(esp_usb_descriptor_index_t index, 386 | const char *value) 387 | { 388 | // truncate the descriptor string (if needed). 389 | size_t str_len = strlen(value); 390 | if (str_len > MAX_DESCRIPTOR_LEN) 391 | { 392 | ESP_LOGE(TAG, "USB descriptor(%d) text will be truncated (%zu > %zu)", 393 | index, str_len, MAX_DESCRIPTOR_LEN); 394 | str_len = MAX_DESCRIPTOR_LEN; 395 | } 396 | s_str_descriptor[index].assign(value, str_len); 397 | ESP_LOGI(TAG, "USB descriptor(%d) text:%s", index, 398 | s_str_descriptor[index].c_str()); 399 | } 400 | 401 | // ============================================================================= 402 | // TinyUSB CALLBACKS 403 | // ============================================================================= 404 | 405 | extern "C" 406 | { 407 | 408 | // Invoked when received GET DEVICE DESCRIPTOR 409 | uint8_t const *tud_descriptor_device_cb(void) 410 | { 411 | return (uint8_t const *)&s_descriptor; 412 | } 413 | 414 | // Invoked when received GET CONFIGURATION DESCRIPTOR 415 | uint8_t const *tud_descriptor_configuration_cb(uint8_t index) 416 | { 417 | (void)index; // for multiple configurations 418 | return desc_configuration; 419 | } 420 | 421 | // Invoked when received GET STRING DESCRIPTOR request 422 | uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) 423 | { 424 | uint8_t chr_count; 425 | // clear the last descriptor 426 | bzero(_desc_str, TU_ARRAY_SIZE(_desc_str)); 427 | 428 | if (index == 0) 429 | { 430 | _desc_str[1] = tu_htole16(0x0409); 431 | chr_count = 1; 432 | } 433 | else if (index >= USB_DESC_MAX_COUNT) 434 | { 435 | // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. 436 | // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors 437 | return NULL; 438 | } 439 | else 440 | { 441 | // copy the string into the temporary array starting at offset 1 442 | size_t idx = 1; 443 | for (char ch : s_str_descriptor[index]) 444 | { 445 | _desc_str[idx++] = tu_htole16(ch); 446 | } 447 | chr_count = s_str_descriptor[index].length(); 448 | } 449 | 450 | // length and type 451 | _desc_str[0] = tu_htole16((TUSB_DESC_STRING << 8) | (2 * chr_count + 2)); 452 | 453 | return _desc_str; 454 | } 455 | 456 | #if CONFIG_ESPUSB_DFU 457 | // Invoked when the DFU Runtime mode is requested 458 | void tud_dfu_rt_reboot_to_dfu(void) 459 | { 460 | REG_WRITE(RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT); 461 | SET_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, RTC_CNTL_SW_PROCPU_RST); 462 | } 463 | #endif // CONFIG_ESPUSB_DFU 464 | 465 | } // extern "C" 466 | -------------------------------------------------------------------------------- /src/usb_cdc.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Mike Dunston (https://github.com/atanisoft) 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "sdkconfig.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #if CONFIG_IDF_TARGET_ESP32S2 21 | #include 22 | #include 23 | #elif CONFIG_IDF_TARGET_ESP32S3 24 | #include 25 | #include 26 | #else 27 | #error Unsupported architecture. 28 | #endif 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include "usb.h" 36 | 37 | /// Tag used for all logging. 38 | static constexpr const char * const TAG = "USB:CDC"; 39 | 40 | #if CONFIG_ESPUSB_CDC 41 | 42 | /// Current state of the USB CDC interface. 43 | static esp_line_state_t cdc_line_state = LINE_STATE_DISCONNECTED; 44 | 45 | /// Maximum number of ticks to allow for TX to complete before giving up. 46 | static constexpr TickType_t WRITE_TIMEOUT_TICKS = 47 | pdMS_TO_TICKS(CONFIG_ESPUSB_CDC_WRITE_FLUSH_TIMEOUT); 48 | 49 | /// System shutdown hook used for flagging that the restart should go into a 50 | /// download mode rather than normal startup mode. 51 | /// 52 | /// NOTE: This will disable the USB peripheral restart on startup and will 53 | /// require manual reset on reinitialization. 54 | static void IRAM_ATTR usb_shutdown_hook(void) 55 | { 56 | // Check if it there is a request to restart into download mode. 57 | if (cdc_line_state == LINE_STATE_REQUEST_DOWNLOAD || 58 | cdc_line_state == LINE_STATE_REQUEST_DOWNLOAD_DFU) 59 | { 60 | ESP_EARLY_LOGV(TAG, "Disabling USB peripheral restart on next boot"); 61 | REG_SET_BIT(RTC_CNTL_USB_CONF_REG, RTC_CNTL_IO_MUX_RESET_DISABLE); 62 | REG_SET_BIT(RTC_CNTL_USB_CONF_REG, RTC_CNTL_USB_RESET_DISABLE); 63 | 64 | periph_module_disable(PERIPH_TIMG1_MODULE); 65 | if (cdc_line_state == LINE_STATE_REQUEST_DOWNLOAD) 66 | { 67 | chip_usb_set_persist_flags(USBDC_PERSIST_ENA); 68 | } 69 | else 70 | { 71 | chip_usb_set_persist_flags(USBDC_BOOT_DFU); 72 | periph_module_disable(PERIPH_TIMG0_MODULE); 73 | } 74 | 75 | ESP_EARLY_LOGV(TAG, "Setting next boot mode to download"); 76 | REG_WRITE(RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT); 77 | SET_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, RTC_CNTL_SW_PROCPU_RST); 78 | } 79 | } 80 | 81 | /// Initializes the USB CDC. 82 | void init_usb_cdc() 83 | { 84 | // register shutdown hook for rebooting into download mode 85 | ESP_ERROR_CHECK(esp_register_shutdown_handler(usb_shutdown_hook)); 86 | } 87 | 88 | // Attempts to write a buffer to the USB CDC if a device is present. 89 | size_t write_to_cdc(const char *buf, size_t size) 90 | { 91 | size_t offs = 0; 92 | uint32_t ticks_start = xTaskGetTickCount(); 93 | uint32_t ticks_now = ticks_start; 94 | if (cdc_line_state != LINE_STATE_CONNECTED && 95 | cdc_line_state != LINE_STATE_MAYBE_CONNECTED) 96 | { 97 | goto exit_write_to_cdc; 98 | } 99 | 100 | // while there is still data remaining and we have not timed out keep 101 | // trying to send data 102 | while (offs < size) 103 | { 104 | // track the current time 105 | ticks_now = xTaskGetTickCount(); 106 | if ((ticks_now - WRITE_TIMEOUT_TICKS) > ticks_start) 107 | { 108 | break; 109 | } 110 | 111 | // pick the smallest buffer size that we can push to the CDC 112 | uint32_t to_send = std::min(tud_cdc_write_available(), size - offs); 113 | 114 | // attempt to send the full buffer in one shot, this will send only 115 | // up to the point of filling the FIFO and return the amount sent. 116 | uint16_t sent = tud_cdc_write(buf + offs, to_send); 117 | 118 | // if we successfully queued at least one character in the CDC TX 119 | // buffer, flush it out onto the wire. 120 | if (sent > 0) 121 | { 122 | tud_cdc_write_flush(); 123 | } 124 | offs += sent; 125 | } 126 | 127 | // If we still have some data left to transmit by the time we reach 128 | // here a FIFO overflow occurred. 129 | if (size) 130 | { 131 | ESP_LOGE(TAG, "TX FIFO Overflow! %d remaining after timeout.", size); 132 | } 133 | 134 | exit_write_to_cdc: 135 | return offs; 136 | } 137 | 138 | // Default implementation of usb_line_state_changed_cb which allows restart. 139 | TU_ATTR_WEAK bool usb_line_state_changed_cb(esp_line_state_t state, bool download) 140 | { 141 | if (download) 142 | { 143 | ESP_LOGI(TAG, "Firmware download request received, allowing restart"); 144 | } 145 | return true; 146 | } 147 | 148 | // Override the flag for line state such that it will trigger DFU mode on the 149 | // next restart. 150 | void request_dfu_mode() 151 | { 152 | cdc_line_state = LINE_STATE_REQUEST_DOWNLOAD_DFU; 153 | } 154 | 155 | extern "C" 156 | { 157 | 158 | // ============================================================================= 159 | // TinyUSB CALLBACKS 160 | // ============================================================================= 161 | 162 | // Invoked when cdc when line state changed e.g connected/disconnected 163 | void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) 164 | { 165 | ESP_LOGV(TAG, "tud_cdc_line_state_cb(%d, %d, %d), state: %d", itf, dtr, 166 | rts, cdc_line_state); 167 | if (!dtr && rts) 168 | { 169 | if (cdc_line_state == LINE_STATE_DISCONNECTED || 170 | cdc_line_state == LINE_STATE_CONNECTED) 171 | { 172 | ESP_LOGD(TAG, "Possible esptool request, waiting for reconnect"); 173 | cdc_line_state = LINE_STATE_MAYBE_ENTER_DOWNLOAD_DTR; 174 | } 175 | else 176 | { 177 | ESP_LOGI(TAG, "USB device disconnected"); 178 | cdc_line_state = LINE_STATE_DISCONNECTED; 179 | } 180 | } 181 | else if (dtr && rts) 182 | { 183 | if (cdc_line_state == LINE_STATE_MAYBE_ENTER_DOWNLOAD_DTR) 184 | { 185 | ESP_LOGD(TAG, "Possible esptool request, waiting for rts low"); 186 | cdc_line_state = LINE_STATE_MAYBE_CONNECTED; 187 | } 188 | else 189 | { 190 | ESP_LOGI(TAG, "USB device connected"); 191 | cdc_line_state = LINE_STATE_CONNECTED; 192 | } 193 | } 194 | else if (dtr && !rts) 195 | { 196 | if (cdc_line_state == LINE_STATE_MAYBE_CONNECTED) 197 | { 198 | ESP_LOGD(TAG, "Possible esptool request, waiting for disconnect"); 199 | cdc_line_state = LINE_STATE_MAYBE_ENTER_DOWNLOAD_RTS; 200 | } 201 | else 202 | { 203 | ESP_LOGI(TAG, "USB device disconnected"); 204 | cdc_line_state = LINE_STATE_DISCONNECTED; 205 | } 206 | } 207 | else if (!dtr && !rts) 208 | { 209 | if (cdc_line_state == LINE_STATE_MAYBE_ENTER_DOWNLOAD_RTS) 210 | { 211 | ESP_LOGD(TAG, "esptool firmware upload requested"); 212 | // request to restart in download mode 213 | cdc_line_state = LINE_STATE_REQUEST_DOWNLOAD; 214 | } 215 | else 216 | { 217 | ESP_LOGI(TAG, "USB device disconnected"); 218 | cdc_line_state = LINE_STATE_DISCONNECTED; 219 | } 220 | } 221 | // check if the callback will handle the restart when there is a download 222 | // request pending. 223 | bool download = (cdc_line_state == LINE_STATE_REQUEST_DOWNLOAD || 224 | cdc_line_state == LINE_STATE_REQUEST_DOWNLOAD_DFU); 225 | bool restart = usb_line_state_changed_cb(cdc_line_state, download); 226 | 227 | // restart the system if the callback is not going to handle it and there 228 | // is a pending download request. 229 | if (restart && download) 230 | { 231 | ESP_LOGV(TAG, "Restarting..."); 232 | esp_restart(); 233 | } 234 | } 235 | 236 | } // extern "C" 237 | 238 | #endif // CONFIG_USBUSB_CDC -------------------------------------------------------------------------------- /src/usb_hid.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Espressif Systems (Shanghai) Co. Ltd. 2 | // Copyright 2020 Mike Dunston (https://github.com/atanisoft) 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | #include "sdkconfig.h" 17 | 18 | // if Esp32USB debug is enabled set the local log level higher than any of the 19 | // pre-defined log levels. 20 | #if CONFIG_ESPUSB_DEBUG 21 | #define LOG_LOCAL_LEVEL 0xFF 22 | #endif 23 | 24 | #if CONFIG_ESPUSB_HID 25 | 26 | #include 27 | 28 | // Invoked when received GET_REPORT control request 29 | // Application must fill buffer report's content and return its length. 30 | // Return zero will cause the stack to STALL request 31 | TU_ATTR_WEAK uint16_t tud_hid_get_report_cb( 32 | uint8_t itf, uint8_t report_id, hid_report_type_t report_type, 33 | uint8_t *buffer, uint16_t reqlen) 34 | { 35 | (void)itf; 36 | (void)report_id; 37 | (void)report_type; 38 | (void)buffer; 39 | (void)reqlen; 40 | 41 | return 0; 42 | } 43 | 44 | // Invoked when received SET_REPORT control request or 45 | // received data on OUT endpoint ( Report ID = 0, Type = 0 ) 46 | TU_ATTR_WEAK void tud_hid_set_report_cb( 47 | uint8_t itf, uint8_t report_id, hid_report_type_t report_type, 48 | uint8_t const *buffer, uint16_t bufsize) 49 | { 50 | (void)itf; 51 | (void)report_id; 52 | (void)report_type; 53 | } 54 | 55 | //--------------------------------------------------------------------+ 56 | // HID Report Descriptor 57 | //--------------------------------------------------------------------+ 58 | 59 | uint8_t const desc_hid_keyboard_report[] = 60 | { 61 | TUD_HID_REPORT_DESC_KEYBOARD() 62 | }; 63 | 64 | uint8_t const desc_hid_mouse_report[] = 65 | { 66 | TUD_HID_REPORT_DESC_MOUSE() 67 | }; 68 | 69 | uint8_t const desc_hid_consumer_report[] = 70 | { 71 | TUD_HID_REPORT_DESC_CONSUMER() 72 | }; 73 | 74 | uint8_t const desc_hid_gamepad_report[] = 75 | { 76 | TUD_HID_REPORT_DESC_GAMEPAD() 77 | }; 78 | 79 | // Invoked when received GET HID REPORT DESCRIPTOR 80 | // Application return pointer to descriptor 81 | // Descriptor contents must exist long enough for transfer to complete 82 | TU_ATTR_WEAK uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance) 83 | { 84 | switch(instance) 85 | { 86 | case 0: 87 | return desc_hid_keyboard_report; 88 | case 1: 89 | return desc_hid_mouse_report; 90 | case 2: 91 | return desc_hid_consumer_report; 92 | case 3: 93 | return desc_hid_gamepad_report; 94 | } 95 | return nullptr; 96 | } 97 | 98 | #endif // CONFIG_ESPUSB_HID 99 | -------------------------------------------------------------------------------- /src/usb_msc.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Mike Dunston (https://github.com/atanisoft) 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "usb.h" 16 | 17 | #if CONFIG_ESPUSB_MSC 18 | 19 | // these can be used to fine tune the debug log levels for hex dump of sector 20 | // data within the read callback. 21 | #define MSC_LOG_LEVEL_BOOT_SECTOR ESP_LOG_VERBOSE 22 | #define MSC_LOG_LEVEL_ROOT_DIRECTORY ESP_LOG_VERBOSE 23 | #define MSC_LOG_LEVEL_FAT_TABLE ESP_LOG_VERBOSE 24 | // in order for debug data to be printed this needs to be defined prior to 25 | // inclusion of esp_log.h. The value below is one higher than ESP_LOG_VERBOSE. 26 | //#define LOG_LOCAL_LEVEL ESP_LOG_INFO 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include "psram_allocator.h" 38 | 39 | static constexpr const char * const TAG = "USB:MSC"; 40 | 41 | typedef enum : uint8_t 42 | { 43 | PART_EMPTY = 0x00, 44 | PART_FAT_12 = 0x01, 45 | PART_FAT_16 = 0x04, 46 | PART_FAT_16B = 0x06, 47 | PART_FAT_32_LBA = 0x0C, 48 | PART_FAT_16B_LBA = 0x0E, 49 | PART_EXTENDED = 0x0F, 50 | } partition_type_t; 51 | 52 | typedef enum : uint8_t 53 | { 54 | PART_STATUS_UNUSED = 0x00, 55 | PART_STATUS_ACTIVE = 0x80, 56 | PART_STATUS_BOOTABLE = 0x80 57 | } partition_status_t; 58 | 59 | typedef struct TU_ATTR_PACKED // start 60 | { // offset notes 61 | partition_status_t status; // 0x00 status of the disk: 62 | // 0x00 = inactive 63 | // 0x01-0x7f = invalid 64 | // 0x80 = bootable 65 | uint8_t first_head; // 0x01 66 | uint8_t first_sector; // 0x02 this field is split between sector and cylinder: 67 | // bits 0-5 (0x3F) are for sector 68 | // bits 6,7 are cylinder bits 8,9 69 | uint8_t first_cylinder; // 0x03 70 | partition_type_t partition_type; // 0x04 71 | uint8_t last_head; // 0x05 72 | uint8_t last_sector; // 0x06 this field is split between sector and cylinder: 73 | // bits 0-5 (0x3F) are for sector 74 | // bits 6,7 are cylinder bits 8,9 75 | uint8_t last_cylinder; // 0x07 76 | uint32_t first_lba; // 0x08 77 | uint32_t sector_count; // 0x0C 78 | } partition_def_t; 79 | 80 | static_assert(sizeof(partition_def_t) == 16, 81 | "partition_def_t should be 16 bytes"); 82 | 83 | typedef struct TU_ATTR_PACKED // start 84 | { // offset notes 85 | uint8_t bootstrap[218]; // 0x000 bootstrap code. 86 | uint16_t disk_timestamp; // 0x0DA 87 | uint8_t original_drive_id; // 0x0DC 88 | uint8_t disk_seconds; // 0x0DD 89 | uint8_t disk_minutes; // 0x0DE 90 | uint8_t disk_hours; // 0x0DF 91 | uint8_t boostrap2[216]; // 0x0E0 bootstrap code part 2 92 | uint32_t disk_signature; // 0x1B8 93 | uint16_t copy_protected; // 0x1BC 94 | partition_def_t partitions[4]; // 0x1BE four partition tables 95 | uint8_t signature[2]; // 0x1FE signature, 0x55, 0xAA 96 | } master_boot_record_t; 97 | 98 | static_assert(sizeof(master_boot_record_t) == 512, 99 | "master_boot_record_t should be 512 bytes"); 100 | 101 | typedef struct TU_ATTR_PACKED // start 102 | { // offset notes 103 | uint8_t jump_instruction[3]; // 0x000 104 | uint8_t oem_info[8]; // 0x003 105 | uint16_t sector_size; // 0x00B bios param block 106 | uint8_t sectors_per_cluster; // 0x00D 107 | uint16_t reserved_sectors; // 0x00E 108 | uint8_t fat_copies; // 0x010 109 | uint16_t root_directory_entries; // 0x011 110 | uint16_t sector_count_16; // 0x013 111 | uint8_t media_descriptor; // 0x015 112 | uint16_t fat_sectors; // 0x016 113 | uint16_t sectors_per_track; // 0x018 DOS 3.31 BPB 114 | uint16_t heads; // 0x01A 115 | uint32_t hidden_sectors; // 0x01C 116 | uint32_t sector_count_32; // 0x020 117 | uint8_t drive_num; // 0x024 extended boot param block (FAT 12/16) 118 | uint8_t reserved; // 0x025 119 | uint8_t boot_sig; // 0x026 120 | uint32_t volume_serial_number; // 0x027 121 | char volume_label[11]; // 0x02B only available if boot_sig = 0x29 122 | uint8_t fs_identifier[8]; // 0x036 only available if boot_sig = 0x29 123 | uint8_t boot_code[0x1FE - 0x03E]; // 0x03E 124 | uint8_t signature[2]; // 0x1FE signature, 0x55, 0xAA 125 | } bios_boot_sector_t; 126 | 127 | static_assert(sizeof(bios_boot_sector_t) == 512, 128 | "bios_boot_sector_t should be 512 bytes"); 129 | 130 | typedef enum : uint8_t 131 | { 132 | DIRENT_READ_ONLY = 0x01, 133 | DIRENT_HIDDEN = 0x02, 134 | DIRENT_SYSTEM = 0x04, 135 | DIRENT_VOLUME_LABEL = 0x08, 136 | DIRENT_SUB_DIRECTORY = 0x10, 137 | DIRENT_ARCHIVE = 0x20, 138 | DIRENT_DEVICE = 0x40, 139 | DIRENT_RESERVED = 0x80 140 | } dirent_attr_t; 141 | 142 | typedef struct TU_ATTR_PACKED // start 143 | { // offset notes 144 | char name[8]; // 0x00 space padded 145 | char ext[3]; // 0x08 space padded 146 | uint8_t attributes; // 0x0B bitmask of dirent_attr_t 147 | uint8_t reserved; // 0x0C 148 | uint8_t create_time_fine; // 0x0D 149 | uint16_t create_time; // 0x0E bits 15-11 are hours, 10-5 minutes, 4-0 seconds 150 | uint16_t create_date; // 0x10 bits 15-9 are year (0=1980), 8-5 month, 4-0 day 151 | uint16_t last_access_date; // 0x12 same format as create_date 152 | uint16_t high_start_cluster; // 0x14 FAT-32 only, high bytes for cluster start 153 | uint16_t update_time; // 0x16 same format as create_time 154 | uint16_t update_date; // 0x18 same format as create_date 155 | uint16_t start_cluster; // 0x1A starting cluster (FAT-16), low order bytes for FAT-32. 156 | uint32_t size; // 0x1C 157 | } fat_direntry_t; 158 | 159 | static_assert(sizeof(fat_direntry_t) == 32, 160 | "fat_direntry_t should be 32 bytes"); 161 | 162 | typedef struct TU_ATTR_PACKED // start 163 | { // offset notes 164 | uint8_t sequence; // 0x00 bit 6 indicates last in sequence, bits 0-5 are index. 165 | uint16_t name[5]; // 0x01 first five UTF-16 characters of name 166 | uint8_t attributes; // 0x0B always 0x0F 167 | uint8_t type; // 0x0C always 0x00 168 | uint8_t checksum; // 0x0D 169 | uint16_t name2[6]; // 0x0E next six UTF-16 characters of name 170 | uint16_t start_cluster; // 0x1A always 0x0000 171 | uint16_t name3[2]; // 0x1C last two UTF-16 charactes of name 172 | } fat_long_filename_t; 173 | 174 | static_assert(sizeof(fat_long_filename_t) == sizeof(fat_direntry_t), 175 | "fat_long_filename_t should be same size as fat_direntry_t"); 176 | 177 | typedef struct 178 | { 179 | char name[8]; 180 | char ext[3]; 181 | const char *content; 182 | uint8_t attributes; 183 | uint32_t size; 184 | uint32_t start_sector; 185 | uint32_t end_sector; 186 | uint16_t start_cluster; 187 | uint16_t end_cluster; 188 | const esp_partition_t *partition; 189 | std::string printable_name; 190 | uint8_t root_dir_sector; 191 | #if CONFIG_ESPUSB_MSC_LONG_FILENAMES 192 | std::vector> lfn_parts; 194 | #endif // CONFIG_ESPUSB_MSC_LONG_FILENAMES 195 | } fat_file_entry_t; 196 | 197 | static_assert((CONFIG_ESPUSB_MSC_VDISK_FILE_COUNT & 15) == 0, 198 | "Number of files on the virtual disk must be a multiple of 16"); 199 | 200 | static constexpr uint16_t DIRENTRIES_PER_SECTOR = 201 | (CONFIG_ESPUSB_MSC_VDISK_SECTOR_SIZE / sizeof(fat_direntry_t)); 202 | static constexpr uint16_t SECTORS_PER_FAT_TABLE = 203 | ((CONFIG_ESPUSB_MSC_VDISK_SECTOR_COUNT * 2) + 204 | (CONFIG_ESPUSB_MSC_VDISK_SECTOR_SIZE - 1)) 205 | / CONFIG_ESPUSB_MSC_VDISK_SECTOR_SIZE; 206 | 207 | static constexpr uint16_t FAT_COPY_0_FIRST_SECTOR = 208 | CONFIG_ESPUSB_MSC_VDISK_RESERVED_SECTOR_COUNT; 209 | static constexpr uint16_t FAT_COPY_1_FIRST_SECTOR = 210 | FAT_COPY_0_FIRST_SECTOR + SECTORS_PER_FAT_TABLE; 211 | static constexpr uint16_t ROOT_DIR_SECTOR_COUNT = 212 | (CONFIG_ESPUSB_MSC_VDISK_FILE_COUNT / DIRENTRIES_PER_SECTOR); 213 | static constexpr uint16_t ROOT_DIR_FIRST_SECTOR = 214 | FAT_COPY_1_FIRST_SECTOR + SECTORS_PER_FAT_TABLE; 215 | static constexpr uint16_t FILE_CONTENT_FIRST_SECTOR = 216 | ROOT_DIR_FIRST_SECTOR + ROOT_DIR_SECTOR_COUNT; 217 | 218 | /// Special marker for FAT cluster end of file (FAT-16). 219 | static constexpr uint16_t FAT_CLUSTER_END_OF_FILE = 0xFFFF; 220 | 221 | #if CONFIG_ESPUSB_MSC_LONG_FILENAMES 222 | /// Maximum length of filename. 223 | /// NOTE: this excludes the period between the filename and extension. 224 | static constexpr uint8_t MAX_FILENAME_LENGTH = 38; 225 | #else 226 | /// Maximum length of filename. 227 | /// NOTE: this excludes the period between the filename and extension. 228 | static constexpr uint8_t MAX_FILENAME_LENGTH = 11; 229 | #endif // CONFIG_ESPUSB_MSC_LONG_FILENAMES 230 | 231 | /// Flag for @ref bios_boot_sector_t boot_sig field indicating that the 232 | /// volume_label and fs_identifier fields are populated. 233 | static constexpr uint8_t BOOT_SIGNATURE_SERIAL_ONLY = 0x28; 234 | 235 | /// Flag for @ref bios_boot_sector_t boot_sig field indicating that the 236 | /// volume_label and fs_identifier fields are not populated and should be 237 | /// ignored. 238 | static constexpr uint8_t BOOT_SIGNATURE_SERIAL_LABEL_IDENT = 0x29; 239 | 240 | /// Static copy of the bios boot sector that will be presented to the operating 241 | /// system on-demand. Note all fields are in little-endian format. 242 | static bios_boot_sector_t s_bios_boot_sector = 243 | { 244 | .jump_instruction = {0xEB, 0x3C, 0x90}, 245 | .oem_info = {'M','S','D','O','S','5','.','0'}, 246 | .sector_size = CONFIG_ESPUSB_MSC_VDISK_SECTOR_SIZE, 247 | .sectors_per_cluster = 1, 248 | .reserved_sectors = CONFIG_ESPUSB_MSC_VDISK_RESERVED_SECTOR_COUNT, 249 | .fat_copies = 2, 250 | .root_directory_entries = CONFIG_ESPUSB_MSC_VDISK_FILE_COUNT, 251 | .sector_count_16 = CONFIG_ESPUSB_MSC_VDISK_SECTOR_COUNT, 252 | .media_descriptor = 0xF8, 253 | .fat_sectors = SECTORS_PER_FAT_TABLE, 254 | .sectors_per_track = 1, 255 | .heads = 1, 256 | .hidden_sectors = 0, 257 | .sector_count_32 = 0, 258 | .drive_num = 0x80, 259 | .reserved = 0, 260 | .boot_sig = BOOT_SIGNATURE_SERIAL_LABEL_IDENT, 261 | .volume_serial_number = 0, 262 | .volume_label = {'e','s','p','3','2','s','2'}, 263 | .fs_identifier = {'F','A','T','1','6',' ',' ',' '}, 264 | .boot_code = {0}, 265 | .signature = {0x55, 0xaa} 266 | }; 267 | 268 | static std::vector> s_root_directory; 270 | 271 | static uint8_t s_root_directory_entry_usage[ROOT_DIR_SECTOR_COUNT]; 272 | 273 | static xTimerHandle msc_write_timer; 274 | static constexpr TickType_t TIMER_EXPIRE_TICKS = pdMS_TO_TICKS(1000); 275 | static constexpr TickType_t TIMER_TICKS_TO_WAIT = 0; 276 | static bool msc_write_active = false; 277 | static esp_chip_id_t current_chip_id = ESP_CHIP_ID_INVALID; 278 | static esp_ota_handle_t ota_update_handle = 0; 279 | const esp_partition_t *ota_update_partition = nullptr; 280 | static size_t ota_bytes_received; 281 | 282 | static const char * const s_vendor_id = CONFIG_ESPUSB_MSC_VENDOR_ID; 283 | static const char * const s_product_id = CONFIG_ESPUSB_MSC_PRODUCT_ID; 284 | static const char * const s_product_rev = CONFIG_ESPUSB_MSC_PRODUCT_REVISION; 285 | 286 | /// Utility function to copy a string into a target field using spaces to pad 287 | /// to a set length. 288 | /// 289 | /// @param dst destination array. 290 | /// @param src source string. 291 | /// @param len size of destination array. 292 | static void space_padded_memcpy(char *dst, const char *src, int len) 293 | { 294 | for (int i = 0; i < len; ++i) 295 | { 296 | *dst++ = *src ? *src++ : ' '; 297 | } 298 | } 299 | 300 | /// FreeRTOS Timer expire callback. 301 | /// 302 | /// @param pxTimer handle of the timer that expired. 303 | static void msc_write_timeout_cb(xTimerHandle pxTimer) 304 | { 305 | ESP_LOGV(TAG, "ota_update_timer expired"); 306 | xTimerStop(pxTimer, TIMER_TICKS_TO_WAIT); 307 | if (ota_update_partition != nullptr && ota_update_handle) 308 | { 309 | esp_err_t err = 310 | ESP_ERROR_CHECK_WITHOUT_ABORT(esp_ota_end(ota_update_handle)); 311 | if (err == ESP_OK) 312 | { 313 | err = ESP_ERROR_CHECK_WITHOUT_ABORT( 314 | esp_ota_set_boot_partition(ota_update_partition)); 315 | } 316 | ota_update_end_cb(ota_bytes_received, err); 317 | } 318 | ota_update_handle = 0; 319 | ota_update_partition = nullptr; 320 | ota_bytes_received = 0; 321 | } 322 | 323 | // default implementation. 324 | TU_ATTR_WEAK bool ota_update_start_cb(esp_app_desc_t *app_desc) 325 | { 326 | return true; 327 | } 328 | 329 | // default implementation. 330 | TU_ATTR_WEAK void ota_update_end_cb(size_t received_bytes, esp_err_t err) 331 | { 332 | ESP_LOGI(TAG, "OTA Update complete callback: %s", esp_err_to_name(err)); 333 | if (err == ESP_OK) 334 | { 335 | ESP_LOGI(TAG, "Restarting..."); 336 | esp_restart(); 337 | } 338 | } 339 | 340 | // configures the virtual disk system 341 | void configure_virtual_disk(std::string label, uint32_t serial_number) 342 | { 343 | space_padded_memcpy(s_bios_boot_sector.volume_label, label.c_str(), 11); 344 | s_bios_boot_sector.volume_serial_number = htole32(serial_number); 345 | uint32_t sector_count = s_bios_boot_sector.sector_count_16; 346 | if (sector_count == 0) 347 | { 348 | sector_count = s_bios_boot_sector.sector_count_32; 349 | } 350 | ESP_LOGI(TAG, 351 | "USB Virtual disk %-11.11s\n" 352 | "%d total sectors (%d bytes)\n" 353 | "%d reserved sector(s)\n" 354 | "%d sectors per fat (%d bytes)\n" 355 | "fat0 sector start: %d\n" 356 | "fat1 sector start: %d\n" 357 | "root directory sector start: %d (%d entries, %d per sector)\n" 358 | "first file sector start: %d\n" 359 | #if CONFIG_ESPUSB_MSC_LONG_FILENAMES 360 | "long filenames: enabled", 361 | #else 362 | "long filenames: disabled", 363 | #endif // CONFIG_ESPUSB_MSC_LONG_FILENAMES 364 | s_bios_boot_sector.volume_label, 365 | sector_count, 366 | (sector_count * s_bios_boot_sector.sector_size), 367 | s_bios_boot_sector.reserved_sectors, 368 | s_bios_boot_sector.fat_sectors, 369 | s_bios_boot_sector.fat_sectors * s_bios_boot_sector.sector_size, 370 | FAT_COPY_0_FIRST_SECTOR, 371 | FAT_COPY_1_FIRST_SECTOR, 372 | ROOT_DIR_FIRST_SECTOR, 373 | CONFIG_ESPUSB_MSC_VDISK_FILE_COUNT, 374 | DIRENTRIES_PER_SECTOR, 375 | FILE_CONTENT_FIRST_SECTOR 376 | ); 377 | 378 | // convert fields to little endian 379 | s_bios_boot_sector.sector_size = htole16(s_bios_boot_sector.sector_size); 380 | s_bios_boot_sector.reserved_sectors = 381 | htole16(s_bios_boot_sector.reserved_sectors); 382 | s_bios_boot_sector.root_directory_entries = 383 | htole16(s_bios_boot_sector.root_directory_entries); 384 | s_bios_boot_sector.sector_count_16 = 385 | htole16(s_bios_boot_sector.sector_count_16); 386 | s_bios_boot_sector.sector_count_32 = 387 | htole32(s_bios_boot_sector.sector_count_32); 388 | s_bios_boot_sector.fat_sectors = htole16(s_bios_boot_sector.fat_sectors); 389 | s_bios_boot_sector.sectors_per_track = 390 | htole16(s_bios_boot_sector.sectors_per_track); 391 | s_bios_boot_sector.heads = htole16(s_bios_boot_sector.heads); 392 | s_bios_boot_sector.hidden_sectors = 393 | htole32(s_bios_boot_sector.hidden_sectors); 394 | s_bios_boot_sector.heads = htole32(s_bios_boot_sector.heads); 395 | 396 | // initialize all root directory sectors to have zero file entries. 397 | memset(s_root_directory_entry_usage, 0, ROOT_DIR_SECTOR_COUNT); 398 | // track the volume label as part of the first sector. 399 | s_root_directory_entry_usage[0] = 1; 400 | 401 | // TODO: remove the usage of FreeRTOS Timer here. 402 | msc_write_timer = 403 | xTimerCreate("msc_write_timer", TIMER_EXPIRE_TICKS, pdTRUE, nullptr, 404 | msc_write_timeout_cb); 405 | current_chip_id = ESP_CHIP_ID_INVALID; 406 | 407 | // determine the type of chip that we are currently 408 | // running and convert it to esp_chip_id_t. 409 | esp_chip_info_t chip_info; 410 | esp_chip_info(&chip_info); 411 | if (chip_info.model == CHIP_ESP32S2) 412 | { 413 | current_chip_id = ESP_CHIP_ID_ESP32S2; 414 | } 415 | else if (chip_info.model == CHIP_ESP32S3) 416 | { 417 | current_chip_id = ESP_CHIP_ID_ESP32S3; 418 | } 419 | } 420 | 421 | esp_err_t register_virtual_file(const std::string name, const char *content, 422 | uint32_t size, bool read_only, 423 | const esp_partition_t *partition) 424 | { 425 | // one directory entry is reserved for the volume label 426 | if (s_root_directory.size() > (CONFIG_ESPUSB_MSC_VDISK_FILE_COUNT - 1)) 427 | { 428 | ESP_LOGE(TAG, 429 | "Maximum file count has been reached, rejecting new file!"); 430 | return ESP_ERR_INVALID_STATE; 431 | } 432 | 433 | fat_file_entry_t file = {}; 434 | 435 | // default base name and extension to spaces 436 | memset(file.name, ' ', TU_ARRAY_SIZE(file.name)); 437 | memset(file.ext, ' ', TU_ARRAY_SIZE(file.ext)); 438 | 439 | // break the provided filename into base name and extension 440 | size_t pos = name.find_first_of('.'); 441 | std::string base_name = name; 442 | std::string extension = ""; 443 | if (pos == std::string::npos) 444 | { 445 | // truncate the filename to the maximum length limit 446 | if (base_name.length() > MAX_FILENAME_LENGTH) 447 | { 448 | base_name.resize(MAX_FILENAME_LENGTH); 449 | } 450 | // fill the file name and extension to spaces by default. 451 | memset(file.name, ' ', 8); 452 | memset(file.ext, ' ', 3); 453 | // copy up to 11 characters of the filename into the name field, this 454 | // will spill over into the ext field. 455 | memcpy(file.name, base_name.c_str(), std::min((size_t)11 456 | , base_name.length())); 457 | // force the file name and extension to be upper case. 458 | for (size_t index = 0; index < 8; index++) 459 | { 460 | file.name[index] = toupper(file.name[index]); 461 | } 462 | for (size_t index = 0; index < 3; index++) 463 | { 464 | file.ext[index] = toupper(file.ext[index]); 465 | } 466 | file.printable_name.assign(std::move(base_name)); 467 | } 468 | else 469 | { 470 | base_name = name.substr(0, pos); 471 | extension = name.substr(pos + 1, 3); 472 | // possibly truncate the base name so it fits within the max file length 473 | if (base_name.length() > MAX_FILENAME_LENGTH - 3) 474 | { 475 | base_name.resize(MAX_FILENAME_LENGTH - 3); 476 | } 477 | for (size_t index = 0; index < std::min((size_t)8, base_name.length()); index++) 478 | { 479 | file.name[index] = toupper(base_name.at(index)); 480 | } 481 | for (size_t index = 0; index < extension.length(); index++) 482 | { 483 | file.ext[index] = toupper(extension.at(index)); 484 | } 485 | file.printable_name.assign(std::move(base_name)).append(".").append(extension); 486 | } 487 | #if CONFIG_ESPUSB_MSC_LONG_FILENAMES 488 | // if the filename is longer than the maximum allowed for 8.3 format 489 | // convert it to a long filename instead 490 | if (file.printable_name.length() > 12) 491 | { 492 | // mark the file name as truncated. 493 | file.name[6] = '~'; 494 | file.name[7] = '1'; 495 | uint8_t lfn_checksum = 0; 496 | uint8_t *p = (uint8_t *)file.name; 497 | for (size_t index = 11; index; index--) 498 | { 499 | lfn_checksum = ((lfn_checksum & 1) << 7) + 500 | (lfn_checksum >> 1) + *p++; 501 | } 502 | size_t offs = 0; 503 | uint8_t fragments = 1; 504 | while (offs < file.printable_name.length()) 505 | { 506 | fat_long_filename_t part; 507 | bzero(&part, sizeof(fat_long_filename_t)); 508 | std::string name_part = file.printable_name.substr(offs, 13); 509 | ESP_LOGD(TAG, "fragment(%d) %s", fragments, name_part.c_str()); 510 | part.sequence = fragments++; 511 | part.checksum = lfn_checksum; 512 | part.attributes = (DIRENT_READ_ONLY | DIRENT_HIDDEN | 513 | DIRENT_SYSTEM | DIRENT_VOLUME_LABEL); 514 | // all long filename entries must have one null character 515 | if (name_part.length() < 13) 516 | { 517 | name_part += (unsigned char)0x00; 518 | } 519 | // pad remaining characters with 0xFF 520 | while (name_part.length() < 13) 521 | { 522 | name_part += (unsigned char)0xFF; 523 | } 524 | // encode the filename parts into the three fields available in the 525 | // LFN version of the file table entry 526 | for (size_t idx = 0; idx < name_part.length(); idx++) 527 | { 528 | if (idx < 5) 529 | { 530 | part.name[idx] = name_part[idx] != 0xFF ? name_part[idx] : 0xFFFF; 531 | } 532 | else if (idx < 11) 533 | { 534 | part.name2[idx - 5] = name_part[idx] != 0xFF ? name_part[idx] : 0xFFFF; 535 | } 536 | else 537 | { 538 | part.name3[idx - 11] = name_part[idx] != 0xFF ? name_part[idx] : 0xFFFF; 539 | } 540 | } 541 | // insert the fragment at the front of the collection 542 | file.lfn_parts.insert(file.lfn_parts.begin(), part); 543 | offs += name_part.length(); 544 | } 545 | file.lfn_parts[0].sequence |= 0x40; // mark as last in sequence 546 | ESP_LOGI(TAG, "Created %d name fragments", file.lfn_parts.size()); 547 | } 548 | #endif // CONFIG_ESPUSB_MSC_LONG_FILENAMES 549 | file.content = content; 550 | file.partition = partition; 551 | file.size = size; 552 | file.attributes = DIRENT_ARCHIVE; 553 | if (read_only) 554 | { 555 | file.attributes |= DIRENT_READ_ONLY; 556 | } 557 | 558 | if (s_root_directory.empty()) 559 | { 560 | file.start_sector = FILE_CONTENT_FIRST_SECTOR; 561 | file.start_cluster = 2; 562 | } 563 | else 564 | { 565 | file.start_sector = s_root_directory.back().end_sector + 1; 566 | file.start_cluster = s_root_directory.back().end_cluster + 1; 567 | } 568 | file.end_sector = file.start_sector; 569 | file.end_sector += (size / s_bios_boot_sector.sector_size); 570 | file.end_cluster = file.start_cluster; 571 | file.end_cluster += (size / s_bios_boot_sector.sector_size); 572 | // scan root directory sectors to assign this file to a root dir sector 573 | for (uint8_t index = 0; index < ROOT_DIR_SECTOR_COUNT; index++) 574 | { 575 | uint8_t entries_needed = 1; 576 | #if CONFIG_ESPUSB_MSC_LONG_FILENAMES 577 | // if the filename is longer than 12 characters (including period) 578 | // calculate the number of additional entries required. Each fragment 579 | // can hold up to 13 characters. 580 | if (file.printable_name.length() > 12) 581 | { 582 | // for long filenames we will always need at least one additional 583 | // entry 584 | entries_needed++; 585 | entries_needed += (file.printable_name.length() > 13); 586 | entries_needed += (file.printable_name.length() > 26); 587 | } 588 | #endif // CONFIG_ESPUSB_MSC_LONG_FILENAMES 589 | if (s_root_directory_entry_usage[index] + entries_needed < DIRENTRIES_PER_SECTOR) 590 | { 591 | s_root_directory_entry_usage[index] += entries_needed; 592 | file.root_dir_sector = index; 593 | break; 594 | } 595 | } 596 | s_root_directory.push_back(file); 597 | ESP_LOGI(TAG, 598 | "File(%s) sectors: %d - %d, clusters: %d - %d, %d bytes, root: %d", 599 | file.printable_name.c_str(), file.start_sector, file.end_sector, 600 | file.start_cluster, file.end_cluster, size, file.root_dir_sector); 601 | 602 | return ESP_OK; 603 | } 604 | 605 | esp_err_t add_readonly_file_to_virtual_disk(const std::string filename, 606 | const char *content, uint32_t size) 607 | { 608 | return register_virtual_file(filename, content, size, true, nullptr); 609 | } 610 | 611 | esp_err_t add_partition_to_virtual_disk(const std::string partition_name, 612 | const std::string filename, 613 | bool writable) 614 | { 615 | const esp_partition_t *part = 616 | esp_partition_find_first(ESP_PARTITION_TYPE_APP, 617 | ESP_PARTITION_SUBTYPE_ANY, 618 | partition_name.c_str()); 619 | if (part == nullptr) 620 | { 621 | // try and find it as a data partition 622 | part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, 623 | ESP_PARTITION_SUBTYPE_ANY, 624 | partition_name.c_str()); 625 | } 626 | if (part != nullptr) 627 | { 628 | return register_virtual_file(filename, nullptr, part->size, writable, 629 | part); 630 | } 631 | ESP_LOGE(TAG, "Unable to find a partition with name '%s'!" 632 | , partition_name.c_str()); 633 | return ESP_ERR_NOT_FOUND; 634 | } 635 | 636 | // registers the firmware as a file in the virtual disk. 637 | esp_err_t add_firmware_to_virtual_disk(const std::string firmware_name) 638 | { 639 | const esp_partition_t *part = esp_ota_get_running_partition(); 640 | if (part != nullptr) 641 | { 642 | const esp_partition_t *part2 = 643 | esp_ota_get_next_update_partition(nullptr); 644 | // if there is not a second OTA partition to receive the updated 645 | // firmware image we need to treat the file as read-only. We do not 646 | // disable OTA usage here as the read-only flag will disable write. 647 | bool read_only = (part2 == nullptr || part2 == part); 648 | return ESP_ERROR_CHECK_WITHOUT_ABORT( 649 | register_virtual_file(firmware_name, nullptr, part->size, 650 | read_only, part)); 651 | } 652 | return ESP_ERR_NOT_FOUND; 653 | } 654 | 655 | // Utility macro for invoking an ESP-IDF API with with failure return code. 656 | #define ESP_RETURN_ON_ERROR_READ(name, return_code, x) \ 657 | { \ 658 | esp_err_t err = ESP_ERROR_CHECK_WITHOUT_ABORT(x); \ 659 | if (err != ESP_OK) \ 660 | { \ 661 | ESP_LOGE(TAG, "%s: %s", name, esp_err_to_name(err));\ 662 | return return_code; \ 663 | } \ 664 | } 665 | 666 | // Utility macro for invoking an ESP-IDF API with with failure return code. 667 | #define ESP_RETURN_ON_ERROR_WRITE(name, return_code, x) \ 668 | { \ 669 | esp_err_t err = ESP_ERROR_CHECK_WITHOUT_ABORT(x); \ 670 | if (err != ESP_OK) \ 671 | { \ 672 | ESP_LOGE(TAG, "%s: %s", name, esp_err_to_name(err));\ 673 | if (ota_update_handle) \ 674 | { \ 675 | ota_update_end_cb(ota_bytes_received, err); \ 676 | ota_update_partition = nullptr; \ 677 | ota_bytes_received = 0; \ 678 | ota_update_handle = 0; \ 679 | } \ 680 | return return_code; \ 681 | } \ 682 | } 683 | 684 | // ============================================================================= 685 | // TinyUSB CALLBACKS 686 | // ============================================================================= 687 | 688 | extern "C" 689 | { 690 | 691 | // Invoked for SCSI_CMD_INQUIRY command. 692 | void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], 693 | uint8_t product_id[16], uint8_t product_rev[4]) 694 | { 695 | memcpy(vendor_id, s_vendor_id, std::min((size_t)8, strlen(s_vendor_id))); 696 | memcpy(product_id, s_product_id, std::min((size_t)16, strlen(s_product_id))); 697 | memcpy(product_rev, s_product_rev, std::min((size_t)4, strlen(s_product_rev))); 698 | } 699 | 700 | // Invoked for Test Unit Ready command. 701 | bool tud_msc_test_unit_ready_cb(uint8_t lun) 702 | { 703 | return true; 704 | } 705 | 706 | // Invoked for SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY 707 | // to determine the disk size. 708 | void tud_msc_capacity_cb(uint8_t lun, uint32_t *block_count, 709 | uint16_t *block_size) 710 | { 711 | if (s_bios_boot_sector.sector_count_16) 712 | { 713 | *block_count = s_bios_boot_sector.sector_count_16; 714 | } 715 | else 716 | { 717 | *block_count = s_bios_boot_sector.sector_count_32; 718 | } 719 | *block_size = s_bios_boot_sector.sector_size; 720 | } 721 | 722 | // Callback for READ10 command. 723 | // @todo: Ensure reads are bounds checked against bufsize so the MSC buffer can 724 | // be configured via Kconfig. 725 | int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, 726 | void *buffer, uint32_t bufsize) 727 | { 728 | bzero(buffer, bufsize); 729 | if (lba == 0) 730 | { 731 | // Requested bios boot sector 732 | memcpy(buffer, &s_bios_boot_sector, bufsize); 733 | ESP_LOG_BUFFER_HEX_LEVEL(TAG, buffer, bufsize, MSC_LOG_LEVEL_BOOT_SECTOR); 734 | } 735 | else if (lba < ROOT_DIR_FIRST_SECTOR) 736 | { 737 | uint32_t fat_table = (lba - FAT_COPY_0_FIRST_SECTOR); 738 | if (fat_table > s_bios_boot_sector.fat_sectors) 739 | { 740 | fat_table -= s_bios_boot_sector.fat_sectors; 741 | } 742 | uint32_t cluster_start = fat_table * 256; 743 | uint32_t cluster_end = ((fat_table + 1) * 256) - 1; 744 | ESP_LOGD(TAG, "FAT: %d (sector: %d-%d)", fat_table, 745 | cluster_start, cluster_end); 746 | uint16_t *buf_16 = (uint16_t *)buffer; 747 | if (fat_table == 0) 748 | { 749 | // cluster zero is reserved for FAT ID and media descriptor. 750 | buf_16[0] = htole16(0xFF00 | s_bios_boot_sector.media_descriptor); 751 | // cluster one is reserved. 752 | buf_16[1] = FAT_CLUSTER_END_OF_FILE; 753 | } 754 | 755 | for(auto &file : s_root_directory) 756 | { 757 | // check if the file is part of this fat cluster 758 | // A: file start cluster 759 | // B: file end cluster 760 | // C: cluster start 761 | // D: cluster end 762 | // in_range: A <= D AND B >= C 763 | if (file.start_cluster <= cluster_end && 764 | file.end_cluster >= cluster_start) 765 | { 766 | ESP_LOGD(TAG, "File: %s (%d-%d) is in range (%d-%d)" 767 | , file.printable_name.c_str(), file.start_cluster 768 | , file.end_cluster, cluster_start, cluster_end); 769 | for(size_t index = 0; index < 256; index++) 770 | { 771 | uint32_t target_cluster = cluster_start + index; 772 | // if the target cluster is between start and end cluster 773 | // of the file mark it as part of the file. 774 | if (target_cluster >= file.start_cluster && 775 | target_cluster <= file.end_cluster) 776 | { 777 | if (target_cluster != file.end_sector) 778 | { 779 | buf_16[index] = htole16(target_cluster + 1); 780 | } 781 | else 782 | { 783 | buf_16[index] = FAT_CLUSTER_END_OF_FILE; 784 | } 785 | } 786 | } 787 | } 788 | } 789 | ESP_LOG_BUFFER_HEX_LEVEL(TAG, buffer, bufsize, MSC_LOG_LEVEL_FAT_TABLE); 790 | } 791 | else if (lba < FILE_CONTENT_FIRST_SECTOR) 792 | { 793 | fat_direntry_t *d = static_cast(buffer); 794 | // Requested sector of the root directory 795 | uint32_t sector_idx = (lba - ROOT_DIR_FIRST_SECTOR); 796 | ESP_LOGD(TAG, "reading root directory sector %d", sector_idx); 797 | if (sector_idx == 0) 798 | { 799 | ESP_LOGD(TAG, "Adding disk volume label: %11.11s", 800 | s_bios_boot_sector.volume_label); 801 | // NOTE this will overrun d->name and spill over into d->ext 802 | memcpy(d->name, s_bios_boot_sector.volume_label, 11); 803 | d->attributes = DIRENT_ARCHIVE | DIRENT_VOLUME_LABEL; 804 | d->start_cluster = 0; 805 | d++; 806 | } 807 | for (auto &file : s_root_directory) 808 | { 809 | if (file.root_dir_sector != sector_idx) 810 | { 811 | continue; 812 | } 813 | ESP_LOGD(TAG, "Creating directory entry for: %s", 814 | file.printable_name.c_str()); 815 | #if CONFIG_ESPUSB_MSC_LONG_FILENAMES 816 | // add directory entries for name fragments. 817 | if (file.lfn_parts.size()) 818 | { 819 | fat_long_filename_t *lfn = (fat_long_filename_t *)d; 820 | for(auto &lfn_part : file.lfn_parts) 821 | { 822 | memcpy(lfn, &lfn_part, sizeof(fat_long_filename_t)); 823 | lfn++; 824 | d++; 825 | } 826 | } 827 | #endif // CONFIG_ESPUSB_MSC_LONG_FILENAMES 828 | // note this will clear the file extension. 829 | space_padded_memcpy(d->name, file.name, 11); 830 | space_padded_memcpy(d->ext, file.ext, 3); 831 | d->attributes = file.attributes; 832 | d->size = file.size; 833 | d->start_cluster = file.start_cluster; 834 | d->create_date = 0x4d99; 835 | d->update_date = 0x4d99; 836 | // move to the next directory entry in the buffer 837 | d++; 838 | } 839 | ESP_LOGD(TAG, "Directory entries added: %d", 840 | s_root_directory_entry_usage[sector_idx]); 841 | ESP_LOG_BUFFER_HEX_LEVEL(TAG, buffer, bufsize, 842 | MSC_LOG_LEVEL_ROOT_DIRECTORY); 843 | } 844 | else 845 | { 846 | // scan the root directory entries for a file that is in the requested sector. 847 | for(auto &file : s_root_directory) 848 | { 849 | if (lba >= file.start_sector && lba <= file.end_sector) 850 | { 851 | // translate the LBA into the on-disk sector index 852 | uint32_t sector_idx = lba - file.start_sector; 853 | 854 | size_t temp_size = bufsize; 855 | size_t sector_offset = 856 | (sector_idx * s_bios_boot_sector.sector_size) + offset; 857 | uint32_t file_size = file.size; 858 | // bounds check to ensure the read does not go beyond the 859 | // recorded file size. 860 | if (bufsize > (file_size - sector_offset)) 861 | { 862 | temp_size = file_size - sector_offset; 863 | } 864 | ESP_LOGV(TAG, "File(%s) READ %d bytes from lba:%d (offs:%d)", 865 | file.printable_name.c_str(), temp_size, lba, offset); 866 | 867 | if (file.partition != nullptr) 868 | { 869 | ESP_RETURN_ON_ERROR_READ("esp_partition_read", -1, 870 | esp_partition_read(file.partition, sector_offset, 871 | buffer, temp_size)); 872 | } 873 | else 874 | { 875 | memcpy(buffer, file.content + sector_offset, temp_size); 876 | } 877 | } 878 | } 879 | } 880 | 881 | return bufsize; 882 | } 883 | 884 | // Callback for WRITE10 command. 885 | int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, 886 | uint8_t* buffer, uint32_t bufsize) 887 | { 888 | if (lba == 0) 889 | { 890 | ESP_LOGV(TAG, "Write to BOOT sector"); 891 | } 892 | else if (lba < ROOT_DIR_FIRST_SECTOR) 893 | { 894 | ESP_LOGV(TAG, "Write to FAT cluster chain"); 895 | } 896 | else if (lba < FILE_CONTENT_FIRST_SECTOR) 897 | { 898 | ESP_LOGD(TAG, "write to root directory"); 899 | fat_direntry_t *entry = (fat_direntry_t *)buffer; 900 | for (uint8_t index = 0; index < DIRENTRIES_PER_SECTOR; index++) 901 | { 902 | if (entry->attributes == 0x0F && entry->start_cluster == 0) 903 | { 904 | // long filename entry will always have attributes set to 0x0F 905 | // and starting cluster as zero. 906 | fat_long_filename_t *lfn = (fat_long_filename_t*)entry; 907 | uint8_t name[13] = {0}; 908 | for (uint8_t idx = 0; idx < 13; idx++) 909 | { 910 | uint8_t ch = '\0'; 911 | if (idx < 5 && (le16toh(lfn->name[idx]) & 0xFF) != 0xFF) 912 | { 913 | ch = (le16toh(lfn->name[idx]) & 0xFF); 914 | } 915 | else if (idx < 11 && (le16toh(lfn->name2[idx - 5]) & 0xFF) != 0xFF) 916 | { 917 | ch = (le16toh(lfn->name2[idx - 5]) & 0xFF); 918 | } 919 | else if (idx < 13 && (le16toh(lfn->name3[idx - 11]) & 0xFF) != 0xFF) 920 | { 921 | ch = (le16toh(lfn->name3[idx - 11]) & 0xFF); 922 | } 923 | name[idx] = ch; 924 | } 925 | ESP_LOGI(TAG, "LFN: idx:%d (last:%d) %13.13s", 926 | (lfn->sequence & 0x1F), 927 | (lfn->sequence & 0x40) == 0x40, name); 928 | } 929 | else if (entry->start_cluster) 930 | { 931 | ESP_LOGI(TAG, "File: %8.8s.%3.3s, size: %d", entry->name, entry->ext, entry->size); 932 | } 933 | entry++; 934 | } 935 | // @todo add callback for file received 936 | } 937 | else 938 | { 939 | // check if this is the first write of a new file. 940 | if (!msc_write_active) 941 | { 942 | // If the first byte received in the buffer is recognized as the 943 | // esp magic byte, try and validate the data as a valid application 944 | // image. 945 | if (buffer[0] == ESP_IMAGE_HEADER_MAGIC) 946 | { 947 | // the first segment of the received binary should have the 948 | // image header, segment header and app description. These are 949 | // used as a first pass validation of the received data to 950 | // ensure it is a valid ESP application image. 951 | esp_image_header_t *image = (esp_image_header_t *)buffer; 952 | esp_app_desc_t *app_desc = 953 | (esp_app_desc_t *)(buffer + sizeof(esp_image_header_t) + 954 | sizeof(esp_image_segment_header_t)); 955 | // validate the image magic byte and chip type to 956 | // ensure it matches the currently running chip. 957 | if (image->magic == ESP_IMAGE_HEADER_MAGIC && 958 | image->chip_id != ESP_CHIP_ID_INVALID && 959 | image->chip_id == current_chip_id && 960 | app_desc->magic_word == ESP_APP_DESC_MAGIC_WORD) 961 | { 962 | ESP_LOGI(TAG, "Received data appears to be firmware:"); 963 | ESP_LOGI(TAG, "Name: %s (%s)", 964 | app_desc->project_name, app_desc->version); 965 | ESP_LOGI(TAG, "ESP-IDF version: %s", app_desc->idf_ver); 966 | ESP_LOGI(TAG, "Compile timestamp: %s %s", app_desc->date, 967 | app_desc->time); 968 | if (!ota_update_start_cb(app_desc)) 969 | { 970 | ESP_LOGE(TAG, "OTA update rejected by application."); 971 | return -1; 972 | } 973 | // it appears to be a firmware, try and find a place to 974 | // write it to 975 | ota_update_partition = 976 | esp_ota_get_next_update_partition(NULL); 977 | if (ota_update_partition == nullptr || 978 | ota_update_partition == esp_ota_get_running_partition()) 979 | { 980 | ESP_LOGE(TAG, "Unable to locate a free OTA partition."); 981 | return -1; 982 | } 983 | ESP_LOGI(TAG, "Attempting to start OTA image"); 984 | ESP_RETURN_ON_ERROR_WRITE("esp_ota_begin", -1, 985 | esp_ota_begin(ota_update_partition, OTA_SIZE_UNKNOWN, 986 | &ota_update_handle)); 987 | ESP_LOGV(TAG, "ota_update_handle:%d", ota_update_handle); 988 | } 989 | } 990 | else 991 | { 992 | // doesn't appear to be a firmware image, allocate a buffer in 993 | // PSRAM (if available) to store the data as it arrives until 994 | // the root directory has been updated to map to a filename. 995 | } 996 | 997 | // track that we are actively receiving data 998 | msc_write_active = true; 999 | } 1000 | // if we are actively writing an ota update process it immediately. 1001 | if (ota_update_handle) 1002 | { 1003 | ESP_RETURN_ON_ERROR_WRITE("esp_ota_write", -1, 1004 | esp_ota_write(ota_update_handle, buffer, bufsize)); 1005 | // track how much has been written 1006 | ota_bytes_received += bufsize; 1007 | } 1008 | else 1009 | { 1010 | // send the data to the temp buffer 1011 | } 1012 | 1013 | // restart the update timer 1014 | xTimerChangePeriod(msc_write_timer, TIMER_EXPIRE_TICKS, 1015 | TIMER_TICKS_TO_WAIT); 1016 | if (!xTimerIsTimerActive(msc_write_timer) && 1017 | xTimerStart(msc_write_timer, TIMER_TICKS_TO_WAIT) != pdPASS) 1018 | { 1019 | ESP_LOGE(TAG, "Failed to restart MSC timer, giving up!"); 1020 | 1021 | if (ota_update_handle) 1022 | { 1023 | ota_update_end_cb(ota_bytes_received, ESP_FAIL); 1024 | } 1025 | 1026 | // reset state so that the timer expire callback does not try to 1027 | // use the received data. 1028 | ota_update_partition = nullptr; 1029 | ota_bytes_received = 0; 1030 | ota_update_handle = 0; 1031 | return -1; 1032 | } 1033 | } 1034 | return bufsize; 1035 | } 1036 | 1037 | // Callback for SCSI command not in built-in list below 1038 | // - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, MODE_SENSE6, REQUEST_SENSE 1039 | // - READ10 and WRITE10 have their own callbacks 1040 | int32_t tud_msc_scsi_cb (uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, 1041 | uint16_t bufsize) 1042 | { 1043 | void const *response = NULL; 1044 | uint16_t resplen = 0; 1045 | 1046 | // most scsi handled is input 1047 | bool in_xfer = true; 1048 | 1049 | switch (scsi_cmd[0]) 1050 | { 1051 | case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL: 1052 | // Host is about to read/write etc ... better not to disconnect disk 1053 | resplen = 0; 1054 | break; 1055 | 1056 | default: 1057 | // Set Sense = Invalid Command Operation 1058 | tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); 1059 | 1060 | // negative means error -> tinyusb could stall and/or response with 1061 | // failed status 1062 | resplen = -1; 1063 | break; 1064 | } 1065 | 1066 | // return resplen must not larger than bufsize 1067 | if (resplen > bufsize) 1068 | resplen = bufsize; 1069 | 1070 | if (response && (resplen > 0)) 1071 | { 1072 | if (in_xfer) 1073 | { 1074 | memcpy(buffer, response, resplen); 1075 | } 1076 | else 1077 | { 1078 | // SCSI output 1079 | } 1080 | } 1081 | 1082 | return resplen; 1083 | } 1084 | 1085 | } // extern "C" 1086 | 1087 | #endif // CONFIG_ESPUSB_MSC --------------------------------------------------------------------------------