From bb78fea5797454c5589fe82efc23e921c4bf5e37 Mon Sep 17 00:00:00 2001 From: Hrvoje Cavrak Date: Sun, 23 Jun 2024 18:07:30 +0200 Subject: [PATCH] Created RC branch with the following changes: - Single unified firmware binary - Improved support for NKRO keyboards - Report mode default for keyboard port - Improved consumer control parser for variable data types (media keys should be better supported) - System control forwarding - Improved HID parser - Web UI to configure instead of keyboard shortcuts - Firmware upgrade while the device remains functional - Only one end will need upgrade, the other will get it automatically - No need to recompile to set most settings - Improved UART routines to use DMA, more reliable link - Fixed a bunch of bugs and issues --- CMakeLists.txt | 66 +- binaries/board_A.uf2 | Bin 159744 -> 0 bytes binaries/board_B.uf2 | Bin 159744 -> 0 bytes binaries/deskhop.uf2 | Bin 0 -> 524288 bytes disk/create.sh | 14 + disk/disk.S | 10 + disk/disk.img | Bin 0 -> 65536 bytes img/deskhop_config_ui.png | Bin 0 -> 189752 bytes memory_map.ld | 62 +- pico-sdk/lib/tinyusb/.swp | Bin 0 -> 12288 bytes pico-sdk/lib/tinyusb/hw/bsp/board.c | 22 + pico-sdk/lib/tinyusb/hw/bsp/board_api.h | 22 + pico-sdk/lib/tinyusb/hw/bsp/board_mcu.h | 2 +- .../lib/tinyusb/hw/bsp/family_support.cmake | 135 +- .../lib/tinyusb/hw/bsp/rp2040/family.cmake | 11 +- pico-sdk/lib/tinyusb/src/CMakeLists.txt | 14 +- pico-sdk/lib/tinyusb/src/class/cdc/cdc.h | 20 +- .../lib/tinyusb/src/class/cdc/cdc_device.c | 47 +- .../lib/tinyusb/src/class/cdc/cdc_device.h | 7 + pico-sdk/lib/tinyusb/src/class/cdc/cdc_host.c | 1174 ---------- pico-sdk/lib/tinyusb/src/class/cdc/cdc_host.h | 204 -- .../lib/tinyusb/src/class/cdc/cdc_rndis.h | 301 --- .../tinyusb/src/class/cdc/cdc_rndis_host.c | 289 --- .../tinyusb/src/class/cdc/cdc_rndis_host.h | 63 - pico-sdk/lib/tinyusb/src/class/hid/hid.h | 498 +++-- .../lib/tinyusb/src/class/hid/hid_device.c | 363 ++-- .../lib/tinyusb/src/class/hid/hid_device.h | 235 ++ pico-sdk/lib/tinyusb/src/class/hid/hid_host.c | 583 +++-- pico-sdk/lib/tinyusb/src/class/hid/hid_host.h | 37 +- .../lib/tinyusb/src/class/msc/msc_device.c | 28 +- .../lib/tinyusb/src/class/msc/msc_device.h | 4 + pico-sdk/lib/tinyusb/src/class/msc/msc_host.c | 497 ----- pico-sdk/lib/tinyusb/src/class/msc/msc_host.h | 126 -- .../tinyusb/src/class/vendor/vendor_device.c | 287 --- .../tinyusb/src/class/vendor/vendor_device.h | 150 -- .../tinyusb/src/class/vendor/vendor_host.c | 146 -- .../tinyusb/src/class/vendor/vendor_host.h | 67 - pico-sdk/lib/tinyusb/src/common/tusb_common.h | 2 + .../lib/tinyusb/src/common/tusb_compiler.h | 2 +- pico-sdk/lib/tinyusb/src/common/tusb_debug.h | 3 +- pico-sdk/lib/tinyusb/src/common/tusb_fifo.c | 32 +- pico-sdk/lib/tinyusb/src/common/tusb_fifo.h | 25 +- pico-sdk/lib/tinyusb/src/common/tusb_mcu.h | 45 +- .../lib/tinyusb/src/common/tusb_private.h | 19 +- pico-sdk/lib/tinyusb/src/common/tusb_types.h | 186 +- pico-sdk/lib/tinyusb/src/common/tusb_verify.h | 8 +- pico-sdk/lib/tinyusb/src/device/dcd.h | 18 + pico-sdk/lib/tinyusb/src/device/usbd.c | 673 +++--- pico-sdk/lib/tinyusb/src/device/usbd.h | 28 +- pico-sdk/lib/tinyusb/src/device/usbd_pvt.h | 19 +- pico-sdk/lib/tinyusb/src/host/hcd.h | 5 +- pico-sdk/lib/tinyusb/src/host/hub.c | 8 +- pico-sdk/lib/tinyusb/src/host/hub.h | 13 +- pico-sdk/lib/tinyusb/src/host/usbh.c | 900 ++++---- pico-sdk/lib/tinyusb/src/host/usbh.h | 22 +- pico-sdk/lib/tinyusb/src/host/usbh_pvt.h | 12 +- pico-sdk/lib/tinyusb/src/osal/osal.h | 3 + pico-sdk/lib/tinyusb/src/osal/osal_freertos.h | 214 -- pico-sdk/lib/tinyusb/src/osal/osal_mynewt.h | 176 -- pico-sdk/lib/tinyusb/src/osal/osal_none.h | 17 +- pico-sdk/lib/tinyusb/src/osal/osal_pico.h | 106 +- pico-sdk/lib/tinyusb/src/osal/osal_rtthread.h | 132 -- pico-sdk/lib/tinyusb/src/osal/osal_rtx4.h | 170 -- .../portable/raspberrypi/rp2040/dcd_rp2040.c | 356 ++- .../portable/raspberrypi/rp2040/hcd_rp2040.c | 20 +- .../portable/raspberrypi/rp2040/rp2040_usb.c | 172 +- pico-sdk/lib/tinyusb/src/tinyusb.mk | 6 +- pico-sdk/lib/tinyusb/src/tusb.c | 304 +-- pico-sdk/lib/tinyusb/src/tusb.h | 5 - pico-sdk/lib/tinyusb/src/tusb_option.h | 40 +- pico-sdk/src/rp2_common/CMakeLists.txt | 9 +- .../rp2_common/pico_btstack/CMakeLists.txt | 357 --- .../src/rp2_common/pico_btstack/LICENSE.RP | 30 - .../pico_btstack/btstack_flash_bank.c | 177 -- .../btstack_run_loop_async_context.c | 155 -- .../pico_btstack/btstack_stdin_pico.c | 60 - pico-sdk/src/rp2_common/pico_btstack/doc.h | 27 - .../include/pico/btstack_flash_bank.h | 38 - .../pico/btstack_run_loop_async_context.h | 29 - .../rp2_common/pico_cyw43_arch/CMakeLists.txt | 89 - .../rp2_common/pico_cyw43_arch/cyw43_arch.c | 188 -- .../pico_cyw43_arch/cyw43_arch_freertos.c | 84 - .../pico_cyw43_arch/cyw43_arch_poll.c | 76 - .../cyw43_arch_threadsafe_background.c | 81 - .../pico_cyw43_arch/include/pico/cyw43_arch.h | 504 ----- .../include/pico/cyw43_arch/arch_freertos.h | 20 - .../include/pico/cyw43_arch/arch_poll.h | 12 - .../cyw43_arch/arch_threadsafe_background.h | 12 - .../pico_cyw43_driver/CMakeLists.txt | 93 - .../pico_cyw43_driver/btstack_chipset_cyw43.c | 26 - .../pico_cyw43_driver/btstack_cyw43.c | 74 - .../btstack_hci_transport_cyw43.c | 159 -- .../cybt_shared_bus/CMakeLists.txt | 23 - .../cybt_shared_bus/cybt_shared_bus.c | 431 ---- .../cybt_shared_bus/cybt_shared_bus_driver.c | 721 ------- .../cybt_shared_bus/cybt_shared_bus_driver.h | 81 - .../pico_cyw43_driver/cyw43_bus_pio_spi.c | 548 ----- .../pico_cyw43_driver/cyw43_bus_pio_spi.pio | 61 - .../pico_cyw43_driver/cyw43_driver.c | 195 -- .../include/cyw43_configport.h | 173 -- .../include/pico/btstack_chipset_cyw43.h | 18 - .../include/pico/btstack_cyw43.h | 44 - .../pico/btstack_hci_transport_cyw43.h | 31 - .../include/pico/cyw43_driver.h | 44 - .../src/rp2_common/pico_lwip/CMakeLists.txt | 306 --- pico-sdk/src/rp2_common/pico_lwip/doc.h | 46 - .../rp2_common/pico_lwip/include/arch/cc.h | 93 - .../pico_lwip/include/pico/lwip_freertos.h | 51 - .../pico_lwip/include/pico/lwip_nosys.h | 49 - .../src/rp2_common/pico_lwip/lwip_freertos.c | 65 - .../src/rp2_common/pico_lwip/lwip_nosys.c | 74 - .../rp2_common/pico_mbedtls/CMakeLists.txt | 173 -- .../rp2_common/pico_mbedtls/pico_mbedtls.c | 15 - pico-sdk/tools/pioasm/CMakeLists.txt | 2 +- src/constants.c | 38 + src/defaults.c | 7 + src/handlers.c | 200 +- src/hid_parser.c | 235 +- src/hid_report.c | 300 +++ src/{ => include}/hid_parser.h | 109 +- src/include/hid_report.h | 37 + src/{ => include}/main.h | 317 ++- src/include/protocol.h | 23 + src/{ => include}/tusb_config.h | 11 +- src/{ => include}/usb_descriptors.h | 76 +- src/{ => include}/user_config.h | 38 +- src/keyboard.c | 112 +- src/led.c | 12 +- src/main.c | 62 +- src/mouse.c | 80 +- src/protocol.c | 69 + src/ramdisk.c | 119 + src/setup.c | 175 +- src/tasks.c | 213 ++ src/uart.c | 129 +- src/usb.c | 132 +- src/usb_descriptors.c | 136 +- src/utils.c | 215 +- tools/crc32.py | 20 + webconfig/Makefile | 2 + webconfig/__pycache__/form.cpython-311.pyc | Bin 0 -> 4493 bytes webconfig/config-unpacked.htm | 1910 +++++++++++++++++ webconfig/config.htm | 1 + webconfig/form.py | 82 + webconfig/render.py | 61 + webconfig/templates/form.html | 58 + webconfig/templates/main.html | 142 ++ webconfig/templates/packer.j2 | 1 + webconfig/templates/script.js | 218 ++ webconfig/templates/style.css | 665 ++++++ 150 files changed, 8206 insertions(+), 12568 deletions(-) delete mode 100644 binaries/board_A.uf2 delete mode 100644 binaries/board_B.uf2 create mode 100644 binaries/deskhop.uf2 create mode 100755 disk/create.sh create mode 100644 disk/disk.S create mode 100644 disk/disk.img create mode 100644 img/deskhop_config_ui.png create mode 100644 pico-sdk/lib/tinyusb/.swp delete mode 100644 pico-sdk/lib/tinyusb/src/class/cdc/cdc_host.c delete mode 100644 pico-sdk/lib/tinyusb/src/class/cdc/cdc_host.h delete mode 100644 pico-sdk/lib/tinyusb/src/class/cdc/cdc_rndis.h delete mode 100644 pico-sdk/lib/tinyusb/src/class/cdc/cdc_rndis_host.c delete mode 100644 pico-sdk/lib/tinyusb/src/class/cdc/cdc_rndis_host.h delete mode 100644 pico-sdk/lib/tinyusb/src/class/msc/msc_host.c delete mode 100644 pico-sdk/lib/tinyusb/src/class/msc/msc_host.h delete mode 100644 pico-sdk/lib/tinyusb/src/class/vendor/vendor_device.c delete mode 100644 pico-sdk/lib/tinyusb/src/class/vendor/vendor_device.h delete mode 100644 pico-sdk/lib/tinyusb/src/class/vendor/vendor_host.c delete mode 100644 pico-sdk/lib/tinyusb/src/class/vendor/vendor_host.h delete mode 100644 pico-sdk/lib/tinyusb/src/osal/osal_freertos.h delete mode 100644 pico-sdk/lib/tinyusb/src/osal/osal_mynewt.h delete mode 100644 pico-sdk/lib/tinyusb/src/osal/osal_rtthread.h delete mode 100644 pico-sdk/lib/tinyusb/src/osal/osal_rtx4.h delete mode 100644 pico-sdk/src/rp2_common/pico_btstack/CMakeLists.txt delete mode 100644 pico-sdk/src/rp2_common/pico_btstack/LICENSE.RP delete mode 100644 pico-sdk/src/rp2_common/pico_btstack/btstack_flash_bank.c delete mode 100644 pico-sdk/src/rp2_common/pico_btstack/btstack_run_loop_async_context.c delete mode 100644 pico-sdk/src/rp2_common/pico_btstack/btstack_stdin_pico.c delete mode 100644 pico-sdk/src/rp2_common/pico_btstack/doc.h delete mode 100644 pico-sdk/src/rp2_common/pico_btstack/include/pico/btstack_flash_bank.h delete mode 100644 pico-sdk/src/rp2_common/pico_btstack/include/pico/btstack_run_loop_async_context.h delete mode 100644 pico-sdk/src/rp2_common/pico_cyw43_arch/CMakeLists.txt delete mode 100644 pico-sdk/src/rp2_common/pico_cyw43_arch/cyw43_arch.c delete mode 100644 pico-sdk/src/rp2_common/pico_cyw43_arch/cyw43_arch_freertos.c delete mode 100644 pico-sdk/src/rp2_common/pico_cyw43_arch/cyw43_arch_poll.c delete mode 100644 pico-sdk/src/rp2_common/pico_cyw43_arch/cyw43_arch_threadsafe_background.c delete mode 100644 pico-sdk/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch.h delete mode 100644 pico-sdk/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_freertos.h delete mode 100644 pico-sdk/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_poll.h delete mode 100644 pico-sdk/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_threadsafe_background.h delete mode 100644 pico-sdk/src/rp2_common/pico_cyw43_driver/CMakeLists.txt delete mode 100644 pico-sdk/src/rp2_common/pico_cyw43_driver/btstack_chipset_cyw43.c delete mode 100644 pico-sdk/src/rp2_common/pico_cyw43_driver/btstack_cyw43.c delete mode 100644 pico-sdk/src/rp2_common/pico_cyw43_driver/btstack_hci_transport_cyw43.c delete mode 100644 pico-sdk/src/rp2_common/pico_cyw43_driver/cybt_shared_bus/CMakeLists.txt delete mode 100644 pico-sdk/src/rp2_common/pico_cyw43_driver/cybt_shared_bus/cybt_shared_bus.c delete mode 100644 pico-sdk/src/rp2_common/pico_cyw43_driver/cybt_shared_bus/cybt_shared_bus_driver.c delete mode 100644 pico-sdk/src/rp2_common/pico_cyw43_driver/cybt_shared_bus/cybt_shared_bus_driver.h delete mode 100644 pico-sdk/src/rp2_common/pico_cyw43_driver/cyw43_bus_pio_spi.c delete mode 100644 pico-sdk/src/rp2_common/pico_cyw43_driver/cyw43_bus_pio_spi.pio delete mode 100644 pico-sdk/src/rp2_common/pico_cyw43_driver/cyw43_driver.c delete mode 100644 pico-sdk/src/rp2_common/pico_cyw43_driver/include/cyw43_configport.h delete mode 100644 pico-sdk/src/rp2_common/pico_cyw43_driver/include/pico/btstack_chipset_cyw43.h delete mode 100644 pico-sdk/src/rp2_common/pico_cyw43_driver/include/pico/btstack_cyw43.h delete mode 100644 pico-sdk/src/rp2_common/pico_cyw43_driver/include/pico/btstack_hci_transport_cyw43.h delete mode 100644 pico-sdk/src/rp2_common/pico_cyw43_driver/include/pico/cyw43_driver.h delete mode 100644 pico-sdk/src/rp2_common/pico_lwip/CMakeLists.txt delete mode 100644 pico-sdk/src/rp2_common/pico_lwip/doc.h delete mode 100644 pico-sdk/src/rp2_common/pico_lwip/include/arch/cc.h delete mode 100644 pico-sdk/src/rp2_common/pico_lwip/include/pico/lwip_freertos.h delete mode 100644 pico-sdk/src/rp2_common/pico_lwip/include/pico/lwip_nosys.h delete mode 100644 pico-sdk/src/rp2_common/pico_lwip/lwip_freertos.c delete mode 100644 pico-sdk/src/rp2_common/pico_lwip/lwip_nosys.c delete mode 100644 pico-sdk/src/rp2_common/pico_mbedtls/CMakeLists.txt delete mode 100644 pico-sdk/src/rp2_common/pico_mbedtls/pico_mbedtls.c create mode 100644 src/constants.c create mode 100644 src/hid_report.c rename src/{ => include}/hid_parser.h (51%) create mode 100644 src/include/hid_report.h rename src/{ => include}/main.h (51%) create mode 100644 src/include/protocol.h rename src/{ => include}/tusb_config.h (97%) rename src/{ => include}/usb_descriptors.h (65%) rename src/{ => include}/user_config.h (91%) create mode 100644 src/protocol.c create mode 100644 src/ramdisk.c create mode 100644 src/tasks.c create mode 100644 tools/crc32.py create mode 100644 webconfig/Makefile create mode 100644 webconfig/__pycache__/form.cpython-311.pyc create mode 100644 webconfig/config-unpacked.htm create mode 100644 webconfig/config.htm create mode 100755 webconfig/form.py create mode 100755 webconfig/render.py create mode 100644 webconfig/templates/form.html create mode 100644 webconfig/templates/main.html create mode 100644 webconfig/templates/packer.j2 create mode 100644 webconfig/templates/script.js create mode 100644 webconfig/templates/style.css diff --git a/CMakeLists.txt b/CMakeLists.txt index ace19ee..cca9686 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,14 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.6) + +set(VERSION_MAJOR 00) +set(VERSION_MINOR 139) set(PICO_SDK_FETCH_FROM_GIT off) set(PICO_BOARD=pico) set(PICO_SDK_PATH ${CMAKE_CURRENT_LIST_DIR}/pico-sdk) include(pico_sdk_import.cmake) -set(CMAKE_C_FLAGS "-Ofast -Wall -mcpu=cortex-m0plus -mtune=cortex-m0plus -funroll-loops") +set(CMAKE_C_FLAGS "-Ofast -Wall -mcpu=cortex-m0plus -mtune=cortex-m0plus -fstack-usage") set(PICO_COPY_TO_RAM 1) @@ -36,22 +39,27 @@ target_include_directories(Pico-PIO-USB PRIVATE ${PICO_PIO_USB_DIR}) set(COMMON_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/usb_descriptors.c ${CMAKE_CURRENT_LIST_DIR}/src/defaults.c + ${CMAKE_CURRENT_LIST_DIR}/src/constants.c + ${CMAKE_CURRENT_LIST_DIR}/src/protocol.c ${CMAKE_CURRENT_LIST_DIR}/src/hid_parser.c + ${CMAKE_CURRENT_LIST_DIR}/src/hid_report.c ${CMAKE_CURRENT_LIST_DIR}/src/utils.c ${CMAKE_CURRENT_LIST_DIR}/src/handlers.c ${CMAKE_CURRENT_LIST_DIR}/src/setup.c ${CMAKE_CURRENT_LIST_DIR}/src/keyboard.c ${CMAKE_CURRENT_LIST_DIR}/src/mouse.c + ${CMAKE_CURRENT_LIST_DIR}/src/tasks.c ${CMAKE_CURRENT_LIST_DIR}/src/led.c ${CMAKE_CURRENT_LIST_DIR}/src/uart.c ${CMAKE_CURRENT_LIST_DIR}/src/usb.c ${CMAKE_CURRENT_LIST_DIR}/src/main.c + ${CMAKE_CURRENT_LIST_DIR}/src/ramdisk.c ${PICO_TINYUSB_PATH}/src/portable/raspberrypi/pio_usb/dcd_pio_usb.c ${PICO_TINYUSB_PATH}/src/portable/raspberrypi/pio_usb/hcd_pio_usb.c ) set(COMMON_INCLUDES - ${CMAKE_CURRENT_LIST_DIR}/src + ${CMAKE_CURRENT_LIST_DIR}/src/include ${PICO_PIO_USB_DIR}/src ) @@ -61,6 +69,7 @@ set(COMMON_LINK_LIBRARIES hardware_uart hardware_gpio hardware_pio + hardware_dma tinyusb_device tinyusb_host @@ -68,30 +77,45 @@ set(COMMON_LINK_LIBRARIES Pico-PIO-USB ) -# Pico A - Keyboard (board_role = 0) -# B - Mouse (board_role = 1) +set(binary deskhop) -set(binaries board_A board_B) +set(DISK_ASM "${CMAKE_CURRENT_LIST_DIR}/disk/disk.S") +set(DISK_BIN "${CMAKE_CURRENT_LIST_DIR}/disk/disk.img") -foreach(board_role RANGE 0 1) - list (GET binaries ${board_role} binary) +set_property(SOURCE ${DISK_ASM} APPEND PROPERTY COMPILE_OPTIONS "-x" "assembler-with-cpp") - add_executable(${binary}) +add_executable(${binary} ${DISK_ASM}) - target_sources(${binary} PUBLIC ${COMMON_SOURCES}) - target_compile_definitions(${binary} PRIVATE BOARD_ROLE=${board_role} PIO_USB_USE_TINYUSB=1 PIO_USB_DP_PIN_DEFAULT=14) +target_sources(${binary} PUBLIC ${COMMON_SOURCES}) +target_compile_definitions(${binary} + PRIVATE + PIO_USB_USE_TINYUSB=1 + PIO_USB_DP_PIN_DEFAULT=14 + # Uncomment to enable debug uart: + # DH_DEBUG=1 + __disk_file_path__="${DISK_BIN}" +) + +target_include_directories(${binary} PUBLIC ${COMMON_INCLUDES}) +target_link_libraries(${binary} PUBLIC ${COMMON_LINK_LIBRARIES}) - target_include_directories(${binary} PUBLIC ${COMMON_INCLUDES}) - target_link_libraries(${binary} PUBLIC ${COMMON_LINK_LIBRARIES}) +pico_enable_stdio_usb(${binary} 0) +pico_enable_stdio_uart(${binary} 0) - pico_enable_stdio_usb(${binary} 0) - pico_add_extra_outputs(${binary}) +pico_add_extra_outputs(${binary}) - target_link_options(${binary} PRIVATE - -Xlinker - --print-memory-usage - ) +add_custom_command( + TARGET ${binary} POST_BUILD + COMMAND python3 ${CMAKE_SOURCE_DIR}/tools/crc32.py ${binary}.bin ${binary}.crc ${VERSION_MAJOR}${VERSION_MINOR} + COMMAND ${CMAKE_OBJCOPY} --update-section .section_metadata=${binary}.crc ${binary}.elf + COMMAND ${CMAKE_OBJCOPY} -O binary ${binary}.elf ${binary}.bin + COMMAND ${CMAKE_BINARY_DIR}/elf2uf2/elf2uf2 ${binary}.elf ${binary}.uf2 + COMMENT "Update CRC32 section to match the actual binary" +) - pico_set_linker_script(${binary} ${CMAKE_SOURCE_DIR}/memory_map.ld) +target_link_options(${binary} PRIVATE + -Xlinker + --print-memory-usage +) -endforeach() +pico_set_linker_script(${binary} ${CMAKE_SOURCE_DIR}/memory_map.ld) diff --git a/binaries/board_A.uf2 b/binaries/board_A.uf2 deleted file mode 100644 index 234d2ca0fade1ba5b11fa367e8aa63babc4ea2c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 159744 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%0Dx@DJwyp5^d=HUR%%iwN*XO}5b@_6~KD z`LZ(cKCazDi(@S)eIzRZJ3RSxo-2JN0`HwE7L?M(PP71}z#cOG;YbEcJFUg%ea?r^ z|KcE$knRGs@A(3Pbivot^f`F-o}6Y<1i)Ue9hp*T!t^1MMdI* zF7J!Iie^!fpg@q(<=xZU3H<{h{S1?aAPqo6f;R}5lcv#l^y9uitnjbm@W)P12iE^5 zj$*&4UEbBbyvoA`lgcxcJLqELZF$1@gHt!7CG>%LH{HswCbS3X1_PNuO#Cq#Y`4D7 zZ-c0--{!XMGmJnA(5TI7Bes_LZAe1e%9%-woM+R##rT+5Dnc-tnkW({JVz|1Q^03u z3R*@-Q!o}`M{bQPk0w%;G-|w1A>4RYJLu5TO^EOmE|1b^qfamUS3r}cMZU#-X zcTF?FzGM@6W+st!{k*yV{IJ5mn!~?30RR78`%L>kv>&wpe{J7aT>p6<0k`)3aBfE&cYHS-f75@w9FEna zxO_7lFYG^_kIVNT&%x#UkEg-0q#wi29r+#5H{iPizMJ990J^&z6Fc^qmVr+g9INbK zU|)>a_zmz>(%>d7j zrwtTgpd+Z!=Gio1m_U`qsmzgve>?c~yj?`ygGx8L!*nx3_1|QFIzGjmYrb*3|4(uH z@6V~e;W-DMGJHCIN_)?Ld&(gU|6uxm8;3u3dN4r${m)Q3McZ5;;Da%RN7TRkJYyUR zIULJ;quUI9A`%rLe)TNVSLj_;m8p;p21(1O8K@@MvXSOho+1hG{r2hjb;Jr&C{@Q> zTzHppQC_Y&E~$dP$Jk6wF$uvlWR=UAv`2TGo+lJ)Hqb{$Rhs&|l3p?RjwbY@k_d?6 zRsxgmdGr$SMkgE>VHVyZH{WFVpX3cI{I_%X zZx6u#f7(v}Sx$IM1hlZ+EF6>FS`2=k=Fy7@;W&c2$)GY19E~vlzaA5SX7Vi~;7%&@ z2)NfLPX2CQ;OcA$-_L^R5BCTG0)4$C*=9yaf` zT(G=p)|u~7{(^2oHk~bj9Vb#PB*sx=L_|VFaiV#PA<;YrZ7|z(vRFyRGlo%`7Y#cI z0d-0h1SX=1<^(g(>}j5g?p1ox`%t?r=}gXA-gt9o(puib=2J=EjGN5IclZqGIs!gm zL8cLsU0`LXXjU}A4y1+$`wmOH6rh`^EQHuGno2FdxPn(V!={}{v@V6+9*A5K1}R?g z44(5^nv*+=VDcinWV)ECKJDQZLNdCr%bV71AVI=0gg=AuujlZu55QkpVt}?KK{}`H z&I7v)B1y9$vPh#@VclnrFdOM5bMD9pD_FcStWf)uaN_F5LLvCuWDjA^q7 zyRy&*sQVMZxgY+{ia?1#^y zJX4-|vSxDD7HSB?KY0Cb;P7t{8Jnt6sI zqSBb`q-Y!Ym}(XAyy!30{T4Mvh%{7~Im|WU#&#~_+F1$_U_%S|XO~HmB;L{Y%XQlJeY$ehP3T`6|%6F~&LZlUW z_V)bh#%<;E`g}I6yjWhKXp$Gs5va`qvkq;x)KEQ+ppqT^a|r6P!7iK+ zu_&GdO%gto2EV0!oo-xqDeO;n!rs)-MIQ|w|2sJRcLd;%_vS_yi@;NRJ?+EMlu2&) zth0GGV44UxZtF~dy`L`czq;e6CKM*4$eU&6F7NN%I67})x28xteS}#%6^$LsCL=)l z9kMu;QNk>O;%#*7bh&FWnq&!5u}G=JI>CgcBA*zS%3QYZ1MQ4`#$TL!ql=*5Ig!B> zcI3Ts z41YZS1y3`MKV1ikEaJx-(9!6#mq!8 z8;Z})b3ag^VIC;9gEem)3I)FoJVxQNpiB#;YDF`ZxSnTRxu_VlIKV4(Hjnw)e#ltd zveo!g%c1BQXoHeyXhpU3V$)Ak4T(Wc=BQ>sqJ$1kEpb zcapz{w03lQk9PjEV}mi44ZBgc?;p)LI_F0n83iag)Il;xY&!L* zjUG{e7#r*{J?@Gq5L6myp#NwVybqp&J}9U|7J~`2N>U$UQSxcFkUnDps$>X%295tc z9R7RYod@RsKYL&Y9$NTYOUR)E`JegMR=k$FlDRb&ucHHNssN7RQvc@yti4afArdEa zYeGlYJ6no=R$gJkmu3Q+d$WT_mX+J+YI;3Iz)V-;0;xaT-{AgUx!B}? znIZab5dM2P{P)894&blP_=IT?e;Fz}#LMLAa?uDIZttL;zuGGRl`-z!Q}?)c7TpGD zegf-qI-vQHL+9RCZec3v^+p-Hl7ZQr9nl7!w81mPepfAwYl8K9kN6C9$U%!W7bGvA zb$`(=J9H1j%Ov0qAqPiTk*Br$gZ4`m(LqjsMA7b!r;4|t+irV~4mkiiJe&(w5pZBJ%o zuWS&llkmtxYQpessGNGn&4V6rtnHquIClLz9Wbj5;m;ua_i^~|3&0<}!}EK8Jkim@ zR53BUV2T@Yo4P;hxLAQwLmabce%+5s#4IrpsajUAt)?3wy295?4Xpla+8`1LjMF>X zPr(@cqPvg|IrNDo7ZDm5<0SHY*ga1B%0i$MzMR&sa##5^M8>-}bm+4LH5Zsh@poVP zyf^jc!QA{VvzdC=I*WdfsiEGpwz%iVvG%{bIbFE+yE^h1px4+H5P$x3`IfN4un0qt zL9YoX77!6sDqM*vLMg;}JI$3qiorkh!-e2fvKBUaufY838t#;miCW&~9z zA-FomRYc!~=BXlp-nz|CzxBAbSPT#eM$k}~FRqj%^IHY=>wt={bqT?re*{orhd~SH zifgB_t>uU4*{Dt_X*lht=`Mnv?ar?d9j;SkN9AbMrn%Y+v0Vm*i zZ6MF_a(ga{VDN0awyU{av*}N-+sBLh!?CA5sQ#|veDDcaLC^L3?|q`f>mA*BVHAu9;p;U0wm|@&V8-=cvm*?>)VO`h(VG9l>=dxsZLEQ7J#Ri#2Stpc?!@!#UxHiyd=mUAxcwQTI%mr#r(aDMEL~ihs{onT|ibqkQ2y9<1?r-FJ5; zwEyWn@8hAV9fAf9gYV!z7wn%|xs>yUl;eif$2X+jyCHS*hE&rHsaN|`4aN@~4eqIQ zfqMYs)i95A`uDK^1-sZXpA&0tiT7slYD1$aKM!pFA${XnmjKUttCZ8N`s>5 zf;7yMg0)3_N$pl zY-gke*%V^hL3Vf%p7{?4p}hoD#U*PS!_Tau+N?jozQceFcnHHkc>aHh!~dlK{H;yW z5)qPUNzjt-^Cp5GxJ`4(43QblAW75d!PXPTy3g8t5@f1aUY-y8XNl7!T3gdPkk87| zD7Y%P2dIDYb+c#PPvr;8wY(rILJg5XS|_P@+h1P?G$ace{;UBg#WZ$;+Auc8IgWYG zzBWzmgxHQnp)Q*yodFMj+5-9@1+iv)0}|*AW^E$UzD7*~f6+egM&EC4gND2EVykHC zM3e{gL!bB4Zkr|*{REO>Yzu&&vkv-*qA3#AARb66-hdrsE+71rF2;KWxD4zqf&UkY zc39zmfW!Yl0RC7*>ToyG?KfWnOFph`tWCKku}H^=(AUzl(#=K%wzmhm(Aum4yD*T~ zbS8kU51-W{zDAD@ZqlHeCJPu;2r4;sg*LAx#?FD(CtD?bPsJggrues6~ z$xcoWZ{5GCE?TIz*$v65&Uoe(=m*Kr4;qY_Y)sK;fBm7pvu@o=G+GFE;K3cRyda3R zX|&Kwsvs&Y?vq#h-UP&(+z8J5yr@f$Y#>SMh5f+8ZbQO3cLP|5iAx|t6};ah=pkCf zx@{Vy{S16ae-hA7h86w?Is6X>;O}WFiJuO>c>hV8s8dbR(__&jl?W(0MBc_!fu1DH z`+X=m0YA5n_W;$W)B^L5$ooKs_gdd|53n<7$^B;a zsC1FPJdxe!{j2L0dpqFvlRIr9%=}IH^yhxtuzNs{Uj>4sRI3Pyw0Bt%=;s~)s_|c4 zCGihZBc~&Bf@%|82=(6sPosd?LH?&nRXB7=^qJ^AiLNAJHTAd(hO_@E?(Ms71y%qy z2fX_wS~Jx5Mwe9gz1u>=%%Jb{9_;$UJ*@CQ#NmG^0Dp`FSkLs&m3#d9H8xs;+Ax0=;6q`^vvT z4m>sEX`0B`Lh~kUri&+RqEnc=l(#XOlO;pz09d_R?R>yElp$&EBp_0_#Y0yzXry5J>A?t9q z|42YM)_|OoWry(8~iWbd=NB+QG%J| zp=g8BnCc)>2-sb|?7qb8Wy%;OPeO@Ozi|*j8rThb)um1OC)kqy>E-v9BvAAsYORU~ zdyjh5Q{nTzcab(ys^IkAeKJ+YGh!cC^WXD> zG79*{vyQz?NumjSmd-ACM>XL-lzggxf0`ioxrvk$juM@bo;|$ue_rPBe>njEHm0P& zr!0r|5%jkYO0EKmACI&9pxvK_wz>z}bFX6(da?W>V0XEEi7AOUp2}5|;Fq5yH`-B( zm?g#vTjQ8#jXVc&6Or}29drR|y&vP@#o*^zEpm~^ip{YZxV&?sZ^7&GeD9yxK}pBrp#)cP8ZV?tV9x#OJqTAWis-5 zKk0(;Z^yfldUp}cy+Z=jnd&ycypl()QC8oLWg8kdJ{JM2$;hk2YW%;#;r~hi{%Uu7 z`QGx;Aos;B90_>fwkZQNB+znlcQHf+{sQPFye4c6*#EIMsey0U$1G#kAKeW500&{W zZz`FvkkD%vZZ>Kmt%DhYchm~nR?h$hnSdJY$=Rp|X@OGEc)bsIkvfPDiS_~t+k1Zi znm`J>hXUw%4R%Qe0(lfce&+^X5#zZ3zw|!?#*5Ejj}K092>JjteWDy1rWm+C?B9Wa z$lf!J0*|XV0d}Xq4Mn4aV2_a!OU?_l8iq&+qcm2Y3ng4}qXaz?cp`@v{;zWQzZ!u5 zC1_V`lME#GB2;K3p-tZH%>o@N+_HiTIL*6?TQ(E400id+x(ueC%CP0`iagydY6QQxvDdW(I@I`BW`tcRlpV^8bF3er+4xKlQpI<3^B ztKRSWmTw{w9#GfY_fB9H!Ey_l?!)NYJc(zlPz-ME75;l>W7Q@pGRqXM54Z{B@hyT$4{9gfdp0igN zQ(BxcPubrxN}X-xUr0p`P}lLdFuyk!bvAD`#x!ZouJr-efXA9=TRT=1%>Z;e; zM`XVUy<;^xXmnq=4C7Pb$LV?xdaQ?B=}&3#oc<8Vrd*aK>va9tzu`ZNd%jGljC6QFpYImdIF&ac*&^^5dw9BJ%LA(go+o;2WG3^v<*y2!L4!b|zs>6{ z?+!ldY0jC(J2DBpdhkEyzZXXGfP1u_ORse{A+xgth4wxu4AL42 zJcFZ~w5*0F7=fQ)2!95Re>;aic6vE5|D*JOIJSaSwV1KG|1c3G3$oGYX@X1!dJoU* z0`RQ?GdtK<^H>sS>~$awAaua{VQuPM94`p3>Ja6_uTRa%b0$s~qDhQzbf7kI4(Jd^ z-sJUu<-@inLB6bM;|c_3J=AFTWfo=?g_yIZp;Q42(mkO4GK!UEq}7)ojn=F$Ig;ti zBw?PJ3DJR++Vp~}-jb_dk$E7c+GCmveGi|bu96_l^=ah#tKOU881FTX(9a!r)ths5 z&y*h>WP!R6#UtH1kThk2HsXG;BA`HXbtr!Z;r}{^|LXzxD=m4jpU-21Al6eRLyWs@ zpQ+ZNy+-kcea4@h4be5QbH5H1n*QxhMmJl%-U!%HsRd5a>z&(0kk7d>?vKLv0Q8xo z@Wp)xx9Oz$1bH0#67EL>{pt$v4|rY{j21ZOYK;`HaK=KQ2tjAB$KUsGSyW)n{{!}U zh}?JWKvSef`Af=5_A8a;ABeAnV!a(}^IB3;EHRvw8c=pdtc6lGC{vI^<@5g5>jC=Y z->{b8d*WyLKh=Yub*O#@;eU+7{}{aR!1(|BcKmBlJ90KzLReSdx7X3@j|$S{&b7K= z!$LGyH;;;#9n_9p9Y(o?LnY4 z=JmF8qU0Z8cW!Zc6}9-jW##3~BueY|d6L`#Cw`h7Y>SBV$s!*TT4ki9zbD5v7^(%jEsWRwIv#Fqh z=!K`0K~{<>whmS7@p}!zzlp;iJ3Sh}e?7C=dWty%RFkMUnhCPbV+yPdkavNB@Ytch zU2DHUJN=rYCyOPOCkq-XNWmHRo{f%r)C zhJM=tZ5zYzlCA7A*fDwpxW${9b+lmX#PU*FSp7&jia$z=YD6yFbLM8ZnS@}Y*5De$ z2H?$@S5_6E#NbNMUK_%nLHNJH;r|A_^8o$_=>%k=gK8sDaD%k6k`8wn z>DN&mXnL*lYkKiuu9vQ?$k#DM&R_O<>J0QGcz!zTdfoVgQ`~Six|ZGoel^yi#ik$J ze9*&W+~wdW3^^Wj9|wK>``l}F6UvV>!3LB(1w1$+_665lmU7VTOuxFsJ&%5uX%Us5 zS0pT;-)Gj8r%~@)bD5jUmDYvU1}jRt-SL-q2`v4c-rlYSw37MDTjp!i6rfJ;A6@fl z1*FYBh#U@g{Qxu;6uO_88O%=VODooC8s{cFEnOMz+;WKV4CyrYqvhba%8?BDA9W?T zjd5Sv1?jgthcNtu$N!rg{%;20KR00xT}qXyBFis|Z~byUDpi%54$z-U_dtYXLaQIQ ztQ^nD19LMPy_=7Yw9Hn}?>pg|tv+m=bm5igmr2DGg1fr41S+s0-RvMgQgX^=F`do$>F*UNTXn4H-;Yi%liuK?;=NOhgcI)5>ddf zLX}uEgwXJ6iLKBnJfC&ucZ3ujhDf!GREN=JC5&lyihGTl&}wbtVpxbCh&A! z6~SCz(UAwwDHb9$@QnC*@v{S0Q!GI$2EWTt{S2D_PjL93fEON^|9!A4=kqH1u>1tm z@&o=_DMkQ$e`s{1!v>O|FgB4vV@9y|#eHlSq>7w#lcrNQPyJDa*I)jZlcuhE42=aX zpTt1#tK$nlhS~j(FUR@MdK`ou-~PLtof@^+)&OmDtNJ%(d3Z%>N?qz2l>(*0}LA zvo(bjn!8B|v%5)10ZgHzxa@=>$%Z5pxex+uh~NY)8}RA`yt)ueC|*NC38909Zd{3i zh@g~}CV=;DC>a&KMnQK|$iB~++2A6#yuZ)y{fq49Gs!v6dCrvcJ@q-yc^=XJ z-HfE*Bjg~_}d2pJDn(aM>Nu&DhK~g51@5mH462I zvd`kVV`{~P(c|X*1tmT3Z?pquM}H4E|EqqoX&ya1;}c!C(id z!F|+|9dbQV@&yb9HED0h*C0ol_vi1HjAnRa5bPaJ1zm-GjvNLKd^tj^Lu!6<#1}pu zt!3vq`u};K?R%S|xl7@HNXGw=AO2MxZLl660_&oMbf27x=^^W|4_<@Rs$RdXMDK`C zL#xtf8Bj-`@1b1{EKGw|wdA%kdMn>EqgSqjN#T?UA;K`wA#MT7-C7vee+C)BO<@G+ zUpGU2y3+om7z#Fi2|kS*X;0&BEPGibV9o!Ti0crpOPyC>K9t>x}zHL02vl=}&^$2q!w-x%DZWlg*Q>o;DfO=xE%KjMThWR0$ zN34c1MI(g8wR0S|pKizVL;hT&d^WL5;s240|3`lK%UZeM@gue__aQcLRe!Jr@lm64 z-+~uN!xRm1eyTRX97^rYQttVhd-H`?=}`J>`wg}0!b+HHD~vm>s4^TpgH%AvQ|?~R zL{KW2aZQ2IeUV2?ZOh!8tK1b{RsrMtFfI^A_m5#+{weQ>-IxuUmxh6gz#ZCE0c&o% zAM2(k9*dwarQUS;IjuqqnYQ?k28F~o3;8j9rhgBo zmJVO3HLff8976A3=~DO~mhnIAhksd|9Yqx=W3Rv&tv`#Csp9AiI#LI+->jbH0vF|2 z!v$(K*`rH)hN|hy_UICCsdmrIk}am$4iu}6gY6zi7$6zABVaYi|2?y_0KHWqm7#0A z?u)m_iEprPaV62GoXU9Az=)G$N#i(rX7<#~$MqviwQ!Cf8Ag`;=1yu??ym9GoxJoH zOWWO-y%RH(yLDwLD1Q3pGF)>EP;-zFBL>8DH(JMKWfGNWr93l)Imaq;wbyO#=p~Mk z#tzgFCQgodf;Q)-=L}n#qCe$WwDk7UpvB`Bk9t0EZ^(iQ!z0;RML-3vgIx;$k7fKn z_QN0MT&vpMZ+NXtU|f>D+uA|7+FkJ|xH4od#*n*xqsb9BFt-jpU%m%4y00}QXKD9o zE0OV*BXEz?feavZPi+U?Y_R!fip?Kc>g_X41R{oi=~RNI&|9v_ zR0iEQn}BWO{PI5BSGpf{>NWe|6y6)IsNAUR51H*Cn~%&;Rm|mxxS&LZkih+mdr~)- zQ^Lv5x0}?PQxF%q1T>4h?g$U)ZpWkg4Na4n2a%Bj+y60$TFPkFq<~*~Jm=pJYZE-a z;B^U}`{Ol$LyZVL*1&(VQ$F3L@INBsf5Z=eyoQ^?yoJ*AYIYqefxUo0(5}s4!#FE5 z0t7j=pnE-6n8-x<_68`l!a`r(X1P}XN`$KVJ<#8%(XRyQ&oxn&?4yPWO_PnvSPT6{ z_7~Q-b)!lnxPkPjlG8aaE*io<8nqUr20yso0U1)W>!XNm=%lrmejjq{GMGbP4PsDJ znBiE?P0r3Or*BGfoFuRT@bvBYrhUeP*70aYfhzVF=&7itolVG)FXq6Rl84byq~7qO z9@o!85yb`WeJ>aHNl>%b2CQj!zYksqf=%yaB-*t*LqJa~cqOiz%iBAyTNzDFm%{(3 zjQ>$T{Bil=GeAgK=i1Hjx|2=~Xl(u*5op$0vUwu$5uQvO1UfwlyfQJGS0(n~)rnfr z2LDtyevub6_%2;1(k3mqlh&(LjTksg7ysdd-$Fr*1LFIcVy98%VhAo39bIJQk-m}=|&@!Q3VStm_mztY!-Xh&hpiQh-O0*`42KNKn_FiiFQ_^xpbPpthJ0k+tW}=Kc~|e@Ee_O3Nt2U9OqVnq}j}&c$07+!+-Dk|5F+N zPyO&8$EZM(>bVx5gJ8CN>l*Bcz&=UywL_>u|2lH((SvV+KG!=<7}ta7Ily%XPa58C zI*3l|H%sZec!gmz^wLr7_2NPFr4Rj`ywb1=a&_KiH?K0RXi~(EyH3Vhum66xoxIxM zXgX|hUeXwz1-spNeC`~Z@z{jNa1=mD@;i*x|7#fozUTe=yQlphlkqW1NulV>DFq=Q3+R+E%xF=fTJb4HX^60DhNRf>j_$z18@Auv<6D%`+xXrY zy+Oi|3Nye?34?_RV>{eO;RMJ{uvy?uz!HXcJ%5xiydn}=VIW~R%KTBruviVI$o=FD z_bc}JyY9jNxQzdCKm4(-+)1Vg_S&`J1q^1|4Hcj*Hq|T_X9+P+R6Nxt5 zM=JmtbCKwS`5yGqW&-V@i=!o4tdBMwXsH($OSDKIZ5q(xFMcA?!u{?SebE~qAcXy8 zhh_TN#VAm>8ZFX8PoJa{!l>lj4(=72<2qm-880aNhu8N9OJyOxo!~%M^_Vv<yW*SpGom)(3!(Nd>PG$(QGpy|-h>h0eM!FD+5by@KfEg*2kdpnxR+o*qPw8t z94xU1Y`K;O8!6Q8KG}v(0iWxDwWys!u_bd#6wM30N;r7{7DnN80!j)N5U0*e?;ArH z`vmJJ=n6Ta3RMwl9qyjqE`|R|8UK@h_>;9^hri9h<$HoFuSEkQ1U1*qI=^oip$92Y zaCUIkc8=V$+>R=dfv5c8$dx4ahhj~B{V6&IB|rSz%35v4;21$j{4gI>nfz?l&ut=_3XQ=^xUR>aM;^voP( zPN9|Wko(A$wUT6M7`o+XcTZ}2*d8(te1xsht`1n`b@y+f;hrgC= z(GmRu7gF>CfQGkUQS^q<3X{gnGLKn$Wtz--n%sj{zUnw3p@G|5h=hKqRYJcCqkr8K z0BDeVgQZn>yZd%)GPKA*Xp!;ur|t1*V968ECTlxxz<4ZUqC|f_2K&p8jp>0QGv3=FVdpg3{8?7r10o!oBh2Qgyh}6G z4=9TJ_IJkl00(R4+`iq3a%L5CJpFO75Lm|Q zN@tWFS{RCkkZQWGS#L=bNJB7+g3ts(X$VD_Hb&6;Xo!Ex73QPP;IW>t!ZSM?0?ryNU>NVGQ!PTnym0rP?zrG-#z$$ zCgcB^AO5%`xA;&H0foM;p;o{FsaI*M0@il8-QGi@jZvnKmU>!CIT1ns)PQYBD%vtuyD9~AbVXO6a=HR!{|&3R8J41=^!Z znq?LPib@B$$Kv?pPA_w*1&=Z6)xjHaKciU1ThMQLPEt1b0~rZGTM%_>IK9RahOC5rXdVef+xz z|IcOoKlj7G-TjtyzF(dL^h6U%yP*KEtFWaM#di}LONoTQ`p_h8Lmc;(egUUSQV73r zJYA+CKx@-}81`qNr%!;@n44Ex-{4da4~P z@abHz=nrMz?k;X=k$6V;)mj6Hz&>YnU|Y_FeSE^W?wg?WqrhiW6Aw+wOj-MV;B-uFj4}`)uFY`WJS%dw9Q--zQO? zqg;^syd35}hX3C6-)R~D(|-7O+H+8&UT|6piHLoJ3Ac>lNfSK=HLP=GGE21K zmL9@m4AFlCPxgO|pT#`pR3zr}%Kj60RsSqr-QU8Kh)BC=?qzuxy!vXB7c&Rbv99Pk z7u1H>Ek@-Iu-djz+0{!}#xk)5(TCwlUdeW}y8B;9LwyZ-U+a>B4j- zsZXnJW_l?%!-b{B^q7hDB*M@5LV(j@K)Gy=wfs%!Quv>d@jv5-KkUyzSu~itSw8VA zmqWtu^g02fbIFOz?j1j#oqjxD@Y~?r1X&I2X)o9XnTXLw>6J370w3i%zz!X644+|XOx2%>c=vr98rNP2(aAI z;XVxK{LY9`NI3}4`!Ril9@F1#`$U|^Xw%lg>9d3BirCq{dFF)^h{}Y^UN_155=&xj zKQ6X(G5CY5Fib62Y#9gtyA=Lk$oPNZhkqu*C6UY58|2UgD3hfU?NNTeFZ3r!m1$G? zc3)^3@O>unP3O1yLNg(UOe^G1`a(s#{qR(7C)nZx(OxVbC(eR(7?tonnxKD!>rT|@ zCY1~Ti<39_AJA-lBC%eVU;3;2mA0$mO2BBRWv@UOh}bKl(cn$N@m(ZR|T@xD;D21Q-HhcZeZUY@HBj2#nn-21~KELXaUSwnjV-a`_S? zqDGSlK?i{f;cWiTa4lAXhZopu-wIa>$Xe}n6Rk>wr7#RX-ug0&+h8KJK?ds2jDlAz zW8r@eS~q>MC4;YzD}f!}S3pIu!ths#md)>vdsm{3gE}bFa`<*1Ef@IyF7f5@+v0wf z_{KvWlxY+AlM>Bqc*Qb-Z{lunC}tOEu_}4A3h!}X+Y~>Pb}9V7l=1)45C46oG2|fn zS8)cn#!?4u^m}M`GYZyN-h*~`O*F=Nhpd~v((*2}x^JX5htuAHHusg-HwUNegBHg_ z>%;WD(B3{5BPIGCXl=(uy+q#)ZS7<60g1i~YT5zt6p#6LLOpYeJtX=LsAYSg{!J^m zW!MxkKYGpR`IaUw+>BywaJ#sm6f|(V@HX`87%yD+{#U(guERFXa+#xbOIS6U$gQXi z1ncr-!%^7vvj~$)A1F1E>$3-UE&Xp^#-I1Y-^i5|kEC~j*550x3geanu<;7km@15U z$XI}>@4D(K3&QjPe1)+>qNecssU;F^5MS@34dvT?v=P9^OMDOW+o(?^J~O}HN2B?Z zJ{rRZ*ZyuR&S~_fZYelmIU^`wrdh!-u0LlO3qG`bCMdy9w1XMtnxBQ!PYNn2eTeI6 zDgBtBmeL2ho{-Xy3K}Ur&Q&a>9~QJydbDefl>VU*Af@+q6-nv1KHaasd)9w~j6a_A zcFKP~5CWz0331t^^4Tv0N#&z<*|-BRy9aL!QaT#&Hdti-GoMzE5+=eKpa$@vrzMO! zL`}^86Q|r~;wEH%;84Gl;%JuE@WapoN1NMon>0Rb&j!h@c1_^wpvn&Smu+R@N~mY} z-d522$5`{Ck@AAwAg~$Aj}=Mjc_&5i&CNe4g3UnBSmr&Ly)~3pmv*2aQb9NC3F7er z&?W%y4@2N|p8_=bKenbBln_#X z8uc7ERTo6en2usz0S}7b>Qtp@fKmYcPM9a5m{yVISSC1=+)Fqtjxxt_sg=j0Hn;lr zKrE>P&v2Smct5Da{Y|S9U4gM0UAY{KfIEgWLzkMh##W(EWM4YPA+5 zzC-QGRceqAZfn*02pO^7J?}vXpI>_C2pYp=GTYwe33sGT%7A-x|; zcb`fZp7BqHTJ#p&X|2nCxPseZA^ESkeU^g)ibI9~S7qGF={qe7zKZHyu+QS`TJoO@ zGX59*@F(I31D58kb^RDXfF@YED;q{sE&nUpr%xvGlDmTqElN5&Pf%aM+5qFY&!XgG zVEvs3v%)n38Hd*f4X}2@aTToCyhCsqBmOl}5vMSGDGtk!^9%)hA27ecl?i7~$Wn0)ss76ak}46llm?>>DYy-T(KuVnnc^1~nFQ6vV&1sWEKQJIq^ zY$K)rh@T+_#RVCj7UjAS=Bo?0+~Hn=%7b}fj<1i$WoQ8z01ek!xX!^f3uFOBaQy<; zO}LhTT%ZiD?jRTF0oOLTw!@VQGJ-ToPOx~K5+#9GuZLm%u)AP8mJpbxpoD_0mONNx zOhn&8|A3`V%MebT18b1QXdZ-+P~yU?5X^~$=Uwfl8ni>|OI~$V7*0$1kWlH`W)yt< z8==qm6z!DwyA=Lk%lLookH71r38wT?S-b~5rcIhpaXAj4CDW+={gu)V^B4E07xnp^l< zVSuzrGMd$bMY_9EN?#}4eLuAveLu;EG4v%Pq$j8(j`H^X6f)HtA$5`ud47qQ0{UGF z&?m*ucn&|q&~Ad%Nxu5PKPM(gZ=sPm=CrrN3>MwTzkB9?^)mkT@Xej;zY0S&N}S|t zCHxa2C6%&PDxpU)4%9Xyq)zg+9llW1Nk|1q9O>=bz>fBQ3Q{NeS`+zIq1hK8^I0Ac@1+eliA8SK%3t`1+I8Fz?5<#_BX0J$^ z)-$_AMeJ_oZBYsIH$)ZCw~A_@ZxS^?UoUEbUM&UyeYF?}^h#+hfY5G08wB)(bv{@8y>d_^GN|3%_^4DiIO)G6S5^cbvSl&+P=>Vk6E(IhC;znT9O z_0r8{RyyFsF|l4BU7DCd?nM=XV}WJH=q`o-B^m!qe)wZe47`drwe+_?!NgjLA@K;- z(^VQcmEF>DR+7y1DR|AYMIfnq(HZL=dC5RC@HPe?_HiCrqw5ZF%CB8vW zQ76RS892v!kjx$tdyd2WHG+mZD0Y|l*MUU#Juyt;uNDHRy<&*OzZN8sZ;Qba{~95P z+A6Q%LH>hfLH|o`)Le(9{R~){I11y%GLY=`1)sld?hicZ!5kiYe6{i*?TcH=#BzmE zyETcL_<)U}m7!e<|I0G|m;LZB;XmNvfT z$!(TEu;lXu^8vax^*gcVNu1s$n2%f+i6QENzra|G%lNQY0}`R5OgC#tR#;t?CG1=T zf*oRAPQuX_g42&HMrH<7t%?LQ3kE2BCJi zrd~u8`VqMvh5t7p-r1gyf{#A10g2)zsb8!6OYb}`ydT#7qqB4>?AMdB5!83whfKK{ zHRs(nvZJiLZgh0{=$+i(&>~&EVx;vam}e|zJfs5r*y+p)l)>m|1{u-1L5h zb4wrOW)V z>^DmP<6BbUyeNf8@GrlgBMdI*Ew}yluMqkcLi29datgyy2t9lICWPuC1b#?hwFzDa z9{s)NAs%US-n46SHCZa_NnD>0l?P&~cf2pUy#2!n$_AGwl%S5o)m*1WkB*xR-oRe2 zO@SwS-FaTpq@r#t_|4s=@c*Zb|3CfkAKlRkK3nrLRZ*?(;0_J^eWAl_0FNpr+XTGl z(&~=tAfko=-F^F2qo8VacY_>HbR2V9C_2+LDIQXqZ{vKkIx_CchkW@i`SSgIyVacs z`Ld`XzI^AUd`w6BUHPuvmY;XTm+u1P8wB~{qnOx)%%VqI-3O$6i5=N@K^9!_f2g%~I6RU;_pdx8=M!g z!}!zrd;<0&UPAb;VJOggMz@`7z=DMB)?2^Pr>62UqX%XBK(3Ffo1*s2kuUbYy=ztVrAk-_> zD}{3)T;dB)hVU1@@C<&LaV@&2$2sTlTOs@roE6<*d5Z4~6kf>ZmVqAbKg-dBpY#84 zOkW^B?UXQ+dmS_dFM{9sPvLI=a1^9YUx4p%RQMC592UNY93S#uIUZfm`8EfnlsYNJ z=}YN+yILu2uavgSm)7~VJEXKXg)W8v6&e34e)!`O-R3KsPV}}&WwTK#w;Cz!cQmV| zwAE5trIhwNx)nn6;p7|`cO}1$>zD(ka>kJDsM8=F(Hj|cjpZmIjxQK=wLBJ~Bm;z8 zR0-Pvmv0--sws9C?mhuftV6&9MKJJ)`z1Pjmc2UyA=LcW&E%D z;UCRKp<2+_H=%;GXs*^W9zqAC&~ym(O7lu#Bow<$sN_!UIf#96C-$;ynQ0T+uE+VY z^mVHgI|Si3T~XW?%V}W-S7$jTTrNlbPw>&D*jjf~snK!-5FB9Cl@Q4n=hxd~N9X{_ zzjSyGWA68bf0Uzv?@8P@;BTts+dJ>LSGwOWiQ{dF|{@48QuY>)+l_D-{kcx{KoBdKry$dCE2XEN1#h4(z%*Xkn#JO4G z+yI<+>H=Ttb6Gx$qu&+p= zf@B4^5q1_SbU+H_@?8r5>oWe={qUzyuVk-O7E_>9N_^o$2!G)VBVnFv8C8Z{I$U;0 zc-FNQV%1D~`hJKFXp~F$G=>qj3!B{fUl0W*2oiJXR z|A>gWK9K6%eu?WR_&ZI#Pq-na)JiE`3V%_?U-ZL&uT(Nuq>_1C_=jJ~$SD_mDT0); zK`OI#Ql3*%N|kV2O4%r-)JQ3~g-=tj5KQhK$NzFSJK1bu#-g7{ZN+-o3V ziHQ3R7*8NK*7{j1h9eUC{IYgLT>{%sQ(|y`j}ji`y|Idzq$~|*jsBCFFecN=?mFOQ1rwuiHI#*!sZ+;y8GA%N2qM0luVL&8oHG~F=_*Itl)9;B8Wc}I1}BRR$wjy zx&E<&eiBasN}F~*N;!R|`FSYkk%5n+Tho-OMR1=>m+EPUv)QMc3tl_X7LWnp@fy^C zK%cieA819fL|io!kW0FV!H;PJ_&7@|pMWe3Sp9H+>cO;M1$q3i1NOu49^=@fuunjM zuXxbipipyZTi9W&^Bai3J~^vLs9EX3>-rU7liXex6pUhe1*4>R0S$OaSngcuoJynS zc^*}C8m~awk@;+w!v8xN|L^?pSD0#O<%pQX?)3`PBQdZ+X-l_h(;^d94KLY_+hP(E zp`60fkTJrRZ>zD9E@S`+Ab8LKo!|kzgTYJWZz$4ifarN@qBf z^H3<~(*lYW#iXciQD8NwInWafCwdjOe}KgwWuY!Dy-V zR6Oy}c64J(^;p!uML^9v+OfRn)*ynyS6H3GN+2EUk9TvOe4052bVjgm`k=w6qW6G) z^XZvLSG9)4>P)2JL~ff7+=qv12_H)}x z8)3MrgLY9JNax#7^Ws*}08hSKn?qyTNTUmzU7+LHu)&3@QS+SEcGEN%{n6P!H5LcL zY|EvDGB-930}oC+16HIDPdSBlz+1E;PU%94CoW!Nw}8fI5&IsU9~qkX z>oaHdKyCLI$!w>({;F;lI`We_aFJdmSL9TQrpWQ}Jj*LQ=*wBIKn=>w08D9E_Yr~kI z1ubZMXhvR6U(Z}jpUAA&T!*t-0WK}<>TO^e)2+;UmKbgwgZQr+11&d&MS$HRn;BJG zdV!ZOrO@4_G_HcOzml%7wD9BM#0c~SHHhkA%M~5eBPO$91LHvRM1}Dq7T3ph%x(*L zZdzn$Qu<{RvZhbXBC4iZtC&c(J9|S%q#_};aWeRpE=rFKid00S6ohNyyDeZ#48|uU z9B~or%busa&gwmNB*CMu>nGAYD38)7hPS4gC+EDdj+dSwao^HW?#1BoE zSu$liwA?81LQ{UAGV$4lo?uU~#T5oJ`w zRmrXzqvUg#Jhqmuwz%uhvDjnaHfY7T4M&MR6WE4H(4Sm(Jq7(qLePrzr=dUjs)?Wq z^(uO{?M+rY^oA{&-e@~$Yq4e6zJxb>-S$=Bh4JJ>IqrdbV>glA-3F zQW_M+efzsd|L;c`{~zItJN5roHYK6&wA!?1V19ix8T>iHwP!d%g@z&YJp6wHt{rd@ zafSN9P}ai>OA9}@EwiN}lFqWZ@^G8`$%FBKqNyez3=K~m21qXzKeF`XKVoOGcz!W4 zau>AZ&?NNDOcC>c?LpL|P169&73l?lWJ#0Fx}O0L0PL*v2-`Gh;Y3;qoG~QUO^aBs zo51w2X-pPoG!tPaD)kl%zdS-~C}&bob^1i>==Ajw(;^~Ok=?+*ceNg^XHfGy9y;m? z$bU3Kfu^NLqlt{3C8$(171}?|kn|y#%X|d&GypVI?pyHT!|uWVUo!sx^20wg$=skZ z29K)`W)oD-V|wF2bAaWJR*2S`d=W|#q0Ya7qMPD60DC# zM8is8J@cAqfOpWQpyqxp7;Ug64{yG%DL7+^C8)k8I1K$I^?A78;ieAi1=EYtNb~;n z4&w_{m%{%i8ULUB@UKMgH7#P3ns8g!LYtqYLa>?wyI9RXNOqw(pv&>5J8@*P*Qv8r zNQ+FiU)|@ubQI$4hfOR>YU7h^E#l=2oTp3^2x|w00oHbDK`_gUfPMECK^KA7YkD>~ zt6TOm-7?x}2^FTY0a?m4Ii`h}>Q3xu9zkxLvrT9ey&WAL4)}HI230e0)}x@?p<`D< z?-7bnb7jjLMx%8CiexuVK?73xiOdtMj_un&^)eb5EEGkNgWf>%Y>$se%`dhf3Tu;e zDg2vd{G0vof0x<8G#}1n%8jtr(yhxo4t8KxFniJqnO8?_GY3G)hSRf{aNA1;3zLcR z^T>ghKm#m`c`e1862$hPgKX!-K4{!v@8FGje}fg?#1`1^w+P}XSfd?Hg!8!IKOFlG zCo`}=M>weqzv-ZJEX(c785L;bjP)9G53w^~tk^_T)>}H7xfbCt#RL%##VDo`8^9Me z)=~AkY40I+y}sI_=5cHgoI{jl190Bkg5yMTH=ZyKi9E#irbDx4*m|(>C^E8#&FhZ# zg116Zz6<63kS&V{O}hMzCR?4^rSSh*#{XwO{9{-Ysf9IP5Iqr)l;;3HK~uv00iFwZ z>i*7*1(br310-*M0&CmN9fZk%HZa=Z8SE?uT6jzq{iSXpJ3D6^y`RP7s@?UGZY%Q` zv(Tor7z8aF5D}>>XB|uiV`eGyAfz3%nAtLBJa~~f!ydP7v0?4daACdf(Toq-C6NC` zmxtwTOKgNO0_GT}^E56gr{zbm9fm0*ZSwlB=h@EHpPg0E3vB1mdRYCPhS|wFn_$Cr zWLrn)4sb!Bvj4;N8v?*9@t|SJ*U%unk<~t==+~w2|3$|C7eD-0f$pan&%ohKY#w_6 z=HH(B^XwTY!>v$;mqDXTb9R*HM$~cMIFR`7EuoAr=@hl zgwtcJ@7f~SD$C(|lQkatdaqmS{Xp#5U(+zDe?)y$5QUx|^b}z5kqA3-gQh{~1F$KN zXBRS#M??YS5O|i^Sp%xYUB(G$l|@y58FF)jo`XCa#o#PV*$_;jzYKaFD67Pdm=~xw zP=(l8Po5X?oc0u4AHjv^wRn8PvL<|a>bjWz&rKQsn|}EF`%`$e7F8hXxOE9bB(DRT zE1|4n53w@cH5t5o5JSc>wdR7s8cUHtGj`D9E;ART_Jx(S!!??D58~_02U0^Jz8+TA ziQo>R%#8JLFvkIjlH8}vjJ0nt<$^@90|o1xV9HIPEHl&sWf|I~!<6vGt#Hr!lBzi``H{A;K`L1ear`=dKzcydZwZ?Jn(Je#+V4Tyoc4o^2#!#}wMi?lx-9D>?XpZyTRd0kMF&@oGrn&CrsI!)NvbbM=_pJYZ zmGS=-KDcxJe>b+M`H~lxrC(bW5}+&}09}CpRGLcv(!8stpyvJ3-Td0Q5CnHy@L%rs zP1}E}cc}R_xZ7QQgz#fiJo@s#-0l7Ua<@5N+`rtKm7grS02{*8{*iH z={Osl?eMeYxhx0orM-lhsJ=eE-Pq!nYJYR?RtKL?Qiq?Pr|y0Jh9>g-ZjI?YRlI|y z3bFSESdmjbQ@rk+jwJPu7YvA`q98W5W1jk3h=DdR#LOKxG*>TxfA1ZHL2b9#mpD*! zgm<^*(gk7%0tykg*umHd18NTS3X-ldZ%VF1v3fYoGwx4n7s(#EIrH_NwiVzTo;pIZ z;7NYP{P%mnO0=EFDMaau2|qNbN|3~dVpS4nGVCMa^+zwf4|yN|?!n(J5GYC!x)Bi@o_`d53{i)G4eAgO3F=8=R|pJh)@08au>I@8r29E zB&K*c(^m;2)2bGSX^1X&v`+mhNEL~=2Xdda@P=;cL>9$XFhMT)oqNehgZ(M61px1& zyYnuJJGH|Awc|d9|6ci@N5 zSP6fpNPnxPzxD8!O3TaJZn*}Y;*;#SjgEr-{uN`1Fhrm**z>nm!@0oM#u6YqaG2!N zo)(=FPKO<>t5z(r^sDdLPZu|=Jhi+>X2saF@+D07%wgr#V^hl)GvN@b7#o)9V1|_s zEk7_;4Qs<4UViMUv0V!P78(B*Kl~krdQ%ojp#*evZ0%UD`?UA>GgiO4Bihb`Odu1a zWMifvG+_)v42_TnY%|C^?GBTxt`}MYC@f_#261;V=q_PUCY3zKU{N;S5Nwur*~!kWU1g%PvzXVuKQ zI4hzkzo@3@Vo}6n`H$5+cJZ-@+4-|;W?!5gF(-dc&76yKA|B6wyyo$Xk2j~nSgF+=W9 z!xkp^HnPqFrTY}xty|{g51xwtrxd&Y9moW;#pmC)3O%# zcOI7kbAwBQ`Gi5SqspNIzwSS@Vg8wIHFxogz%Sg(zZNy0m-xp5fAcb@^iJRx+@Ln9 zlUv*eAjiLZrw`lk7Y%4zD}tqJ=uz(9-#z%Z%lNm$CwI>OP;z*l2li{KO`l~o*Ik(6 zp!%c5WLUqKYlFcjz(Rhc@p$&}+_&Z)&mLvlUV!3A(9?9e7WKP29fuAz9-<5dWCD1D zXlw(kt|a)1Sl{@NWXl(ar#3!s@`8FW4l|7(fz7z00-P_cvHeb%YTTE#Rk}xL<6d8= z!?oKN`q8z^7rN});R~I1z3mI_alHvvd`YnMv$*k%T-S8`oa#n2P%CWBrohs(Qg9fy zWK|^pmF%52kEzHGgc@7j=j|t2+zFne6tQpd{R=*P***B*mhr#shyOxViepl@WRRse z;^3TTb5h&t-2LPYHM&sja+s!&D%i(Y(uDD7oXJp@dlN03)M|e%m#89ljs9}=o4})hY2X&0-17m`t-zajhMJa%9ZvL}IeA1LA(a(h z*qD_mx8KXIjLbO&`7)c_u2(CkLwZSz~0dcqv!PN%W!?1?KV@w!=M3`SL1X?RxBk+G9 zeF)b=xJJO`!Fa$^XTVbp1)%3Ij6zeI4x~ITQFEF|7(Gyu1*jQKoDc6&KD-|U>aeC< zsGs;G0oU|Xze7l_*9p)|T$vO#(xpSqGg^!ylYzhJU&0u941u@0Uw`+seJm4WEevr-hLe!3=lU!UBY^JqfpCr6c-1SLW!aW%5T^4J-0$! zzH4a9&Fi3*0X&Toj|O;}C5num*E0(J=ofcG{CmInPY@UV;v&Sa`NjVU@y6@F*M_e> zYntv??D2QqgFhkTPx#?)0P`-z=3DM=-lNQi&?a%4#qIMM1Ly-~f%SwZ5VcIBIA6mF zU}+EZe8$$lKn|lVQ$`#B0WlE6j^^U3mE#2s-E8P3eUZ^_KVJLI5(JzlCsLxOe{iLEB zUC3F?+m~Fiql5q+bV&q9%oU;+4Lt=vKpBrbhp(`Q2wrC>PDLK~vD*#eYF1n*eJ+QCv(ACN&pj^J6`4%N%uu&pcCvP4~%!k?7!C;jlhvc#}_r-f($dl*ab zsgA4cQevtu5mCh+_r}|e~W04^ecJwcJ3)w(+A|fxCmScTt zu1yiXD;x_buk z#BxpOg>BqKbtvzkLamEc(cnXP7E7UUTO!P8ykM)!et0`qAMg{ubs0+dftTNOlvkCd zqPrCS3K@TeAO5N4Epb1q!fD)2a9aj>?>lh)(_Z)T3raAb-xD-`%Dmf`p(H}U^#fz9 zfwr#T32?SeJ$FmQ6sH%({v^I=TFfbOJ`Y%h6dBv5>Y-P;avNi}1F(~2`ZT9PA?T%A zWzr-*zMf3V=M_l<1#5OcZlR{=d{#~}2X=yE%9*U(aF-1YXBGO3XsHe*sCa8J)*>rr zFiXwCZ_#^p2R3&MIVYwr@iyG>J(m0H(poiIqNt8u}!(tbfC`bB3OxP!F8PH{>@HH}P^y~u9lK%dioQdF9rW+*PuSWQzP zMmo^PBwEPuRZ{ zYZ-!yA3?y#9(C4=rs__kqq@t<1gp2|%??vIysxU>t_Q3)R=C+=)YFxUK$?V{T8QTp;k3ipU4_MVB|U zy>Gkss<+JR{at_Q7ZVZVsr5{Zi>F zW*PSp(_5L(q|#jqf3=Li+7JIG`ZLbS1gOL52$tBijgGJdE;T>moSn7wAoI)YWlKL> znmVUs&K$;USTcQdes3~R*_+;CJPVc=-(ya)qnY=K{pk#oiI(ZVLix-l{iz|TwmHmT z+c@UeLPZ?hvtLg-i;1#D%^O5V6-}QDp_so&5nZ9yLW4_?gX~X zj`skoU}tD$tr96#j-H2DkDG7LhuxViwn}a)bHTZb>4U!HR+|r*{4NoRhdJPV)2(3CrBq=Ut>S(PwjBd#4R-vLoDsQ>Zw|Lwiw|tf9ac^l+ zR(RaqTVHlU=RY6>G{-&ecUy=kWwpn>qUBXnm%?8wEO(}~&f1=^T#=}s~6Wbl2 zB|4MYQb-jmHhsv=E^gzL6)B5$O7$Su`418v^klc%>wJ9s6G1RQh9Em!d zsx=MV-=a=CyBPvf!xUaZu}MRNo$<8`COb>1;&n_VXdHv=?$URzqbAf(rvQXaVaAY( z(6zxkXTrwLv!sme*%nPr0-6ihh8aEX(=8a;;GG`#$rfZ(t#ibkQoO{ZDD!jXv8m=c zFv6=he{Y+d^@zHMc_h7q&Ci`@8)hcxoi-J9%Y789!rZsNd*nZXGX8;n_(q!~tA zsjN9{9*=dzQ*oP~!lba>=)txn+zGCbz2ThBQ0!T!*UjF3yg0776rI-Bqg36Q)OWb# zjBxf^@kljc*3;p(S*WM}I(k+=6is8!3~k^>u)%85Y@|onE)Utr#pR~4-zi3#gXlC{ z_+8qG$dr1gN7-}xLKEmN7!Q~ohI6W;Mm5SHdt_=Wx|zcieQ4~jFt8%1N?DzT0#__LaTf%?O3$VSBH-M1q32%ll$8{0juZ@XL-HTM&qF5n`pZzz%E=7SGd(xJ|G& zfPUMMPiiYkMX!wIDvkMR+ju6h(##;^4O<$#!?yM`l!|a&^0=GZyJhAwqto&GWH9)B zHg|l_24sE=>wr``K-5C5t!{NNbdwrFRjta3Q}B-R`@ws{GtKZ0tKiCm^NLDzv>*ia zLMXnsu*Jx$USb;AZlSKQq@jxV6xIYi&lIKiu@SLv zniZ5HrlBw#dV*fI9`h7*kD^}l`eQr(waJnUX?yz>(DQsTPKlbSR5LMTKXU^02AeIH zh$J(j@DmD|Xqt5-qXawA{6b5f=~I*(7)I~2e{09Pk?>sOIj~yZ$SV8wJX22C(pB-3X{GA`!B?js>h}060rl3!!Wiu3rgbt{oT|4yUF->^TYpR z^GOEVQ%OO+)g$^SidUN#qObL>s4t7^UZ?xo95D}|Hwb>{KlM@cdYy$C0qcR@wpiPk zxsJK@X?<)uo2)BJn8NDPi=z$O&17*HoW|>6gOUfCKV95hw}HWD2px5gGlh(H=o$1e z`U$Kzg~qVHkYf6@Op`#&Kv{vm$&lZnRvT0UfAcb*s->6{VOlmC(x%@14N21^~!O&Jw36#1jH zLBPJ(lri0;Rz8a%`mHrjFonSCGQ@VYKt6@p4D|u~^^$c*>0(#iI;O041+30N!?Qw5 zeStF4p=QK{#yJ?};KiuO^)=LtpP*)p0-HhQQ#2vAs3i=lE@KMP#}DdfpTxZ3B>NZg z|2JGY|ADJZ;U6mFAL@ty-DNrQKa|qlT=D-`T)m`vGcyWy;{nH?%%d!8|09OWp!Uq> z=P*jc*@5HWWI<}K((n=UA#;!kjUy@)zWhCe9`p1T53lH-+d$Ny#B3f_pN0LMv<|B8 z-4?2B(BCKmb?6Y3NL8Ze=0^i9s{XpF>+a$b2mJnwJ2hxRr6l zIO1la-1OUsA6!UG&?l<{=`dR%6J|rzUibI6i5Qz@Kg=0A?fxOK&u=XaE{!Bj^fAW@ zPFZrAQ_NplI(q(+`Jc`YI}GysMZJ&`U34PY5t@=VdiIG4WZI__j&jI^;x$5133wzh z64dquR~O7$U_X4Ne6@u*cfEYC^XfteMU2G0NjM|*%oIzQ*_v#7h5~h#?#XWV$c_YL zd-ArsunlYl1(rYa#WNSd`T*FUmpG-6ZaO|&T@5t)jeunsd7QVHF7GwjVd64XLtaX8&r zNV?uE?nS>@h>RA%Xijl&dQRbS4)y;{7*ldfI=_YOHs>4he7r2*{Nxw9-A*EQ`-CeC z&APefEAy|H+h)IJQPfW{%*wB_sLoN~03Q1eLX`dRb@1)jmlq^)WpWxaFC~X9%i6JQGS#y+H|TjHk@i zrSR`A7yD{;x;vTU(zp^n0Kxosev=df%WbL(fMBn-B}==GiugOfwQmzk93 zoMpS^faB_a%QL7m&#f|-l;`U|JV(0oo##kTW&fB5KWD$+bN)@9|3CXsT?+pmGX6dM z@c)0e4~Olrhk*TfB)sR+^c%+oaRbOc^m5J-6RAw1A4Z>XZx8`$Jw4crOhYXbcw#_0 zPYxKyD+cuEl>;bV1-b_60b2gc{I|jS+!0+bM3}k{-isd0n~A=hhH3A*HtJ*wk=99j z^WV^p=~=v*h|hL4hRhmzDs z=83yQndhTK-l6RFQ3CEzZuHq?sjZjSp7Onq==$bi>YMd9#Ur{#xW0nxG+amF+Apq3 ze+&NCh`4SX(d|X@7j`N9d&&6s^1~lBye3|Paw&!DNx1$5mlfu1zwhxWk*f}|WwqVV zHnwn{*B$7!mYvW{_6c86Z|RAX;~bdA#^kT7(VD657z2 zeuiT3&&OOTrqgTESHeEhPOdu}s@7$mN((fPp#Q{-utnQ)m<3?v^AcE<>x;hU&I|>4 z1dG#CK?1yw3&|W&{F_3G70zVRjZM?v$WI~rDTmW~Nd1u`*8SqV0aE7Dj=NU^o$|?l z|2>WKx)lDsW&C^l;g4-3W&ImGbN)Z!`Q87(bJrj6{MR4w%>IvfhW`gVc@v53b$!%} zxhL4KxOprQd((WzOc;{PR&%U*oVft?r(w0Ac|e$vQ#<#)xz(_0;bW83E4c!e=ccn~ z%qM`mo4HHj|A37D1Ah1$%tXv=^E^}tGkStrufdl5-k&=@&Ipd+)$h8p7CD2M(t}82jbGVH5bDSc10b2yUmP2fr2c(N{2!fv=HA zc>_>*$&bJnyloMR|C~1)uuOMd`w7WA#kaf#d{JHps@MH`d!zUX_>QXQ=|w2%f<$|{ zU65#}B-#my)}`>*$@uI1@L$yaiBu*Bq%x_K%A}l|b1mbGpp235iYVhTzI`#QvGYZ&>y7=(;WeLyB8NVT+kClwft$IbvtR z=yJgP1WByATwDg!ZDu`-Dg)UO5AOgQ9r?^^dPZ?K+oMeW^l{88CV$#&*egyXdx3UP zcV#bnA?OX&nsd{==rjGPlx{YC@qDyKAIQ4V5<1WZmO?jup+7Zvpe>X|8;8+ao0i=H ze>WA7Bi857x}V{{cmGE(fFtz{k; zUwYBmXgKD?@v;`eXkdfpML-Xe0JesfIKxsfJ*fr%y(s;?+A_f~tKe*6Pd-0U#YZRh zf*OY{`fYC`jI)c8VV8M)+=N)rH33WE3ftC43m>%~PJv@=UN_(Nqv()$Uu*kTdOr`m z+l_@>oGc|WW={nvL5SJwUeq#|yPqq-J^McqGX4>M_{WzP11e`FT!u;|U(<3{>Qxh4 zCzv#-vA{Y6#fMya*YW_^)`XJpx63>sZakc^kW2pUM<1W$Ih>!+%tt4L^Kr!(r8Jl& z=C+AolXnDSpPZQDc5^N9=pf9_mb6{=Ve?4a*Ah0k-sq)T@_*Vp7w9OeJdNL*7fAqV zP%tRSO~NA&5;_ntKB-Ee2?!W@xKUAQlEO43-I?wVbfUXdA-EA8#Roh1nhBr|jyi*J zn8DX>6^tvRGTW_=gSxt5)C67EMV%dRBxJvT)eUKBoZYkM%#rg5oy;XIq>i^w) z`=+|9svK^VV9zX{el74Beg`BPQgOW(pF(+sOfXm3$H5_aeMh$*qgpb@KGvEqug=XB z`AymL8Wv^GJyW@@a!^dz-Vgg~(F7)zqgFWeX3$rEwD-s8f~5W$>mLuYB%TudKiBd9 z+&KSd;}?8SUv~4Z9NaCvJleUS5O06Yh_1rtXlnEcyfv4__I5pa`7@q^{KNRRcT{wG z?ke0JHX=G>eCf6Lok(m7D>%Obe*Cf*S81a(_wk7|{LW$NeEGD7g`V2`n>t;Jr@(bNAr4V z+e1-~`7yok_RukIJ+UW0b$kxTw@FDnCHOza@qbL5|2sUb^S95+!I(|hZ*MF-v@CBk z-sz=!GVSySU3>7X^^RGU9+#co+(mEkS~B}VjK-d-d_VSkysNbPS(tpwwRjfX^QC3+rQs(-usP~XvO zPN<$#EUjzf7ai>E2yVx7pIP*ZLo9E5=%;vYT;I_&)G`*EVb9MvF=t^KzPFxJ{gmg# z(eL+_^c3e7xr>%P>Q2qLuAy;RZNr;N7v2tqZ>E(|O09C8qT+tC-*oaz|Io=Nv$q!Y z;FY0j65F5n`9Ie2e{7up<>HIZ;w2w1oiXX-rH`#1UiDa=rzbkYo$<3sWy432ss=v( z73EI~pO~-YDIq1OT+hy~I@ZZ6_I7f-+hI~qWo3DNr@D<-?CZQOsCwGbbFt`)SdRP7LQ`<WbbufcSnSmp{Kx|Wi|Oc_tJT>B?9|QvHOaKXkP0T zovg6p9s9GtS3ytX^b>D)ZVzt5XV&}}%iR|0=-%E&dwQXJ2l|k=I=2PywsZDJJM1Id z5xlee?l#p!>&)oc)W$FTR{1~9@qb*L|IyC&;Fj)N+bTWOW+OeS=WaxWa@TkF9bM78 z8~1tRUd!GWd&udldvCS%%H9imYVuj`NVwE@^seKZ?9}jT3#m=VAMK`PXV}YbK0dXl zMzH*BwC=v6HywZ6-hM-GQxBD^_jcInn|jZ&(~;f{_zj=)_eS5*CwtL;@tcux`L&8Q z$hG^`v&ZEQz9?Urm6<+oh*GD#zD&>JxZV?8HZHG5alOznjtj;t)36lI_nE1Bin^Eo zvzeg}QeR@}){Dlklr;XTIYb|-9$>?)e=&Zeobj#lUpW4YIR9C;^|JAbGMN9`%+fQ} z{rsSrqmNeKV&_`#7;h`*u=A|b#&4C8{CDQr`Y82H{+c;L zKU0034Yy)ORB`bmrduylKV#FZFO1KXsceSzN8_k+KL0Osx?ZmSfn97JH+q#zc%L~_ zzf?Ve`wlMDFH*l^7g%2!$CL_IU>!C-QVRLMo5gyO`Z531EY+u||H(?M|1dtm-J~Cw zmX|4v`0{uBFK>jmR^>s9eXcw(d6` zP_E(knOEtx>Q>%vR_k-sTiIpSZN?^L9$RSLVce-)&cAOi(C4eSvsu;#W1}*g|Gha! z_o%n<^=74BrQXa`%P@5OjAihsvFT@=nUl@OoINaU^oX;@rDP31XV8%IMrNj->l&Jo zm2wfo{qG%r%1UF5rzW%giQ~UK$N#)I|5-{_E`ATf@%oQJ=|-wxJSz=J1SU-`xEu@C z(*jl-Pu3VudEOh_^1>*ebkr4L)s+R*&))21KaY=ci3V!W-0tca7KA z=v~pMF;?vj)~~hm8oj|LF`-^~8-tp+z7@Oh`kL(Rvx|y~M2)&|Zk51XHp@ntw4LyV zLRv7)R{DL#WH^-hy?)XXN+;`-@b_Q7<9~j<|2X-}Tf$^flhzanwj#YqYt+0Ujjad- z!c(tbJ9YZ^+!b!*mz7R0ErT}u>H-ifM{uuK*#d8?D07Rc?y2RhX71&pOe|g?3Sd!* zC@(yXPqdDk);W!fr~}Qxzzsof6Y7Gu4$d>S)ZgIs*EeeQV(y|VggY`JlFhVc+Im7r zto5R<$f^)mU#h(RNnueJ@P~c=7EMG}YC#e33!fjg&bJ2DPi{};C+n2p|9Hp$@u&D7 zXbCf)pD~J7Fwlq~T@yx=LiV-sd0H?S2wsa|#jRHtXlicM!Wv~W*|=Bw{Ph%Hcf{=; zYUj&xQQ$zqaph2cf8qUQQ(k=G9`ppL0O5eRK?{rKKrlR^z63QO6!wN&LfP!poC~RT z)MqolwpP?@YkYMY*-vXBd$I^>A*UERzGR&e{GZ_XKOxTlFgi&YA75NU3Wa<(__g{{ zQSS@E62Df5pa&60bn#5BuSfhsXv6&tPmB0M;Sj|U?RSkA6(NjT)F1ffK%kM9$^8V; z&!EE>D}#Zien+UQW+zP{kI;6Vs+G+Kyhlfl@TuD#vO&TIhEdSBmPE6$p12{yVYCTGA5q zpKMCafwfxD_C0IBq;T9C_OjXjmL@Ie4g1gnSK93u9}b1tY}U!T#h5=JG=F__z=xWH z9^O5*cBow^W-U-O?n`ZhD6u>v8jpP&ihK=S$eZ%Z^naS`w))btovZ?eY7vnr773cNg zQ*1D*!`CKcWAw`K@Iwb$*azo1ZJoqZg8v1M{{?aWpFI6B3%_e5Pp53x zpY4QJpP7z~1Fzmba~O2mw5MmDXX_U;Y2dwb7Lyg$so==?WG7p^FLH{!1wR zg;qG63G#FSl*-ef8PFE!Am~PDCUgrl3;H3HIuAaAiNVm%kfxK%LYj`F40>ff)&WQ+ z?Udkuq2qrcg53Z7XNti>84H>}9Uhsl#4WGD{4J}PSOtwpt(Qu9r{lUmlb^Yo3A666 zPtux?6HD;F$nn1@&j0?HZ-sYn0@D7x|Ep#M{&!{2dO7f&G=BdBo*bF!%#T+by~B6>YsPxRQC^;Dxl*k(>s8ml zzloiSYu_%eq?s?XbPVbq99Wmx>#|o*{+qa6Vf#Adx;WC!_)hrV@mw4q$HfrA1+tQM zN@)Kjj{hZb{vQ)ui~*w&g|h&SlW_eP|6`1d_~QjyH1S8yaN0_Zoy3?9V>6D`Tr{A< z@=JzcInv8zdMK7tS`<0Umtr}ROw6Vc8%uepfBEcDc6#^8^cXw+TiPEs$eV4C=2J$? z^7CfX2#}?e$+Vck?Xg|_G1EBTnP`vwQ~Y2*xv%^8pK*f!rH=ol2uuI?4=&rQ^WD`A zyzCASo73#cs^D|m=kON}lww?s_ChO&>1a22-aF0H^l;q#K)e~Nna0?Bummgu3l@Va z#uk?|Rt4V3<>DYHuf%t|c&qg3COV>@d;a`#{SoG(woKdKG=+-~zDNMV=ubjGv;kC%^V>i*=tbVh%heK-B(B9PGj-H!k6{|Ep7a%cHn$=?)# z1pmt%|I5Df|C>UVRQU=1Pj&pC`YrSy_km(qF)#)+F;*aw%J5T8Cc*z{j{no*{NIll z5K+lNDA6T33?+_8jzI}l=8y>`T#_6pAtVJ*!Y!$Q5{jf6O4LYdp~P}Y1C(f%M4*H& zX@?S(N)AGaF3DjiaYS+qO0a1TnNY$d!DE61AtVJ*!Y!$Q z5{jf6O4LYdp~P}Y1C(f%M4*H&X@?S|9r>)^W*%F55dseaE<*&CU&o9;-gz} VefLu)F5oQb;}jo*WSS@c{~H2oLx=zX diff --git a/binaries/board_B.uf2 b/binaries/board_B.uf2 deleted file mode 100644 index 3cd3995b93f24cb7352977c4a760d877283319f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 159744 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 diff --git a/binaries/deskhop.uf2 b/binaries/deskhop.uf2 new file mode 100644 index 0000000000000000000000000000000000000000..d55fbb88d00f32d346824c443b288c00b361293c GIT binary patch literal 524288 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 literal 0 HcmV?d00001 diff --git a/disk/create.sh b/disk/create.sh new file mode 100755 index 0000000..5411419 --- /dev/null +++ b/disk/create.sh @@ -0,0 +1,14 @@ + +#!/bin/bash + +dd if=/dev/zero of=fat.img bs=2M count=1 +# sudo mkdosfs -F 16 -R 1 -n DESKHOP fat.img + +mkdosfs -F12 -n DESKHOP -i 0 fat.img + +sudo mount -o loop -t vfat fat.img /mnt/disk/ +sudo cp ../webconfig/config.htm /mnt/disk/config.htm +sudo umount /mnt/disk + +dd if=fat.img of=disk.img bs=512 count=128 +rm fat.img diff --git a/disk/disk.S b/disk/disk.S new file mode 100644 index 0000000..3016b23 --- /dev/null +++ b/disk/disk.S @@ -0,0 +1,10 @@ + .section .section_disk,"a" + .global _disk_start + +_disk_start: + .incbin __disk_file_path__ + .global _disk_end + +_disk_end: + + diff --git a/disk/disk.img b/disk/disk.img new file mode 100644 index 0000000000000000000000000000000000000000..2606fc3c294a2e29b56cb57eb288544dda7202a5 GIT binary patch literal 65536 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�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ℑNd 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%0AEY>Y3(B)#YUa{~W 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
+ +
+ + + +
+ +
+

DeskHop Config

+
+
+ +
+ + +
+
+ + + + + + + + + + + + + + + + +
+ +
+ + + + + + + +

Output A

+ + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+
+ + + + + + + + + +
+ +
+ + + + + + + + + + + + + + +
+
+ + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+ + + + + + + + + + + + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + +

Output B

+ + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+
+ + + + + + + + + +
+ +
+ + + + + + + + + + + + + + +
+
+ + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+ + + + + + + + + + + + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+
+
+ +
+ + +

Common Config

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+ + + + + + + + + + + + + + +
+ + + + + + +
+ + + + + + + + + + + + + + +
+
+ + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+ + + + + + + + + + + + + + +
+ + + + + + +
+ + + + + + + + + + + + + + +
+ + + + + + +
+ + + + + + +
+ +
+

Device Status

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+
+ + + + + \ No newline at end of file diff --git a/webconfig/config.htm b/webconfig/config.htm new file mode 100644 index 0000000..0ea590b --- /dev/null +++ b/webconfig/config.htm @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/webconfig/form.py b/webconfig/form.py new file mode 100755 index 0000000..d432a5e --- /dev/null +++ b/webconfig/form.py @@ -0,0 +1,82 @@ +#!/usr/bin/python3 + +from dataclasses import dataclass, field + +@dataclass +class FormField: + offset: int + name: str + default: int | None = None + values: dict[int, str] = field(default_factory=dict) + data_type: str = "int32" + elem: str | None = None + +SHORTCUTS = { + 0x73: "None", + 0x2A: "Backspace", + 0x39: "Caps Lock", + 0x2B: "Tab", + 0x46: "Print Screen", + 0x47: "Scroll Lock", + 0x53: "Num Lock", + } + +STATUS_ = [ + FormField(78, "Running FW version", None, {}, "uint16", elem="hex_info"), + FormField(79, "Running FW checksum", None, {}, "uint32", elem="hex_info"), +] + +CONFIG_ = [ + FormField(1001, "Mouse", elem="label"), + FormField(71, "Force Mouse Boot Mode", None, {}, "uint8", "checkbox"), + FormField(75, "Enable Acceleration", None, {}, "uint8", "checkbox"), + FormField(77, "Jump Treshold", 0, {"min": 0, "max": 1024}, "uint16", "range"), + + FormField(1002, "Keyboard", elem="label"), + FormField(72, "Force KBD Boot Protocol", None, {}, "uint8", "checkbox"), + FormField(73, "KBD LED as Indicator", None, {}, "uint8", "checkbox"), + + FormField(76, "Enforce Ports", None, {}, "uint8", "checkbox"), +] + +OUTPUT_ = [ + FormField(1, "Screen Count", 1, {1: "1", 2: "2", 3: "3"}, "uint32"), + FormField(2, "Speed X", 16, {"min": 1, "max": 100}, "int32", "range"), + FormField(3, "Speed Y", 16, {"min": 1, "max": 100}, "int32", "range"), + FormField(4, "Border Top", None, {}, "int32"), + FormField(5, "Border Bottom", None, {}, "int32"), + FormField(6, "Operating System", 1, {1: "Linux", 2: "MacOS", 3: "Windows", 4: "Android", 255: "Other"}, "uint8"), + FormField(7, "Screen Position", 1, {1: "Left", 2: "Right"}, "uint8"), + FormField(8, "Cursor Park Position", 0, {0: "Top", 1: "Bottom", 3: "Previous"}, "uint8"), + FormField(1003, "Screensaver", elem="label"), + FormField(9, "Enabled", None, {}, "uint8", "checkbox"), + FormField(10, "Only If Inactive", None, {}, "uint8", "checkbox"), + FormField(11, "Idle Time (μs)", None, {}, "uint64"), + FormField(12, "Max Time (μs)", None, {}, "uint64"), +] + +def generate_output(base, data): + output = [ + { + "name": field.name, + "key": base + field.offset, + "default": field.default, + "values": field.values, + "type": field.data_type, + "elem": field.elem, + } + for field in data + ] + return output + +def output_A(base=10): + return generate_output(base, data=OUTPUT_) + +def output_B(base=40): + return generate_output(base, data=OUTPUT_) + +def output_status(): + return generate_output(0, data=STATUS_) + +def output_config(): + return generate_output(0, data=CONFIG_) diff --git a/webconfig/render.py b/webconfig/render.py new file mode 100755 index 0000000..9368b71 --- /dev/null +++ b/webconfig/render.py @@ -0,0 +1,61 @@ +#!/usr/bin/python3 + +# Takes a HTML file, outputs a minified and compressed version that self-decompresses when loaded. +# This way, the device config page can be fitted in a small 64 kB "flash" partition and distributed +# with the main binary. + +from jinja2 import Environment, FileSystemLoader +from form import * +import base64 +import zlib + +# Input and output +TEMPLATE_PATH = "templates/" +INPUT_FILENAME = "main.html" +PACKER_FILENAME = "packer.j2" +OUTPUT_FILENAME = "config.htm" +OUTPUT_UNPACKED = "config-unpacked.htm" + +def render(filename, *args, **kwargs): + env = Environment(loader=FileSystemLoader(TEMPLATE_PATH)) + template = env.get_template(filename) + return template.render(*args, **kwargs) + + +def write_file(payload, filename=OUTPUT_FILENAME): + with open(filename, 'w') as file: + file.write(payload) + + +def encode_file(payload): + # Compress using raw DEFLATE + compressed_data = zlib.compress(payload.encode('utf-8'))[2:-4] + + # Encode to base64 + base64_compressed_data = base64.b64encode(compressed_data).decode('utf-8') + + return base64_compressed_data + + +if __name__ == "__main__": + # Read main template contents + webpage = render( + INPUT_FILENAME, + screen_A=output_A(), + screen_B=output_B(), + status=output_status(), + config=output_config(), + ) + + # Compress file and encode to base64 + encoded_data = {'payload': encode_file(webpage)} + + # Tiny Inflate JS decoder (https://github.com/foliojs/tiny-inflate) + # Decompress the data and replace existing HTML with the decoded version + self_extracting_webpage = render(PACKER_FILENAME, encoded_data) + + # Write data to output filename + write_file(self_extracting_webpage) + + # Write unpacked webpage + write_file(webpage, OUTPUT_UNPACKED) \ No newline at end of file diff --git a/webconfig/templates/form.html b/webconfig/templates/form.html new file mode 100644 index 0000000..bf8cd31 --- /dev/null +++ b/webconfig/templates/form.html @@ -0,0 +1,58 @@ +{# templates/form.html #} + +{% set key = item.key %} + +{% macro input(item, type='text', class='api', name='name') %} + {{ item.name }}{{ add }} +{% endmacro %} + +{% block form_render %} + {% if item.get("elem") == "hex_info" %} + {{ label(item, add=':') }} + {{ input(item, class='content api') }} data-hex readonly /> + + {% elif item.get("elem") == "label" %} + {{ label(item, class='') }} + + {% elif item.get("elem") == "range" %} +
+
+ {{ label(item, add='=') }} + + {{ input(item, class='input-inline', type='number', name='aInput') }} + readonly oninput="this.form.aRange{{ key }}.value=this.value" /> + + {{ input(item, class='range api', type='range', name='aRange') }} + min="{{ item.values.min }}" max="{{ item.values.max }}" oninput="this.form.aInput{{key}}.value=this.value" /> +
+ +
+ + {% elif item.get("elem") == "checkbox" %} +
+ {{ label(item) }} + {{ input(item, type="checkbox") }} /> + +
+ + {% elif item["values"] %} + {{ label(item, class='') }} +
+ + {% else %} + {{ label(item, class='') }} + {{ input(item) }} /> + + {% endif %} +{% endblock %} \ No newline at end of file diff --git a/webconfig/templates/main.html b/webconfig/templates/main.html new file mode 100644 index 0000000..dda7e9b --- /dev/null +++ b/webconfig/templates/main.html @@ -0,0 +1,142 @@ +{# templates/main.j2 #} + + + + + + + + + + DeskHop Config + + + + +
+ +
+ + + +
+ +
+

DeskHop Config

+
+
+ +
+ + +
+
+ + + + + + + + + + + + + + + + +
+ +
+ + + + + + + +

Output A

+ + {% for item in screen_A %} + {% include "form.html" with context %} + {% endfor %} + +
+
+ + + + + + + +

Output B

+ + {% for item in screen_B %} + {% include "form.html" with context %} + {% endfor %} + +
+ +
+ +
+
+
+
+
+ +
+ + +

Common Config

+ + {% for item in config %} + {% include "form.html" with context %} + {% endfor %} + + +
+ +
+

Device Status

+ + {% for item in status %} + {% include "form.html" with context %} + {% endfor %} + +
+ +
+
+
+
+ + + + + diff --git a/webconfig/templates/packer.j2 b/webconfig/templates/packer.j2 new file mode 100644 index 0000000..d5651bb --- /dev/null +++ b/webconfig/templates/packer.j2 @@ -0,0 +1 @@ +{% raw %}{% endraw %} diff --git a/webconfig/templates/script.js b/webconfig/templates/script.js new file mode 100644 index 0000000..48da171 --- /dev/null +++ b/webconfig/templates/script.js @@ -0,0 +1,218 @@ +const mgmtReportId = 6; +var device; + +const packetType = { + keyboardReportMsg: 1, mouseReportMsg: 2, outputSelectMsg: 3, firmwareUpgradeMsg: 4, switchLockMsg: 7, + syncBordersMsg: 8, flashLedMsg: 9, wipeConfigMsg: 10, readConfigMsg: 16, writeConfigMsg: 17, saveConfigMsg: 18, + rebootMsg: 19, getValMsg: 20, setValMsg: 21, proxyPacketMsg: 23 +}; + +function calcChecksum(report) { + let checksum = 0; + for (let i = 3; i < 11; i++) + checksum ^= report[i]; + + return checksum; +} + +async function sendReport(type, payload = [], sendBoth = false) { + if (!device || !device.opened) + return; + + /* First send this one, if the first one gets e.g. rebooted */ + if (sendBoth) { + var reportProxy = makeReport(type, payload, true); + await device.sendReport(mgmtReportId, reportProxy); + } + + var report = makeReport(type, payload, false); + await device.sendReport(mgmtReportId, report); +} + +function makeReport(type, payload, proxy=false) { + var dataOffset = proxy ? 4 : 3; + report = new Uint8Array([0xaa, 0x55, type, ...new Array(9).fill(0)]); + + if (proxy) + report = new Uint8Array([0xaa, 0x55, packetType.proxyPacketMsg, type, ...new Array(7).fill(0), type]); + + if (payload) { + report.set([...payload], dataOffset); + report[report.length - 1] = calcChecksum(report); + } + return report; +} + +function packValue(element, key, dataType, buffer) { + const dataOffset = 1; + var buffer = new ArrayBuffer(8); + var view = new DataView(buffer); + + const methods = { + "uint32": view.setUint32, + "uint64": view.setUint32, /* Yes, I know. :-| */ + "int32": view.setInt32, + "uint16": view.setUint16, + "uint8": view.setUint8, + "int16": view.setInt16, + "int8": view.setInt8 + }; + + if (dataType in methods) { + const method = methods[dataType]; + if (element.type === 'checkbox') + view.setUint8(dataOffset, element.checked ? 1 : 0, true); + else + method.call(view, dataOffset, element.value, true); + } + + view.setUint8(0, key); + return new Uint8Array(buffer); +} + +window.addEventListener('load', function () { + if (!("hid" in navigator)) { + document.getElementById('warning').style.display = 'block'; + } + + this.document.getElementById('menu-buttons').addEventListener('click', function (event) { + window[event.target.dataset.handler](); + }) +}); + +document.getElementById('submitButton').addEventListener('click', async () => { await saveHandler(); }); + +async function connectHandler() { + if (device && device.opened) + return; + + var devices = await navigator.hid.requestDevice({ + filters: [{ vendorId: 0x1209, productId: 0xc000, usagePage: 0xff00, usage: 0x10 }] + }); + + device = devices[0]; + device.open().then(async () => { + document.querySelectorAll('.online').forEach(element => { element.style.opacity = 1.0; }); + await readHandler(); + }); +} + +async function blinkHandler() { + await sendReport(packetType.flashLedMsg, []); +} + +async function blinkBothHandler() { + await sendReport(packetType.flashLedMsg, [], true); +} + +function getValue(element) { + if (element.type === 'checkbox') + return element.checked ? 1 : 0; + else + return element.value; +} + +function setValue(element, value) { + element.setAttribute('fetched-value', value); + + if (element.type === 'checkbox') + element.checked = value; + else + element.value = value; + element.dispatchEvent(new Event('input', { bubbles: true })); +} + + +function updateElement(element, event, dataType) { + var dataOffset = 3; + + const methods = { + "uint32": event.data.getUint32, + "uint64": event.data.getUint32, /* Yes, I know. :-| */ + "int32": event.data.getInt32, + "uint16": event.data.getUint16, + "uint8": event.data.getUint8, + "int16": event.data.getInt16, + "int8": event.data.getInt8 + }; + + if (dataType in methods) { + var value = methods[dataType].call(event.data, dataOffset, true); + setValue(element, value); + + if (element.hasAttribute('data-hex')) + setValue(element, parseInt(value).toString(16)); + } +} + +async function readHandler() { + if (!device || !device.opened) + await connectHandler(); + + const elements = document.querySelectorAll('.api'); + + for (const element of elements) { + var key = element.getAttribute('data-key'); + var dataType = element.getAttribute('data-type'); + + await sendReport(packetType.getValMsg, [key]); + + let incomingReport = await new Promise((resolve, reject) => { + const handleInputReport = (event) => { + updateElement(element, event, dataType); + + device.removeEventListener('inputreport', handleInputReport); + resolve(); + } + device.addEventListener('inputreport', handleInputReport); + }); + } +} + +async function rebootHandler() { + await sendReport(packetType.rebootMsg); +} + +async function enterBootloaderHandler() { + await sendReport(packetType.firmwareUpgradeMsg, true, true); +} + +async function valueChangedHandler(element) { + var key = element.getAttribute('data-key'); + var dataType = element.getAttribute('data-type'); + + var origValue = element.getAttribute('fetched-value'); + var newValue = getValue(element); + + if (origValue != newValue) { + uintBuffer = packValue(element, key, dataType); + + /* Send to both devices */ + await sendReport(packetType.setValMsg, uintBuffer, true); + + /* Set this as the current value */ + element.setAttribute('fetched-value', newValue); + } +} + +async function saveHandler() { + const elements = document.querySelectorAll('.api'); + + if (!device || !device.opened) + return; + + for (const element of elements) { + var origValue = element.getAttribute('fetched-value') + + if (element.hasAttribute('readonly')) + continue; + + if (origValue != getValue(element)) + await valueChangedHandler(element); + } + await sendReport(packetType.saveConfigMsg, [], true); +} + +async function wipeConfigHandler() { + await sendReport(packetType.wipeConfigMsg, [], true); +} \ No newline at end of file diff --git a/webconfig/templates/style.css b/webconfig/templates/style.css new file mode 100644 index 0000000..913fe21 --- /dev/null +++ b/webconfig/templates/style.css @@ -0,0 +1,665 @@ +/*! + * Milligram v1.4.1 + * https://milligram.io + * + * Copyright (c) 2020 CJ Patoilo + * Licensed under the MIT license + */ + +*, +*:after, +*:before { + box-sizing: inherit; +} + +:root { + --highlight-color: #384955; + --font-color: #384955; + --highlight-color2: #5e9f41; +} + +html { + box-sizing: border-box; + font-size: 62.5%; +} + +body { + color: var(--font-color); + font-family: 'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; + font-size: 1.6em; + font-weight: 300; + letter-spacing: .01em; + line-height: 1.6; +} + +blockquote { + border-left: 0.3rem solid #384955; + margin-left: 0; + margin-right: 0; + padding: 1rem 1.5rem; +} + +blockquote *:last-child { + margin-bottom: 0; +} + +.button, +button, +input[type='button'], +input[type='reset'], +input[type='submit'] { + background-color: var(--highlight-color); + border: 0.1rem solid var(--highlight-color); + border-radius: .4rem; + color: #fff; + cursor: pointer; + display: inline-block; + font-size: 1.1rem; + font-weight: 700; + height: 3.8rem; + letter-spacing: .1rem; + line-height: 3.8rem; + padding: 0.0rem 2.0rem; + text-align: center; + text-decoration: none; + text-transform: uppercase; + white-space: nowrap; +} + +.button:focus, .button:hover, +button:focus, +button:hover, +input[type='button']:focus, +input[type='button']:hover, +input[type='reset']:focus, +input[type='reset']:hover, +input[type='submit']:focus, +input[type='submit']:hover { + background-color: var(--highlight-color2); + border-color: var(--highlight-color2); + color: #fff; + outline: 0; +} + +.button[disabled], +button[disabled], +input[type='button'][disabled], +input[type='reset'][disabled], +input[type='submit'][disabled] { + cursor: default; + opacity: .5; +} + +.button[disabled]:focus, .button[disabled]:hover, +button[disabled]:focus, +button[disabled]:hover, +input[type='button'][disabled]:focus, +input[type='button'][disabled]:hover, +input[type='reset'][disabled]:focus, +input[type='reset'][disabled]:hover, +input[type='submit'][disabled]:focus, +input[type='submit'][disabled]:hover { + background-color: var(--highlight-color); + border-color: var(--highlight-color); +} + +.button.button-outline, +button.button-outline, +input[type='button'].button-outline, +input[type='reset'].button-outline, +input[type='submit'].button-outline { + background-color: transparent; + color: var(--highlight-color); +} + +.button.button-outline:focus, .button.button-outline:hover, +button.button-outline:focus, +button.button-outline:hover, +input[type='button'].button-outline:focus, +input[type='button'].button-outline:hover, +input[type='reset'].button-outline:focus, +input[type='reset'].button-outline:hover, +input[type='submit'].button-outline:focus, +input[type='submit'].button-outline:hover { + background-color: transparent; + border-color: var(--highlight-color2); + color: var(--highlight-color2); +} + +.button.button-outline[disabled]:focus, .button.button-outline[disabled]:hover, +button.button-outline[disabled]:focus, +button.button-outline[disabled]:hover, +input[type='button'].button-outline[disabled]:focus, +input[type='button'].button-outline[disabled]:hover, +input[type='reset'].button-outline[disabled]:focus, +input[type='reset'].button-outline[disabled]:hover, +input[type='submit'].button-outline[disabled]:focus, +input[type='submit'].button-outline[disabled]:hover { + border-color: inherit; + color: var(--highlight-color); +} + +.button.button-clear, +button.button-clear, +input[type='button'].button-clear, +input[type='reset'].button-clear, +input[type='submit'].button-clear { + background-color: transparent; + border-color: transparent; + color: var(--highlight-color); +} + +.button.button-clear:focus, .button.button-clear:hover, +button.button-clear:focus, +button.button-clear:hover, +input[type='button'].button-clear:focus, +input[type='button'].button-clear:hover, +input[type='reset'].button-clear:focus, +input[type='reset'].button-clear:hover, +input[type='submit'].button-clear:focus, +input[type='submit'].button-clear:hover { + background-color: transparent; + border-color: transparent; + color: var(--highlight-color2); +} + +.button.button-clear[disabled]:focus, .button.button-clear[disabled]:hover, +button.button-clear[disabled]:focus, +button.button-clear[disabled]:hover, +input[type='button'].button-clear[disabled]:focus, +input[type='button'].button-clear[disabled]:hover, +input[type='reset'].button-clear[disabled]:focus, +input[type='reset'].button-clear[disabled]:hover, +input[type='submit'].button-clear[disabled]:focus, +input[type='submit'].button-clear[disabled]:hover { + color: var(--highlight-color); +} + +.button.button-shifted { + margin-left: 10%; +} + +code { + background: #f4f5f6; + border-radius: .4rem; + font-size: 86%; + margin: 0 .2rem; + padding: .2rem .5rem; + white-space: nowrap; +} + +pre { + background: #f4f5f6; + border-left: 0.3rem solid var(--highlight-color); + overflow-y: hidden; +} + +pre > code { + border-radius: 0; + display: block; + padding: 1rem 1.5rem; + white-space: pre; +} + +hr { + border: 0; + border-top: 0.1rem dotted lightblue; + margin: 1.0rem 3.0rem; +} + +input[type='color'], +input[type='date'], +input[type='datetime'], +input[type='datetime-local'], +input[type='email'], +input[type='month'], +input[type='password'], +input[type='search'], +input[type='number'], +input[type='tel'], +input[type='text'], +input[type='url'], +input[type='week'], +input:not([type]), +textarea, +select { + -webkit-appearance: none; + background-color: transparent; + border: 0.1rem solid #d1d1d1; + border-radius: .4rem; + box-shadow: none; + box-sizing: inherit; + height: 3.8rem; + padding: .6rem 1.0rem .7rem; + width: 100%; +} + +input[type='color']:focus, +input[type='date']:focus, +input[type='datetime']:focus, +input[type='datetime-local']:focus, +input[type='email']:focus, +input[type='month']:focus, +input[type='number']:focus, +input[type='password']:focus, +input[type='search']:focus, +input[type='tel']:focus, +input[type='text']:focus, +input[type='url']:focus, +input[type='week']:focus, +input:not([type]):focus, +textarea:focus, +select:focus { + border-color: var(--highlight-color); + outline: 0; +} + +select { + background: url('data:image/svg+xml;utf8,') center right no-repeat; + padding-right: 3.0rem; +} + +select:focus { + background-image: url('data:image/svg+xml;utf8,'); +} + +select[multiple] { + background: none; + height: auto; +} + +textarea { + min-height: 6.5rem; +} + +label, +legend { + display: block; + font-size: 1.6rem; + font-weight: 700; + margin-bottom: .5rem; +} + +fieldset { + border-width: 0; + padding: 0; +} + +input[type='checkbox'], +input[type='radio'] { + display: inline; +} + +.label-inline { + display: inline-block; + font-weight: normal; + margin-left: .5rem; +} + +.label-inline-bold { + display: inline-block; + font-size: 1.6rem; + font-weight: 700; +} + +input[type='range'].input-inline, +input[type='number'].input-inline { + display: inline-block; + font-weight: normal; + border: none; + width: 20%; +} + +.range { + width: 100%; +} + +.container { + margin: 0 auto; + max-width: 112.0rem; + padding: 0 2.0rem; + position: relative; + width: 100%; +} + +.row { + display: flex; + flex-direction: column; + padding: 0; + width: 100%; +} + +.row.row-no-padding { + padding: 0; +} + +.row.row-no-padding > .column { + padding: 0; +} + +.row.row-wrap { + flex-wrap: wrap; +} + +.row.row-top { + align-items: flex-start; +} + +.row.row-bottom { + align-items: flex-end; +} + +.row.row-center { + align-items: center; +} + +.row.row-stretch { + align-items: stretch; +} + +.row.row-baseline { + align-items: baseline; +} + +.row .column { + display: block; + flex: 1 1 auto; + margin-left: 0; + max-width: 100%; + width: 100%; +} + +.row .column.column-offset-10 { + margin-left: 10%; +} + +.row .column.column-offset-20 { + margin-left: 20%; +} + +.row .column.column-offset-25 { + margin-left: 25%; +} + +.row .column.column-offset-33, .row .column.column-offset-34 { + margin-left: 33.3333%; +} + +.row .column.column-offset-40 { + margin-left: 40%; +} + +.row .column.column-offset-50 { + margin-left: 50%; +} + +.row .column.column-offset-60 { + margin-left: 60%; +} + +.row .column.column-offset-66, .row .column.column-offset-67 { + margin-left: 66.6666%; +} + +.row .column.column-offset-75 { + margin-left: 75%; +} + +.row .column.column-offset-80 { + margin-left: 80%; +} + +.row .column.column-offset-90 { + margin-left: 90%; +} + +.row .column.column-10 { + flex: 0 0 10%; + max-width: 10%; +} + +.row .column.column-20 { + flex: 0 0 20%; + max-width: 20%; +} + +.row .column.column-25 { + flex: 0 0 25%; + max-width: 25%; +} + +.row .column.column-33, .row .column.column-34 { + flex: 0 0 33.3333%; + max-width: 33.3333%; +} + +.row .column.column-40 { + flex: 0 0 40%; + max-width: 40%; +} + +.row .column.column-50 { + flex: 0 0 50%; + max-width: 50%; +} + +.row .column.column-60 { + flex: 0 0 60%; + max-width: 60%; +} + +.row .column.column-66, .row .column.column-67 { + flex: 0 0 66.6666%; + max-width: 66.6666%; +} + +.row .column.column-75 { + flex: 0 0 75%; + max-width: 75%; +} + +.row .column.column-80 { + flex: 0 0 80%; + max-width: 80%; +} + +.row .column.column-90 { + flex: 0 0 90%; + max-width: 90%; +} + +.row .column .column-top { + align-self: flex-start; +} + +.row .column .column-bottom { + align-self: flex-end; +} + +.row .column .column-center { + align-self: center; +} + +@media (min-width: 40rem) { + .row { + flex-direction: row; + + width: calc(100% + 2.0rem); + } + .row .column { + margin-bottom: inherit; + padding: 0 1.0rem; + } +} + +a { + color: var(--highlight-color); + text-decoration: none; +} + +a:focus, a:hover { + color: var(--highlight-color2); +} + +dl, +ol, +ul { + list-style: none; + margin-top: 0; + padding-left: 0; +} + +dl dl, +dl ol, +dl ul, +ol dl, +ol ol, +ol ul, +ul dl, +ul ol, +ul ul { + font-size: 90%; + margin: 1.5rem 0 1.5rem 3.0rem; +} + +ol { + list-style: decimal inside; +} + +ul { + list-style: circle inside; +} + +.button, +button, +dd, +dt, +li { + margin-bottom: 1.0rem; +} + +fieldset, +input, +select, +textarea { + margin-bottom: 1.5rem; +} + +blockquote, +dl, +figure, +form, +ol, +p, +pre, +table, +ul { + margin-bottom: 2.5rem; +} + +table { + border-spacing: 0; + display: block; + overflow-x: auto; + text-align: left; + width: 100%; +} + +td, +th { + border-bottom: 0.1rem solid #e1e1e1; + padding: 1.2rem 1.5rem; +} + +td:first-child, +th:first-child { + padding-left: 0; +} + +td:last-child, +th:last-child { + padding-right: 0; +} + +@media (min-width: 40rem) { + table { + display: table; + overflow-x: initial; + } +} + +b, +strong { + font-weight: bold; +} + +p { + margin-top: 0; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: 300; + letter-spacing: -.1rem; + margin-bottom: 2.0rem; + margin-top: 0; +} + +h1 { + font-size: 4.6rem; + line-height: 1.2; +} + +h2 { + font-size: 3.6rem; + line-height: 1.25; +} + +h3 { + font-size: 2.8rem; + line-height: 1.3; +} + +h4 { + font-size: 2.2rem; + letter-spacing: -.08rem; + line-height: 1.35; +} + +h5 { + font-size: 1.8rem; + letter-spacing: -.05rem; + line-height: 1.5; +} + +h6 { + font-size: 1.6rem; + letter-spacing: 0; + line-height: 1.4; +} + +img { + max-width: 100%; +} + +.clearfix:after { + clear: both; + content: ' '; + display: table; +} + +.float-left { + float: left; +} + +.float-right { + float: right; +} + +.online { + opacity: 0.5; +} \ No newline at end of file

tr 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 literal 0 HcmV?d00001 diff --git a/memory_map.ld b/memory_map.ld index 51fe3bf..cceaa03 100644 --- a/memory_map.ld +++ b/memory_map.ld @@ -21,16 +21,33 @@ __stack (== StackTop) */ +/* Total image is 256 kB, consisting of: + Executable = 188 kB + FAT disk image = 64 kB + Firmware metadata = 4 kB (contains checksum) +*/ + +__FLASH_LEN = 188k; +__DISK_IMAGE_LEN = 64k; +__METADATA_LEN = 4k; +__TOTAL_IMAGE_LENGTH = 256k; + __CONFIG_STORAGE_LEN = 4k; MEMORY { - FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 2048k - __CONFIG_STORAGE_LEN + FLASH(rx) : ORIGIN = 0x10000000, LENGTH = __FLASH_LEN + DISK_IMAGE(rw) : ORIGIN = 0x10000000 + __FLASH_LEN, LENGTH = __DISK_IMAGE_LEN + FW_METADATA(rw) : ORIGIN = 0x10000000 + (__TOTAL_IMAGE_LENGTH - __METADATA_LEN), LENGTH = __METADATA_LEN + FW_STAGING(rw) : ORIGIN = 0x10000000 + __TOTAL_IMAGE_LENGTH, LENGTH = __TOTAL_IMAGE_LENGTH + FLASH_CONFIG(rw) : ORIGIN = 0x10000000 + (2048k - __CONFIG_STORAGE_LEN), LENGTH = __CONFIG_STORAGE_LEN RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 256k SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k } +PROVIDE(_staging_metadata = ORIGIN(FW_STAGING) + (__TOTAL_IMAGE_LENGTH - __METADATA_LEN)); +PROVIDE(_firmware_metadata = ORIGIN(FW_METADATA)); ENTRY(_entry_point) @@ -42,6 +59,7 @@ SECTIONS */ .flash_begin : { + ADDR_FW_RUNNING = .; __flash_binary_start = .; } > FLASH @@ -180,6 +198,7 @@ SECTIONS .uninitialized_data (NOLOAD): { . = ALIGN(4); + __udata_end__ = .; *(.uninitialized_data*) } > RAM @@ -217,10 +236,35 @@ SECTIONS __HeapLimit = .; } > RAM - /* Configuration flash section (4k in size, end of flash) */ - - .section_config : { - "ADDR_CONFIG" = .; + /* Store web configuration utility HTML */ + .section_disk : { + ADDR_DISK_IMAGE = .; + KEEP(*(.section_disk)) + } > DISK_IMAGE + + /* Firmware metadata section (4k in size, contains version, checksum etc.) */ + .section_metadata : { + ADDR_FW_METADATA = .; + KEEP(*(.section_metadata)); + } > FW_METADATA + + /* Firmware staging section (256k in size, near the end of flash) */ + .section_staging : { + ADDR_FW_STAGING = .; + KEEP(*(.section_staging)) + } > FW_STAGING + + /* Just padding so we can have a nice, consistently-sized .bin file */ + .fill : { + FILL(0x00); + . = ORIGIN(FW_METADATA) + LENGTH(FW_METADATA) - 1; + BYTE(0x00) + ___ROM_AT = .; + } > FW_METADATA + + /* Configuration flash section (4k in size, end of flash) */ + .section_config (NOLOAD) : { + ADDR_CONFIG = .; } > FLASH_CONFIG /* .stack*_dummy section doesn't contains any symbols. It is only @@ -245,6 +289,14 @@ SECTIONS __flash_binary_end = .; } > FLASH + .pad : { + /* This section will be filled with zeroes */ + FILL(0x00) + . = ADDR_DISK_IMAGE - __flash_binary_end - 1; + BYTE(0x00) + KEEP(*(.pad)) + } > FLASH + /* stack limit is poorly named, but historically is maximum heap ptr */ __StackLimit = ORIGIN(RAM) + LENGTH(RAM); __StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X); diff --git a/pico-sdk/lib/tinyusb/.swp b/pico-sdk/lib/tinyusb/.swp new file mode 100644 index 0000000000000000000000000000000000000000..525ad1450a3d78f877f32a16226740105f0ed5c9 GIT binary patch literal 12288 zcmeI%%SyvQ6vpwXJ2xuT2RQ0VC{lH$Z{WgvTd$jGGV0_qC(KMDxN+-~iBIBlcv{e)A(ndz0;>!b0doqZd3AOL~6 z5LoUX?C*`g>f2kJwBGK|#jdak2tWV=5P$##AOHafK;VA_V%1V_GHPiuZey~xeyx*J zED(SI1Rwwb2tWV=5P$##AOHafETDi+mHJvy>U32$FaQ5ffB%2V`MdNYHBv1-NcYm6 zbSvFR*U~`hOFijIx|A-YbLmVPzk2~~uw@8900Izz00bZa0SG_<0uX?}Uj%HaDd>`^ z(w>}2IYb?GuH{*7m@-4JI%h{mJ2bsedYyMD)lLUWY6_MrE=*}7WgO&*)bSHp%Dr)Q ul$E^=(MGFeROF}&H-2wjar9st_mode = S_IFCHR; //} +// Clang use picolibc +#if defined(__clang__) +static int cl_putc(char c, FILE *f) { + (void) f; + return sys_write(0, &c, 1); +} + +static int cl_getc(FILE* f) { + (void) f; + char c; + return sys_read(0, &c, 1) > 0 ? c : -1; +} + +static FILE __stdio = FDEV_SETUP_STREAM(cl_putc, cl_getc, NULL, _FDEV_SETUP_RW); +FILE *const stdin = &__stdio; +__strong_reference(stdin, stdout); +__strong_reference(stdin, stderr); +#endif + +//--------------------------------------------------------------------+ +// Board API +//--------------------------------------------------------------------+ int board_getchar(void) { char c; return (sys_read(0, &c, 1) > 0) ? (int) c : (-1); diff --git a/pico-sdk/lib/tinyusb/hw/bsp/board_api.h b/pico-sdk/lib/tinyusb/hw/bsp/board_api.h index 404509a..eee9ed9 100644 --- a/pico-sdk/lib/tinyusb/hw/bsp/board_api.h +++ b/pico-sdk/lib/tinyusb/hw/bsp/board_api.h @@ -32,10 +32,30 @@ extern "C" { #endif #include +#include #include +#include #include "tusb.h" +#if CFG_TUSB_OS == OPT_OS_FREERTOS +#if TUP_MCU_ESPRESSIF + // ESP-IDF need "freertos/" prefix in include path. + // CFG_TUSB_OS_INC_PATH should be defined accordingly. + #include "freertos/FreeRTOS.h" + #include "freertos/semphr.h" + #include "freertos/queue.h" + #include "freertos/task.h" + #include "freertos/timers.h" +#else + #include "FreeRTOS.h" + #include "semphr.h" + #include "queue.h" + #include "task.h" + #include "timers.h" +#endif +#endif + // Define the default baudrate #ifndef CFG_BOARD_UART_BAUDRATE #define CFG_BOARD_UART_BAUDRATE 115200 ///< Default baud rate @@ -99,6 +119,7 @@ static inline uint32_t board_millis(void) { #elif CFG_TUSB_OS == OPT_OS_CUSTOM // Implement your own board_millis() in any of .c file +uint32_t board_millis(void); #else #error "board_millis() is not implemented for this OS" @@ -121,6 +142,7 @@ static inline size_t board_usb_get_serial(uint16_t desc_str1[], size_t max_chars uint8_t uid[16] TU_ATTR_ALIGNED(4); size_t uid_len; + // TODO work with make, but not working with esp32s3 cmake if ( board_get_unique_id ) { uid_len = board_get_unique_id(uid, sizeof(uid)); }else { diff --git a/pico-sdk/lib/tinyusb/hw/bsp/board_mcu.h b/pico-sdk/lib/tinyusb/hw/bsp/board_mcu.h index d40f33b..013eb1c 100644 --- a/pico-sdk/lib/tinyusb/hw/bsp/board_mcu.h +++ b/pico-sdk/lib/tinyusb/hw/bsp/board_mcu.h @@ -47,7 +47,7 @@ #elif TU_CHECK_MCU(OPT_MCU_LPC51UXX, OPT_MCU_LPC54XXX, OPT_MCU_LPC55XX, OPT_MCU_MCXN9) #include "fsl_device_registers.h" -#elif TU_CHECK_MCU(OPT_MCU_KINETIS_KL, OPT_MCU_KINETIS_K32L) +#elif TU_CHECK_MCU(OPT_MCU_KINETIS_KL, OPT_MCU_KINETIS_K32L, OPT_MCU_KINETIS_K) #include "fsl_device_registers.h" #elif CFG_TUSB_MCU == OPT_MCU_NRF5X diff --git a/pico-sdk/lib/tinyusb/hw/bsp/family_support.cmake b/pico-sdk/lib/tinyusb/hw/bsp/family_support.cmake index 539a776..6eef5b8 100644 --- a/pico-sdk/lib/tinyusb/hw/bsp/family_support.cmake +++ b/pico-sdk/lib/tinyusb/hw/bsp/family_support.cmake @@ -6,12 +6,33 @@ include(CMakePrintHelpers) set(TOP "${CMAKE_CURRENT_LIST_DIR}/../..") get_filename_component(TOP ${TOP} ABSOLUTE) -# Default to gcc +#------------------------------------------------------------- +# Toolchain +# Can be changed via -DTOOLCHAIN=gcc|iar or -DCMAKE_C_COMPILER= +#------------------------------------------------------------- +# Detect toolchain based on CMAKE_C_COMPILER +if (DEFINED CMAKE_C_COMPILER) + string(FIND ${CMAKE_C_COMPILER} "iccarm" IS_IAR) + string(FIND ${CMAKE_C_COMPILER} "clang" IS_CLANG) + string(FIND ${CMAKE_C_COMPILER} "gcc" IS_GCC) + + if (NOT IS_IAR EQUAL -1) + set(TOOLCHAIN iar) + elseif (NOT IS_CLANG EQUAL -1) + set(TOOLCHAIN clang) + elseif (NOT IS_GCC EQUAL -1) + set(TOOLCHAIN gcc) + endif () +endif () + +# default to gcc if (NOT DEFINED TOOLCHAIN) set(TOOLCHAIN gcc) endif () -# FAMILY not defined, try to detect it from BOARD +#------------------------------------------------------------- +# FAMILY and BOARD +#------------------------------------------------------------- if (NOT DEFINED FAMILY) if (NOT DEFINED BOARD) message(FATAL_ERROR "You must set a FAMILY variable for the build (e.g. rp2040, espressif). @@ -74,22 +95,35 @@ set(WARNING_FLAGS_GNU set(WARNING_FLAGS_IAR "") +#------------------------------------------------------------- +# Functions +#------------------------------------------------------------- # Filter example based on only.txt and skip.txt function(family_filter RESULT DIR) get_filename_component(DIR ${DIR} ABSOLUTE BASE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) - if (EXISTS "${DIR}/only.txt") - file(READ "${DIR}/only.txt" ONLYS) - # Replace newlines with semicolon so that it is treated as a list by CMake - string(REPLACE "\n" ";" ONLYS_LINES ${ONLYS}) + if (EXISTS "${DIR}/skip.txt") + file(STRINGS "${DIR}/skip.txt" SKIPS_LINES) + foreach(MCU IN LISTS FAMILY_MCUS) + # For each line in only.txt + foreach(_line ${SKIPS_LINES}) + # If mcu:xxx exists for this mcu then skip + if (${_line} STREQUAL "mcu:${MCU}" OR ${_line} STREQUAL "board:${BOARD}" OR ${_line} STREQUAL "family:${FAMILY}") + set(${RESULT} 0 PARENT_SCOPE) + return() + endif() + endforeach() + endforeach() + endif () - # For each mcu + if (EXISTS "${DIR}/only.txt") + file(STRINGS "${DIR}/only.txt" ONLYS_LINES) foreach(MCU IN LISTS FAMILY_MCUS) # For each line in only.txt foreach(_line ${ONLYS_LINES}) # If mcu:xxx exists for this mcu or board:xxx then include - if (${_line} STREQUAL "mcu:${MCU}" OR ${_line} STREQUAL "board:${BOARD}") + if (${_line} STREQUAL "mcu:${MCU}" OR ${_line} STREQUAL "board:${BOARD}" OR ${_line} STREQUAL "family:${FAMILY}") set(${RESULT} 1 PARENT_SCOPE) return() endif() @@ -98,29 +132,8 @@ function(family_filter RESULT DIR) # Didn't find it in only file so don't build set(${RESULT} 0 PARENT_SCOPE) - - elseif (EXISTS "${DIR}/skip.txt") - file(READ "${DIR}/skip.txt" SKIPS) - # Replace newlines with semicolon so that it is treated as a list by CMake - string(REPLACE "\n" ";" SKIPS_LINES ${SKIPS}) - - # For each mcu - foreach(MCU IN LISTS FAMILY_MCUS) - # For each line in only.txt - foreach(_line ${SKIPS_LINES}) - # If mcu:xxx exists for this mcu then skip - if (${_line} STREQUAL "mcu:${MCU}") - set(${RESULT} 0 PARENT_SCOPE) - return() - endif() - endforeach() - endforeach() - - # Didn't find in skip file so build - set(${RESULT} 1 PARENT_SCOPE) else() - - # Didn't find skip or only file so build + # only.txt not exist so build set(${RESULT} 1 PARENT_SCOPE) endif() endfunction() @@ -206,12 +219,12 @@ function(family_configure_common TARGET RTOS) if (CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 12.0) target_link_options(${TARGET} PUBLIC "LINKER:--no-warn-rwx-segments") endif () - endif() - if (CMAKE_C_COMPILER_ID STREQUAL "IAR") + elseif (CMAKE_C_COMPILER_ID STREQUAL "Clang") + target_link_options(${TARGET} PUBLIC "LINKER:-Map=$.map") + elseif (CMAKE_C_COMPILER_ID STREQUAL "IAR") target_link_options(${TARGET} PUBLIC "LINKER:--map=$.map") endif() - # ETM Trace option if (TRACE_ETM STREQUAL "1") target_compile_definitions(${TARGET} PUBLIC TRACE_ETM) @@ -226,7 +239,7 @@ function(family_configure_common TARGET RTOS) if (NOT TARGET segger_rtt) add_library(segger_rtt STATIC ${TOP}/lib/SEGGER_RTT/RTT/SEGGER_RTT.c) target_include_directories(segger_rtt PUBLIC ${TOP}/lib/SEGGER_RTT/RTT) - #target_compile_definitions(segger_rtt PUBLIC SEGGER_RTT_MODE_DEFAULT=SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL) +# target_compile_definitions(segger_rtt PUBLIC SEGGER_RTT_MODE_DEFAULT=SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL) endif() target_link_libraries(${TARGET} PUBLIC segger_rtt) endif () @@ -372,6 +385,10 @@ function(family_flash_jlink TARGET) set(JLINKEXE JLinkExe) endif () + if (NOT DEFINED JLINK_IF) + set(JLINK_IF swd) + endif () + file(GENERATE OUTPUT $/${TARGET}.jlink CONTENT "halt @@ -383,7 +400,7 @@ exit" add_custom_target(${TARGET}-jlink DEPENDS ${TARGET} - COMMAND ${JLINKEXE} -device ${JLINK_DEVICE} -if swd -JTAGConf -1,-1 -speed auto -CommandFile $/${TARGET}.jlink + COMMAND ${JLINKEXE} -device ${JLINK_DEVICE} -if ${JLINK_IF} -JTAGConf -1,-1 -speed auto -CommandFile $/${TARGET}.jlink ) endfunction() @@ -402,21 +419,36 @@ endfunction() # Add flash openocd target -function(family_flash_openocd TARGET CLI_OPTIONS) +function(family_flash_openocd TARGET) if (NOT DEFINED OPENOCD) set(OPENOCD openocd) endif () - separate_arguments(CLI_OPTIONS_LIST UNIX_COMMAND ${CLI_OPTIONS}) + if (NOT DEFINED OPENOCD_OPTION2) + set(OPENOCD_OPTION2 "") + endif () + + separate_arguments(OPTION_LIST UNIX_COMMAND ${OPENOCD_OPTION}) + separate_arguments(OPTION_LIST2 UNIX_COMMAND ${OPENOCD_OPTION2}) # note skip verify since it has issue with rp2040 add_custom_target(${TARGET}-openocd DEPENDS ${TARGET} - COMMAND ${OPENOCD} ${CLI_OPTIONS_LIST} -c "program $ reset exit" + COMMAND ${OPENOCD} ${OPTION_LIST} -c "program $ reset" ${OPTION_LIST2} -c exit VERBATIM ) endfunction() +# Add flash openocd-wch target +# compiled from https://github.com/hathach/riscv-openocd-wch or https://github.com/dragonlock2/miscboards/blob/main/wch/SDK/riscv-openocd.tar.xz +function(family_flash_openocd_wch TARGET) + if (NOT DEFINED OPENOCD) + set(OPENOCD $ENV{HOME}/app/riscv-openocd-wch/src/openocd) + endif () + + family_flash_openocd(${TARGET}) +endfunction() + # Add flash pycod target function(family_flash_pyocd TARGET) if (NOT DEFINED PYOC) @@ -430,6 +462,18 @@ function(family_flash_pyocd TARGET) endfunction() +# Add flash teensy_cli target +function(family_flash_teensy TARGET) + if (NOT DEFINED TEENSY_CLI) + set(TEENSY_CLI teensy_loader_cli) + endif () + + add_custom_target(${TARGET}-teensy + DEPENDS ${TARGET} + COMMAND ${TEENSY_CLI} --mcu=${TEENSY_MCU} -w -s $/${TARGET}.hex + ) +endfunction() + # Add flash using NXP's LinkServer (redserver) # https://www.nxp.com/design/software/development-software/mcuxpresso-software-and-tools-/linkserver-for-microcontrollers:LINKERSERVER function(family_flash_nxplink TARGET) @@ -460,6 +504,21 @@ function(family_flash_dfu_util TARGET OPTION) ) endfunction() +function(family_flash_msp430flasher TARGET) + if (NOT DEFINED MSP430Flasher) + set(MSP430FLASHER MSP430Flasher) + endif () + + # set LD_LIBRARY_PATH to find libmsp430.so (directory containing MSP430Flasher) + find_program(MSP430FLASHER_PATH MSP430Flasher) + get_filename_component(MSP430FLASHER_PARENT_DIR "${MSP430FLASHER_PATH}" DIRECTORY) + add_custom_target(${TARGET}-msp430flasher + DEPENDS ${TARGET} + COMMAND ${CMAKE_COMMAND} -E env LD_LIBRARY_PATH=${MSP430FLASHER_PARENT_DIR} + ${MSP430FLASHER} -w $/${TARGET}.hex -z [VCC] + ) +endfunction() + #---------------------------------- # Family specific #---------------------------------- diff --git a/pico-sdk/lib/tinyusb/hw/bsp/rp2040/family.cmake b/pico-sdk/lib/tinyusb/hw/bsp/rp2040/family.cmake index 222b4e7..93b4f72 100644 --- a/pico-sdk/lib/tinyusb/hw/bsp/rp2040/family.cmake +++ b/pico-sdk/lib/tinyusb/hw/bsp/rp2040/family.cmake @@ -21,6 +21,7 @@ if (NOT PICO_TINYUSB_PATH) endif() if (NOT TINYUSB_OPT_OS) + message("Setting OPT_OS_PICO") set(TINYUSB_OPT_OS OPT_OS_PICO) endif() @@ -71,6 +72,7 @@ target_sources(tinyusb_device_base INTERFACE ${TOP}/src/device/usbd_control.c ${TOP}/src/class/cdc/cdc_device.c ${TOP}/src/class/hid/hid_device.c + ${TOP}/src/class/msc/msc_device.c ) #------------------------------------ @@ -82,10 +84,7 @@ target_sources(tinyusb_host_base INTERFACE ${TOP}/src/portable/raspberrypi/rp2040/rp2040_usb.c ${TOP}/src/host/usbh.c ${TOP}/src/host/hub.c - ${TOP}/src/class/cdc/cdc_host.c ${TOP}/src/class/hid/hid_host.c - ${TOP}/src/class/msc/msc_host.c - ${TOP}/src/class/vendor/vendor_host.c ) # Sometimes have to do host specific actions in mostly common functions @@ -122,6 +121,8 @@ target_link_libraries(tinyusb_bsp INTERFACE pico_unique_id) # tinyusb_additions will hold our extra settings for examples add_library(tinyusb_additions INTERFACE) + +message("Setting PICO workarounds") target_compile_definitions(tinyusb_additions INTERFACE PICO_RP2040_USB_DEVICE_ENUMERATION_FIX=1 PICO_RP2040_USB_DEVICE_UFRAME_FIX=1 @@ -307,7 +308,6 @@ function(suppress_tinyusb_warnings) ${PICO_TINYUSB_PATH}/src/device/usbd_control.c ${PICO_TINYUSB_PATH}/src/host/usbh.c ${PICO_TINYUSB_PATH}/src/class/cdc/cdc_device.c - ${PICO_TINYUSB_PATH}/src/class/cdc/cdc_host.c ${PICO_TINYUSB_PATH}/src/class/hid/hid_device.c ${PICO_TINYUSB_PATH}/src/class/hid/hid_host.c ${PICO_TINYUSB_PATH}/src/class/audio/audio_device.c @@ -360,9 +360,6 @@ function(suppress_tinyusb_warnings) set_source_files_properties( ${PICO_TINYUSB_PATH}/src/class/cdc/cdc_device.c COMPILE_FLAGS "-Wno-unreachable-code") - set_source_files_properties( - ${PICO_TINYUSB_PATH}/src/class/cdc/cdc_host.c - COMPILE_FLAGS "-Wno-unreachable-code-fallthrough") set_source_files_properties( ${PICO_TINYUSB_PATH}/lib/fatfs/source/ff.c PROPERTIES diff --git a/pico-sdk/lib/tinyusb/src/CMakeLists.txt b/pico-sdk/lib/tinyusb/src/CMakeLists.txt index 4dcd33c..b6d6c2e 100644 --- a/pico-sdk/lib/tinyusb/src/CMakeLists.txt +++ b/pico-sdk/lib/tinyusb/src/CMakeLists.txt @@ -13,14 +13,22 @@ function(add_tinyusb TARGET) # device ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/device/usbd.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/device/usbd_control.c + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/audio/audio_device.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/cdc/cdc_device.c + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/dfu/dfu_device.c + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/dfu/dfu_rt_device.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/hid/hid_device.c + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/midi/midi_device.c + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/msc/msc_device.c + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/net/ecm_rndis_device.c + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/net/ncm_device.c + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/usbtmc/usbtmc_device.c + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/vendor/vendor_device.c + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/video/video_device.c # host ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/host/usbh.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/host/hub.c - ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/cdc/cdc_host.c ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/hid/hid_host.c - ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/class/vendor/vendor_host.c # typec ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/typec/usbc.c ) @@ -30,7 +38,7 @@ function(add_tinyusb TARGET) ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../lib/networking ) - if (CMAKE_C_COMPILER_ID STREQUAL "GNU") + if (CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang") target_compile_options(${TARGET} PRIVATE -Wall -Wextra diff --git a/pico-sdk/lib/tinyusb/src/class/cdc/cdc.h b/pico-sdk/lib/tinyusb/src/class/cdc/cdc.h index deec32a..5cbd658 100644 --- a/pico-sdk/lib/tinyusb/src/class/cdc/cdc.h +++ b/pico-sdk/lib/tinyusb/src/class/cdc/cdc.h @@ -136,8 +136,7 @@ typedef enum{ //--------------------------------------------------------------------+ /// Communication Interface Management Element Request Codes -typedef enum -{ +typedef enum { CDC_REQUEST_SEND_ENCAPSULATED_COMMAND = 0x00, ///< is used to issue a command in the format of the supported control protocol of the Communications Class interface CDC_REQUEST_GET_ENCAPSULATED_RESPONSE = 0x01, ///< is used to request a response in the format of the supported control protocol of the Communications Class interface. CDC_REQUEST_SET_COMM_FEATURE = 0x02, @@ -180,39 +179,38 @@ typedef enum CDC_REQUEST_GET_ATM_VC_STATISTICS = 0x53, CDC_REQUEST_MDLM_SEMANTIC_MODEL = 0x60, -}cdc_management_request_t; +} cdc_management_request_t; -enum { +typedef enum { CDC_CONTROL_LINE_STATE_DTR = 0x01, CDC_CONTROL_LINE_STATE_RTS = 0x02, -}; +} cdc_control_line_state_t; -enum { +typedef enum { CDC_LINE_CODING_STOP_BITS_1 = 0, // 1 bit CDC_LINE_CODING_STOP_BITS_1_5 = 1, // 1.5 bits CDC_LINE_CODING_STOP_BITS_2 = 2, // 2 bits -}; +} cdc_line_coding_stopbits_t; // TODO Backward compatible for typos. Maybe removed in the future release #define CDC_LINE_CONDING_STOP_BITS_1 CDC_LINE_CODING_STOP_BITS_1 #define CDC_LINE_CONDING_STOP_BITS_1_5 CDC_LINE_CODING_STOP_BITS_1_5 #define CDC_LINE_CONDING_STOP_BITS_2 CDC_LINE_CODING_STOP_BITS_2 -enum { +typedef enum { CDC_LINE_CODING_PARITY_NONE = 0, CDC_LINE_CODING_PARITY_ODD = 1, CDC_LINE_CODING_PARITY_EVEN = 2, CDC_LINE_CODING_PARITY_MARK = 3, CDC_LINE_CODING_PARITY_SPACE = 4, -}; +} cdc_line_coding_parity_t; //--------------------------------------------------------------------+ // Management Element Notification (Notification Endpoint) //--------------------------------------------------------------------+ /// 6.3 Notification Codes -typedef enum -{ +typedef enum { CDC_NOTIF_NETWORK_CONNECTION = 0x00, ///< This notification allows the device to notify the host about network connection status. CDC_NOTIF_RESPONSE_AVAILABLE = 0x01, ///< This notification allows the device to notify the hostthat a response is available. This response can be retrieved with a subsequent \ref CDC_REQUEST_GET_ENCAPSULATED_RESPONSE request. CDC_NOTIF_AUX_JACK_HOOK_STATE = 0x08, diff --git a/pico-sdk/lib/tinyusb/src/class/cdc/cdc_device.c b/pico-sdk/lib/tinyusb/src/class/cdc/cdc_device.c index c26264e..f36725e 100644 --- a/pico-sdk/lib/tinyusb/src/class/cdc/cdc_device.c +++ b/pico-sdk/lib/tinyusb/src/class/cdc/cdc_device.c @@ -43,10 +43,7 @@ //--------------------------------------------------------------------+ // MACRO CONSTANT TYPEDEF //--------------------------------------------------------------------+ -enum -{ - BULK_PACKET_SIZE = (TUD_OPT_HIGH_SPEED ? 512 : 64) -}; +#define BULK_PACKET_SIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) typedef struct { @@ -176,9 +173,11 @@ uint32_t tud_cdc_n_write(uint8_t itf, void const* buffer, uint32_t bufsize) uint16_t ret = tu_fifo_write_n(&p_cdc->tx_ff, buffer, (uint16_t) TU_MIN(bufsize, UINT16_MAX)); // flush if queue more than packet size - // may need to suppress -Wunreachable-code since most of the time CFG_TUD_CDC_TX_BUFSIZE < BULK_PACKET_SIZE - if ( (tu_fifo_count(&p_cdc->tx_ff) >= BULK_PACKET_SIZE) || ((CFG_TUD_CDC_TX_BUFSIZE < BULK_PACKET_SIZE) && tu_fifo_full(&p_cdc->tx_ff)) ) - { + if ( tu_fifo_count(&p_cdc->tx_ff) >= BULK_PACKET_SIZE + #if CFG_TUD_CDC_TX_BUFSIZE < BULK_PACKET_SIZE + || tu_fifo_full(&p_cdc->tx_ff) // check full if fifo size is less than packet size + #endif + ) { tud_cdc_n_write_flush(itf); } @@ -253,11 +252,39 @@ void cdcd_init(void) // In this way, the most current data is prioritized. tu_fifo_config(&p_cdc->tx_ff, p_cdc->tx_ff_buf, TU_ARRAY_SIZE(p_cdc->tx_ff_buf), 1, true); - tu_fifo_config_mutex(&p_cdc->rx_ff, NULL, osal_mutex_create(&p_cdc->rx_ff_mutex)); - tu_fifo_config_mutex(&p_cdc->tx_ff, osal_mutex_create(&p_cdc->tx_ff_mutex), NULL); + #if OSAL_MUTEX_REQUIRED + osal_mutex_t mutex_rd = osal_mutex_create(&p_cdc->rx_ff_mutex); + osal_mutex_t mutex_wr = osal_mutex_create(&p_cdc->tx_ff_mutex); + TU_ASSERT(mutex_rd != NULL && mutex_wr != NULL, ); + + tu_fifo_config_mutex(&p_cdc->rx_ff, NULL, mutex_rd); + tu_fifo_config_mutex(&p_cdc->tx_ff, mutex_wr, NULL); + #endif } } +bool cdcd_deinit(void) { + #if OSAL_MUTEX_REQUIRED + for(uint8_t i=0; irx_ff.mutex_rd; + osal_mutex_t mutex_wr = p_cdc->tx_ff.mutex_wr; + + if (mutex_rd) { + osal_mutex_delete(mutex_rd); + tu_fifo_config_mutex(&p_cdc->rx_ff, NULL, NULL); + } + + if (mutex_wr) { + osal_mutex_delete(mutex_wr); + tu_fifo_config_mutex(&p_cdc->tx_ff, NULL, NULL); + } + } + #endif + + return true; +} + void cdcd_reset(uint8_t rhport) { (void) rhport; @@ -268,7 +295,9 @@ void cdcd_reset(uint8_t rhport) tu_memclr(p_cdc, ITF_MEM_RESET_SIZE); tu_fifo_clear(&p_cdc->rx_ff); + #if !CFG_TUD_CDC_PERSISTENT_TX_BUFF tu_fifo_clear(&p_cdc->tx_ff); + #endif tu_fifo_set_overwritable(&p_cdc->tx_ff, true); } } diff --git a/pico-sdk/lib/tinyusb/src/class/cdc/cdc_device.h b/pico-sdk/lib/tinyusb/src/class/cdc/cdc_device.h index a6e07aa..db709b3 100644 --- a/pico-sdk/lib/tinyusb/src/class/cdc/cdc_device.h +++ b/pico-sdk/lib/tinyusb/src/class/cdc/cdc_device.h @@ -41,6 +41,12 @@ #define CFG_TUD_CDC_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) #endif +// By default the TX fifo buffer is cleared on connect / bus reset. +// Enable this to persist any data in the fifo instead. +#ifndef CFG_TUD_CDC_PERSISTENT_TX_BUFF + #define CFG_TUD_CDC_PERSISTENT_TX_BUFF (0) +#endif + #ifdef __cplusplus extern "C" { #endif @@ -247,6 +253,7 @@ static inline bool tud_cdc_write_clear(void) // INTERNAL USBD-CLASS DRIVER API //--------------------------------------------------------------------+ void cdcd_init (void); +bool cdcd_deinit (void); void cdcd_reset (uint8_t rhport); uint16_t cdcd_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); bool cdcd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); diff --git a/pico-sdk/lib/tinyusb/src/class/cdc/cdc_host.c b/pico-sdk/lib/tinyusb/src/class/cdc/cdc_host.c deleted file mode 100644 index 2463671..0000000 --- a/pico-sdk/lib/tinyusb/src/class/cdc/cdc_host.c +++ /dev/null @@ -1,1174 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#include "tusb_option.h" - -#if (CFG_TUH_ENABLED && CFG_TUH_CDC) - -#include "host/usbh.h" -#include "host/usbh_pvt.h" - -#include "cdc_host.h" - -// Level where CFG_TUSB_DEBUG must be at least for this driver is logged -#ifndef CFG_TUH_CDC_LOG_LEVEL - #define CFG_TUH_CDC_LOG_LEVEL CFG_TUH_LOG_LEVEL -#endif - -#define TU_LOG_DRV(...) TU_LOG(CFG_TUH_CDC_LOG_LEVEL, __VA_ARGS__) - -//--------------------------------------------------------------------+ -// Host CDC Interface -//--------------------------------------------------------------------+ - -typedef struct { - uint8_t daddr; - uint8_t bInterfaceNumber; - uint8_t bInterfaceSubClass; - uint8_t bInterfaceProtocol; - - uint8_t serial_drid; // Serial Driver ID - cdc_acm_capability_t acm_capability; - uint8_t ep_notif; - - uint8_t line_state; // DTR (bit0), RTS (bit1) - TU_ATTR_ALIGNED(4) cdc_line_coding_t line_coding; // Baudrate, stop bits, parity, data width - - tuh_xfer_cb_t user_control_cb; - - struct { - tu_edpt_stream_t tx; - tu_edpt_stream_t rx; - - uint8_t tx_ff_buf[CFG_TUH_CDC_TX_BUFSIZE]; - CFG_TUH_MEM_ALIGN uint8_t tx_ep_buf[CFG_TUH_CDC_TX_EPSIZE]; - - uint8_t rx_ff_buf[CFG_TUH_CDC_TX_BUFSIZE]; - CFG_TUH_MEM_ALIGN uint8_t rx_ep_buf[CFG_TUH_CDC_TX_EPSIZE]; - } stream; - -} cdch_interface_t; - -CFG_TUH_MEM_SECTION -static cdch_interface_t cdch_data[CFG_TUH_CDC]; - -//--------------------------------------------------------------------+ -// Serial Driver -//--------------------------------------------------------------------+ - -//------------- ACM prototypes -------------// -static bool acm_open(uint8_t daddr, tusb_desc_interface_t const *itf_desc, uint16_t max_len); -static void acm_process_config(tuh_xfer_t* xfer); - -static bool acm_set_line_coding(cdch_interface_t* p_cdc, cdc_line_coding_t const* line_coding, tuh_xfer_cb_t complete_cb, uintptr_t user_data); -static bool acm_set_control_line_state(cdch_interface_t* p_cdc, uint16_t line_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data); -static bool acm_set_baudrate(cdch_interface_t* p_cdc, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data); - -//------------- FTDI prototypes -------------// -#if CFG_TUH_CDC_FTDI -#include "serial/ftdi_sio.h" - -static uint16_t const ftdi_vid_pid_list[][2] = {CFG_TUH_CDC_FTDI_VID_PID_LIST }; -enum { - FTDI_PID_COUNT = sizeof(ftdi_vid_pid_list) / sizeof(ftdi_vid_pid_list[0]) -}; - -// Store last request baudrate since divisor to baudrate is not easy -static uint32_t _ftdi_requested_baud; - -static bool ftdi_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, uint16_t max_len); -static void ftdi_process_config(tuh_xfer_t* xfer); - -static bool ftdi_sio_set_modem_ctrl(cdch_interface_t* p_cdc, uint16_t line_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data); -static bool ftdi_sio_set_baudrate(cdch_interface_t* p_cdc, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data); -#endif - -//------------- CP210X prototypes -------------// -#if CFG_TUH_CDC_CP210X -#include "serial/cp210x.h" - -static uint16_t const cp210x_vid_pid_list[][2] = {CFG_TUH_CDC_CP210X_VID_PID_LIST }; -enum { - CP210X_PID_COUNT = sizeof(cp210x_vid_pid_list) / sizeof(cp210x_vid_pid_list[0]) -}; - -static bool cp210x_open(uint8_t daddr, tusb_desc_interface_t const *itf_desc, uint16_t max_len); -static void cp210x_process_config(tuh_xfer_t* xfer); - -static bool cp210x_set_modem_ctrl(cdch_interface_t* p_cdc, uint16_t line_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data); -static bool cp210x_set_baudrate(cdch_interface_t* p_cdc, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data); -#endif - -enum { - SERIAL_DRIVER_ACM = 0, - -#if CFG_TUH_CDC_FTDI - SERIAL_DRIVER_FTDI, -#endif - -#if CFG_TUH_CDC_CP210X - SERIAL_DRIVER_CP210X, -#endif -}; - -typedef struct { - void (*const process_set_config)(tuh_xfer_t* xfer); - bool (*const set_control_line_state)(cdch_interface_t* p_cdc, uint16_t line_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data); - bool (*const set_baudrate)(cdch_interface_t* p_cdc, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data); -} cdch_serial_driver_t; - -// Note driver list must be in the same order as SERIAL_DRIVER enum -static const cdch_serial_driver_t serial_drivers[] = { - { .process_set_config = acm_process_config, - .set_control_line_state = acm_set_control_line_state, - .set_baudrate = acm_set_baudrate - }, - - #if CFG_TUH_CDC_FTDI - { .process_set_config = ftdi_process_config, - .set_control_line_state = ftdi_sio_set_modem_ctrl, - .set_baudrate = ftdi_sio_set_baudrate - }, - #endif - - #if CFG_TUH_CDC_CP210X - { .process_set_config = cp210x_process_config, - .set_control_line_state = cp210x_set_modem_ctrl, - .set_baudrate = cp210x_set_baudrate - }, - #endif -}; - -enum { - SERIAL_DRIVER_COUNT = sizeof(serial_drivers) / sizeof(serial_drivers[0]) -}; - -//--------------------------------------------------------------------+ -// INTERNAL OBJECT & FUNCTION DECLARATION -//--------------------------------------------------------------------+ - -static inline cdch_interface_t* get_itf(uint8_t idx) -{ - TU_ASSERT(idx < CFG_TUH_CDC, NULL); - cdch_interface_t* p_cdc = &cdch_data[idx]; - - return (p_cdc->daddr != 0) ? p_cdc : NULL; -} - -static inline uint8_t get_idx_by_ep_addr(uint8_t daddr, uint8_t ep_addr) -{ - for(uint8_t i=0; idaddr == daddr) && - (ep_addr == p_cdc->ep_notif || ep_addr == p_cdc->stream.rx.ep_addr || ep_addr == p_cdc->stream.tx.ep_addr)) - { - return i; - } - } - - return TUSB_INDEX_INVALID_8; -} - - -static cdch_interface_t* make_new_itf(uint8_t daddr, tusb_desc_interface_t const *itf_desc) -{ - for(uint8_t i=0; idaddr = daddr; - p_cdc->bInterfaceNumber = itf_desc->bInterfaceNumber; - p_cdc->bInterfaceSubClass = itf_desc->bInterfaceSubClass; - p_cdc->bInterfaceProtocol = itf_desc->bInterfaceProtocol; - p_cdc->line_state = 0; - return p_cdc; - } - } - - return NULL; -} - -static bool open_ep_stream_pair(cdch_interface_t* p_cdc , tusb_desc_endpoint_t const *desc_ep); -static void set_config_complete(cdch_interface_t * p_cdc, uint8_t idx, uint8_t itf_num); -static void cdch_internal_control_complete(tuh_xfer_t* xfer); - -//--------------------------------------------------------------------+ -// APPLICATION API -//--------------------------------------------------------------------+ - -uint8_t tuh_cdc_itf_get_index(uint8_t daddr, uint8_t itf_num) -{ - for(uint8_t i=0; idaddr == daddr && p_cdc->bInterfaceNumber == itf_num) return i; - } - - return TUSB_INDEX_INVALID_8; -} - -bool tuh_cdc_itf_get_info(uint8_t idx, tuh_itf_info_t* info) -{ - cdch_interface_t* p_cdc = get_itf(idx); - TU_VERIFY(p_cdc && info); - - info->daddr = p_cdc->daddr; - - // re-construct descriptor - tusb_desc_interface_t* desc = &info->desc; - desc->bLength = sizeof(tusb_desc_interface_t); - desc->bDescriptorType = TUSB_DESC_INTERFACE; - - desc->bInterfaceNumber = p_cdc->bInterfaceNumber; - desc->bAlternateSetting = 0; - desc->bNumEndpoints = 2u + (p_cdc->ep_notif ? 1u : 0u); - desc->bInterfaceClass = TUSB_CLASS_CDC; - desc->bInterfaceSubClass = p_cdc->bInterfaceSubClass; - desc->bInterfaceProtocol = p_cdc->bInterfaceProtocol; - desc->iInterface = 0; // not used yet - - return true; -} - -bool tuh_cdc_mounted(uint8_t idx) -{ - cdch_interface_t* p_cdc = get_itf(idx); - return p_cdc != NULL; -} - -bool tuh_cdc_get_dtr(uint8_t idx) -{ - cdch_interface_t* p_cdc = get_itf(idx); - TU_VERIFY(p_cdc); - - return (p_cdc->line_state & CDC_CONTROL_LINE_STATE_DTR) ? true : false; -} - -bool tuh_cdc_get_rts(uint8_t idx) -{ - cdch_interface_t* p_cdc = get_itf(idx); - TU_VERIFY(p_cdc); - - return (p_cdc->line_state & CDC_CONTROL_LINE_STATE_RTS) ? true : false; -} - -bool tuh_cdc_get_local_line_coding(uint8_t idx, cdc_line_coding_t* line_coding) -{ - cdch_interface_t* p_cdc = get_itf(idx); - TU_VERIFY(p_cdc); - - *line_coding = p_cdc->line_coding; - - return true; -} - -//--------------------------------------------------------------------+ -// Write -//--------------------------------------------------------------------+ - -uint32_t tuh_cdc_write(uint8_t idx, void const* buffer, uint32_t bufsize) -{ - cdch_interface_t* p_cdc = get_itf(idx); - TU_VERIFY(p_cdc); - - return tu_edpt_stream_write(&p_cdc->stream.tx, buffer, bufsize); -} - -uint32_t tuh_cdc_write_flush(uint8_t idx) -{ - cdch_interface_t* p_cdc = get_itf(idx); - TU_VERIFY(p_cdc); - - return tu_edpt_stream_write_xfer(&p_cdc->stream.tx); -} - -bool tuh_cdc_write_clear(uint8_t idx) -{ - cdch_interface_t* p_cdc = get_itf(idx); - TU_VERIFY(p_cdc); - - return tu_edpt_stream_clear(&p_cdc->stream.tx); -} - -uint32_t tuh_cdc_write_available(uint8_t idx) -{ - cdch_interface_t* p_cdc = get_itf(idx); - TU_VERIFY(p_cdc); - - return tu_edpt_stream_write_available(&p_cdc->stream.tx); -} - -//--------------------------------------------------------------------+ -// Read -//--------------------------------------------------------------------+ - -uint32_t tuh_cdc_read (uint8_t idx, void* buffer, uint32_t bufsize) -{ - cdch_interface_t* p_cdc = get_itf(idx); - TU_VERIFY(p_cdc); - - return tu_edpt_stream_read(&p_cdc->stream.rx, buffer, bufsize); -} - -uint32_t tuh_cdc_read_available(uint8_t idx) -{ - cdch_interface_t* p_cdc = get_itf(idx); - TU_VERIFY(p_cdc); - - return tu_edpt_stream_read_available(&p_cdc->stream.rx); -} - -bool tuh_cdc_peek(uint8_t idx, uint8_t* ch) -{ - cdch_interface_t* p_cdc = get_itf(idx); - TU_VERIFY(p_cdc); - - return tu_edpt_stream_peek(&p_cdc->stream.rx, ch); -} - -bool tuh_cdc_read_clear (uint8_t idx) -{ - cdch_interface_t* p_cdc = get_itf(idx); - TU_VERIFY(p_cdc); - - bool ret = tu_edpt_stream_clear(&p_cdc->stream.rx); - tu_edpt_stream_read_xfer(&p_cdc->stream.rx); - return ret; -} - -//--------------------------------------------------------------------+ -// Control Endpoint API -//--------------------------------------------------------------------+ - -// internal control complete to update state such as line state, encoding -static void cdch_internal_control_complete(tuh_xfer_t* xfer) -{ - uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); - uint8_t idx = tuh_cdc_itf_get_index(xfer->daddr, itf_num); - cdch_interface_t* p_cdc = get_itf(idx); - TU_ASSERT(p_cdc, ); - - if (xfer->result == XFER_RESULT_SUCCESS) - { - switch (p_cdc->serial_drid) { - case SERIAL_DRIVER_ACM: - switch (xfer->setup->bRequest) { - case CDC_REQUEST_SET_CONTROL_LINE_STATE: - p_cdc->line_state = (uint8_t) tu_le16toh(xfer->setup->wValue); - break; - - case CDC_REQUEST_SET_LINE_CODING: { - uint16_t const len = tu_min16(sizeof(cdc_line_coding_t), tu_le16toh(xfer->setup->wLength)); - memcpy(&p_cdc->line_coding, xfer->buffer, len); - } - break; - - default: break; - } - break; - - #if CFG_TUH_CDC_FTDI - case SERIAL_DRIVER_FTDI: - switch (xfer->setup->bRequest) { - case FTDI_SIO_MODEM_CTRL: - p_cdc->line_state = (uint8_t) (tu_le16toh(xfer->setup->wValue) & 0x00ff); - break; - - case FTDI_SIO_SET_BAUD_RATE: - // convert from divisor to baudrate is not supported - p_cdc->line_coding.bit_rate = _ftdi_requested_baud; - break; - - default: break; - } - break; - #endif - - #if CFG_TUH_CDC_CP210X - case SERIAL_DRIVER_CP210X: - switch(xfer->setup->bRequest) { - case CP210X_SET_MHS: - p_cdc->line_state = (uint8_t) (tu_le16toh(xfer->setup->wValue) & 0x00ff); - break; - - case CP210X_SET_BAUDRATE: { - uint32_t baudrate; - memcpy(&baudrate, xfer->buffer, sizeof(uint32_t)); - p_cdc->line_coding.bit_rate = tu_le32toh(baudrate); - } - break; - } - break; - #endif - - default: break; - } - } - - xfer->complete_cb = p_cdc->user_control_cb; - if (xfer->complete_cb) { - xfer->complete_cb(xfer); - } -} - -bool tuh_cdc_set_control_line_state(uint8_t idx, uint16_t line_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - cdch_interface_t* p_cdc = get_itf(idx); - TU_VERIFY(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT); - cdch_serial_driver_t const* driver = &serial_drivers[p_cdc->serial_drid]; - - if ( complete_cb ) { - return driver->set_control_line_state(p_cdc, line_state, complete_cb, user_data); - }else { - // blocking - xfer_result_t result = XFER_RESULT_INVALID; - bool ret = driver->set_control_line_state(p_cdc, line_state, complete_cb, (uintptr_t) &result); - - if (user_data) { - // user_data is not NULL, return result via user_data - *((xfer_result_t*) user_data) = result; - } - - TU_VERIFY(ret && result == XFER_RESULT_SUCCESS); - - p_cdc->line_state = (uint8_t) line_state; - return true; - } -} - -bool tuh_cdc_set_baudrate(uint8_t idx, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - cdch_interface_t* p_cdc = get_itf(idx); - TU_VERIFY(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT); - cdch_serial_driver_t const* driver = &serial_drivers[p_cdc->serial_drid]; - - if ( complete_cb ) { - return driver->set_baudrate(p_cdc, baudrate, complete_cb, user_data); - }else { - // blocking - xfer_result_t result = XFER_RESULT_INVALID; - bool ret = driver->set_baudrate(p_cdc, baudrate, complete_cb, (uintptr_t) &result); - - if (user_data) { - // user_data is not NULL, return result via user_data - *((xfer_result_t*) user_data) = result; - } - - TU_VERIFY(ret && result == XFER_RESULT_SUCCESS); - - p_cdc->line_coding.bit_rate = baudrate; - return true; - } -} - -bool tuh_cdc_set_line_coding(uint8_t idx, cdc_line_coding_t const* line_coding, tuh_xfer_cb_t complete_cb, uintptr_t user_data) -{ - cdch_interface_t* p_cdc = get_itf(idx); - // only ACM support this set line coding request - TU_VERIFY(p_cdc && p_cdc->serial_drid == SERIAL_DRIVER_ACM); - TU_VERIFY(p_cdc->acm_capability.support_line_request); - - if ( complete_cb ) { - return acm_set_line_coding(p_cdc, line_coding, complete_cb, user_data); - }else { - // blocking - xfer_result_t result = XFER_RESULT_INVALID; - bool ret = acm_set_line_coding(p_cdc, line_coding, complete_cb, (uintptr_t) &result); - - if (user_data) { - // user_data is not NULL, return result via user_data - *((xfer_result_t*) user_data) = result; - } - - TU_VERIFY(ret && result == XFER_RESULT_SUCCESS); - - p_cdc->line_coding = *line_coding; - return true; - } -} - -//--------------------------------------------------------------------+ -// CLASS-USBH API -//--------------------------------------------------------------------+ - -void cdch_init(void) -{ - tu_memclr(cdch_data, sizeof(cdch_data)); - - for(size_t i=0; istream.tx, true, true, false, - p_cdc->stream.tx_ff_buf, CFG_TUH_CDC_TX_BUFSIZE, - p_cdc->stream.tx_ep_buf, CFG_TUH_CDC_TX_EPSIZE); - - tu_edpt_stream_init(&p_cdc->stream.rx, true, false, false, - p_cdc->stream.rx_ff_buf, CFG_TUH_CDC_RX_BUFSIZE, - p_cdc->stream.rx_ep_buf, CFG_TUH_CDC_RX_EPSIZE); - } -} - -void cdch_close(uint8_t daddr) -{ - for(uint8_t idx=0; idxdaddr == daddr) - { - TU_LOG_DRV(" CDCh close addr = %u index = %u\r\n", daddr, idx); - - // Invoke application callback - if (tuh_cdc_umount_cb) tuh_cdc_umount_cb(idx); - - //tu_memclr(p_cdc, sizeof(cdch_interface_t)); - p_cdc->daddr = 0; - p_cdc->bInterfaceNumber = 0; - tu_edpt_stream_close(&p_cdc->stream.tx); - tu_edpt_stream_close(&p_cdc->stream.rx); - } - } -} - -bool cdch_xfer_cb(uint8_t daddr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes) { - // TODO handle stall response, retry failed transfer ... - TU_ASSERT(event == XFER_RESULT_SUCCESS); - - uint8_t const idx = get_idx_by_ep_addr(daddr, ep_addr); - cdch_interface_t * p_cdc = get_itf(idx); - TU_ASSERT(p_cdc); - - if ( ep_addr == p_cdc->stream.tx.ep_addr ) { - // invoke tx complete callback to possibly refill tx fifo - if (tuh_cdc_tx_complete_cb) tuh_cdc_tx_complete_cb(idx); - - if ( 0 == tu_edpt_stream_write_xfer(&p_cdc->stream.tx) ) { - // If there is no data left, a ZLP should be sent if: - // - xferred_bytes is multiple of EP Packet size and not zero - tu_edpt_stream_write_zlp_if_needed(&p_cdc->stream.tx, xferred_bytes); - } - } - else if ( ep_addr == p_cdc->stream.rx.ep_addr ) { - #if CFG_TUH_CDC_FTDI - if (p_cdc->serial_drid == SERIAL_DRIVER_FTDI) { - // FTDI reserve 2 bytes for status - // FTDI status -// uint8_t status[2] = { -// p_cdc->stream.rx.ep_buf[0], -// p_cdc->stream.rx.ep_buf[1] -// }; - tu_edpt_stream_read_xfer_complete_offset(&p_cdc->stream.rx, xferred_bytes, 2); - }else - #endif - { - tu_edpt_stream_read_xfer_complete(&p_cdc->stream.rx, xferred_bytes); - } - - // invoke receive callback - if (tuh_cdc_rx_cb) tuh_cdc_rx_cb(idx); - - // prepare for next transfer if needed - tu_edpt_stream_read_xfer(&p_cdc->stream.rx); - }else if ( ep_addr == p_cdc->ep_notif ) { - // TODO handle notification endpoint - }else { - TU_ASSERT(false); - } - - return true; -} - -//--------------------------------------------------------------------+ -// Enumeration -//--------------------------------------------------------------------+ - -static bool open_ep_stream_pair(cdch_interface_t* p_cdc, tusb_desc_endpoint_t const *desc_ep) -{ - for(size_t i=0; i<2; i++) - { - TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && - TUSB_XFER_BULK == desc_ep->bmAttributes.xfer); - - TU_ASSERT(tuh_edpt_open(p_cdc->daddr, desc_ep)); - - if ( tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN ) - { - tu_edpt_stream_open(&p_cdc->stream.rx, p_cdc->daddr, desc_ep); - }else - { - tu_edpt_stream_open(&p_cdc->stream.tx, p_cdc->daddr, desc_ep); - } - - desc_ep = (tusb_desc_endpoint_t const*) tu_desc_next(desc_ep); - } - - return true; -} - -bool cdch_open(uint8_t rhport, uint8_t daddr, tusb_desc_interface_t const *itf_desc, uint16_t max_len) -{ - (void) rhport; - - // Only support ACM subclass - // Note: Protocol 0xFF can be RNDIS device - if ( TUSB_CLASS_CDC == itf_desc->bInterfaceClass && - CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL == itf_desc->bInterfaceSubClass) - { - return acm_open(daddr, itf_desc, max_len); - } - #if CFG_TUH_CDC_FTDI || CFG_TUH_CDC_CP210X - else if ( TUSB_CLASS_VENDOR_SPECIFIC == itf_desc->bInterfaceClass ) - { - uint16_t vid, pid; - TU_VERIFY(tuh_vid_pid_get(daddr, &vid, &pid)); - - #if CFG_TUH_CDC_FTDI - for (size_t i = 0; i < FTDI_PID_COUNT; i++) { - if (ftdi_vid_pid_list[i][0] == vid && ftdi_vid_pid_list[i][1] == pid) { - return ftdi_open(daddr, itf_desc, max_len); - } - } - #endif - - #if CFG_TUH_CDC_CP210X - for (size_t i = 0; i < CP210X_PID_COUNT; i++) { - if (cp210x_vid_pid_list[i][0] == vid && cp210x_vid_pid_list[i][1] == pid) { - return cp210x_open(daddr, itf_desc, max_len); - } - } - #endif - } - #endif - - return false; -} - -static void set_config_complete(cdch_interface_t * p_cdc, uint8_t idx, uint8_t itf_num) { - if (tuh_cdc_mount_cb) tuh_cdc_mount_cb(idx); - - // Prepare for incoming data - tu_edpt_stream_read_xfer(&p_cdc->stream.rx); - - // notify usbh that driver enumeration is complete - usbh_driver_set_config_complete(p_cdc->daddr, itf_num); -} - - -bool cdch_set_config(uint8_t daddr, uint8_t itf_num) -{ - tusb_control_request_t request; - request.wIndex = tu_htole16((uint16_t) itf_num); - - // fake transfer to kick-off process - tuh_xfer_t xfer; - xfer.daddr = daddr; - xfer.result = XFER_RESULT_SUCCESS; - xfer.setup = &request; - xfer.user_data = 0; // initial state - - uint8_t const idx = tuh_cdc_itf_get_index(daddr, itf_num); - cdch_interface_t * p_cdc = get_itf(idx); - TU_ASSERT(p_cdc && p_cdc->serial_drid < SERIAL_DRIVER_COUNT); - - serial_drivers[p_cdc->serial_drid].process_set_config(&xfer); - return true; -} - -//--------------------------------------------------------------------+ -// ACM -//--------------------------------------------------------------------+ - -enum { - CONFIG_ACM_SET_CONTROL_LINE_STATE = 0, - CONFIG_ACM_SET_LINE_CODING, - CONFIG_ACM_COMPLETE, -}; - -static bool acm_open(uint8_t daddr, tusb_desc_interface_t const *itf_desc, uint16_t max_len) -{ - uint8_t const * p_desc_end = ((uint8_t const*) itf_desc) + max_len; - - cdch_interface_t * p_cdc = make_new_itf(daddr, itf_desc); - TU_VERIFY(p_cdc); - - p_cdc->serial_drid = SERIAL_DRIVER_ACM; - - //------------- Control Interface -------------// - uint8_t const * p_desc = tu_desc_next(itf_desc); - - // Communication Functional Descriptors - while( (p_desc < p_desc_end) && (TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc)) ) - { - if ( CDC_FUNC_DESC_ABSTRACT_CONTROL_MANAGEMENT == cdc_functional_desc_typeof(p_desc) ) - { - // save ACM bmCapabilities - p_cdc->acm_capability = ((cdc_desc_func_acm_t const *) p_desc)->bmCapabilities; - } - - p_desc = tu_desc_next(p_desc); - } - - // Open notification endpoint of control interface if any - if (itf_desc->bNumEndpoints == 1) - { - TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(p_desc)); - tusb_desc_endpoint_t const * desc_ep = (tusb_desc_endpoint_t const *) p_desc; - - TU_ASSERT( tuh_edpt_open(daddr, desc_ep) ); - p_cdc->ep_notif = desc_ep->bEndpointAddress; - - p_desc = tu_desc_next(p_desc); - } - - //------------- Data Interface (if any) -------------// - if ( (TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) && - (TUSB_CLASS_CDC_DATA == ((tusb_desc_interface_t const *) p_desc)->bInterfaceClass) ) - { - // next to endpoint descriptor - p_desc = tu_desc_next(p_desc); - - // data endpoints expected to be in pairs - TU_ASSERT(open_ep_stream_pair(p_cdc, (tusb_desc_endpoint_t const *) p_desc)); - } - - return true; -} - -static void acm_process_config(tuh_xfer_t* xfer) -{ - uintptr_t const state = xfer->user_data; - uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); - uint8_t const idx = tuh_cdc_itf_get_index(xfer->daddr, itf_num); - cdch_interface_t * p_cdc = get_itf(idx); - TU_ASSERT(p_cdc, ); - - switch(state) - { - case CONFIG_ACM_SET_CONTROL_LINE_STATE: - #if CFG_TUH_CDC_LINE_CONTROL_ON_ENUM - if (p_cdc->acm_capability.support_line_request) - { - TU_ASSERT(acm_set_control_line_state(p_cdc, CFG_TUH_CDC_LINE_CONTROL_ON_ENUM, acm_process_config, - CONFIG_ACM_SET_LINE_CODING), ); - break; - } - #endif - TU_ATTR_FALLTHROUGH; - - case CONFIG_ACM_SET_LINE_CODING: - #ifdef CFG_TUH_CDC_LINE_CODING_ON_ENUM - if (p_cdc->acm_capability.support_line_request) - { - cdc_line_coding_t line_coding = CFG_TUH_CDC_LINE_CODING_ON_ENUM; - TU_ASSERT(acm_set_line_coding(p_cdc, &line_coding, acm_process_config, CONFIG_ACM_COMPLETE), ); - break; - } - #endif - TU_ATTR_FALLTHROUGH; - - case CONFIG_ACM_COMPLETE: - // itf_num+1 to account for data interface as well - set_config_complete(p_cdc, idx, itf_num+1); - break; - - default: break; - } -} - -static bool acm_set_control_line_state(cdch_interface_t* p_cdc, uint16_t line_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - TU_VERIFY(p_cdc->acm_capability.support_line_request); - TU_LOG_DRV("CDC ACM Set Control Line State\r\n"); - - tusb_control_request_t const request = { - .bmRequestType_bit = { - .recipient = TUSB_REQ_RCPT_INTERFACE, - .type = TUSB_REQ_TYPE_CLASS, - .direction = TUSB_DIR_OUT - }, - .bRequest = CDC_REQUEST_SET_CONTROL_LINE_STATE, - .wValue = tu_htole16(line_state), - .wIndex = tu_htole16((uint16_t) p_cdc->bInterfaceNumber), - .wLength = 0 - }; - - p_cdc->user_control_cb = complete_cb; - - tuh_xfer_t xfer = { - .daddr = p_cdc->daddr, - .ep_addr = 0, - .setup = &request, - .buffer = NULL, - .complete_cb = complete_cb ? cdch_internal_control_complete : NULL, // complete_cb is NULL for sync call - .user_data = user_data - }; - - TU_ASSERT(tuh_control_xfer(&xfer)); - return true; -} - -static bool acm_set_line_coding(cdch_interface_t* p_cdc, cdc_line_coding_t const* line_coding, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - TU_LOG_DRV("CDC ACM Set Line Conding\r\n"); - - tusb_control_request_t const request = { - .bmRequestType_bit = { - .recipient = TUSB_REQ_RCPT_INTERFACE, - .type = TUSB_REQ_TYPE_CLASS, - .direction = TUSB_DIR_OUT - }, - .bRequest = CDC_REQUEST_SET_LINE_CODING, - .wValue = 0, - .wIndex = tu_htole16(p_cdc->bInterfaceNumber), - .wLength = tu_htole16(sizeof(cdc_line_coding_t)) - }; - - // use usbh enum buf to hold line coding since user line_coding variable does not live long enough - uint8_t* enum_buf = usbh_get_enum_buf(); - memcpy(enum_buf, line_coding, sizeof(cdc_line_coding_t)); - - p_cdc->user_control_cb = complete_cb; - tuh_xfer_t xfer = { - .daddr = p_cdc->daddr, - .ep_addr = 0, - .setup = &request, - .buffer = enum_buf, - .complete_cb = complete_cb ? cdch_internal_control_complete : NULL, // complete_cb is NULL for sync call - .user_data = user_data - }; - - TU_ASSERT(tuh_control_xfer(&xfer)); - return true; -} - -static bool acm_set_baudrate(cdch_interface_t* p_cdc, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - TU_VERIFY(p_cdc->acm_capability.support_line_request); - cdc_line_coding_t line_coding = p_cdc->line_coding; - line_coding.bit_rate = baudrate; - return acm_set_line_coding(p_cdc, &line_coding, complete_cb, user_data); -} - -//--------------------------------------------------------------------+ -// FTDI -//--------------------------------------------------------------------+ -#if CFG_TUH_CDC_FTDI - -enum { - CONFIG_FTDI_RESET = 0, - CONFIG_FTDI_MODEM_CTRL, - CONFIG_FTDI_SET_BAUDRATE, - CONFIG_FTDI_SET_DATA, - CONFIG_FTDI_COMPLETE -}; - -static bool ftdi_open(uint8_t daddr, const tusb_desc_interface_t *itf_desc, uint16_t max_len) { - // FTDI Interface includes 1 vendor interface + 2 bulk endpoints - TU_VERIFY(itf_desc->bInterfaceSubClass == 0xff && itf_desc->bInterfaceProtocol == 0xff && itf_desc->bNumEndpoints == 2); - TU_VERIFY(sizeof(tusb_desc_interface_t) + 2*sizeof(tusb_desc_endpoint_t) <= max_len); - - cdch_interface_t * p_cdc = make_new_itf(daddr, itf_desc); - TU_VERIFY(p_cdc); - - TU_LOG_DRV("FTDI opened\r\n"); - - p_cdc->serial_drid = SERIAL_DRIVER_FTDI; - - // endpoint pair - tusb_desc_endpoint_t const * desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc); - - // data endpoints expected to be in pairs - return open_ep_stream_pair(p_cdc, desc_ep); -} - -// set request without data -static bool ftdi_sio_set_request(cdch_interface_t* p_cdc, uint8_t command, uint16_t value, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - tusb_control_request_t const request = { - .bmRequestType_bit = { - .recipient = TUSB_REQ_RCPT_DEVICE, - .type = TUSB_REQ_TYPE_VENDOR, - .direction = TUSB_DIR_OUT - }, - .bRequest = command, - .wValue = tu_htole16(value), - .wIndex = 0, - .wLength = 0 - }; - - tuh_xfer_t xfer = { - .daddr = p_cdc->daddr, - .ep_addr = 0, - .setup = &request, - .buffer = NULL, - .complete_cb = complete_cb, - .user_data = user_data - }; - - return tuh_control_xfer(&xfer); -} - -static bool ftdi_sio_reset(cdch_interface_t* p_cdc, tuh_xfer_cb_t complete_cb, uintptr_t user_data) -{ - return ftdi_sio_set_request(p_cdc, FTDI_SIO_RESET, FTDI_SIO_RESET_SIO, complete_cb, user_data); -} - -static bool ftdi_sio_set_modem_ctrl(cdch_interface_t* p_cdc, uint16_t line_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data) -{ - TU_LOG_DRV("CDC FTDI Set Control Line State\r\n"); - p_cdc->user_control_cb = complete_cb; - TU_ASSERT(ftdi_sio_set_request(p_cdc, FTDI_SIO_MODEM_CTRL, 0x0300 | line_state, - complete_cb ? cdch_internal_control_complete : NULL, user_data)); - return true; -} - -static uint32_t ftdi_232bm_baud_base_to_divisor(uint32_t baud, uint32_t base) -{ - const uint8_t divfrac[8] = { 0, 3, 2, 4, 1, 5, 6, 7 }; - uint32_t divisor; - - /* divisor shifted 3 bits to the left */ - uint32_t divisor3 = base / (2 * baud); - divisor = (divisor3 >> 3); - divisor |= (uint32_t) divfrac[divisor3 & 0x7] << 14; - - /* Deal with special cases for highest baud rates. */ - if (divisor == 1) { /* 1.0 */ - divisor = 0; - } - else if (divisor == 0x4001) { /* 1.5 */ - divisor = 1; - } - - return divisor; -} - -static uint32_t ftdi_232bm_baud_to_divisor(uint32_t baud) -{ - return ftdi_232bm_baud_base_to_divisor(baud, 48000000u); -} - -static bool ftdi_sio_set_baudrate(cdch_interface_t* p_cdc, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data) -{ - uint16_t const divisor = (uint16_t) ftdi_232bm_baud_to_divisor(baudrate); - TU_LOG_DRV("CDC FTDI Set BaudRate = %lu, divisor = 0x%04x\r\n", baudrate, divisor); - - p_cdc->user_control_cb = complete_cb; - _ftdi_requested_baud = baudrate; - TU_ASSERT(ftdi_sio_set_request(p_cdc, FTDI_SIO_SET_BAUD_RATE, divisor, - complete_cb ? cdch_internal_control_complete : NULL, user_data)); - - return true; -} - -static void ftdi_process_config(tuh_xfer_t* xfer) { - uintptr_t const state = xfer->user_data; - uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); - uint8_t const idx = tuh_cdc_itf_get_index(xfer->daddr, itf_num); - cdch_interface_t * p_cdc = get_itf(idx); - TU_ASSERT(p_cdc, ); - - switch(state) { - // Note may need to read FTDI eeprom - case CONFIG_FTDI_RESET: - TU_ASSERT(ftdi_sio_reset(p_cdc, ftdi_process_config, CONFIG_FTDI_MODEM_CTRL),); - break; - - case CONFIG_FTDI_MODEM_CTRL: - #if CFG_TUH_CDC_LINE_CONTROL_ON_ENUM - TU_ASSERT( - ftdi_sio_set_modem_ctrl(p_cdc, CFG_TUH_CDC_LINE_CONTROL_ON_ENUM, ftdi_process_config, CONFIG_FTDI_SET_BAUDRATE),); - break; - #else - TU_ATTR_FALLTHROUGH; - #endif - - case CONFIG_FTDI_SET_BAUDRATE: { - #ifdef CFG_TUH_CDC_LINE_CODING_ON_ENUM - cdc_line_coding_t line_coding = CFG_TUH_CDC_LINE_CODING_ON_ENUM; - TU_ASSERT(ftdi_sio_set_baudrate(p_cdc, line_coding.bit_rate, ftdi_process_config, CONFIG_FTDI_SET_DATA),); - break; - #else - TU_ATTR_FALLTHROUGH; - #endif - } - - case CONFIG_FTDI_SET_DATA: { - #if 0 // TODO set data format - #ifdef CFG_TUH_CDC_LINE_CODING_ON_ENUM - cdc_line_coding_t line_coding = CFG_TUH_CDC_LINE_CODING_ON_ENUM; - TU_ASSERT(ftdi_sio_set_data(p_cdc, process_ftdi_config, CONFIG_FTDI_COMPLETE),); - break; - #endif - #endif - - TU_ATTR_FALLTHROUGH; - } - - case CONFIG_FTDI_COMPLETE: - set_config_complete(p_cdc, idx, itf_num); - break; - - default: - break; - } -} - -#endif - -//--------------------------------------------------------------------+ -// CP210x -//--------------------------------------------------------------------+ - -#if CFG_TUH_CDC_CP210X - -enum { - CONFIG_CP210X_IFC_ENABLE = 0, - CONFIG_CP210X_SET_BAUDRATE, - CONFIG_CP210X_SET_LINE_CTL, - CONFIG_CP210X_SET_DTR_RTS, - CONFIG_CP210X_COMPLETE -}; - -static bool cp210x_open(uint8_t daddr, tusb_desc_interface_t const *itf_desc, uint16_t max_len) { - // CP210x Interface includes 1 vendor interface + 2 bulk endpoints - TU_VERIFY(itf_desc->bInterfaceSubClass == 0 && itf_desc->bInterfaceProtocol == 0 && itf_desc->bNumEndpoints == 2); - TU_VERIFY(sizeof(tusb_desc_interface_t) + 2*sizeof(tusb_desc_endpoint_t) <= max_len); - - cdch_interface_t * p_cdc = make_new_itf(daddr, itf_desc); - TU_VERIFY(p_cdc); - - TU_LOG_DRV("CP210x opened\r\n"); - p_cdc->serial_drid = SERIAL_DRIVER_CP210X; - - // endpoint pair - tusb_desc_endpoint_t const * desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc); - - // data endpoints expected to be in pairs - return open_ep_stream_pair(p_cdc, desc_ep); -} - -static bool cp210x_set_request(cdch_interface_t* p_cdc, uint8_t command, uint16_t value, uint8_t* buffer, uint16_t length, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - tusb_control_request_t const request = { - .bmRequestType_bit = { - .recipient = TUSB_REQ_RCPT_INTERFACE, - .type = TUSB_REQ_TYPE_VENDOR, - .direction = TUSB_DIR_OUT - }, - .bRequest = command, - .wValue = tu_htole16(value), - .wIndex = p_cdc->bInterfaceNumber, - .wLength = tu_htole16(length) - }; - - // use usbh enum buf since application variable does not live long enough - uint8_t* enum_buf = NULL; - - if (buffer && length > 0) { - enum_buf = usbh_get_enum_buf(); - tu_memcpy_s(enum_buf, CFG_TUH_ENUMERATION_BUFSIZE, buffer, length); - } - - tuh_xfer_t xfer = { - .daddr = p_cdc->daddr, - .ep_addr = 0, - .setup = &request, - .buffer = enum_buf, - .complete_cb = complete_cb, - .user_data = user_data - }; - - return tuh_control_xfer(&xfer); -} - -static bool cp210x_ifc_enable(cdch_interface_t* p_cdc, uint16_t enabled, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - return cp210x_set_request(p_cdc, CP210X_IFC_ENABLE, enabled, NULL, 0, complete_cb, user_data); -} - -static bool cp210x_set_baudrate(cdch_interface_t* p_cdc, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { - TU_LOG_DRV("CDC CP210x Set BaudRate = %lu\r\n", baudrate); - uint32_t baud_le = tu_htole32(baudrate); - p_cdc->user_control_cb = complete_cb; - return cp210x_set_request(p_cdc, CP210X_SET_BAUDRATE, 0, (uint8_t *) &baud_le, 4, - complete_cb ? cdch_internal_control_complete : NULL, user_data); -} - -static bool cp210x_set_modem_ctrl(cdch_interface_t* p_cdc, uint16_t line_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data) -{ - TU_LOG_DRV("CDC CP210x Set Control Line State\r\n"); - p_cdc->user_control_cb = complete_cb; - return cp210x_set_request(p_cdc, CP210X_SET_MHS, 0x0300 | line_state, NULL, 0, - complete_cb ? cdch_internal_control_complete : NULL, user_data); -} - -static void cp210x_process_config(tuh_xfer_t* xfer) { - uintptr_t const state = xfer->user_data; - uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); - uint8_t const idx = tuh_cdc_itf_get_index(xfer->daddr, itf_num); - cdch_interface_t *p_cdc = get_itf(idx); - TU_ASSERT(p_cdc,); - - switch (state) { - case CONFIG_CP210X_IFC_ENABLE: - TU_ASSERT(cp210x_ifc_enable(p_cdc, 1, cp210x_process_config, CONFIG_CP210X_SET_BAUDRATE),); - break; - - case CONFIG_CP210X_SET_BAUDRATE: { - #ifdef CFG_TUH_CDC_LINE_CODING_ON_ENUM - cdc_line_coding_t line_coding = CFG_TUH_CDC_LINE_CODING_ON_ENUM; - TU_ASSERT(cp210x_set_baudrate(p_cdc, line_coding.bit_rate, cp210x_process_config, CONFIG_CP210X_SET_LINE_CTL),); - break; - #else - TU_ATTR_FALLTHROUGH; - #endif - } - - case CONFIG_CP210X_SET_LINE_CTL: { - #if defined(CFG_TUH_CDC_LINE_CODING_ON_ENUM) && 0 // skip for now - cdc_line_coding_t line_coding = CFG_TUH_CDC_LINE_CODING_ON_ENUM; - break; - #else - TU_ATTR_FALLTHROUGH; - #endif - } - - case CONFIG_CP210X_SET_DTR_RTS: - #if CFG_TUH_CDC_LINE_CONTROL_ON_ENUM - TU_ASSERT( - cp210x_set_modem_ctrl(p_cdc, CFG_TUH_CDC_LINE_CONTROL_ON_ENUM, cp210x_process_config, CONFIG_CP210X_COMPLETE),); - break; - #else - TU_ATTR_FALLTHROUGH; - #endif - - case CONFIG_CP210X_COMPLETE: - set_config_complete(p_cdc, idx, itf_num); - break; - - default: break; - } -} - -#endif - -#endif diff --git a/pico-sdk/lib/tinyusb/src/class/cdc/cdc_host.h b/pico-sdk/lib/tinyusb/src/class/cdc/cdc_host.h deleted file mode 100644 index 9e5edd9..0000000 --- a/pico-sdk/lib/tinyusb/src/class/cdc/cdc_host.h +++ /dev/null @@ -1,204 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#ifndef _TUSB_CDC_HOST_H_ -#define _TUSB_CDC_HOST_H_ - -#include "cdc.h" - -#ifdef __cplusplus - extern "C" { -#endif - -//--------------------------------------------------------------------+ -// Class Driver Configuration -//--------------------------------------------------------------------+ - -// Set Line Control state on enumeration/mounted: DTR ( bit 0), RTS (bit 1) -#ifndef CFG_TUH_CDC_LINE_CONTROL_ON_ENUM -#define CFG_TUH_CDC_LINE_CONTROL_ON_ENUM 0 -#endif - -// Set Line Coding on enumeration/mounted, value for cdc_line_coding_t -//#ifndef CFG_TUH_CDC_LINE_CODING_ON_ENUM -//#define CFG_TUH_CDC_LINE_CODING_ON_ENUM { 115200, CDC_LINE_CODING_STOP_BITS_1, CDC_LINE_CODING_PARITY_NONE, 8 } -//#endif - -// RX FIFO size -#ifndef CFG_TUH_CDC_RX_BUFSIZE -#define CFG_TUH_CDC_RX_BUFSIZE USBH_EPSIZE_BULK_MAX -#endif - -// RX Endpoint size -#ifndef CFG_TUH_CDC_RX_EPSIZE -#define CFG_TUH_CDC_RX_EPSIZE USBH_EPSIZE_BULK_MAX -#endif - -// TX FIFO size -#ifndef CFG_TUH_CDC_TX_BUFSIZE -#define CFG_TUH_CDC_TX_BUFSIZE USBH_EPSIZE_BULK_MAX -#endif - -// TX Endpoint size -#ifndef CFG_TUH_CDC_TX_EPSIZE -#define CFG_TUH_CDC_TX_EPSIZE USBH_EPSIZE_BULK_MAX -#endif - -//--------------------------------------------------------------------+ -// Application API -//--------------------------------------------------------------------+ - -// Get Interface index from device address + interface number -// return TUSB_INDEX_INVALID_8 (0xFF) if not found -uint8_t tuh_cdc_itf_get_index(uint8_t daddr, uint8_t itf_num); - -// Get Interface information -// return true if index is correct and interface is currently mounted -bool tuh_cdc_itf_get_info(uint8_t idx, tuh_itf_info_t* info); - -// Check if a interface is mounted -bool tuh_cdc_mounted(uint8_t idx); - -// Get current DTR status -bool tuh_cdc_get_dtr(uint8_t idx); - -// Get current RTS status -bool tuh_cdc_get_rts(uint8_t idx); - -// Check if interface is connected (DTR active) -TU_ATTR_ALWAYS_INLINE static inline bool tuh_cdc_connected(uint8_t idx) -{ - return tuh_cdc_get_dtr(idx); -} - -// Get local (saved/cached) version of line coding. -// This function should return correct values if tuh_cdc_set_line_coding() / tuh_cdc_get_line_coding() -// are invoked previously or CFG_TUH_CDC_LINE_CODING_ON_ENUM is defined. -// NOTE: This function does not make any USB transfer request to device. -bool tuh_cdc_get_local_line_coding(uint8_t idx, cdc_line_coding_t* line_coding); - -//--------------------------------------------------------------------+ -// Write API -//--------------------------------------------------------------------+ - -// Get the number of bytes available for writing -uint32_t tuh_cdc_write_available(uint8_t idx); - -// Write to cdc interface -uint32_t tuh_cdc_write(uint8_t idx, void const* buffer, uint32_t bufsize); - -// Force sending data if possible, return number of forced bytes -uint32_t tuh_cdc_write_flush(uint8_t idx); - -// Clear the transmit FIFO -bool tuh_cdc_write_clear(uint8_t idx); - -//--------------------------------------------------------------------+ -// Read API -//--------------------------------------------------------------------+ - -// Get the number of bytes available for reading -uint32_t tuh_cdc_read_available(uint8_t idx); - -// Read from cdc interface -uint32_t tuh_cdc_read (uint8_t idx, void* buffer, uint32_t bufsize); - -// Get a byte from RX FIFO without removing it -bool tuh_cdc_peek(uint8_t idx, uint8_t* ch); - -// Clear the received FIFO -bool tuh_cdc_read_clear (uint8_t idx); - -//--------------------------------------------------------------------+ -// Control Endpoint (Request) API -// Each Function will make a USB control transfer request to/from device -// - If complete_cb is provided, the function will return immediately and invoke -// the callback when request is complete. -// - If complete_cb is NULL, the function will block until request is complete. -// - In this case, user_data should be pointed to xfer_result_t to hold the transfer result. -// - The function will return true if transfer is successful, false otherwise. -//--------------------------------------------------------------------+ - -// Request to Set Control Line State: DTR (bit 0), RTS (bit 1) -bool tuh_cdc_set_control_line_state(uint8_t idx, uint16_t line_state, tuh_xfer_cb_t complete_cb, uintptr_t user_data); - -// Request to set baudrate -bool tuh_cdc_set_baudrate(uint8_t idx, uint32_t baudrate, tuh_xfer_cb_t complete_cb, uintptr_t user_data); - -// Request to Set Line Coding (ACM only) -// Should only use if you don't work with serial devices such as FTDI/CP210x -bool tuh_cdc_set_line_coding(uint8_t idx, cdc_line_coding_t const* line_coding, tuh_xfer_cb_t complete_cb, uintptr_t user_data); - -// Request to Get Line Coding (ACM only) -// Should only use if tuh_cdc_set_line_coding() / tuh_cdc_get_line_coding() never got invoked and -// CFG_TUH_CDC_LINE_CODING_ON_ENUM is not defined -// bool tuh_cdc_get_line_coding(uint8_t idx, cdc_line_coding_t* coding); - -// Connect by set both DTR, RTS -TU_ATTR_ALWAYS_INLINE static inline -bool tuh_cdc_connect(uint8_t idx, tuh_xfer_cb_t complete_cb, uintptr_t user_data) -{ - return tuh_cdc_set_control_line_state(idx, CDC_CONTROL_LINE_STATE_DTR | CDC_CONTROL_LINE_STATE_RTS, complete_cb, user_data); -} - -// Disconnect by clear both DTR, RTS -TU_ATTR_ALWAYS_INLINE static inline -bool tuh_cdc_disconnect(uint8_t idx, tuh_xfer_cb_t complete_cb, uintptr_t user_data) -{ - return tuh_cdc_set_control_line_state(idx, 0x00, complete_cb, user_data); -} - -//--------------------------------------------------------------------+ -// CDC APPLICATION CALLBACKS -//--------------------------------------------------------------------+ - -// Invoked when a device with CDC interface is mounted -// idx is index of cdc interface in the internal pool. -TU_ATTR_WEAK extern void tuh_cdc_mount_cb(uint8_t idx); - -// Invoked when a device with CDC interface is unmounted -TU_ATTR_WEAK extern void tuh_cdc_umount_cb(uint8_t idx); - -// Invoked when received new data -TU_ATTR_WEAK extern void tuh_cdc_rx_cb(uint8_t idx); - -// Invoked when a TX is complete and therefore space becomes available in TX buffer -TU_ATTR_WEAK extern void tuh_cdc_tx_complete_cb(uint8_t idx); - -//--------------------------------------------------------------------+ -// Internal Class Driver API -//--------------------------------------------------------------------+ -void cdch_init (void); -bool cdch_open (uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *itf_desc, uint16_t max_len); -bool cdch_set_config (uint8_t dev_addr, uint8_t itf_num); -bool cdch_xfer_cb (uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); -void cdch_close (uint8_t dev_addr); - -#ifdef __cplusplus - } -#endif - -#endif /* _TUSB_CDC_HOST_H_ */ diff --git a/pico-sdk/lib/tinyusb/src/class/cdc/cdc_rndis.h b/pico-sdk/lib/tinyusb/src/class/cdc/cdc_rndis.h deleted file mode 100644 index ad153e0..0000000 --- a/pico-sdk/lib/tinyusb/src/class/cdc/cdc_rndis.h +++ /dev/null @@ -1,301 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -/** \ingroup ClassDriver_CDC Communication Device Class (CDC) - * \defgroup CDC_RNDIS Remote Network Driver Interface Specification (RNDIS) - * @{ - * \defgroup CDC_RNDIS_Common Common Definitions - * @{ */ - -#ifndef _TUSB_CDC_RNDIS_H_ -#define _TUSB_CDC_RNDIS_H_ - -#include "cdc.h" - -#ifdef __cplusplus - extern "C" { -#endif - -#ifdef __CC_ARM -#pragma diag_suppress 66 // Suppress Keil warnings #66-D: enumeration value is out of "int" range -#endif - -/// RNDIS Message Types -typedef enum -{ - RNDIS_MSG_PACKET = 0x00000001UL, ///< The host and device use this to send network data to one another. - - RNDIS_MSG_INITIALIZE = 0x00000002UL, ///< Sent by the host to initialize the device. - RNDIS_MSG_INITIALIZE_CMPLT = 0x80000002UL, ///< Device response to an initialize message. - - RNDIS_MSG_HALT = 0x00000003UL, ///< Sent by the host to halt the device. This does not have a response. It is optional for the device to send this message to the host. - - RNDIS_MSG_QUERY = 0x00000004UL, ///< Sent by the host to send a query OID. - RNDIS_MSG_QUERY_CMPLT = 0x80000004UL, ///< Device response to a query OID. - - RNDIS_MSG_SET = 0x00000005UL, ///< Sent by the host to send a set OID. - RNDIS_MSG_SET_CMPLT = 0x80000005UL, ///< Device response to a set OID. - - RNDIS_MSG_RESET = 0x00000006UL, ///< Sent by the host to perform a soft reset on the device. - RNDIS_MSG_RESET_CMPLT = 0x80000006UL, ///< Device response to reset message. - - RNDIS_MSG_INDICATE_STATUS = 0x00000007UL, ///< Sent by the device to indicate its status or an error when an unrecognized message is received. - - RNDIS_MSG_KEEP_ALIVE = 0x00000008UL, ///< During idle periods, sent every few seconds by the host to check that the device is still responsive. It is optional for the device to send this message to check if the host is active. - RNDIS_MSG_KEEP_ALIVE_CMPLT = 0x80000008UL ///< The device response to a keepalivemessage. The host can respond with this message to a keepalive message from the device when the device implements the optional KeepAliveTimer. -}rndis_msg_type_t; - -/// RNDIS Message Status Values -typedef enum -{ - RNDIS_STATUS_SUCCESS = 0x00000000UL, ///< Success - RNDIS_STATUS_FAILURE = 0xC0000001UL, ///< Unspecified error - RNDIS_STATUS_INVALID_DATA = 0xC0010015UL, ///< Invalid data error - RNDIS_STATUS_NOT_SUPPORTED = 0xC00000BBUL, ///< Unsupported request error - RNDIS_STATUS_MEDIA_CONNECT = 0x4001000BUL, ///< Device is connected to a network medium. - RNDIS_STATUS_MEDIA_DISCONNECT = 0x4001000CUL ///< Device is disconnected from the medium. -}rndis_msg_status_t; - -#ifdef __CC_ARM -#pragma diag_default 66 // return Keil 66 to normal severity -#endif - -//--------------------------------------------------------------------+ -// MESSAGE STRUCTURE -//--------------------------------------------------------------------+ - -//------------- Initialize -------------// -/// \brief Initialize Message -/// \details This message MUST be sent by the host to initialize the device. -typedef struct { - uint32_t type ; ///< Message type, must be \ref RNDIS_MSG_INITIALIZE - uint32_t length ; ///< Message length in bytes, must be 0x18 - uint32_t request_id ; ///< A 32-bit integer value, generated by the host, used to match the host's sent request to the response from the device. - uint32_t major_version ; ///< The major version of the RNDIS Protocol implemented by the host. - uint32_t minor_version ; ///< The minor version of the RNDIS Protocol implemented by the host - uint32_t max_xfer_size ; ///< The maximum size, in bytes, of any single bus data transfer that the host expects to receive from the device. -}rndis_msg_initialize_t; - -/// \brief Initialize Complete Message -/// \details This message MUST be sent by the device in response to an initialize message. -typedef struct { - uint32_t type ; ///< Message Type, must be \ref RNDIS_MSG_INITIALIZE_CMPLT - uint32_t length ; ///< Message length in bytes, must be 0x30 - uint32_t request_id ; ///< A 32-bit integer value from \a request_id field of the \ref rndis_msg_initialize_t to which this message is a response. - uint32_t status ; ///< The initialization status of the device, has value from \ref rndis_msg_status_t - uint32_t major_version ; ///< the highest-numbered RNDIS Protocol version supported by the device. - uint32_t minor_version ; ///< the highest-numbered RNDIS Protocol version supported by the device. - uint32_t device_flags ; ///< MUST be set to 0x000000010. Other values are reserved for future use. - uint32_t medium ; ///< is 0x00 for RNDIS_MEDIUM_802_3 - uint32_t max_packet_per_xfer ; ///< The maximum number of concatenated \ref RNDIS_MSG_PACKET messages that the device can handle in a single bus transfer to it. This value MUST be at least 1. - uint32_t max_xfer_size ; ///< The maximum size, in bytes, of any single bus data transfer that the device expects to receive from the host. - uint32_t packet_alignment_factor ; ///< The byte alignment the device expects for each RNDIS message that is part of a multimessage transfer to it. The value is specified as an exponent of 2; for example, the host uses 2{PacketAlignmentFactor} as the alignment value. - uint32_t reserved[2] ; -} rndis_msg_initialize_cmplt_t; - -//------------- Query -------------// -/// \brief Query Message -/// \details This message MUST be sent by the host to query an OID. -typedef struct { - uint32_t type ; ///< Message Type, must be \ref RNDIS_MSG_QUERY - uint32_t length ; ///< Message length in bytes, including the header and the \a oid_buffer - uint32_t request_id ; ///< A 32-bit integer value, generated by the host, used to match the host's sent request to the response from the device. - uint32_t oid ; ///< The integer value of the host operating system-defined identifier, for the parameter of the device being queried for. - uint32_t buffer_length ; ///< The length, in bytes, of the input data required for the OID query. This MUST be set to 0 when there is no input data associated with the OID. - uint32_t buffer_offset ; ///< The offset, in bytes, from the beginning of \a request_id field where the input data for the query is located in the message. This value MUST be set to 0 when there is no input data associated with the OID. - uint32_t reserved ; - uint8_t oid_buffer[] ; ///< Flexible array contains the input data supplied by the host, required for the OID query request processing by the device, as per the host NDIS specification. -} rndis_msg_query_t, rndis_msg_set_t; - -TU_VERIFY_STATIC(sizeof(rndis_msg_query_t) == 28, "Make sure flexible array member does not affect layout"); - -/// \brief Query Complete Message -/// \details This message MUST be sent by the device in response to a query OID message. -typedef struct { - uint32_t type ; ///< Message Type, must be \ref RNDIS_MSG_QUERY_CMPLT - uint32_t length ; ///< Message length in bytes, including the header and the \a oid_buffer - uint32_t request_id ; ///< A 32-bit integer value from \a request_id field of the \ref rndis_msg_query_t to which this message is a response. - uint32_t status ; ///< The status of processing for the query request, has value from \ref rndis_msg_status_t. - uint32_t buffer_length ; ///< The length, in bytes, of the data in the response to the query. This MUST be set to 0 when there is no OIDInputBuffer. - uint32_t buffer_offset ; ///< The offset, in bytes, from the beginning of \a request_id field where the response data for the query is located in the message. This MUST be set to 0 when there is no \ref oid_buffer. - uint8_t oid_buffer[] ; ///< Flexible array member contains the response data to the OID query request as specified by the host. -} rndis_msg_query_cmplt_t; - -TU_VERIFY_STATIC(sizeof(rndis_msg_query_cmplt_t) == 24, "Make sure flexible array member does not affect layout"); - -//------------- Reset -------------// -/// \brief Reset Message -/// \details This message MUST be sent by the host to perform a soft reset on the device. -typedef struct { - uint32_t type ; ///< Message Type, must be \ref RNDIS_MSG_RESET - uint32_t length ; ///< Message length in bytes, MUST be 0x06 - uint32_t reserved ; -} rndis_msg_reset_t; - -/// \brief Reset Complete Message -/// \details This message MUST be sent by the device in response to a reset message. -typedef struct { - uint32_t type ; ///< Message Type, must be \ref RNDIS_MSG_RESET_CMPLT - uint32_t length ; ///< Message length in bytes, MUST be 0x10 - uint32_t status ; ///< The status of processing for the \ref rndis_msg_reset_t, has value from \ref rndis_msg_status_t. - uint32_t addressing_reset ; ///< This field indicates whether the addressing information, which is the multicast address list or packet filter, has been lost during the reset operation. This MUST be set to 0x00000001 if the device requires that the host to resend addressing information or MUST be set to zero otherwise. -} rndis_msg_reset_cmplt_t; - -//typedef struct { -// uint32_t type; -// uint32_t length; -// uint32_t status; -// uint32_t buffer_length; -// uint32_t buffer_offset; -// uint32_t diagnostic_status; // optional -// uint32_t diagnostic_error_offset; // optional -// uint32_t status_buffer[0]; // optional -//} rndis_msg_indicate_status_t; - -/// \brief Keep Alive Message -/// \details This message MUST be sent by the host to check that device is still responsive. It is optional for the device to send this message to check if the host is active -typedef struct { - uint32_t type ; ///< Message Type - uint32_t length ; ///< Message length in bytes, MUST be 0x10 - uint32_t request_id ; -} rndis_msg_keep_alive_t, rndis_msg_halt_t; - -/// \brief Set Complete Message -/// \brief This message MUST be sent in response to a the request message -typedef struct { - uint32_t type ; ///< Message Type - uint32_t length ; ///< Message length in bytes, MUST be 0x10 - uint32_t request_id ; ///< must be the same as requesting message - uint32_t status ; ///< The status of processing for the request message request by the device to which this message is the response. -} rndis_msg_set_cmplt_t, rndis_msg_keep_alive_cmplt_t; - -/// \brief Packet Data Message -/// \brief This message MUST be used by the host and the device to send network data to one another. -typedef struct { - uint32_t type ; ///< Message Type, must be \ref RNDIS_MSG_PACKET - uint32_t length ; ///< Message length in bytes, The total length of this RNDIS message including the header, payload, and padding. - uint32_t data_offset ; ///< Specifies the offset, in bytes, from the start of this \a data_offset field of this message to the start of the data. This MUST be an integer multiple of 4. - uint32_t data_length ; ///< Specifies the number of bytes in the payload of this message. - uint32_t out_of_band_data_offet ; ///< Specifies the offset, in bytes, of the first out-of-band data record from the start of the DataOffset field in this message. MUST be an integer multiple of 4 when out-of-band data is present or set to 0 otherwise. When there are multiple out-ofband data records, each subsequent record MUST immediately follow the previous out-of-band data record. - uint32_t out_of_band_data_length ; ///< Specifies, in bytes, the total length of the out-of-band data. - uint32_t num_out_of_band_data_elements ; ///< Specifies the number of out-of-band records in this message. - uint32_t per_packet_info_offset ; ///< Specifies the offset, in bytes, of the start of per-packet-info data record from the start of the \a data_offset field in this message. MUST be an integer multiple of 4 when per-packet-info data record is present or MUST be set to 0 otherwise. When there are multiple per-packet-info data records, each subsequent record MUST immediately follow the previous record. - uint32_t per_packet_info_length ; ///< Specifies, in bytes, the total length of per-packetinformation contained in this message. - uint32_t reserved[2] ; - uint32_t payload[0] ; ///< Network data contained in this message. - - // uint8_t padding[0] - // Additional bytes of zeros added at the end of the message to comply with - // the internal and external padding requirements. Internal padding SHOULD be as per the - // specification of the out-of-band data record and per-packet-info data record. The external - //padding size SHOULD be determined based on the PacketAlignmentFactor field specification - //in REMOTE_NDIS_INITIALIZE_CMPLT message by the device, when multiple - //REMOTE_NDIS_PACKET_MSG messages are bundled together in a single bus-native message. - //In this case, all but the very last REMOTE_NDIS_PACKET_MSG MUST respect the - //PacketAlignmentFactor field. - - // rndis_msg_packet_t [0] : (optional) more packet if multiple packet per bus transaction is supported -} rndis_msg_packet_t; - - -typedef struct { - uint32_t size ; ///< Length, in bytes, of this header and appended data and padding. This value MUST be an integer multiple of 4. - uint32_t type ; ///< MUST be as per host operating system specification. - uint32_t offset ; ///< The byte offset from the beginning of this record to the beginning of data. - uint32_t data[0] ; ///< Flexible array contains data -} rndis_msg_out_of_band_data_t, rndis_msg_per_packet_info_t; - -//--------------------------------------------------------------------+ -// NDIS Object ID -//--------------------------------------------------------------------+ - -/// NDIS Object ID -typedef enum -{ - //------------- General Required OIDs -------------// - RNDIS_OID_GEN_SUPPORTED_LIST = 0x00010101, ///< List of supported OIDs - RNDIS_OID_GEN_HARDWARE_STATUS = 0x00010102, ///< Hardware status - RNDIS_OID_GEN_MEDIA_SUPPORTED = 0x00010103, ///< Media types supported (encoded) - RNDIS_OID_GEN_MEDIA_IN_USE = 0x00010104, ///< Media types in use (encoded) - RNDIS_OID_GEN_MAXIMUM_LOOKAHEAD = 0x00010105, ///< - RNDIS_OID_GEN_MAXIMUM_FRAME_SIZE = 0x00010106, ///< Maximum frame size in bytes - RNDIS_OID_GEN_LINK_SPEED = 0x00010107, ///< Link speed in units of 100 bps - RNDIS_OID_GEN_TRANSMIT_BUFFER_SPACE = 0x00010108, ///< Transmit buffer space - RNDIS_OID_GEN_RECEIVE_BUFFER_SPACE = 0x00010109, ///< Receive buffer space - RNDIS_OID_GEN_TRANSMIT_BLOCK_SIZE = 0x0001010A, ///< Minimum amount of storage, in bytes, that a single packet occupies in the transmit buffer space of the NIC - RNDIS_OID_GEN_RECEIVE_BLOCK_SIZE = 0x0001010B, ///< Amount of storage, in bytes, that a single packet occupies in the receive buffer space of the NIC - RNDIS_OID_GEN_VENDOR_ID = 0x0001010C, ///< Vendor NIC code - RNDIS_OID_GEN_VENDOR_DESCRIPTION = 0x0001010D, ///< Vendor network card description - RNDIS_OID_GEN_CURRENT_PACKET_FILTER = 0x0001010E, ///< Current packet filter (encoded) - RNDIS_OID_GEN_CURRENT_LOOKAHEAD = 0x0001010F, ///< Current lookahead size in bytes - RNDIS_OID_GEN_DRIVER_VERSION = 0x00010110, ///< NDIS version number used by the driver - RNDIS_OID_GEN_MAXIMUM_TOTAL_SIZE = 0x00010111, ///< Maximum total packet length in bytes - RNDIS_OID_GEN_PROTOCOL_OPTIONS = 0x00010112, ///< Optional protocol flags (encoded) - RNDIS_OID_GEN_MAC_OPTIONS = 0x00010113, ///< Optional NIC flags (encoded) - RNDIS_OID_GEN_MEDIA_CONNECT_STATUS = 0x00010114, ///< Whether the NIC is connected to the network - RNDIS_OID_GEN_MAXIMUM_SEND_PACKETS = 0x00010115, ///< The maximum number of send packets the driver can accept per call to its MiniportSendPacketsfunction - - //------------- General Optional OIDs -------------// - RNDIS_OID_GEN_VENDOR_DRIVER_VERSION = 0x00010116, ///< Vendor-assigned version number of the driver - RNDIS_OID_GEN_SUPPORTED_GUIDS = 0x00010117, ///< The custom GUIDs (Globally Unique Identifier) supported by the miniport driver - RNDIS_OID_GEN_NETWORK_LAYER_ADDRESSES = 0x00010118, ///< List of network-layer addresses associated with the binding between a transport and the driver - RNDIS_OID_GEN_TRANSPORT_HEADER_OFFSET = 0x00010119, ///< Size of packets' additional headers - RNDIS_OID_GEN_MEDIA_CAPABILITIES = 0x00010201, ///< - RNDIS_OID_GEN_PHYSICAL_MEDIUM = 0x00010202, ///< Physical media supported by the miniport driver (encoded) - - //------------- 802.3 Objects (Ethernet) -------------// - RNDIS_OID_802_3_PERMANENT_ADDRESS = 0x01010101, ///< Permanent station address - RNDIS_OID_802_3_CURRENT_ADDRESS = 0x01010102, ///< Current station address - RNDIS_OID_802_3_MULTICAST_LIST = 0x01010103, ///< Current multicast address list - RNDIS_OID_802_3_MAXIMUM_LIST_SIZE = 0x01010104, ///< Maximum size of multicast address list -} rndis_oid_type_t; - -/// RNDIS Packet Filter Bits \ref RNDIS_OID_GEN_CURRENT_PACKET_FILTER. -typedef enum -{ - RNDIS_PACKET_TYPE_DIRECTED = 0x00000001, ///< Directed packets. Directed packets contain a destination address equal to the station address of the NIC. - RNDIS_PACKET_TYPE_MULTICAST = 0x00000002, ///< Multicast address packets sent to addresses in the multicast address list. - RNDIS_PACKET_TYPE_ALL_MULTICAST = 0x00000004, ///< All multicast address packets, not just the ones enumerated in the multicast address list. - RNDIS_PACKET_TYPE_BROADCAST = 0x00000008, ///< Broadcast packets. - RNDIS_PACKET_TYPE_SOURCE_ROUTING = 0x00000010, ///< All source routing packets. If the protocol driver sets this bit, the NDIS library attempts to act as a source routing bridge. - RNDIS_PACKET_TYPE_PROMISCUOUS = 0x00000020, ///< Specifies all packets regardless of whether VLAN filtering is enabled or not and whether the VLAN identifier matches or not. - RNDIS_PACKET_TYPE_SMT = 0x00000040, ///< SMT packets that an FDDI NIC receives. - RNDIS_PACKET_TYPE_ALL_LOCAL = 0x00000080, ///< All packets sent by installed protocols and all packets indicated by the NIC that is identified by a given NdisBindingHandle. - RNDIS_PACKET_TYPE_GROUP = 0x00001000, ///< Packets sent to the current group address. - RNDIS_PACKET_TYPE_ALL_FUNCTIONAL = 0x00002000, ///< All functional address packets, not just the ones in the current functional address. - RNDIS_PACKET_TYPE_FUNCTIONAL = 0x00004000, ///< Functional address packets sent to addresses included in the current functional address. - RNDIS_PACKET_TYPE_MAC_FRAME = 0x00008000, ///< NIC driver frames that a Token Ring NIC receives. - RNDIS_PACKET_TYPE_NO_LOCAL = 0x00010000, -} rndis_packet_filter_type_t; - -#ifdef __cplusplus - } -#endif - -#endif /* _TUSB_CDC_RNDIS_H_ */ - -/** @} */ -/** @} */ diff --git a/pico-sdk/lib/tinyusb/src/class/cdc/cdc_rndis_host.c b/pico-sdk/lib/tinyusb/src/class/cdc/cdc_rndis_host.c deleted file mode 100644 index 11a5355..0000000 --- a/pico-sdk/lib/tinyusb/src/class/cdc/cdc_rndis_host.c +++ /dev/null @@ -1,289 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#include "tusb_option.h" - -#if (CFG_TUH_ENABLED && CFG_TUH_CDC && CFG_TUH_CDC_RNDIS) - -//--------------------------------------------------------------------+ -// INCLUDE -//--------------------------------------------------------------------+ -#include "common/tusb_common.h" -#include "cdc_host.h" -#include "cdc_rndis_host.h" - -#if 0 // TODO remove subtask related macros later -// Sub Task -#define OSAL_SUBTASK_BEGIN -#define OSAL_SUBTASK_END return TUSB_ERROR_NONE; - -#define STASK_RETURN(_error) return _error; -#define STASK_INVOKE(_subtask, _status) (_status) = _subtask -#define STASK_ASSERT(_cond) TU_VERIFY(_cond, TUSB_ERROR_OSAL_TASK_FAILED) -#endif - -//--------------------------------------------------------------------+ -// MACRO CONSTANT TYPEDEF -//--------------------------------------------------------------------+ -#define RNDIS_MSG_PAYLOAD_MAX (1024*4) - -CFG_TUH_MEM_SECTION static uint8_t msg_notification[CFG_TUH_DEVICE_MAX][8]; -CFG_TUH_MEM_SECTION CFG_TUH_MEM_ALIGN static uint8_t msg_payload[RNDIS_MSG_PAYLOAD_MAX]; - -static rndish_data_t rndish_data[CFG_TUH_DEVICE_MAX]; - -// TODO Microsoft requires message length for any get command must be at least 4096 bytes - -//--------------------------------------------------------------------+ -// INTERNAL OBJECT & FUNCTION DECLARATION -//--------------------------------------------------------------------+ -static tusb_error_t rndis_body_subtask(void); -static tusb_error_t send_message_get_response_subtask( uint8_t dev_addr, cdch_data_t *p_cdc, - uint8_t * p_mess, uint32_t mess_length, - uint8_t *p_response ); - -//--------------------------------------------------------------------+ -// APPLICATION API -//--------------------------------------------------------------------+ -tusb_error_t tusbh_cdc_rndis_get_mac_addr(uint8_t dev_addr, uint8_t mac_address[6]) -{ - TU_ASSERT( tusbh_cdc_rndis_is_mounted(dev_addr), TUSB_ERROR_CDCH_DEVICE_NOT_MOUNTED); - TU_VERIFY( mac_address, TUSB_ERROR_INVALID_PARA); - - memcpy(mac_address, rndish_data[dev_addr-1].mac_address, 6); - - return TUSB_ERROR_NONE; -} - -//--------------------------------------------------------------------+ -// IMPLEMENTATION -//--------------------------------------------------------------------+ - -// To enable the TASK_ASSERT style (quick return on false condition) in a real RTOS, a task must act as a wrapper -// and is used mainly to call subtasks. Within a subtask return statement can be called freely, the task with -// forever loop cannot have any return at all. -OSAL_TASK_FUNCTION(cdch_rndis_task) (void* param;) -{ - OSAL_TASK_BEGIN - rndis_body_subtask(); - OSAL_TASK_END -} - -static tusb_error_t rndis_body_subtask(void) -{ - static uint8_t relative_addr; - - OSAL_SUBTASK_BEGIN - - for (relative_addr = 0; relative_addr < CFG_TUH_DEVICE_MAX; relative_addr++) - { - - } - - osal_task_delay(100); - - OSAL_SUBTASK_END -} - -//--------------------------------------------------------------------+ -// RNDIS-CDC Driver API -//--------------------------------------------------------------------+ -void rndish_init(void) -{ - tu_memclr(rndish_data, sizeof(rndish_data_t)*CFG_TUH_DEVICE_MAX); - - //------------- Task creation -------------// - - //------------- semaphore creation for notification pipe -------------// - for(uint8_t i=0; itype == RNDIS_MSG_INITIALIZE_CMPLT && p_init_cmpt->status == RNDIS_STATUS_SUCCESS && - p_init_cmpt->max_packet_per_xfer == 1 && p_init_cmpt->max_xfer_size <= RNDIS_MSG_PAYLOAD_MAX); - rndish_data[dev_addr-1].max_xfer_size = p_init_cmpt->max_xfer_size; - - //------------- Message Query 802.3 Permanent Address -------------// - memcpy(msg_payload, &msg_query_permanent_addr, sizeof(rndis_msg_query_t)); - tu_memclr(msg_payload + sizeof(rndis_msg_query_t), 6); // 6 bytes for MAC address - - STASK_INVOKE( - send_message_get_response_subtask( dev_addr, p_cdc, - msg_payload, sizeof(rndis_msg_query_t) + 6, - msg_payload), - error - ); - if ( TUSB_ERROR_NONE != error ) STASK_RETURN(error); - - rndis_msg_query_cmplt_t * const p_query_cmpt = (rndis_msg_query_cmplt_t *) msg_payload; - STASK_ASSERT(p_query_cmpt->type == RNDIS_MSG_QUERY_CMPLT && p_query_cmpt->status == RNDIS_STATUS_SUCCESS); - memcpy(rndish_data[dev_addr-1].mac_address, msg_payload + 8 + p_query_cmpt->buffer_offset, 6); - - //------------- Set OID_GEN_CURRENT_PACKET_FILTER to (DIRECTED | MULTICAST | BROADCAST) -------------// - memcpy(msg_payload, &msg_set_packet_filter, sizeof(rndis_msg_set_t)); - tu_memclr(msg_payload + sizeof(rndis_msg_set_t), 4); // 4 bytes for filter flags - ((rndis_msg_set_t*) msg_payload)->oid_buffer[0] = (RNDIS_PACKET_TYPE_DIRECTED | RNDIS_PACKET_TYPE_MULTICAST | RNDIS_PACKET_TYPE_BROADCAST); - - STASK_INVOKE( - send_message_get_response_subtask( dev_addr, p_cdc, - msg_payload, sizeof(rndis_msg_set_t) + 4, - msg_payload), - error - ); - if ( TUSB_ERROR_NONE != error ) STASK_RETURN(error); - - rndis_msg_set_cmplt_t * const p_set_cmpt = (rndis_msg_set_cmplt_t *) msg_payload; - STASK_ASSERT(p_set_cmpt->type == RNDIS_MSG_SET_CMPLT && p_set_cmpt->status == RNDIS_STATUS_SUCCESS); - - tusbh_cdc_rndis_mounted_cb(dev_addr); - - OSAL_SUBTASK_END -} - -void rndish_xfer_isr(cdch_data_t *p_cdc, pipe_handle_t pipe_hdl, xfer_result_t event, uint32_t xferred_bytes) -{ - if ( pipehandle_is_equal(pipe_hdl, p_cdc->pipe_notification) ) - { - osal_semaphore_post( rndish_data[pipe_hdl.dev_addr-1].sem_notification_hdl ); - } -} - -//--------------------------------------------------------------------+ -// INTERNAL & HELPER -//--------------------------------------------------------------------+ -static tusb_error_t send_message_get_response_subtask( uint8_t dev_addr, cdch_data_t *p_cdc, - uint8_t * p_mess, uint32_t mess_length, - uint8_t *p_response) -{ - tusb_error_t error; - - OSAL_SUBTASK_BEGIN - - //------------- Send RNDIS Control Message -------------// - STASK_INVOKE( - usbh_control_xfer_subtask( dev_addr, bm_request_type(TUSB_DIR_OUT, TUSB_REQ_TYPE_CLASS, TUSB_REQ_RCPT_INTERFACE), - CDC_REQUEST_SEND_ENCAPSULATED_COMMAND, 0, p_cdc->interface_number, - mess_length, p_mess), - error - ); - if ( TUSB_ERROR_NONE != error ) STASK_RETURN(error); - - //------------- waiting for Response Available notification -------------// - (void) usbh_edpt_xfer(p_cdc->pipe_notification, msg_notification[dev_addr-1], 8); - osal_semaphore_wait(rndish_data[dev_addr-1].sem_notification_hdl, OSAL_TIMEOUT_NORMAL, &error); - if ( TUSB_ERROR_NONE != error ) STASK_RETURN(error); - STASK_ASSERT(msg_notification[dev_addr-1][0] == 1); - - //------------- Get RNDIS Message Initialize Complete -------------// - STASK_INVOKE( - usbh_control_xfer_subtask( dev_addr, bm_request_type(TUSB_DIR_IN, TUSB_REQ_TYPE_CLASS, TUSB_REQ_RCPT_INTERFACE), - CDC_REQUEST_GET_ENCAPSULATED_RESPONSE, 0, p_cdc->interface_number, - RNDIS_MSG_PAYLOAD_MAX, p_response), - error - ); - if ( TUSB_ERROR_NONE != error ) STASK_RETURN(error); - - OSAL_SUBTASK_END -} - -//static tusb_error_t send_process_msg_initialize_subtask(uint8_t dev_addr, cdch_data_t *p_cdc) -//{ -// tusb_error_t error; -// -// OSAL_SUBTASK_BEGIN -// -// *((rndis_msg_initialize_t*) msg_payload) = (rndis_msg_initialize_t) -// { -// .type = RNDIS_MSG_INITIALIZE, -// .length = sizeof(rndis_msg_initialize_t), -// .request_id = 1, // TODO should use some magic number -// .major_version = 1, -// .minor_version = 0, -// .max_xfer_size = 0x4000 // TODO mimic windows -// }; -// -// -// -// OSAL_SUBTASK_END -//} -#endif diff --git a/pico-sdk/lib/tinyusb/src/class/cdc/cdc_rndis_host.h b/pico-sdk/lib/tinyusb/src/class/cdc/cdc_rndis_host.h deleted file mode 100644 index bb431ec..0000000 --- a/pico-sdk/lib/tinyusb/src/class/cdc/cdc_rndis_host.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -/** \ingroup CDC_RNDIS - * \defgroup CDC_RNSID_Host Host - * @{ */ - -#ifndef _TUSB_CDC_RNDIS_HOST_H_ -#define _TUSB_CDC_RNDIS_HOST_H_ - -#include "common/tusb_common.h" -#include "host/usbh.h" -#include "cdc_rndis.h" - -#ifdef __cplusplus - extern "C" { -#endif - -//--------------------------------------------------------------------+ -// INTERNAL RNDIS-CDC Driver API -//--------------------------------------------------------------------+ -typedef struct { - OSAL_SEM_DEF(semaphore_notification); - osal_semaphore_handle_t sem_notification_hdl; // used to wait on notification pipe - uint32_t max_xfer_size; // got from device's msg initialize complete - uint8_t mac_address[6]; -}rndish_data_t; - -void rndish_init(void); -bool rndish_open_subtask(uint8_t dev_addr, cdch_data_t *p_cdc); -void rndish_xfer_isr(cdch_data_t *p_cdc, pipe_handle_t pipe_hdl, xfer_result_t event, uint32_t xferred_bytes); -void rndish_close(uint8_t dev_addr); - -#ifdef __cplusplus - } -#endif - -#endif /* _TUSB_CDC_RNDIS_HOST_H_ */ - -/** @} */ diff --git a/pico-sdk/lib/tinyusb/src/class/hid/hid.h b/pico-sdk/lib/tinyusb/src/class/hid/hid.h index fbd3eef..2fe9221 100644 --- a/pico-sdk/lib/tinyusb/src/class/hid/hid.h +++ b/pico-sdk/lib/tinyusb/src/class/hid/hid.h @@ -300,6 +300,19 @@ typedef struct TU_ATTR_PACKED int8_t pan; // using AC Pan } hid_mouse_report_t; + +// Absolute Mouse: same as the Standard (relative) Mouse Report but +// with int16_t instead of int8_t for X and Y coordinates. +typedef struct TU_ATTR_PACKED +{ + uint8_t buttons; /**< buttons mask for currently pressed buttons in the mouse. */ + int16_t x; /**< Current x position of the mouse. */ + int16_t y; /**< Current y position of the mouse. */ + int8_t wheel; /**< Current delta wheel movement on the mouse. */ + int8_t pan; // using AC Pan +} hid_abs_mouse_report_t; + + /// Standard Mouse Buttons Bitmap typedef enum { @@ -353,177 +366,224 @@ typedef enum //--------------------------------------------------------------------+ // HID KEYCODE //--------------------------------------------------------------------+ -#define HID_KEY_NONE 0x00 -#define HID_KEY_A 0x04 -#define HID_KEY_B 0x05 -#define HID_KEY_C 0x06 -#define HID_KEY_D 0x07 -#define HID_KEY_E 0x08 -#define HID_KEY_F 0x09 -#define HID_KEY_G 0x0A -#define HID_KEY_H 0x0B -#define HID_KEY_I 0x0C -#define HID_KEY_J 0x0D -#define HID_KEY_K 0x0E -#define HID_KEY_L 0x0F -#define HID_KEY_M 0x10 -#define HID_KEY_N 0x11 -#define HID_KEY_O 0x12 -#define HID_KEY_P 0x13 -#define HID_KEY_Q 0x14 -#define HID_KEY_R 0x15 -#define HID_KEY_S 0x16 -#define HID_KEY_T 0x17 -#define HID_KEY_U 0x18 -#define HID_KEY_V 0x19 -#define HID_KEY_W 0x1A -#define HID_KEY_X 0x1B -#define HID_KEY_Y 0x1C -#define HID_KEY_Z 0x1D -#define HID_KEY_1 0x1E -#define HID_KEY_2 0x1F -#define HID_KEY_3 0x20 -#define HID_KEY_4 0x21 -#define HID_KEY_5 0x22 -#define HID_KEY_6 0x23 -#define HID_KEY_7 0x24 -#define HID_KEY_8 0x25 -#define HID_KEY_9 0x26 -#define HID_KEY_0 0x27 -#define HID_KEY_ENTER 0x28 -#define HID_KEY_ESCAPE 0x29 -#define HID_KEY_BACKSPACE 0x2A -#define HID_KEY_TAB 0x2B -#define HID_KEY_SPACE 0x2C -#define HID_KEY_MINUS 0x2D -#define HID_KEY_EQUAL 0x2E -#define HID_KEY_BRACKET_LEFT 0x2F -#define HID_KEY_BRACKET_RIGHT 0x30 -#define HID_KEY_BACKSLASH 0x31 -#define HID_KEY_EUROPE_1 0x32 -#define HID_KEY_SEMICOLON 0x33 -#define HID_KEY_APOSTROPHE 0x34 -#define HID_KEY_GRAVE 0x35 -#define HID_KEY_COMMA 0x36 -#define HID_KEY_PERIOD 0x37 -#define HID_KEY_SLASH 0x38 -#define HID_KEY_CAPS_LOCK 0x39 -#define HID_KEY_F1 0x3A -#define HID_KEY_F2 0x3B -#define HID_KEY_F3 0x3C -#define HID_KEY_F4 0x3D -#define HID_KEY_F5 0x3E -#define HID_KEY_F6 0x3F -#define HID_KEY_F7 0x40 -#define HID_KEY_F8 0x41 -#define HID_KEY_F9 0x42 -#define HID_KEY_F10 0x43 -#define HID_KEY_F11 0x44 -#define HID_KEY_F12 0x45 -#define HID_KEY_PRINT_SCREEN 0x46 -#define HID_KEY_SCROLL_LOCK 0x47 -#define HID_KEY_PAUSE 0x48 -#define HID_KEY_INSERT 0x49 -#define HID_KEY_HOME 0x4A -#define HID_KEY_PAGE_UP 0x4B -#define HID_KEY_DELETE 0x4C -#define HID_KEY_END 0x4D -#define HID_KEY_PAGE_DOWN 0x4E -#define HID_KEY_ARROW_RIGHT 0x4F -#define HID_KEY_ARROW_LEFT 0x50 -#define HID_KEY_ARROW_DOWN 0x51 -#define HID_KEY_ARROW_UP 0x52 -#define HID_KEY_NUM_LOCK 0x53 -#define HID_KEY_KEYPAD_DIVIDE 0x54 -#define HID_KEY_KEYPAD_MULTIPLY 0x55 -#define HID_KEY_KEYPAD_SUBTRACT 0x56 -#define HID_KEY_KEYPAD_ADD 0x57 -#define HID_KEY_KEYPAD_ENTER 0x58 -#define HID_KEY_KEYPAD_1 0x59 -#define HID_KEY_KEYPAD_2 0x5A -#define HID_KEY_KEYPAD_3 0x5B -#define HID_KEY_KEYPAD_4 0x5C -#define HID_KEY_KEYPAD_5 0x5D -#define HID_KEY_KEYPAD_6 0x5E -#define HID_KEY_KEYPAD_7 0x5F -#define HID_KEY_KEYPAD_8 0x60 -#define HID_KEY_KEYPAD_9 0x61 -#define HID_KEY_KEYPAD_0 0x62 -#define HID_KEY_KEYPAD_DECIMAL 0x63 -#define HID_KEY_EUROPE_2 0x64 -#define HID_KEY_APPLICATION 0x65 -#define HID_KEY_POWER 0x66 -#define HID_KEY_KEYPAD_EQUAL 0x67 -#define HID_KEY_F13 0x68 -#define HID_KEY_F14 0x69 -#define HID_KEY_F15 0x6A -#define HID_KEY_F16 0x6B -#define HID_KEY_F17 0x6C -#define HID_KEY_F18 0x6D -#define HID_KEY_F19 0x6E -#define HID_KEY_F20 0x6F -#define HID_KEY_F21 0x70 -#define HID_KEY_F22 0x71 -#define HID_KEY_F23 0x72 -#define HID_KEY_F24 0x73 -#define HID_KEY_EXECUTE 0x74 -#define HID_KEY_HELP 0x75 -#define HID_KEY_MENU 0x76 -#define HID_KEY_SELECT 0x77 -#define HID_KEY_STOP 0x78 -#define HID_KEY_AGAIN 0x79 -#define HID_KEY_UNDO 0x7A -#define HID_KEY_CUT 0x7B -#define HID_KEY_COPY 0x7C -#define HID_KEY_PASTE 0x7D -#define HID_KEY_FIND 0x7E -#define HID_KEY_MUTE 0x7F -#define HID_KEY_VOLUME_UP 0x80 -#define HID_KEY_VOLUME_DOWN 0x81 -#define HID_KEY_LOCKING_CAPS_LOCK 0x82 -#define HID_KEY_LOCKING_NUM_LOCK 0x83 -#define HID_KEY_LOCKING_SCROLL_LOCK 0x84 -#define HID_KEY_KEYPAD_COMMA 0x85 -#define HID_KEY_KEYPAD_EQUAL_SIGN 0x86 -#define HID_KEY_KANJI1 0x87 -#define HID_KEY_KANJI2 0x88 -#define HID_KEY_KANJI3 0x89 -#define HID_KEY_KANJI4 0x8A -#define HID_KEY_KANJI5 0x8B -#define HID_KEY_KANJI6 0x8C -#define HID_KEY_KANJI7 0x8D -#define HID_KEY_KANJI8 0x8E -#define HID_KEY_KANJI9 0x8F -#define HID_KEY_LANG1 0x90 -#define HID_KEY_LANG2 0x91 -#define HID_KEY_LANG3 0x92 -#define HID_KEY_LANG4 0x93 -#define HID_KEY_LANG5 0x94 -#define HID_KEY_LANG6 0x95 -#define HID_KEY_LANG7 0x96 -#define HID_KEY_LANG8 0x97 -#define HID_KEY_LANG9 0x98 -#define HID_KEY_ALTERNATE_ERASE 0x99 -#define HID_KEY_SYSREQ_ATTENTION 0x9A -#define HID_KEY_CANCEL 0x9B -#define HID_KEY_CLEAR 0x9C -#define HID_KEY_PRIOR 0x9D -#define HID_KEY_RETURN 0x9E -#define HID_KEY_SEPARATOR 0x9F -#define HID_KEY_OUT 0xA0 -#define HID_KEY_OPER 0xA1 -#define HID_KEY_CLEAR_AGAIN 0xA2 -#define HID_KEY_CRSEL_PROPS 0xA3 -#define HID_KEY_EXSEL 0xA4 -// RESERVED 0xA5-DF -#define HID_KEY_CONTROL_LEFT 0xE0 -#define HID_KEY_SHIFT_LEFT 0xE1 -#define HID_KEY_ALT_LEFT 0xE2 -#define HID_KEY_GUI_LEFT 0xE3 -#define HID_KEY_CONTROL_RIGHT 0xE4 -#define HID_KEY_SHIFT_RIGHT 0xE5 -#define HID_KEY_ALT_RIGHT 0xE6 -#define HID_KEY_GUI_RIGHT 0xE7 +#define HID_KEY_NONE 0x00 +#define HID_KEY_A 0x04 +#define HID_KEY_B 0x05 +#define HID_KEY_C 0x06 +#define HID_KEY_D 0x07 +#define HID_KEY_E 0x08 +#define HID_KEY_F 0x09 +#define HID_KEY_G 0x0A +#define HID_KEY_H 0x0B +#define HID_KEY_I 0x0C +#define HID_KEY_J 0x0D +#define HID_KEY_K 0x0E +#define HID_KEY_L 0x0F +#define HID_KEY_M 0x10 +#define HID_KEY_N 0x11 +#define HID_KEY_O 0x12 +#define HID_KEY_P 0x13 +#define HID_KEY_Q 0x14 +#define HID_KEY_R 0x15 +#define HID_KEY_S 0x16 +#define HID_KEY_T 0x17 +#define HID_KEY_U 0x18 +#define HID_KEY_V 0x19 +#define HID_KEY_W 0x1A +#define HID_KEY_X 0x1B +#define HID_KEY_Y 0x1C +#define HID_KEY_Z 0x1D +#define HID_KEY_1 0x1E +#define HID_KEY_2 0x1F +#define HID_KEY_3 0x20 +#define HID_KEY_4 0x21 +#define HID_KEY_5 0x22 +#define HID_KEY_6 0x23 +#define HID_KEY_7 0x24 +#define HID_KEY_8 0x25 +#define HID_KEY_9 0x26 +#define HID_KEY_0 0x27 +#define HID_KEY_ENTER 0x28 +#define HID_KEY_ESCAPE 0x29 +#define HID_KEY_BACKSPACE 0x2A +#define HID_KEY_TAB 0x2B +#define HID_KEY_SPACE 0x2C +#define HID_KEY_MINUS 0x2D +#define HID_KEY_EQUAL 0x2E +#define HID_KEY_BRACKET_LEFT 0x2F +#define HID_KEY_BRACKET_RIGHT 0x30 +#define HID_KEY_BACKSLASH 0x31 +#define HID_KEY_EUROPE_1 0x32 +#define HID_KEY_SEMICOLON 0x33 +#define HID_KEY_APOSTROPHE 0x34 +#define HID_KEY_GRAVE 0x35 +#define HID_KEY_COMMA 0x36 +#define HID_KEY_PERIOD 0x37 +#define HID_KEY_SLASH 0x38 +#define HID_KEY_CAPS_LOCK 0x39 +#define HID_KEY_F1 0x3A +#define HID_KEY_F2 0x3B +#define HID_KEY_F3 0x3C +#define HID_KEY_F4 0x3D +#define HID_KEY_F5 0x3E +#define HID_KEY_F6 0x3F +#define HID_KEY_F7 0x40 +#define HID_KEY_F8 0x41 +#define HID_KEY_F9 0x42 +#define HID_KEY_F10 0x43 +#define HID_KEY_F11 0x44 +#define HID_KEY_F12 0x45 +#define HID_KEY_PRINT_SCREEN 0x46 +#define HID_KEY_SCROLL_LOCK 0x47 +#define HID_KEY_PAUSE 0x48 +#define HID_KEY_INSERT 0x49 +#define HID_KEY_HOME 0x4A +#define HID_KEY_PAGE_UP 0x4B +#define HID_KEY_DELETE 0x4C +#define HID_KEY_END 0x4D +#define HID_KEY_PAGE_DOWN 0x4E +#define HID_KEY_ARROW_RIGHT 0x4F +#define HID_KEY_ARROW_LEFT 0x50 +#define HID_KEY_ARROW_DOWN 0x51 +#define HID_KEY_ARROW_UP 0x52 +#define HID_KEY_NUM_LOCK 0x53 +#define HID_KEY_KEYPAD_DIVIDE 0x54 +#define HID_KEY_KEYPAD_MULTIPLY 0x55 +#define HID_KEY_KEYPAD_SUBTRACT 0x56 +#define HID_KEY_KEYPAD_ADD 0x57 +#define HID_KEY_KEYPAD_ENTER 0x58 +#define HID_KEY_KEYPAD_1 0x59 +#define HID_KEY_KEYPAD_2 0x5A +#define HID_KEY_KEYPAD_3 0x5B +#define HID_KEY_KEYPAD_4 0x5C +#define HID_KEY_KEYPAD_5 0x5D +#define HID_KEY_KEYPAD_6 0x5E +#define HID_KEY_KEYPAD_7 0x5F +#define HID_KEY_KEYPAD_8 0x60 +#define HID_KEY_KEYPAD_9 0x61 +#define HID_KEY_KEYPAD_0 0x62 +#define HID_KEY_KEYPAD_DECIMAL 0x63 +#define HID_KEY_EUROPE_2 0x64 +#define HID_KEY_APPLICATION 0x65 +#define HID_KEY_POWER 0x66 +#define HID_KEY_KEYPAD_EQUAL 0x67 +#define HID_KEY_F13 0x68 +#define HID_KEY_F14 0x69 +#define HID_KEY_F15 0x6A +#define HID_KEY_F16 0x6B +#define HID_KEY_F17 0x6C +#define HID_KEY_F18 0x6D +#define HID_KEY_F19 0x6E +#define HID_KEY_F20 0x6F +#define HID_KEY_F21 0x70 +#define HID_KEY_F22 0x71 +#define HID_KEY_F23 0x72 +#define HID_KEY_F24 0x73 +#define HID_KEY_EXECUTE 0x74 +#define HID_KEY_HELP 0x75 +#define HID_KEY_MENU 0x76 +#define HID_KEY_SELECT 0x77 +#define HID_KEY_STOP 0x78 +#define HID_KEY_AGAIN 0x79 +#define HID_KEY_UNDO 0x7A +#define HID_KEY_CUT 0x7B +#define HID_KEY_COPY 0x7C +#define HID_KEY_PASTE 0x7D +#define HID_KEY_FIND 0x7E +#define HID_KEY_MUTE 0x7F +#define HID_KEY_VOLUME_UP 0x80 +#define HID_KEY_VOLUME_DOWN 0x81 +#define HID_KEY_LOCKING_CAPS_LOCK 0x82 +#define HID_KEY_LOCKING_NUM_LOCK 0x83 +#define HID_KEY_LOCKING_SCROLL_LOCK 0x84 +#define HID_KEY_KEYPAD_COMMA 0x85 +#define HID_KEY_KEYPAD_EQUAL_SIGN 0x86 +#define HID_KEY_KANJI1 0x87 +#define HID_KEY_KANJI2 0x88 +#define HID_KEY_KANJI3 0x89 +#define HID_KEY_KANJI4 0x8A +#define HID_KEY_KANJI5 0x8B +#define HID_KEY_KANJI6 0x8C +#define HID_KEY_KANJI7 0x8D +#define HID_KEY_KANJI8 0x8E +#define HID_KEY_KANJI9 0x8F +#define HID_KEY_LANG1 0x90 +#define HID_KEY_LANG2 0x91 +#define HID_KEY_LANG3 0x92 +#define HID_KEY_LANG4 0x93 +#define HID_KEY_LANG5 0x94 +#define HID_KEY_LANG6 0x95 +#define HID_KEY_LANG7 0x96 +#define HID_KEY_LANG8 0x97 +#define HID_KEY_LANG9 0x98 +#define HID_KEY_ALTERNATE_ERASE 0x99 +#define HID_KEY_SYSREQ_ATTENTION 0x9A +#define HID_KEY_CANCEL 0x9B +#define HID_KEY_CLEAR 0x9C +#define HID_KEY_PRIOR 0x9D +#define HID_KEY_RETURN 0x9E +#define HID_KEY_SEPARATOR 0x9F +#define HID_KEY_OUT 0xA0 +#define HID_KEY_OPER 0xA1 +#define HID_KEY_CLEAR_AGAIN 0xA2 +#define HID_KEY_CRSEL_PROPS 0xA3 +#define HID_KEY_EXSEL 0xA4 +// RESERVED 0xA5-AF +#define HID_KEY_KEYPAD_00 0xB0 +#define HID_KEY_KEYPAD_000 0xB1 +#define HID_KEY_THOUSANDS_SEPARATOR 0xB2 +#define HID_KEY_DECIMAL_SEPARATOR 0xB3 +#define HID_KEY_CURRENCY_UNIT 0xB4 +#define HID_KEY_CURRENCY_SUBUNIT 0xB5 +#define HID_KEY_KEYPAD_LEFT_PARENTHESIS 0xB6 +#define HID_KEY_KEYPAD_RIGHT_PARENTHESIS 0xB7 +#define HID_KEY_KEYPAD_LEFT_BRACE 0xB8 +#define HID_KEY_KEYPAD_RIGHT_BRACE 0xB9 +#define HID_KEY_KEYPAD_TAB 0xBA +#define HID_KEY_KEYPAD_BACKSPACE 0xBB +#define HID_KEY_KEYPAD_A 0xBC +#define HID_KEY_KEYPAD_B 0xBD +#define HID_KEY_KEYPAD_C 0xBE +#define HID_KEY_KEYPAD_D 0xBF +#define HID_KEY_KEYPAD_E 0xC0 +#define HID_KEY_KEYPAD_F 0xC1 +#define HID_KEY_KEYPAD_XOR 0xC2 +#define HID_KEY_KEYPAD_CARET 0xC3 +#define HID_KEY_KEYPAD_PERCENT 0xC4 +#define HID_KEY_KEYPAD_LESS_THAN 0xC5 +#define HID_KEY_KEYPAD_GREATER_THAN 0xC6 +#define HID_KEY_KEYPAD_AMPERSAND 0xC7 +#define HID_KEY_KEYPAD_DOUBLE_AMPERSAND 0xC8 +#define HID_KEY_KEYPAD_VERTICAL_BAR 0xC9 +#define HID_KEY_KEYPAD_DOUBLE_VERTICAL_BAR 0xCA +#define HID_KEY_KEYPAD_COLON 0xCB +#define HID_KEY_KEYPAD_HASH 0xCC +#define HID_KEY_KEYPAD_SPACE 0xCD +#define HID_KEY_KEYPAD_AT 0xCE +#define HID_KEY_KEYPAD_EXCLAMATION 0xCF +#define HID_KEY_KEYPAD_MEMORY_STORE 0xD0 +#define HID_KEY_KEYPAD_MEMORY_RECALL 0xD1 +#define HID_KEY_KEYPAD_MEMORY_CLEAR 0xD2 +#define HID_KEY_KEYPAD_MEMORY_ADD 0xD3 +#define HID_KEY_KEYPAD_MEMORY_SUBTRACT 0xD4 +#define HID_KEY_KEYPAD_MEMORY_MULTIPLY 0xD5 +#define HID_KEY_KEYPAD_MEMORY_DIVIDE 0xD6 +#define HID_KEY_KEYPAD_PLUS_MINUS 0xD7 +#define HID_KEY_KEYPAD_CLEAR 0xD8 +#define HID_KEY_KEYPAD_CLEAR_ENTRY 0xD9 +#define HID_KEY_KEYPAD_BINARY 0xDA +#define HID_KEY_KEYPAD_OCTAL 0xDB +#define HID_KEY_KEYPAD_DECIMAL_2 0xDC +#define HID_KEY_KEYPAD_HEXADECIMAL 0xDD +// RESERVED 0xDE-DF +#define HID_KEY_CONTROL_LEFT 0xE0 +#define HID_KEY_SHIFT_LEFT 0xE1 +#define HID_KEY_ALT_LEFT 0xE2 +#define HID_KEY_GUI_LEFT 0xE3 +#define HID_KEY_CONTROL_RIGHT 0xE4 +#define HID_KEY_SHIFT_RIGHT 0xE5 +#define HID_KEY_ALT_RIGHT 0xE6 +#define HID_KEY_GUI_RIGHT 0xE7 //--------------------------------------------------------------------+ @@ -684,32 +744,33 @@ enum { /// HID Usage Table - Table 1: Usage Page Summary enum { - HID_USAGE_PAGE_DESKTOP = 0x01, - HID_USAGE_PAGE_SIMULATE = 0x02, - HID_USAGE_PAGE_VIRTUAL_REALITY = 0x03, - HID_USAGE_PAGE_SPORT = 0x04, - HID_USAGE_PAGE_GAME = 0x05, - HID_USAGE_PAGE_GENERIC_DEVICE = 0x06, - HID_USAGE_PAGE_KEYBOARD = 0x07, - HID_USAGE_PAGE_LED = 0x08, - HID_USAGE_PAGE_BUTTON = 0x09, - HID_USAGE_PAGE_ORDINAL = 0x0a, - HID_USAGE_PAGE_TELEPHONY = 0x0b, - HID_USAGE_PAGE_CONSUMER = 0x0c, - HID_USAGE_PAGE_DIGITIZER = 0x0d, - HID_USAGE_PAGE_PID = 0x0f, - HID_USAGE_PAGE_UNICODE = 0x10, - HID_USAGE_PAGE_ALPHA_DISPLAY = 0x14, - HID_USAGE_PAGE_MEDICAL = 0x40, - HID_USAGE_PAGE_MONITOR = 0x80, //0x80 - 0x83 - HID_USAGE_PAGE_POWER = 0x84, // 0x084 - 0x87 - HID_USAGE_PAGE_BARCODE_SCANNER = 0x8c, - HID_USAGE_PAGE_SCALE = 0x8d, - HID_USAGE_PAGE_MSR = 0x8e, - HID_USAGE_PAGE_CAMERA = 0x90, - HID_USAGE_PAGE_ARCADE = 0x91, - HID_USAGE_PAGE_FIDO = 0xF1D0, // FIDO alliance HID usage page - HID_USAGE_PAGE_VENDOR = 0xFF00 // 0xFF00 - 0xFFFF + HID_USAGE_PAGE_DESKTOP = 0x01, + HID_USAGE_PAGE_SIMULATE = 0x02, + HID_USAGE_PAGE_VIRTUAL_REALITY = 0x03, + HID_USAGE_PAGE_SPORT = 0x04, + HID_USAGE_PAGE_GAME = 0x05, + HID_USAGE_PAGE_GENERIC_DEVICE = 0x06, + HID_USAGE_PAGE_KEYBOARD = 0x07, + HID_USAGE_PAGE_LED = 0x08, + HID_USAGE_PAGE_BUTTON = 0x09, + HID_USAGE_PAGE_ORDINAL = 0x0a, + HID_USAGE_PAGE_TELEPHONY = 0x0b, + HID_USAGE_PAGE_CONSUMER = 0x0c, + HID_USAGE_PAGE_DIGITIZER = 0x0d, + HID_USAGE_PAGE_PID = 0x0f, + HID_USAGE_PAGE_UNICODE = 0x10, + HID_USAGE_PAGE_ALPHA_DISPLAY = 0x14, + HID_USAGE_PAGE_MEDICAL = 0x40, + HID_USAGE_PAGE_LIGHTING_AND_ILLUMINATION = 0x59, + HID_USAGE_PAGE_MONITOR = 0x80, //0x80 - 0x83 + HID_USAGE_PAGE_POWER = 0x84, // 0x084 - 0x87 + HID_USAGE_PAGE_BARCODE_SCANNER = 0x8c, + HID_USAGE_PAGE_SCALE = 0x8d, + HID_USAGE_PAGE_MSR = 0x8e, + HID_USAGE_PAGE_CAMERA = 0x90, + HID_USAGE_PAGE_ARCADE = 0x91, + HID_USAGE_PAGE_FIDO = 0xF1D0, // FIDO alliance HID usage page + HID_USAGE_PAGE_VENDOR = 0xFF00 // 0xFF00 - 0xFFFF }; /// HID Usage Table - Table 6: Generic Desktop Page @@ -788,8 +849,7 @@ enum { /// HID Usage Table: Consumer Page (0x0C) /// Only contains controls that supported by Windows (whole list is too long) -enum -{ +enum { // Generic Control HID_USAGE_CONSUMER_CONTROL = 0x0001, @@ -845,9 +905,45 @@ enum HID_USAGE_CONSUMER_AC_PAN = 0x0238, }; +/// HID Usage Table - Lighting And Illumination Page (0x59) +enum { + HID_USAGE_LIGHTING_LAMP_ARRAY = 0x01, + HID_USAGE_LIGHTING_LAMP_ARRAY_ATTRIBUTES_REPORT = 0x02, + HID_USAGE_LIGHTING_LAMP_COUNT = 0x03, + HID_USAGE_LIGHTING_BOUNDING_BOX_WIDTH_IN_MICROMETERS = 0x04, + HID_USAGE_LIGHTING_BOUNDING_BOX_HEIGHT_IN_MICROMETERS = 0x05, + HID_USAGE_LIGHTING_BOUNDING_BOX_DEPTH_IN_MICROMETERS = 0x06, + HID_USAGE_LIGHTING_LAMP_ARRAY_KIND = 0x07, + HID_USAGE_LIGHTING_MIN_UPDATE_INTERVAL_IN_MICROSECONDS = 0x08, + HID_USAGE_LIGHTING_LAMP_ATTRIBUTES_REQUEST_REPORT = 0x20, + HID_USAGE_LIGHTING_LAMP_ID = 0x21, + HID_USAGE_LIGHTING_LAMP_ATTRIBUTES_RESPONSE_REPORT = 0x22, + HID_USAGE_LIGHTING_POSITION_X_IN_MICROMETERS = 0x23, + HID_USAGE_LIGHTING_POSITION_Y_IN_MICROMETERS = 0x24, + HID_USAGE_LIGHTING_POSITION_Z_IN_MICROMETERS = 0x25, + HID_USAGE_LIGHTING_LAMP_PURPOSES = 0x26, + HID_USAGE_LIGHTING_UPDATE_LATENCY_IN_MICROSECONDS = 0x27, + HID_USAGE_LIGHTING_RED_LEVEL_COUNT = 0x28, + HID_USAGE_LIGHTING_GREEN_LEVEL_COUNT = 0x29, + HID_USAGE_LIGHTING_BLUE_LEVEL_COUNT = 0x2A, + HID_USAGE_LIGHTING_INTENSITY_LEVEL_COUNT = 0x2B, + HID_USAGE_LIGHTING_IS_PROGRAMMABLE = 0x2C, + HID_USAGE_LIGHTING_INPUT_BINDING = 0x2D, + HID_USAGE_LIGHTING_LAMP_MULTI_UPDATE_REPORT = 0x50, + HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL = 0x51, + HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL = 0x52, + HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL = 0x53, + HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL = 0x54, + HID_USAGE_LIGHTING_LAMP_UPDATE_FLAGS = 0x55, + HID_USAGE_LIGHTING_LAMP_RANGE_UPDATE_REPORT = 0x60, + HID_USAGE_LIGHTING_LAMP_ID_START = 0x61, + HID_USAGE_LIGHTING_LAMP_ID_END = 0x62, + HID_USAGE_LIGHTING_LAMP_ARRAY_CONTROL_REPORT = 0x70, + HID_USAGE_LIGHTING_AUTONOMOUS_MODE = 0x71, +}; + /// HID Usage Table: FIDO Alliance Page (0xF1D0) -enum -{ +enum { HID_USAGE_FIDO_U2FHID = 0x01, // U2FHID usage for top-level collection HID_USAGE_FIDO_DATA_IN = 0x20, // Raw IN data report HID_USAGE_FIDO_DATA_OUT = 0x21 // Raw OUT data report diff --git a/pico-sdk/lib/tinyusb/src/class/hid/hid_device.c b/pico-sdk/lib/tinyusb/src/class/hid/hid_device.c index 5637ea6..ada0158 100644 --- a/pico-sdk/lib/tinyusb/src/class/hid/hid_device.c +++ b/pico-sdk/lib/tinyusb/src/class/hid/hid_device.c @@ -39,23 +39,23 @@ //--------------------------------------------------------------------+ // MACRO CONSTANT TYPEDEF //--------------------------------------------------------------------+ -typedef struct -{ +typedef struct { uint8_t itf_num; uint8_t ep_in; - uint8_t ep_out; // optional Out endpoint - uint8_t itf_protocol; // Boot mouse or keyboard + uint8_t ep_out; // optional Out endpoint + uint8_t itf_protocol; // Boot mouse or keyboard - uint8_t protocol_mode; // Boot (0) or Report protocol (1) - uint8_t idle_rate; // up to application to handle idle rate uint16_t report_desc_len; + CFG_TUSB_MEM_ALIGN uint8_t protocol_mode; // Boot (0) or Report protocol (1) + CFG_TUSB_MEM_ALIGN uint8_t idle_rate; // up to application to handle idle rate CFG_TUSB_MEM_ALIGN uint8_t epin_buf[CFG_TUD_HID_EP_BUFSIZE]; CFG_TUSB_MEM_ALIGN uint8_t epout_buf[CFG_TUD_HID_EP_BUFSIZE]; + CFG_TUSB_MEM_ALIGN uint8_t ctrl_buf[CFG_TUD_HID_EP_BUFSIZE]; // TODO save hid descriptor since host can specifically request this after enumeration // Note: HID descriptor may be not available from application after enumeration - tusb_hid_descriptor_hid_t const * hid_descriptor; + tusb_hid_descriptor_hid_t const *hid_descriptor; } hidd_interface_t; CFG_TUD_MEM_SECTION tu_static hidd_interface_t _hidd_itf[CFG_TUD_HID]; @@ -63,12 +63,12 @@ CFG_TUD_MEM_SECTION tu_static hidd_interface_t _hidd_itf[CFG_TUD_HID]; /*------------- Helpers -------------*/ static inline uint8_t get_index_by_itfnum(uint8_t itf_num) { - for (uint8_t i=0; i < CFG_TUD_HID; i++ ) - { - if ( itf_num == _hidd_itf[i].itf_num ) return i; - } + for (uint8_t i = 0; i < CFG_TUD_HID; i++) { + if (itf_num == _hidd_itf[i].itf_num) + return i; + } - return 0xFF; + return 0xFF; } //--------------------------------------------------------------------+ @@ -81,37 +81,29 @@ bool tud_hid_n_ready(uint8_t instance) return tud_ready() && (ep_in != 0) && !usbd_edpt_busy(rhport, ep_in); } -bool tud_hid_n_report(uint8_t instance, uint8_t report_id, void const* report, uint16_t len) +bool tud_hid_n_report(uint8_t instance, uint8_t report_id, void const *report, uint16_t len) { uint8_t const rhport = 0; - hidd_interface_t * p_hid = &_hidd_itf[instance]; + hidd_interface_t *p_hid = &_hidd_itf[instance]; // claim endpoint - TU_VERIFY( usbd_edpt_claim(rhport, p_hid->ep_in) ); + TU_VERIFY(usbd_edpt_claim(rhport, p_hid->ep_in)); // prepare data - if (report_id) - { + if (report_id) { p_hid->epin_buf[0] = report_id; - TU_VERIFY(0 == tu_memcpy_s(p_hid->epin_buf+1, CFG_TUD_HID_EP_BUFSIZE-1, report, len)); + TU_VERIFY(0 == tu_memcpy_s(p_hid->epin_buf + 1, CFG_TUD_HID_EP_BUFSIZE - 1, report, len)); len++; - }else - { + } else { TU_VERIFY(0 == tu_memcpy_s(p_hid->epin_buf, CFG_TUD_HID_EP_BUFSIZE, report, len)); } return usbd_edpt_xfer(rhport, p_hid->ep_in, p_hid->epin_buf, len); } -uint8_t tud_hid_n_interface_protocol(uint8_t instance) -{ - return _hidd_itf[instance].itf_protocol; -} +uint8_t tud_hid_n_interface_protocol(uint8_t instance) { return _hidd_itf[instance].itf_protocol; } -uint8_t tud_hid_n_get_protocol(uint8_t instance) -{ - return _hidd_itf[instance].protocol_mode; -} +uint8_t tud_hid_n_get_protocol(uint8_t instance) { return _hidd_itf[instance].protocol_mode; } bool tud_hid_n_keyboard_report(uint8_t instance, uint8_t report_id, uint8_t modifier, uint8_t keycode[6]) { @@ -120,44 +112,51 @@ bool tud_hid_n_keyboard_report(uint8_t instance, uint8_t report_id, uint8_t modi report.modifier = modifier; report.reserved = 0; - if ( keycode ) - { + if (keycode) { memcpy(report.keycode, keycode, sizeof(report.keycode)); - }else - { + } else { tu_memclr(report.keycode, 6); } return tud_hid_n_report(instance, report_id, &report, sizeof(report)); } -bool tud_hid_n_mouse_report(uint8_t instance, uint8_t report_id, - uint8_t buttons, int8_t x, int8_t y, int8_t vertical, int8_t horizontal) +bool tud_hid_n_mouse_report(uint8_t instance, uint8_t report_id, uint8_t buttons, int8_t x, int8_t y, int8_t vertical, int8_t horizontal) { - hid_mouse_report_t report = - { + hid_mouse_report_t report = { .buttons = buttons, - .x = x, - .y = y, - .wheel = vertical, - .pan = horizontal + .x = x, + .y = y, + .wheel = vertical, + .pan = horizontal }; return tud_hid_n_report(instance, report_id, &report, sizeof(report)); } -bool tud_hid_n_gamepad_report(uint8_t instance, uint8_t report_id, - int8_t x, int8_t y, int8_t z, int8_t rz, int8_t rx, int8_t ry, uint8_t hat, uint32_t buttons) { - hid_gamepad_report_t report = - { - .x = x, - .y = y, - .z = z, - .rz = rz, - .rx = rx, - .ry = ry, - .hat = hat, +bool tud_hid_n_abs_mouse_report(uint8_t instance, uint8_t report_id, uint8_t buttons, int16_t x, int16_t y, int8_t vertical, int8_t horizontal) +{ + hid_abs_mouse_report_t report = { .buttons = buttons, + .x = x, + .y = y, + .wheel = vertical, + .pan = horizontal + }; + return tud_hid_n_report(instance, report_id, &report, sizeof(report)); +} + +bool tud_hid_n_gamepad_report(uint8_t instance, uint8_t report_id, int8_t x, int8_t y, int8_t z, int8_t rz, int8_t rx, int8_t ry, uint8_t hat, uint32_t buttons) +{ + hid_gamepad_report_t report = { + .x = x, + .y = y, + .z = z, + .rz = rz, + .rx = rx, + .ry = ry, + .hat = hat, + .buttons = buttons, }; return tud_hid_n_report(instance, report_id, &report, sizeof(report)); @@ -171,59 +170,59 @@ void hidd_init(void) hidd_reset(0); } +bool hidd_deinit(void) +{ + return true; +} + void hidd_reset(uint8_t rhport) { - (void) rhport; + (void)rhport; tu_memclr(_hidd_itf, sizeof(_hidd_itf)); } -uint16_t hidd_open(uint8_t rhport, tusb_desc_interface_t const * desc_itf, uint16_t max_len) - { +uint16_t hidd_open(uint8_t rhport, tusb_desc_interface_t const *desc_itf, uint16_t max_len) +{ TU_VERIFY(TUSB_CLASS_HID == desc_itf->bInterfaceClass, 0); // len = interface + hid + n*endpoints - uint16_t const drv_len = - (uint16_t) (sizeof(tusb_desc_interface_t) + sizeof(tusb_hid_descriptor_hid_t) + - desc_itf->bNumEndpoints * sizeof(tusb_desc_endpoint_t)); + uint16_t const drv_len = (uint16_t)(sizeof(tusb_desc_interface_t) + sizeof(tusb_hid_descriptor_hid_t) + desc_itf->bNumEndpoints * sizeof(tusb_desc_endpoint_t)); TU_ASSERT(max_len >= drv_len, 0); // Find available interface - hidd_interface_t * p_hid = NULL; + hidd_interface_t *p_hid = NULL; uint8_t hid_id; - for(hid_id=0; hid_idhid_descriptor = (tusb_hid_descriptor_hid_t const *) p_desc; + p_hid->hid_descriptor = (tusb_hid_descriptor_hid_t const *)p_desc; //------------- Endpoint Descriptor -------------// p_desc = tu_desc_next(p_desc); TU_ASSERT(usbd_open_edpt_pair(rhport, p_desc, desc_itf->bNumEndpoints, TUSB_XFER_INTERRUPT, &p_hid->ep_out, &p_hid->ep_in), 0); - if ( desc_itf->bInterfaceSubClass == HID_SUBCLASS_BOOT ) p_hid->itf_protocol = desc_itf->bInterfaceProtocol; + if (desc_itf->bInterfaceSubClass == HID_SUBCLASS_BOOT) + p_hid->itf_protocol = desc_itf->bInterfaceProtocol; p_hid->protocol_mode = HID_PROTOCOL_REPORT; // Per Specs: default is report mode - p_hid->itf_num = desc_itf->bInterfaceNumber; + p_hid->itf_num = desc_itf->bInterfaceNumber; // Use offsetof to avoid pointer to the odd/misaligned address - p_hid->report_desc_len = tu_unaligned_read16((uint8_t const*) p_hid->hid_descriptor + offsetof(tusb_hid_descriptor_hid_t, wReportLength)); + p_hid->report_desc_len = tu_unaligned_read16((uint8_t const *)p_hid->hid_descriptor + offsetof(tusb_hid_descriptor_hid_t, wReportLength)); // Prepare for output endpoint - if (p_hid->ep_out) - { - if ( !usbd_edpt_xfer(rhport, p_hid->ep_out, p_hid->epout_buf, sizeof(p_hid->epout_buf)) ) - { + if (p_hid->ep_out) { + if (!usbd_edpt_xfer(rhport, p_hid->ep_out, p_hid->epout_buf, sizeof(p_hid->epout_buf))) { TU_LOG_FAILED(); TU_BREAKPOINT(); } @@ -235,144 +234,120 @@ uint16_t hidd_open(uint8_t rhport, tusb_desc_interface_t const * desc_itf, uint1 // Invoked when a control transfer occurred on an interface of this class // Driver response accordingly to the request and the transfer stage (setup/data/ack) // return false to stall control endpoint (e.g unsupported request) -bool hidd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) +bool hidd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) { TU_VERIFY(request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE); - uint8_t const hid_itf = get_index_by_itfnum((uint8_t) request->wIndex); + uint8_t const hid_itf = get_index_by_itfnum((uint8_t)request->wIndex); TU_VERIFY(hid_itf < CFG_TUD_HID); - hidd_interface_t* p_hid = &_hidd_itf[hid_itf]; + hidd_interface_t *p_hid = &_hidd_itf[hid_itf]; - if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD) - { + if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD) { //------------- STD Request -------------// - if ( stage == CONTROL_STAGE_SETUP ) - { - uint8_t const desc_type = tu_u16_high(request->wValue); - //uint8_t const desc_index = tu_u16_low (request->wValue); + if (stage == CONTROL_STAGE_SETUP) { + uint8_t const desc_type = tu_u16_high(request->wValue); + // uint8_t const desc_index = tu_u16_low (request->wValue); - if (request->bRequest == TUSB_REQ_GET_DESCRIPTOR && desc_type == HID_DESC_TYPE_HID) - { + if (request->bRequest == TUSB_REQ_GET_DESCRIPTOR && desc_type == HID_DESC_TYPE_HID) { TU_VERIFY(p_hid->hid_descriptor); - TU_VERIFY(tud_control_xfer(rhport, request, (void*)(uintptr_t) p_hid->hid_descriptor, p_hid->hid_descriptor->bLength)); - } - else if (request->bRequest == TUSB_REQ_GET_DESCRIPTOR && desc_type == HID_DESC_TYPE_REPORT) - { - uint8_t const * desc_report = tud_hid_descriptor_report_cb(hid_itf); - tud_control_xfer(rhport, request, (void*)(uintptr_t) desc_report, p_hid->report_desc_len); - } - else - { + TU_VERIFY(tud_control_xfer(rhport, request, (void *)(uintptr_t)p_hid->hid_descriptor, p_hid->hid_descriptor->bLength)); + } else if (request->bRequest == TUSB_REQ_GET_DESCRIPTOR && desc_type == HID_DESC_TYPE_REPORT) { + uint8_t const *desc_report = tud_hid_descriptor_report_cb(hid_itf); + tud_control_xfer(rhport, request, (void *)(uintptr_t)desc_report, p_hid->report_desc_len); + } else { return false; // stall unsupported request } } - } - else if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS) - { + } else if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS) { //------------- Class Specific Request -------------// - switch( request->bRequest ) - { - case HID_REQ_CONTROL_GET_REPORT: - if ( stage == CONTROL_STAGE_SETUP ) - { - uint8_t const report_type = tu_u16_high(request->wValue); - uint8_t const report_id = tu_u16_low(request->wValue); + switch (request->bRequest) { + case HID_REQ_CONTROL_GET_REPORT: + if (stage == CONTROL_STAGE_SETUP) { + uint8_t const report_type = tu_u16_high(request->wValue); + uint8_t const report_id = tu_u16_low(request->wValue); - uint8_t* report_buf = p_hid->epin_buf; - uint16_t req_len = tu_min16(request->wLength, CFG_TUD_HID_EP_BUFSIZE); + uint8_t *report_buf = p_hid->ctrl_buf; + uint16_t req_len = tu_min16(request->wLength, CFG_TUD_HID_EP_BUFSIZE); - uint16_t xferlen = 0; + uint16_t xferlen = 0; - // If host request a specific Report ID, add ID to as 1 byte of response - if ( (report_id != HID_REPORT_TYPE_INVALID) && (req_len > 1) ) - { - *report_buf++ = report_id; - req_len--; + // If host request a specific Report ID, add ID to as 1 byte of response + if ((report_id != HID_REPORT_TYPE_INVALID) && (req_len > 1)) { + *report_buf++ = report_id; + req_len--; - xferlen++; - } - - xferlen += tud_hid_get_report_cb(hid_itf, report_id, (hid_report_type_t) report_type, report_buf, req_len); - TU_ASSERT( xferlen > 0 ); - - tud_control_xfer(rhport, request, p_hid->epin_buf, xferlen); + xferlen++; } + + xferlen += tud_hid_get_report_cb(hid_itf, report_id, (hid_report_type_t)report_type, report_buf, req_len); + TU_ASSERT(xferlen > 0); + + tud_control_xfer(rhport, request, p_hid->ctrl_buf, xferlen); + } break; - case HID_REQ_CONTROL_SET_REPORT: - if ( stage == CONTROL_STAGE_SETUP ) - { - TU_VERIFY(request->wLength <= sizeof(p_hid->epout_buf)); - tud_control_xfer(rhport, request, p_hid->epout_buf, request->wLength); + case HID_REQ_CONTROL_SET_REPORT: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(request->wLength <= sizeof(p_hid->ctrl_buf)); + tud_control_xfer(rhport, request, p_hid->ctrl_buf, request->wLength); + } else if (stage == CONTROL_STAGE_ACK) { + uint8_t const report_type = tu_u16_high(request->wValue); + uint8_t const report_id = tu_u16_low(request->wValue); + + uint8_t const *report_buf = p_hid->ctrl_buf; + uint16_t report_len = tu_min16(request->wLength, CFG_TUD_HID_EP_BUFSIZE); + + // If host request a specific Report ID, extract report ID in buffer before invoking callback + if ((report_id != HID_REPORT_TYPE_INVALID) && (report_len > 1) && (report_id == report_buf[0])) { + report_buf++; + report_len--; } - else if ( stage == CONTROL_STAGE_ACK ) - { - uint8_t const report_type = tu_u16_high(request->wValue); - uint8_t const report_id = tu_u16_low(request->wValue); - uint8_t const* report_buf = p_hid->epout_buf; - uint16_t report_len = tu_min16(request->wLength, CFG_TUD_HID_EP_BUFSIZE); - - // If host request a specific Report ID, extract report ID in buffer before invoking callback - if ( (report_id != HID_REPORT_TYPE_INVALID) && (report_len > 1) && (report_id == report_buf[0]) ) - { - report_buf++; - report_len--; - } - - tud_hid_set_report_cb(hid_itf, report_id, (hid_report_type_t) report_type, report_buf, report_len); - } + tud_hid_set_report_cb(hid_itf, report_id, (hid_report_type_t)report_type, report_buf, report_len); + } break; - case HID_REQ_CONTROL_SET_IDLE: - if ( stage == CONTROL_STAGE_SETUP ) - { - p_hid->idle_rate = tu_u16_high(request->wValue); - if ( tud_hid_set_idle_cb ) - { - // stall request if callback return false - TU_VERIFY( tud_hid_set_idle_cb( hid_itf, p_hid->idle_rate) ); - } - - tud_control_status(rhport, request); + case HID_REQ_CONTROL_SET_IDLE: + if (stage == CONTROL_STAGE_SETUP) { + p_hid->idle_rate = tu_u16_high(request->wValue); + if (tud_hid_set_idle_cb) { + // stall request if callback return false + TU_VERIFY(tud_hid_set_idle_cb(hid_itf, p_hid->idle_rate)); } + + tud_control_status(rhport, request); + } break; - case HID_REQ_CONTROL_GET_IDLE: - if ( stage == CONTROL_STAGE_SETUP ) - { - // TODO idle rate of report - tud_control_xfer(rhport, request, &p_hid->idle_rate, 1); - } + case HID_REQ_CONTROL_GET_IDLE: + if (stage == CONTROL_STAGE_SETUP) { + // TODO idle rate of report + tud_control_xfer(rhport, request, &p_hid->idle_rate, 1); + } break; - case HID_REQ_CONTROL_GET_PROTOCOL: - if ( stage == CONTROL_STAGE_SETUP ) - { - tud_control_xfer(rhport, request, &p_hid->protocol_mode, 1); - } + case HID_REQ_CONTROL_GET_PROTOCOL: + if (stage == CONTROL_STAGE_SETUP) { + tud_control_xfer(rhport, request, &p_hid->protocol_mode, 1); + } break; - case HID_REQ_CONTROL_SET_PROTOCOL: - if ( stage == CONTROL_STAGE_SETUP ) - { - tud_control_status(rhport, request); - } - else if ( stage == CONTROL_STAGE_ACK ) - { - p_hid->protocol_mode = (uint8_t) request->wValue; - if (tud_hid_set_protocol_cb) - { - tud_hid_set_protocol_cb(hid_itf, p_hid->protocol_mode); - } + case HID_REQ_CONTROL_SET_PROTOCOL: + if (stage == CONTROL_STAGE_SETUP) { + tud_control_status(rhport, request); + } else if (stage == CONTROL_STAGE_ACK) { + p_hid->protocol_mode = (uint8_t)request->wValue; + if (tud_hid_set_protocol_cb) { + tud_hid_set_protocol_cb(hid_itf, p_hid->protocol_mode); } + } break; - default: return false; // stall unsupported request + default: + return false; // stall unsupported request } - }else - { + } else { return false; // stall unsupported request } @@ -381,31 +356,43 @@ bool hidd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t bool hidd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { - (void) result; + (void)result; uint8_t instance = 0; - hidd_interface_t * p_hid = _hidd_itf; + hidd_interface_t *p_hid = _hidd_itf; // Identify which interface to use - for (instance = 0; instance < CFG_TUD_HID; instance++) - { + for (instance = 0; instance < CFG_TUD_HID; instance++) { p_hid = &_hidd_itf[instance]; - if ( (ep_addr == p_hid->ep_out) || (ep_addr == p_hid->ep_in) ) break; + if ((ep_addr == p_hid->ep_out) || (ep_addr == p_hid->ep_in)) + break; } TU_ASSERT(instance < CFG_TUD_HID); + // Check if there was a problem + if (XFER_RESULT_SUCCESS != result) { // Inform application about the issue + if (tud_hid_report_fail_cb) { + tud_hid_report_fail_cb(instance, ep_addr, (uint16_t)xferred_bytes); + } + + // Allow a new transfer to be received if issue happened on an OUT endpoint + if (ep_addr == p_hid->ep_out) { + // Prepare the OUT endpoint to be able to receive a new transfer + TU_ASSERT(usbd_edpt_xfer(rhport, p_hid->ep_out, p_hid->epout_buf, sizeof(p_hid->epout_buf))); + } + + return true; + } + // Sent report successfully - if (ep_addr == p_hid->ep_in) - { - if (tud_hid_report_complete_cb) - { - tud_hid_report_complete_cb(instance, p_hid->epin_buf, (uint16_t) xferred_bytes); + if (ep_addr == p_hid->ep_in) { + if (tud_hid_report_complete_cb) { + tud_hid_report_complete_cb(instance, p_hid->epin_buf, (uint16_t)xferred_bytes); } } - // Received report - else if (ep_addr == p_hid->ep_out) - { - tud_hid_set_report_cb(instance, 0, HID_REPORT_TYPE_INVALID, p_hid->epout_buf, (uint16_t) xferred_bytes); + // Received report successfully + else if (ep_addr == p_hid->ep_out) { + tud_hid_set_report_cb(instance, 0, HID_REPORT_TYPE_OUTPUT, p_hid->epout_buf, (uint16_t)xferred_bytes); TU_ASSERT(usbd_edpt_xfer(rhport, p_hid->ep_out, p_hid->epout_buf, sizeof(p_hid->epout_buf))); } diff --git a/pico-sdk/lib/tinyusb/src/class/hid/hid_device.h b/pico-sdk/lib/tinyusb/src/class/hid/hid_device.h index 17b24de..fcbf161 100644 --- a/pico-sdk/lib/tinyusb/src/class/hid/hid_device.h +++ b/pico-sdk/lib/tinyusb/src/class/hid/hid_device.h @@ -72,6 +72,16 @@ bool tud_hid_n_keyboard_report(uint8_t instance, uint8_t report_id, uint8_t modi // use template layout report as defined by hid_mouse_report_t bool tud_hid_n_mouse_report(uint8_t instance, uint8_t report_id, uint8_t buttons, int8_t x, int8_t y, int8_t vertical, int8_t horizontal); +// ABSOLUTE MOUSE: convenient helper to send absolute mouse report if application +// use template layout report as defined by hid_abs_mouse_report_t +bool tud_hid_n_abs_mouse_report(uint8_t instance, uint8_t report_id, uint8_t buttons, int16_t x, int16_t y, int8_t vertical, int8_t horizontal); + + +static inline bool tud_hid_abs_mouse_report(uint8_t report_id, uint8_t buttons, int16_t x, int16_t y, int8_t vertical, int8_t horizontal) +{ + return tud_hid_n_abs_mouse_report(0, report_id, buttons, x, y, vertical, horizontal); +} + // Gamepad: convenient helper to send gamepad report if application // use template layout report TUD_HID_REPORT_DESC_GAMEPAD bool tud_hid_n_gamepad_report(uint8_t instance, uint8_t report_id, int8_t x, int8_t y, int8_t z, int8_t rz, int8_t rx, int8_t ry, uint8_t hat, uint32_t buttons); @@ -118,6 +128,8 @@ TU_ATTR_WEAK bool tud_hid_set_idle_cb(uint8_t instance, uint8_t idle_rate); // Note: For composite reports, report[0] is report ID TU_ATTR_WEAK void tud_hid_report_complete_cb(uint8_t instance, uint8_t const* report, uint16_t len); +// Invoked when a transfer wasn't successful +TU_ATTR_WEAK void tud_hid_report_fail_cb(uint8_t instance, uint8_t ep_addr, uint16_t len); //--------------------------------------------------------------------+ // Inline Functions @@ -266,6 +278,55 @@ static inline bool tud_hid_gamepad_report(uint8_t report_id, int8_t x, int8_t y HID_COLLECTION_END , \ HID_COLLECTION_END \ +// Absolute Mouse Report Descriptor Template +#define TUD_HID_REPORT_DESC_ABSMOUSE(...) \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_MOUSE ) ,\ + HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\ + /* Report ID if any */\ + __VA_ARGS__ \ + HID_USAGE ( HID_USAGE_DESKTOP_POINTER ) ,\ + HID_COLLECTION ( HID_COLLECTION_PHYSICAL ) ,\ + HID_USAGE_PAGE ( HID_USAGE_PAGE_BUTTON ) ,\ + HID_USAGE_MIN ( 1 ) ,\ + HID_USAGE_MAX ( 5 ) ,\ + HID_LOGICAL_MIN ( 0 ) ,\ + HID_LOGICAL_MAX ( 1 ) ,\ + /* Left, Right, Middle, Backward, Forward buttons */ \ + HID_REPORT_COUNT( 5 ) ,\ + HID_REPORT_SIZE ( 1 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + /* 3 bit padding */ \ + HID_REPORT_COUNT( 1 ) ,\ + HID_REPORT_SIZE ( 3 ) ,\ + HID_INPUT ( HID_CONSTANT ) ,\ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + /* X, Y absolute position [0, 32767] */ \ + HID_USAGE ( HID_USAGE_DESKTOP_X ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_Y ) ,\ + HID_LOGICAL_MIN ( 0x00 ) ,\ + HID_LOGICAL_MAX_N( 0x7FFF, 2 ) ,\ + HID_REPORT_SIZE ( 16 ) ,\ + HID_REPORT_COUNT ( 2 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + /* Vertical wheel scroll [-127, 127] */ \ + HID_USAGE ( HID_USAGE_DESKTOP_WHEEL ) ,\ + HID_LOGICAL_MIN ( 0x81 ) ,\ + HID_LOGICAL_MAX ( 0x7f ) ,\ + HID_REPORT_COUNT( 1 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_RELATIVE ) ,\ + HID_USAGE_PAGE ( HID_USAGE_PAGE_CONSUMER ), \ + /* Horizontal wheel scroll [-127, 127] */ \ + HID_USAGE_N ( HID_USAGE_CONSUMER_AC_PAN, 2 ), \ + HID_LOGICAL_MIN ( 0x81 ), \ + HID_LOGICAL_MAX ( 0x7f ), \ + HID_REPORT_COUNT( 1 ), \ + HID_REPORT_SIZE ( 8 ), \ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_RELATIVE ), \ + HID_COLLECTION_END , \ + HID_COLLECTION_END \ + // Consumer Control Report Descriptor Template #define TUD_HID_REPORT_DESC_CONSUMER(...) \ HID_USAGE_PAGE ( HID_USAGE_PAGE_CONSUMER ) ,\ @@ -402,15 +463,189 @@ static inline bool tud_hid_gamepad_report(uint8_t report_id, int8_t x, int8_t y HID_OUTPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ HID_COLLECTION_END \ +// HID Lighting and Illumination Report Descriptor Template +// - 1st parameter is report id (required) +// Creates 6 report ids for lighting HID usages in the following order: +// report_id+0: HID_USAGE_LIGHTING_LAMP_ARRAY_ATTRIBUTES_REPORT +// report_id+1: HID_USAGE_LIGHTING_LAMP_ATTRIBUTES_REQUEST_REPORT +// report_id+2: HID_USAGE_LIGHTING_LAMP_ATTRIBUTES_RESPONSE_REPORT +// report_id+3: HID_USAGE_LIGHTING_LAMP_MULTI_UPDATE_REPORT +// report_id+4: HID_USAGE_LIGHTING_LAMP_RANGE_UPDATE_REPORT +// report_id+5: HID_USAGE_LIGHTING_LAMP_ARRAY_CONTROL_REPORT +#define TUD_HID_REPORT_DESC_LIGHTING(report_id) \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_LIGHTING_AND_ILLUMINATION ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ARRAY ),\ + HID_COLLECTION ( HID_COLLECTION_APPLICATION ),\ + /* Lamp Array Attributes Report */ \ + HID_REPORT_ID (report_id ) \ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ARRAY_ATTRIBUTES_REPORT ),\ + HID_COLLECTION ( HID_COLLECTION_LOGICAL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_COUNT ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 65535, 3 ),\ + HID_REPORT_SIZE ( 16 ),\ + HID_REPORT_COUNT ( 1 ),\ + HID_FEATURE ( HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BOUNDING_BOX_WIDTH_IN_MICROMETERS ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BOUNDING_BOX_HEIGHT_IN_MICROMETERS ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BOUNDING_BOX_DEPTH_IN_MICROMETERS ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ARRAY_KIND ),\ + HID_USAGE ( HID_USAGE_LIGHTING_MIN_UPDATE_INTERVAL_IN_MICROSECONDS ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 2147483647, 3 ),\ + HID_REPORT_SIZE ( 32 ),\ + HID_REPORT_COUNT ( 5 ),\ + HID_FEATURE ( HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_COLLECTION_END ,\ + /* Lamp Attributes Request Report */ \ + HID_REPORT_ID ( report_id + 1 ) \ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ATTRIBUTES_REQUEST_REPORT ),\ + HID_COLLECTION ( HID_COLLECTION_LOGICAL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ID ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 65535, 3 ),\ + HID_REPORT_SIZE ( 16 ),\ + HID_REPORT_COUNT ( 1 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_COLLECTION_END ,\ + /* Lamp Attributes Response Report */ \ + HID_REPORT_ID ( report_id + 2 ) \ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ATTRIBUTES_RESPONSE_REPORT ),\ + HID_COLLECTION ( HID_COLLECTION_LOGICAL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ID ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 65535, 3 ),\ + HID_REPORT_SIZE ( 16 ),\ + HID_REPORT_COUNT ( 1 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_USAGE ( HID_USAGE_LIGHTING_POSITION_X_IN_MICROMETERS ),\ + HID_USAGE ( HID_USAGE_LIGHTING_POSITION_Y_IN_MICROMETERS ),\ + HID_USAGE ( HID_USAGE_LIGHTING_POSITION_Z_IN_MICROMETERS ),\ + HID_USAGE ( HID_USAGE_LIGHTING_UPDATE_LATENCY_IN_MICROSECONDS ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_PURPOSES ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 2147483647, 3 ),\ + HID_REPORT_SIZE ( 32 ),\ + HID_REPORT_COUNT ( 5 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_LEVEL_COUNT ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_LEVEL_COUNT ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_LEVEL_COUNT ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_LEVEL_COUNT ),\ + HID_USAGE ( HID_USAGE_LIGHTING_IS_PROGRAMMABLE ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INPUT_BINDING ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 255, 2 ),\ + HID_REPORT_SIZE ( 8 ),\ + HID_REPORT_COUNT ( 6 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_COLLECTION_END ,\ + /* Lamp Multi-Update Report */ \ + HID_REPORT_ID ( report_id + 3 ) \ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_MULTI_UPDATE_REPORT ),\ + HID_COLLECTION ( HID_COLLECTION_LOGICAL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_COUNT ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_UPDATE_FLAGS ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX ( 8 ),\ + HID_REPORT_SIZE ( 8 ),\ + HID_REPORT_COUNT ( 2 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ID ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 65535, 3 ),\ + HID_REPORT_SIZE ( 16 ),\ + HID_REPORT_COUNT ( 8 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 255, 2 ),\ + HID_REPORT_SIZE ( 8 ),\ + HID_REPORT_COUNT ( 32 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_COLLECTION_END ,\ + /* Lamp Range Update Report */ \ + HID_REPORT_ID ( report_id + 4 ) \ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_RANGE_UPDATE_REPORT ),\ + HID_COLLECTION ( HID_COLLECTION_LOGICAL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_UPDATE_FLAGS ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX ( 8 ),\ + HID_REPORT_SIZE ( 8 ),\ + HID_REPORT_COUNT ( 1 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ID_START ),\ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ID_END ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 65535, 3 ),\ + HID_REPORT_SIZE ( 16 ),\ + HID_REPORT_COUNT ( 2 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_USAGE ( HID_USAGE_LIGHTING_RED_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_GREEN_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_BLUE_UPDATE_CHANNEL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_INTENSITY_UPDATE_CHANNEL ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX_N ( 255, 2 ),\ + HID_REPORT_SIZE ( 8 ),\ + HID_REPORT_COUNT ( 4 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_COLLECTION_END ,\ + /* Lamp Array Control Report */ \ + HID_REPORT_ID ( report_id + 5 ) \ + HID_USAGE ( HID_USAGE_LIGHTING_LAMP_ARRAY_CONTROL_REPORT ),\ + HID_COLLECTION ( HID_COLLECTION_LOGICAL ),\ + HID_USAGE ( HID_USAGE_LIGHTING_AUTONOMOUS_MODE ),\ + HID_LOGICAL_MIN ( 0 ),\ + HID_LOGICAL_MAX ( 1 ),\ + HID_REPORT_SIZE ( 8 ),\ + HID_REPORT_COUNT ( 1 ),\ + HID_FEATURE ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_COLLECTION_END ,\ + HID_COLLECTION_END \ + //--------------------------------------------------------------------+ // Internal Class Driver API //--------------------------------------------------------------------+ void hidd_init (void); +bool hidd_deinit (void); void hidd_reset (uint8_t rhport); uint16_t hidd_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); bool hidd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); bool hidd_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); + #ifdef __cplusplus } #endif diff --git a/pico-sdk/lib/tinyusb/src/class/hid/hid_host.c b/pico-sdk/lib/tinyusb/src/class/hid/hid_host.c index 54e6c98..115a8a4 100644 --- a/pico-sdk/lib/tinyusb/src/class/hid/hid_host.c +++ b/pico-sdk/lib/tinyusb/src/class/hid/hid_host.c @@ -39,22 +39,22 @@ #endif #define TU_LOG_DRV(...) TU_LOG(CFG_TUH_HID_LOG_LEVEL, __VA_ARGS__) + //--------------------------------------------------------------------+ // MACRO CONSTANT TYPEDEF //--------------------------------------------------------------------+ - -typedef struct -{ +typedef struct { uint8_t daddr; uint8_t itf_num; uint8_t ep_in; uint8_t ep_out; + bool mounted; // Enumeration is complete uint8_t itf_protocol; // None, Keyboard, Mouse uint8_t protocol_mode; // Boot (0) or Report protocol (1) - uint8_t report_desc_type; + uint8_t report_desc_type; uint16_t report_desc_len; uint16_t epin_size; @@ -72,78 +72,57 @@ tu_static uint8_t _hidh_default_protocol = HID_PROTOCOL_BOOT; //--------------------------------------------------------------------+ // Helper //--------------------------------------------------------------------+ - -TU_ATTR_ALWAYS_INLINE static inline -hidh_interface_t* get_hid_itf(uint8_t daddr, uint8_t idx) -{ +TU_ATTR_ALWAYS_INLINE static inline hidh_interface_t* get_hid_itf(uint8_t daddr, uint8_t idx) { TU_ASSERT(daddr > 0 && idx < CFG_TUH_HID, NULL); hidh_interface_t* p_hid = &_hidh_itf[idx]; return (p_hid->daddr == daddr) ? p_hid : NULL; } // Get instance ID by endpoint address -static uint8_t get_idx_by_epaddr(uint8_t daddr, uint8_t ep_addr) -{ - for ( uint8_t idx = 0; idx < CFG_TUH_HID; idx++ ) - { - hidh_interface_t const * p_hid = &_hidh_itf[idx]; - - if ( p_hid->daddr == daddr && - (p_hid->ep_in == ep_addr || p_hid->ep_out == ep_addr) ) - { +static uint8_t get_idx_by_epaddr(uint8_t daddr, uint8_t ep_addr) { + for (uint8_t idx = 0; idx < CFG_TUH_HID; idx++) { + hidh_interface_t const* p_hid = &_hidh_itf[idx]; + if (p_hid->daddr == daddr && + (p_hid->ep_in == ep_addr || p_hid->ep_out == ep_addr)) { return idx; } } - return TUSB_INDEX_INVALID_8; } -static hidh_interface_t* find_new_itf(void) -{ - for(uint8_t i=0; imounted; } -bool tuh_hid_itf_get_info(uint8_t daddr, uint8_t idx, tuh_itf_info_t* info) -{ +bool tuh_hid_itf_get_info(uint8_t daddr, uint8_t idx, tuh_itf_info_t* info) { hidh_interface_t* p_hid = get_hid_itf(daddr, idx); TU_VERIFY(p_hid && info); @@ -151,34 +130,30 @@ bool tuh_hid_itf_get_info(uint8_t daddr, uint8_t idx, tuh_itf_info_t* info) // re-construct descriptor tusb_desc_interface_t* desc = &info->desc; - desc->bLength = sizeof(tusb_desc_interface_t); - desc->bDescriptorType = TUSB_DESC_INTERFACE; + desc->bLength = sizeof(tusb_desc_interface_t); + desc->bDescriptorType = TUSB_DESC_INTERFACE; - desc->bInterfaceNumber = p_hid->itf_num; - desc->bAlternateSetting = 0; - desc->bNumEndpoints = (uint8_t) ((p_hid->ep_in ? 1u : 0u) + (p_hid->ep_out ? 1u : 0u)); - desc->bInterfaceClass = TUSB_CLASS_HID; + desc->bInterfaceNumber = p_hid->itf_num; + desc->bAlternateSetting = 0; + desc->bNumEndpoints = (uint8_t) ((p_hid->ep_in ? 1u : 0u) + (p_hid->ep_out ? 1u : 0u)); + desc->bInterfaceClass = TUSB_CLASS_HID; desc->bInterfaceSubClass = (p_hid->itf_protocol ? HID_SUBCLASS_BOOT : HID_SUBCLASS_NONE); desc->bInterfaceProtocol = p_hid->itf_protocol; - desc->iInterface = 0; // not used yet + desc->iInterface = 0; // not used yet return true; } -uint8_t tuh_hid_itf_get_index(uint8_t daddr, uint8_t itf_num) -{ - for ( uint8_t idx = 0; idx < CFG_TUH_HID; idx++ ) - { - hidh_interface_t const * p_hid = &_hidh_itf[idx]; - - if ( p_hid->daddr == daddr && p_hid->itf_num == itf_num) return idx; +uint8_t tuh_hid_itf_get_index(uint8_t daddr, uint8_t itf_num) { + for (uint8_t idx = 0; idx < CFG_TUH_HID; idx++) { + hidh_interface_t const* p_hid = &_hidh_itf[idx]; + if (p_hid->daddr == daddr && p_hid->itf_num == itf_num) return idx; } return TUSB_INDEX_INVALID_8; } -uint8_t tuh_hid_interface_protocol(uint8_t daddr, uint8_t idx) -{ +uint8_t tuh_hid_interface_protocol(uint8_t daddr, uint8_t idx) { hidh_interface_t* p_hid = get_hid_itf(daddr, idx); return p_hid ? p_hid->itf_protocol : 0; } @@ -186,29 +161,24 @@ uint8_t tuh_hid_interface_protocol(uint8_t daddr, uint8_t idx) //--------------------------------------------------------------------+ // Control Endpoint API //--------------------------------------------------------------------+ - -uint8_t tuh_hid_get_protocol(uint8_t daddr, uint8_t idx) -{ +uint8_t tuh_hid_get_protocol(uint8_t daddr, uint8_t idx) { hidh_interface_t* p_hid = get_hid_itf(daddr, idx); return p_hid ? p_hid->protocol_mode : 0; } -static void set_protocol_complete(tuh_xfer_t* xfer) -{ +static void set_protocol_complete(tuh_xfer_t* xfer) { uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); - uint8_t const daddr = xfer->daddr; - uint8_t const idx = tuh_hid_itf_get_index(daddr, itf_num); + uint8_t const daddr = xfer->daddr; + uint8_t const idx = tuh_hid_itf_get_index(daddr, itf_num); hidh_interface_t* p_hid = get_hid_itf(daddr, idx); - TU_VERIFY(p_hid, ); + TU_VERIFY(p_hid,); - if (XFER_RESULT_SUCCESS == xfer->result) - { + if (XFER_RESULT_SUCCESS == xfer->result) { p_hid->protocol_mode = (uint8_t) tu_le16toh(xfer->setup->wValue); } - if (tuh_hid_set_protocol_complete_cb) - { + if (tuh_hid_set_protocol_complete_cb) { tuh_hid_set_protocol_complete_cb(daddr, idx, p_hid->protocol_mode); } } @@ -217,123 +187,153 @@ void tuh_hid_set_default_protocol(uint8_t protocol) { _hidh_default_protocol = protocol; } -static bool _hidh_set_protocol(uint8_t daddr, uint8_t itf_num, uint8_t protocol, tuh_xfer_cb_t complete_cb, uintptr_t user_data) -{ +static bool _hidh_set_protocol(uint8_t daddr, uint8_t itf_num, uint8_t protocol, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { TU_LOG_DRV("HID Set Protocol = %d\r\n", protocol); - tusb_control_request_t const request = - { - .bmRequestType_bit = - { - .recipient = TUSB_REQ_RCPT_INTERFACE, - .type = TUSB_REQ_TYPE_CLASS, - .direction = TUSB_DIR_OUT - }, - .bRequest = HID_REQ_CONTROL_SET_PROTOCOL, - .wValue = protocol, - .wIndex = itf_num, - .wLength = 0 + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_OUT + }, + .bRequest = HID_REQ_CONTROL_SET_PROTOCOL, + .wValue = protocol, + .wIndex = itf_num, + .wLength = 0 }; - tuh_xfer_t xfer = - { - .daddr = daddr, - .ep_addr = 0, - .setup = &request, - .buffer = NULL, - .complete_cb = complete_cb, - .user_data = user_data + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = NULL, + .complete_cb = complete_cb, + .user_data = user_data }; return tuh_control_xfer(&xfer); } -bool tuh_hid_set_protocol(uint8_t daddr, uint8_t idx, uint8_t protocol) -{ +bool tuh_hid_set_protocol(uint8_t daddr, uint8_t idx, uint8_t protocol) { hidh_interface_t* p_hid = get_hid_itf(daddr, idx); TU_VERIFY(p_hid && p_hid->itf_protocol != HID_ITF_PROTOCOL_NONE); return _hidh_set_protocol(daddr, p_hid->itf_num, protocol, set_protocol_complete, 0); } -static void set_report_complete(tuh_xfer_t* xfer) -{ - TU_LOG_DRV("HID Set Report complete\r\n"); +static void get_report_complete(tuh_xfer_t* xfer) { + TU_LOG_DRV("HID Get Report complete\r\n"); - if (tuh_hid_set_report_complete_cb) - { + if (tuh_hid_get_report_complete_cb) { uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); - uint8_t const idx = tuh_hid_itf_get_index(xfer->daddr, itf_num); + uint8_t const idx = tuh_hid_itf_get_index(xfer->daddr, itf_num); uint8_t const report_type = tu_u16_high(xfer->setup->wValue); - uint8_t const report_id = tu_u16_low(xfer->setup->wValue); + uint8_t const report_id = tu_u16_low(xfer->setup->wValue); + + tuh_hid_get_report_complete_cb(xfer->daddr, idx, report_id, report_type, + (xfer->result == XFER_RESULT_SUCCESS) ? xfer->setup->wLength : 0); + } +} + +bool tuh_hid_get_report(uint8_t daddr, uint8_t idx, uint8_t report_id, uint8_t report_type, void* report, uint16_t len) { + hidh_interface_t* p_hid = get_hid_itf(daddr, idx); + TU_VERIFY(p_hid); + TU_LOG_DRV("HID Get Report: id = %u, type = %u, len = %u\r\n", report_id, report_type, len); + + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_IN + }, + .bRequest = HID_REQ_CONTROL_GET_REPORT, + .wValue = tu_htole16(tu_u16(report_type, report_id)), + .wIndex = tu_htole16((uint16_t) p_hid->itf_num), + .wLength = len + }; + + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = report, + .complete_cb = get_report_complete, + .user_data = 0 + }; + + return tuh_control_xfer(&xfer); +} + +static void set_report_complete(tuh_xfer_t* xfer) { + TU_LOG_DRV("HID Set Report complete\r\n"); + + if (tuh_hid_set_report_complete_cb) { + uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); + uint8_t const idx = tuh_hid_itf_get_index(xfer->daddr, itf_num); + + uint8_t const report_type = tu_u16_high(xfer->setup->wValue); + uint8_t const report_id = tu_u16_low(xfer->setup->wValue); tuh_hid_set_report_complete_cb(xfer->daddr, idx, report_id, report_type, (xfer->result == XFER_RESULT_SUCCESS) ? xfer->setup->wLength : 0); } } -bool tuh_hid_set_report(uint8_t daddr, uint8_t idx, uint8_t report_id, uint8_t report_type, void* report, uint16_t len) -{ +bool tuh_hid_set_report(uint8_t daddr, uint8_t idx, uint8_t report_id, uint8_t report_type, void* report, uint16_t len) { hidh_interface_t* p_hid = get_hid_itf(daddr, idx); TU_VERIFY(p_hid); - TU_LOG_DRV("HID Set Report: id = %u, type = %u, len = %u\r\n", report_id, report_type, len); - tusb_control_request_t const request = - { - .bmRequestType_bit = - { - .recipient = TUSB_REQ_RCPT_INTERFACE, - .type = TUSB_REQ_TYPE_CLASS, - .direction = TUSB_DIR_OUT - }, - .bRequest = HID_REQ_CONTROL_SET_REPORT, - .wValue = tu_htole16(tu_u16(report_type, report_id)), - .wIndex = tu_htole16((uint16_t)p_hid->itf_num), - .wLength = len + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_OUT + }, + .bRequest = HID_REQ_CONTROL_SET_REPORT, + .wValue = tu_htole16(tu_u16(report_type, report_id)), + .wIndex = tu_htole16((uint16_t) p_hid->itf_num), + .wLength = len }; - tuh_xfer_t xfer = - { - .daddr = daddr, - .ep_addr = 0, - .setup = &request, - .buffer = report, - .complete_cb = set_report_complete, - .user_data = 0 + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = report, + .complete_cb = set_report_complete, + .user_data = 0 }; return tuh_control_xfer(&xfer); } -static bool _hidh_set_idle(uint8_t daddr, uint8_t itf_num, uint16_t idle_rate, tuh_xfer_cb_t complete_cb, uintptr_t user_data) -{ +static bool _hidh_set_idle(uint8_t daddr, uint8_t itf_num, uint16_t idle_rate, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { // SET IDLE request, device can stall if not support this request TU_LOG_DRV("HID Set Idle \r\n"); - tusb_control_request_t const request = - { - .bmRequestType_bit = - { - .recipient = TUSB_REQ_RCPT_INTERFACE, - .type = TUSB_REQ_TYPE_CLASS, - .direction = TUSB_DIR_OUT - }, - .bRequest = HID_REQ_CONTROL_SET_IDLE, - .wValue = tu_htole16(idle_rate), - .wIndex = tu_htole16((uint16_t)itf_num), - .wLength = 0 + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_OUT + }, + .bRequest = HID_REQ_CONTROL_SET_IDLE, + .wValue = tu_htole16(idle_rate), + .wIndex = tu_htole16((uint16_t) itf_num), + .wLength = 0 }; - tuh_xfer_t xfer = - { - .daddr = daddr, - .ep_addr = 0, - .setup = &request, - .buffer = NULL, - .complete_cb = complete_cb, - .user_data = user_data + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = NULL, + .complete_cb = complete_cb, + .user_data = user_data }; return tuh_control_xfer(&xfer); @@ -344,68 +344,60 @@ static bool _hidh_set_idle(uint8_t daddr, uint8_t itf_num, uint16_t idle_rate, t //--------------------------------------------------------------------+ // Check if HID interface is ready to receive report -bool tuh_hid_receive_ready(uint8_t dev_addr, uint8_t idx) -{ +bool tuh_hid_receive_ready(uint8_t dev_addr, uint8_t idx) { hidh_interface_t* p_hid = get_hid_itf(dev_addr, idx); TU_VERIFY(p_hid); - return !usbh_edpt_busy(dev_addr, p_hid->ep_in); } -bool tuh_hid_receive_report(uint8_t daddr, uint8_t idx) -{ +bool tuh_hid_receive_report(uint8_t daddr, uint8_t idx) { hidh_interface_t* p_hid = get_hid_itf(daddr, idx); TU_VERIFY(p_hid); // claim endpoint - TU_VERIFY( usbh_edpt_claim(daddr, p_hid->ep_in) ); + TU_VERIFY(usbh_edpt_claim(daddr, p_hid->ep_in)); - if ( !usbh_edpt_xfer(daddr, p_hid->ep_in, p_hid->epin_buf, p_hid->epin_size) ) - { + if (!usbh_edpt_xfer(daddr, p_hid->ep_in, p_hid->epin_buf, p_hid->epin_size)) { usbh_edpt_release(daddr, p_hid->ep_in); return false; } return true; } - -bool tuh_hid_send_ready(uint8_t dev_addr, uint8_t idx) -{ +bool tuh_hid_receive_abort(uint8_t dev_addr, uint8_t idx) { hidh_interface_t* p_hid = get_hid_itf(dev_addr, idx); TU_VERIFY(p_hid); + return tuh_edpt_abort_xfer(dev_addr, p_hid->ep_in); +} +bool tuh_hid_send_ready(uint8_t dev_addr, uint8_t idx) { + hidh_interface_t* p_hid = get_hid_itf(dev_addr, idx); + TU_VERIFY(p_hid); return !usbh_edpt_busy(dev_addr, p_hid->ep_out); } -bool tuh_hid_send_report(uint8_t daddr, uint8_t idx, uint8_t report_id, const void* report, uint16_t len) -{ +bool tuh_hid_send_report(uint8_t daddr, uint8_t idx, uint8_t report_id, const void* report, uint16_t len) { TU_LOG_DRV("HID Send Report %d\r\n", report_id); hidh_interface_t* p_hid = get_hid_itf(daddr, idx); TU_VERIFY(p_hid); - if (p_hid->ep_out == 0) - { + if (p_hid->ep_out == 0) { // This HID does not have an out endpoint (other than control) return false; - } - else if (len > CFG_TUH_HID_EPOUT_BUFSIZE || - (report_id != 0 && len > (CFG_TUH_HID_EPOUT_BUFSIZE - 1))) - { + } else if (len > CFG_TUH_HID_EPOUT_BUFSIZE || + (report_id != 0 && len > (CFG_TUH_HID_EPOUT_BUFSIZE - 1))) { // ep_out buffer is not large enough to hold contents return false; } // claim endpoint - TU_VERIFY( usbh_edpt_claim(daddr, p_hid->ep_out) ); + TU_VERIFY(usbh_edpt_claim(daddr, p_hid->ep_out)); - if (report_id == 0) - { + if (report_id == 0) { // No report ID in transmission memcpy(&p_hid->epout_buf[0], report, len); - } - else - { + } else { p_hid->epout_buf[0] = report_id; memcpy(&p_hid->epout_buf[1], report, len); ++len; // 1 more byte for report_id @@ -413,8 +405,7 @@ bool tuh_hid_send_report(uint8_t daddr, uint8_t idx, uint8_t report_id, const vo TU_LOG3_MEM(p_hid->epout_buf, len, 2); - if ( !usbh_edpt_xfer(daddr, p_hid->ep_out, p_hid->epout_buf, len) ) - { + if (!usbh_edpt_xfer(daddr, p_hid->ep_out, p_hid->epout_buf, len)) { usbh_edpt_release(daddr, p_hid->ep_out); return false; } @@ -425,13 +416,17 @@ bool tuh_hid_send_report(uint8_t daddr, uint8_t idx, uint8_t report_id, const vo //--------------------------------------------------------------------+ // USBH API //--------------------------------------------------------------------+ -void hidh_init(void) -{ +bool hidh_init(void) { + TU_LOG_DRV("sizeof(hidh_interface_t) = %u\r\n", sizeof(hidh_interface_t)); tu_memclr(_hidh_itf, sizeof(_hidh_itf)); + return true; } -bool hidh_xfer_cb(uint8_t daddr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) -{ +bool hidh_deinit(void) { + return true; +} + +bool hidh_xfer_cb(uint8_t daddr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { (void) result; uint8_t const dir = tu_edpt_dir(ep_addr); @@ -440,29 +435,26 @@ bool hidh_xfer_cb(uint8_t daddr, uint8_t ep_addr, xfer_result_t result, uint32_t hidh_interface_t* p_hid = get_hid_itf(daddr, idx); TU_VERIFY(p_hid); - if ( dir == TUSB_DIR_IN ) - { - // TU_LOG_DRV(" Get Report callback (%u, %u)\r\n", daddr, idx); + if (dir == TUSB_DIR_IN) { + TU_LOG_DRV(" Get Report callback (%u, %u)\r\n", daddr, idx); TU_LOG3_MEM(p_hid->epin_buf, xferred_bytes, 2); tuh_hid_report_received_cb(daddr, idx, p_hid->epin_buf, (uint16_t) xferred_bytes); - }else - { - if (tuh_hid_report_sent_cb) tuh_hid_report_sent_cb(daddr, idx, p_hid->epout_buf, (uint16_t) xferred_bytes); + } else { + if (tuh_hid_report_sent_cb) { + tuh_hid_report_sent_cb(daddr, idx, p_hid->epout_buf, (uint16_t) xferred_bytes); + } } return true; } -void hidh_close(uint8_t daddr) -{ - for(uint8_t i=0; idaddr == daddr) - { + if (p_hid->daddr == daddr) { TU_LOG_DRV(" HIDh close addr = %u index = %u\r\n", daddr, i); - if(tuh_hid_umount_cb) tuh_hid_umount_cb(daddr, i); - p_hid->daddr = 0; + if (tuh_hid_umount_cb) tuh_hid_umount_cb(daddr, i); + tu_memclr(p_hid, sizeof(hidh_interface_t)); } } } @@ -471,25 +463,22 @@ void hidh_close(uint8_t daddr) // Enumeration //--------------------------------------------------------------------+ -bool hidh_open(uint8_t rhport, uint8_t daddr, tusb_desc_interface_t const *desc_itf, uint16_t max_len) -{ +bool hidh_open(uint8_t rhport, uint8_t daddr, tusb_desc_interface_t const* desc_itf, uint16_t max_len) { (void) rhport; (void) max_len; TU_VERIFY(TUSB_CLASS_HID == desc_itf->bInterfaceClass); - TU_LOG_DRV("[%u] HID opening Interface %u\r\n", daddr, desc_itf->bInterfaceNumber); // len = interface + hid + n*endpoints uint16_t const drv_len = (uint16_t) (sizeof(tusb_desc_interface_t) + sizeof(tusb_hid_descriptor_hid_t) + desc_itf->bNumEndpoints * sizeof(tusb_desc_endpoint_t)); TU_ASSERT(max_len >= drv_len); - - uint8_t const *p_desc = (uint8_t const *) desc_itf; + uint8_t const* p_desc = (uint8_t const*) desc_itf; //------------- HID descriptor -------------// p_desc = tu_desc_next(p_desc); - tusb_hid_descriptor_hid_t const *desc_hid = (tusb_hid_descriptor_hid_t const *) p_desc; + tusb_hid_descriptor_hid_t const* desc_hid = (tusb_hid_descriptor_hid_t const*) p_desc; TU_ASSERT(HID_DESC_TYPE_HID == desc_hid->bDescriptorType); hidh_interface_t* p_hid = find_new_itf(); @@ -498,38 +487,33 @@ bool hidh_open(uint8_t rhport, uint8_t daddr, tusb_desc_interface_t const *desc_ //------------- Endpoint Descriptors -------------// p_desc = tu_desc_next(p_desc); - tusb_desc_endpoint_t const * desc_ep = (tusb_desc_endpoint_t const *) p_desc; + tusb_desc_endpoint_t const* desc_ep = (tusb_desc_endpoint_t const*) p_desc; - for(int i = 0; i < desc_itf->bNumEndpoints; i++) - { + for (int i = 0; i < desc_itf->bNumEndpoints; i++) { TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType); - TU_ASSERT( tuh_edpt_open(daddr, desc_ep) ); + TU_ASSERT(tuh_edpt_open(daddr, desc_ep)); - if(tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) - { - p_hid->ep_in = desc_ep->bEndpointAddress; + if (tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN) { + p_hid->ep_in = desc_ep->bEndpointAddress; p_hid->epin_size = tu_edpt_packet_size(desc_ep); - } - else - { - p_hid->ep_out = desc_ep->bEndpointAddress; + } else { + p_hid->ep_out = desc_ep->bEndpointAddress; p_hid->epout_size = tu_edpt_packet_size(desc_ep); } p_desc = tu_desc_next(p_desc); - desc_ep = (tusb_desc_endpoint_t const *) p_desc; + desc_ep = (tusb_desc_endpoint_t const*) p_desc; } - p_hid->itf_num = desc_itf->bInterfaceNumber; + p_hid->itf_num = desc_itf->bInterfaceNumber; // Assume bNumDescriptors = 1 p_hid->report_desc_type = desc_hid->bReportType; - p_hid->report_desc_len = tu_unaligned_read16(&desc_hid->wReportLength); + p_hid->report_desc_len = tu_unaligned_read16(&desc_hid->wReportLength); // Per HID Specs: default is Report protocol, though we will force Boot protocol when set_config p_hid->protocol_mode = _hidh_default_protocol; - if ( HID_SUBCLASS_BOOT == desc_itf->bInterfaceSubClass ) - { + if (HID_SUBCLASS_BOOT == desc_itf->bInterfaceSubClass) { p_hid->itf_protocol = desc_itf->bInterfaceProtocol; } @@ -550,15 +534,14 @@ enum { static void config_driver_mount_complete(uint8_t daddr, uint8_t idx, uint8_t const* desc_report, uint16_t desc_len); static void process_set_config(tuh_xfer_t* xfer); -bool hidh_set_config(uint8_t daddr, uint8_t itf_num) -{ +bool hidh_set_config(uint8_t daddr, uint8_t itf_num) { tusb_control_request_t request; request.wIndex = tu_htole16((uint16_t) itf_num); tuh_xfer_t xfer; - xfer.daddr = daddr; - xfer.result = XFER_RESULT_SUCCESS; - xfer.setup = &request; + xfer.daddr = daddr; + xfer.result = XFER_RESULT_SUCCESS; + xfer.setup = &request; xfer.user_data = CONFG_SET_IDLE; // fake request to kick-off the set config process @@ -567,71 +550,68 @@ bool hidh_set_config(uint8_t daddr, uint8_t itf_num) return true; } -static void process_set_config(tuh_xfer_t* xfer) -{ +static void process_set_config(tuh_xfer_t* xfer) { // Stall is a valid response for SET_IDLE, sometime SET_PROTOCOL as well // therefore we could ignore its result - if ( !(xfer->setup->bRequest == HID_REQ_CONTROL_SET_IDLE || - xfer->setup->bRequest == HID_REQ_CONTROL_SET_PROTOCOL) ) - { - TU_ASSERT(xfer->result == XFER_RESULT_SUCCESS, ); + if (!(xfer->setup->bRequest == HID_REQ_CONTROL_SET_IDLE || + xfer->setup->bRequest == HID_REQ_CONTROL_SET_PROTOCOL)) { + TU_ASSERT(xfer->result == XFER_RESULT_SUCCESS,); } uintptr_t const state = xfer->user_data; uint8_t const itf_num = (uint8_t) tu_le16toh(xfer->setup->wIndex); - uint8_t const daddr = xfer->daddr; + uint8_t const daddr = xfer->daddr; - uint8_t const idx = tuh_hid_itf_get_index(daddr, itf_num); + uint8_t const idx = tuh_hid_itf_get_index(daddr, itf_num); hidh_interface_t* p_hid = get_hid_itf(daddr, idx); - TU_VERIFY(p_hid, ); + TU_VERIFY(p_hid,); - switch(state) - { - case CONFG_SET_IDLE: - { + switch (state) { + case CONFG_SET_IDLE: { // Idle rate = 0 mean only report when there is changes const uint16_t idle_rate = 0; - const uintptr_t next_state = (p_hid->itf_protocol != HID_ITF_PROTOCOL_NONE) ? CONFIG_SET_PROTOCOL : CONFIG_GET_REPORT_DESC; + const uintptr_t next_state = (p_hid->itf_protocol != HID_ITF_PROTOCOL_NONE) + ? CONFIG_SET_PROTOCOL : CONFIG_GET_REPORT_DESC; _hidh_set_idle(daddr, itf_num, idle_rate, process_set_config, next_state); + break; } - break; case CONFIG_SET_PROTOCOL: _hidh_set_protocol(daddr, p_hid->itf_num, _hidh_default_protocol, process_set_config, CONFIG_GET_REPORT_DESC); - break; + break; case CONFIG_GET_REPORT_DESC: // Get Report Descriptor if possible // using usbh enumeration buffer since report descriptor can be very long - if( p_hid->report_desc_len > CFG_TUH_ENUMERATION_BUFSIZE ) - { + if (p_hid->report_desc_len > CFG_TUH_ENUMERATION_BUFSIZE) { TU_LOG_DRV("HID Skip Report Descriptor since it is too large %u bytes\r\n", p_hid->report_desc_len); // Driver is mounted without report descriptor config_driver_mount_complete(daddr, idx, NULL, 0); - }else - { - tuh_descriptor_get_hid_report(daddr, itf_num, p_hid->report_desc_type, 0, usbh_get_enum_buf(), p_hid->report_desc_len, process_set_config, CONFIG_COMPLETE); + } else { + tuh_descriptor_get_hid_report(daddr, itf_num, p_hid->report_desc_type, 0, + usbh_get_enum_buf(), p_hid->report_desc_len, + process_set_config, CONFIG_COMPLETE); } break; - case CONFIG_COMPLETE: - { + case CONFIG_COMPLETE: { uint8_t const* desc_report = usbh_get_enum_buf(); - uint16_t const desc_len = tu_le16toh(xfer->setup->wLength); + uint16_t const desc_len = tu_le16toh(xfer->setup->wLength); config_driver_mount_complete(daddr, idx, desc_report, desc_len); + break; } - break; - default: break; + default: + break; } } -static void config_driver_mount_complete(uint8_t daddr, uint8_t idx, uint8_t const* desc_report, uint16_t desc_len) -{ +static void config_driver_mount_complete(uint8_t daddr, uint8_t idx, uint8_t const* desc_report, uint16_t desc_len) { hidh_interface_t* p_hid = get_hid_itf(daddr, idx); - TU_VERIFY(p_hid, ); + TU_VERIFY(p_hid,); + p_hid->mounted = true; // enumeration is complete if (tuh_hid_mount_cb) tuh_hid_mount_cb(daddr, idx, desc_report, desc_len); @@ -644,21 +624,19 @@ static void config_driver_mount_complete(uint8_t daddr, uint8_t idx, uint8_t con // Report Descriptor Parser //--------------------------------------------------------------------+ -uint8_t tuh_hid_parse_report_descriptor(tuh_hid_report_info_t* report_info_arr, uint8_t arr_count, uint8_t const* desc_report, uint16_t desc_len) -{ +uint8_t tuh_hid_parse_report_descriptor(tuh_hid_report_info_t* report_info_arr, uint8_t arr_count, + uint8_t const* desc_report, uint16_t desc_len) { // Report Item 6.2.2.2 USB HID 1.11 - union TU_ATTR_PACKED - { + union TU_ATTR_PACKED { uint8_t byte; - struct TU_ATTR_PACKED - { - uint8_t size : 2; - uint8_t type : 2; - uint8_t tag : 4; + struct TU_ATTR_PACKED { + uint8_t size : 2; + uint8_t type : 2; + uint8_t tag : 4; }; } header; - tu_memclr(report_info_arr, arr_count*sizeof(tuh_hid_report_info_t)); + tu_memclr(report_info_arr, arr_count * sizeof(tuh_hid_report_info_t)); uint8_t report_num = 0; tuh_hid_report_info_t* info = report_info_arr; @@ -668,114 +646,105 @@ uint8_t tuh_hid_parse_report_descriptor(tuh_hid_report_info_t* report_info_arr, // uint8_t ri_report_size = 0; uint8_t ri_collection_depth = 0; - - while(desc_len && report_num < arr_count) - { + while (desc_len && report_num < arr_count) { header.byte = *desc_report++; desc_len--; - uint8_t const tag = header.tag; + uint8_t const tag = header.tag; uint8_t const type = header.type; uint8_t const size = header.size; uint8_t const data8 = desc_report[0]; TU_LOG(3, "tag = %d, type = %d, size = %d, data = ", tag, type, size); - for(uint32_t i=0; iusage_page, desc_report, size); - break; + if (ri_collection_depth == 0) memcpy(&info->usage_page, desc_report, size); + break; - case RI_GLOBAL_LOGICAL_MIN : break; - case RI_GLOBAL_LOGICAL_MAX : break; - case RI_GLOBAL_PHYSICAL_MIN : break; - case RI_GLOBAL_PHYSICAL_MAX : break; + case RI_GLOBAL_LOGICAL_MIN: break; + case RI_GLOBAL_LOGICAL_MAX: break; + case RI_GLOBAL_PHYSICAL_MIN: break; + case RI_GLOBAL_PHYSICAL_MAX: break; case RI_GLOBAL_REPORT_ID: info->report_id = data8; - break; + break; case RI_GLOBAL_REPORT_SIZE: // ri_report_size = data8; - break; + break; case RI_GLOBAL_REPORT_COUNT: // ri_report_count = data8; - break; + break; - case RI_GLOBAL_UNIT_EXPONENT : break; - case RI_GLOBAL_UNIT : break; - case RI_GLOBAL_PUSH : break; - case RI_GLOBAL_POP : break; + case RI_GLOBAL_UNIT_EXPONENT: break; + case RI_GLOBAL_UNIT: break; + case RI_GLOBAL_PUSH: break; + case RI_GLOBAL_POP: break; default: break; } - break; + break; case RI_TYPE_LOCAL: - switch(tag) - { + switch (tag) { case RI_LOCAL_USAGE: // only take in account the "usage" before starting REPORT ID - if ( ri_collection_depth == 0 ) info->usage = data8; - break; + if (ri_collection_depth == 0) info->usage = data8; + break; - case RI_LOCAL_USAGE_MIN : break; - case RI_LOCAL_USAGE_MAX : break; - case RI_LOCAL_DESIGNATOR_INDEX : break; - case RI_LOCAL_DESIGNATOR_MIN : break; - case RI_LOCAL_DESIGNATOR_MAX : break; - case RI_LOCAL_STRING_INDEX : break; - case RI_LOCAL_STRING_MIN : break; - case RI_LOCAL_STRING_MAX : break; - case RI_LOCAL_DELIMITER : break; + case RI_LOCAL_USAGE_MIN: break; + case RI_LOCAL_USAGE_MAX: break; + case RI_LOCAL_DESIGNATOR_INDEX: break; + case RI_LOCAL_DESIGNATOR_MIN: break; + case RI_LOCAL_DESIGNATOR_MAX: break; + case RI_LOCAL_STRING_INDEX: break; + case RI_LOCAL_STRING_MIN: break; + case RI_LOCAL_STRING_MAX: break; + case RI_LOCAL_DELIMITER: break; default: break; } - break; + break; - // error + // error default: break; } desc_report += size; - desc_len -= size; + desc_len -= size; } - for ( uint8_t i = 0; i < report_num; i++ ) - { - info = report_info_arr+i; + for (uint8_t i = 0; i < report_num; i++) { + info = report_info_arr + i; TU_LOG_DRV("%u: id = %u, usage_page = %u, usage = %u\r\n", i, info->report_id, info->usage_page, info->usage); } diff --git a/pico-sdk/lib/tinyusb/src/class/hid/hid_host.h b/pico-sdk/lib/tinyusb/src/class/hid/hid_host.h index 238b7c6..9681c70 100644 --- a/pico-sdk/lib/tinyusb/src/class/hid/hid_host.h +++ b/pico-sdk/lib/tinyusb/src/class/hid/hid_host.h @@ -30,7 +30,7 @@ #include "hid.h" #ifdef __cplusplus - extern "C" { +extern "C" { #endif //--------------------------------------------------------------------+ @@ -47,10 +47,9 @@ #endif -typedef struct -{ - uint8_t report_id; - uint8_t usage; +typedef struct { + uint8_t report_id; + uint8_t usage; uint16_t usage_page; // TODO still use the endpoint size for now @@ -86,7 +85,8 @@ bool tuh_hid_mounted(uint8_t dev_addr, uint8_t idx); // Parse report descriptor into array of report_info struct and return number of reports. // For complicated report, application should write its own parser. -uint8_t tuh_hid_parse_report_descriptor(tuh_hid_report_info_t* reports_info_arr, uint8_t arr_count, uint8_t const* desc_report, uint16_t desc_len) TU_ATTR_UNUSED; +TU_ATTR_UNUSED uint8_t tuh_hid_parse_report_descriptor(tuh_hid_report_info_t* reports_info_arr, uint8_t arr_count, + uint8_t const* desc_report, uint16_t desc_len); //--------------------------------------------------------------------+ // Control Endpoint API @@ -105,9 +105,14 @@ void tuh_hid_set_default_protocol(uint8_t protocol); // This function is only supported by Boot interface (tuh_n_hid_interface_protocol() != NONE) bool tuh_hid_set_protocol(uint8_t dev_addr, uint8_t idx, uint8_t protocol); +// Get Report using control endpoint +// report_type is either Input, Output or Feature, (value from hid_report_type_t) +bool tuh_hid_get_report(uint8_t dev_addr, uint8_t idx, uint8_t report_id, uint8_t report_type, void* report, uint16_t len); + // Set Report using control endpoint // report_type is either Input, Output or Feature, (value from hid_report_type_t) -bool tuh_hid_set_report(uint8_t dev_addr, uint8_t idx, uint8_t report_id, uint8_t report_type, void* report, uint16_t len); +bool tuh_hid_set_report(uint8_t dev_addr, uint8_t idx, uint8_t report_id, uint8_t report_type, + void* report, uint16_t len); //--------------------------------------------------------------------+ // Interrupt Endpoint API @@ -121,6 +126,9 @@ bool tuh_hid_receive_ready(uint8_t dev_addr, uint8_t idx); // - false if failed to queue the transfer e.g endpoint is busy bool tuh_hid_receive_report(uint8_t dev_addr, uint8_t idx); +// Abort receiving report on Interrupt Endpoint +bool tuh_hid_receive_abort(uint8_t dev_addr, uint8_t idx); + // Check if HID interface is ready to send report bool tuh_hid_send_ready(uint8_t dev_addr, uint8_t idx); @@ -149,6 +157,10 @@ void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t idx, uint8_t const* re // Invoked when sent report to device successfully via interrupt endpoint TU_ATTR_WEAK void tuh_hid_report_sent_cb(uint8_t dev_addr, uint8_t idx, uint8_t const* report, uint16_t len); +// Invoked when Get Report to device via either control endpoint +// len = 0 indicate there is error in the transfer e.g stalled response +TU_ATTR_WEAK void tuh_hid_get_report_complete_cb(uint8_t dev_addr, uint8_t idx, uint8_t report_id, uint8_t report_type, uint16_t len); + // Invoked when Sent Report to device via either control endpoint // len = 0 indicate there is error in the transfer e.g stalled response TU_ATTR_WEAK void tuh_hid_set_report_complete_cb(uint8_t dev_addr, uint8_t idx, uint8_t report_id, uint8_t report_type, uint16_t len); @@ -159,11 +171,12 @@ TU_ATTR_WEAK void tuh_hid_set_protocol_complete_cb(uint8_t dev_addr, uint8_t idx //--------------------------------------------------------------------+ // Internal Class Driver API //--------------------------------------------------------------------+ -void hidh_init (void); -bool hidh_open (uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *desc_itf, uint16_t max_len); -bool hidh_set_config (uint8_t dev_addr, uint8_t itf_num); -bool hidh_xfer_cb (uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); -void hidh_close (uint8_t dev_addr); +bool hidh_init(void); +bool hidh_deinit(void); +bool hidh_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const* desc_itf, uint16_t max_len); +bool hidh_set_config(uint8_t dev_addr, uint8_t itf_num); +bool hidh_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); +void hidh_close(uint8_t dev_addr); #ifdef __cplusplus } diff --git a/pico-sdk/lib/tinyusb/src/class/msc/msc_device.c b/pico-sdk/lib/tinyusb/src/class/msc/msc_device.c index 2589dcd..447560b 100644 --- a/pico-sdk/lib/tinyusb/src/class/msc/msc_device.c +++ b/pico-sdk/lib/tinyusb/src/class/msc/msc_device.c @@ -203,7 +203,7 @@ uint8_t rdwr10_validate_cmd(msc_cbw_t const* cbw) //--------------------------------------------------------------------+ // Debug //--------------------------------------------------------------------+ -#if CFG_TUSB_DEBUG >= 2 +#if CFG_TUSB_DEBUG >= CFG_TUD_MSC_LOG_LEVEL TU_ATTR_UNUSED tu_static tu_lookup_entry_t const _msc_scsi_cmd_lookup[] = { @@ -251,11 +251,15 @@ static inline void set_sense_medium_not_present(uint8_t lun) //--------------------------------------------------------------------+ // USBD Driver API //--------------------------------------------------------------------+ -void mscd_init(void) -{ +void mscd_init(void) { tu_memclr(&_mscd_itf, sizeof(mscd_interface_t)); } +bool mscd_deinit(void) { + // nothing to do + return true; +} + void mscd_reset(uint8_t rhport) { (void) rhport; @@ -685,6 +689,24 @@ static int32_t proc_builtin_scsi(uint8_t lun, uint8_t const scsi_cmd[16], uint8_ } break; + case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL: + resplen = 0; + + if (tud_msc_prevent_allow_medium_removal_cb) + { + scsi_prevent_allow_medium_removal_t const * prevent_allow = (scsi_prevent_allow_medium_removal_t const *) scsi_cmd; + if ( !tud_msc_prevent_allow_medium_removal_cb(lun, prevent_allow->prohibit_removal, prevent_allow->control) ) + { + // Failed status response + resplen = - 1; + + // set default sense if not set by callback + if ( p_msc->sense_key == 0 ) set_sense_medium_not_present(lun); + } + } + break; + + case SCSI_CMD_READ_CAPACITY_10: { uint32_t block_count; diff --git a/pico-sdk/lib/tinyusb/src/class/msc/msc_device.h b/pico-sdk/lib/tinyusb/src/class/msc/msc_device.h index 72f95be..29acd28 100644 --- a/pico-sdk/lib/tinyusb/src/class/msc/msc_device.h +++ b/pico-sdk/lib/tinyusb/src/class/msc/msc_device.h @@ -131,6 +131,9 @@ TU_ATTR_WEAK uint8_t tud_msc_get_maxlun_cb(void); // - Start = 1 : active mode, if load_eject = 1 : load disk storage TU_ATTR_WEAK bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject); +//Invoked when we receive the Prevent / Allow Medium Removal command +TU_ATTR_WEAK bool tud_msc_prevent_allow_medium_removal_cb(uint8_t lun, uint8_t prohibit_removal, uint8_t control); + // Invoked when received REQUEST_SENSE TU_ATTR_WEAK int32_t tud_msc_request_sense_cb(uint8_t lun, void* buffer, uint16_t bufsize); @@ -150,6 +153,7 @@ TU_ATTR_WEAK bool tud_msc_is_writable_cb(uint8_t lun); // Internal Class Driver API //--------------------------------------------------------------------+ void mscd_init (void); +bool mscd_deinit (void); void mscd_reset (uint8_t rhport); uint16_t mscd_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); bool mscd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * p_request); diff --git a/pico-sdk/lib/tinyusb/src/class/msc/msc_host.c b/pico-sdk/lib/tinyusb/src/class/msc/msc_host.c deleted file mode 100644 index 39f2d9f..0000000 --- a/pico-sdk/lib/tinyusb/src/class/msc/msc_host.c +++ /dev/null @@ -1,497 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#include "tusb_option.h" - -#if CFG_TUH_ENABLED && CFG_TUH_MSC - -#include "host/usbh.h" -#include "host/usbh_pvt.h" - -#include "msc_host.h" - -// Level where CFG_TUSB_DEBUG must be at least for this driver is logged -#ifndef CFG_TUH_MSC_LOG_LEVEL - #define CFG_TUH_MSC_LOG_LEVEL CFG_TUH_LOG_LEVEL -#endif - -#define TU_LOG_DRV(...) TU_LOG(CFG_TUH_MSC_LOG_LEVEL, __VA_ARGS__) - -//--------------------------------------------------------------------+ -// MACRO CONSTANT TYPEDEF -//--------------------------------------------------------------------+ -enum { - MSC_STAGE_IDLE = 0, - MSC_STAGE_CMD, - MSC_STAGE_DATA, - MSC_STAGE_STATUS, -}; - -typedef struct { - uint8_t itf_num; - uint8_t ep_in; - uint8_t ep_out; - - uint8_t max_lun; - - volatile bool configured; // Receive SET_CONFIGURE - volatile bool mounted; // Enumeration is complete - - struct { - uint32_t block_size; - uint32_t block_count; - } capacity[CFG_TUH_MSC_MAXLUN]; - - //------------- SCSI -------------// - uint8_t stage; - void* buffer; - tuh_msc_complete_cb_t complete_cb; - uintptr_t complete_arg; - - CFG_TUH_MEM_ALIGN msc_cbw_t cbw; - CFG_TUH_MEM_ALIGN msc_csw_t csw; -} msch_interface_t; - -CFG_TUH_MEM_SECTION static msch_interface_t _msch_itf[CFG_TUH_DEVICE_MAX]; - -// buffer used to read scsi information when mounted -// largest response data currently is inquiry TODO Inquiry is not part of enum anymore -CFG_TUH_MEM_SECTION CFG_TUH_MEM_ALIGN -static uint8_t _msch_buffer[sizeof(scsi_inquiry_resp_t)]; - -// FIXME potential nul reference -TU_ATTR_ALWAYS_INLINE -static inline msch_interface_t* get_itf(uint8_t dev_addr) { - return &_msch_itf[dev_addr - 1]; -} - -//--------------------------------------------------------------------+ -// PUBLIC API -//--------------------------------------------------------------------+ -uint8_t tuh_msc_get_maxlun(uint8_t dev_addr) { - msch_interface_t* p_msc = get_itf(dev_addr); - return p_msc->max_lun; -} - -uint32_t tuh_msc_get_block_count(uint8_t dev_addr, uint8_t lun) { - msch_interface_t* p_msc = get_itf(dev_addr); - return p_msc->capacity[lun].block_count; -} - -uint32_t tuh_msc_get_block_size(uint8_t dev_addr, uint8_t lun) { - msch_interface_t* p_msc = get_itf(dev_addr); - return p_msc->capacity[lun].block_size; -} - -bool tuh_msc_mounted(uint8_t dev_addr) { - msch_interface_t* p_msc = get_itf(dev_addr); - return p_msc->mounted; -} - -bool tuh_msc_ready(uint8_t dev_addr) { - msch_interface_t* p_msc = get_itf(dev_addr); - return p_msc->mounted && !usbh_edpt_busy(dev_addr, p_msc->ep_in) && !usbh_edpt_busy(dev_addr, p_msc->ep_out); -} - -//--------------------------------------------------------------------+ -// PUBLIC API: SCSI COMMAND -//--------------------------------------------------------------------+ -static inline void cbw_init(msc_cbw_t* cbw, uint8_t lun) { - tu_memclr(cbw, sizeof(msc_cbw_t)); - cbw->signature = MSC_CBW_SIGNATURE; - cbw->tag = 0x54555342; // TUSB - cbw->lun = lun; -} - -bool tuh_msc_scsi_command(uint8_t daddr, msc_cbw_t const* cbw, void* data, - tuh_msc_complete_cb_t complete_cb, uintptr_t arg) { - msch_interface_t* p_msc = get_itf(daddr); - TU_VERIFY(p_msc->configured); - - // claim endpoint - TU_VERIFY(usbh_edpt_claim(daddr, p_msc->ep_out)); - - p_msc->cbw = *cbw; - p_msc->stage = MSC_STAGE_CMD; - p_msc->buffer = data; - p_msc->complete_cb = complete_cb; - p_msc->complete_arg = arg; - - if (!usbh_edpt_xfer(daddr, p_msc->ep_out, (uint8_t*) &p_msc->cbw, sizeof(msc_cbw_t))) { - usbh_edpt_release(daddr, p_msc->ep_out); - return false; - } - - return true; -} - -bool tuh_msc_read_capacity(uint8_t dev_addr, uint8_t lun, scsi_read_capacity10_resp_t* response, - tuh_msc_complete_cb_t complete_cb, uintptr_t arg) { - msch_interface_t* p_msc = get_itf(dev_addr); - TU_VERIFY(p_msc->configured); - - msc_cbw_t cbw; - cbw_init(&cbw, lun); - - cbw.total_bytes = sizeof(scsi_read_capacity10_resp_t); - cbw.dir = TUSB_DIR_IN_MASK; - cbw.cmd_len = sizeof(scsi_read_capacity10_t); - cbw.command[0] = SCSI_CMD_READ_CAPACITY_10; - - return tuh_msc_scsi_command(dev_addr, &cbw, response, complete_cb, arg); -} - -bool tuh_msc_inquiry(uint8_t dev_addr, uint8_t lun, scsi_inquiry_resp_t* response, - tuh_msc_complete_cb_t complete_cb, uintptr_t arg) { - msch_interface_t* p_msc = get_itf(dev_addr); - TU_VERIFY(p_msc->mounted); - - msc_cbw_t cbw; - cbw_init(&cbw, lun); - - cbw.total_bytes = sizeof(scsi_inquiry_resp_t); - cbw.dir = TUSB_DIR_IN_MASK; - cbw.cmd_len = sizeof(scsi_inquiry_t); - - scsi_inquiry_t const cmd_inquiry = { - .cmd_code = SCSI_CMD_INQUIRY, - .alloc_length = sizeof(scsi_inquiry_resp_t) - }; - memcpy(cbw.command, &cmd_inquiry, cbw.cmd_len); - - return tuh_msc_scsi_command(dev_addr, &cbw, response, complete_cb, arg); -} - -bool tuh_msc_test_unit_ready(uint8_t dev_addr, uint8_t lun, tuh_msc_complete_cb_t complete_cb, uintptr_t arg) { - msch_interface_t* p_msc = get_itf(dev_addr); - TU_VERIFY(p_msc->configured); - - msc_cbw_t cbw; - cbw_init(&cbw, lun); - - cbw.total_bytes = 0; - cbw.dir = TUSB_DIR_OUT; - cbw.cmd_len = sizeof(scsi_test_unit_ready_t); - cbw.command[0] = SCSI_CMD_TEST_UNIT_READY; - cbw.command[1] = lun; // according to wiki TODO need verification - - return tuh_msc_scsi_command(dev_addr, &cbw, NULL, complete_cb, arg); -} - -bool tuh_msc_request_sense(uint8_t dev_addr, uint8_t lun, void* response, - tuh_msc_complete_cb_t complete_cb, uintptr_t arg) { - msc_cbw_t cbw; - cbw_init(&cbw, lun); - - cbw.total_bytes = 18; // TODO sense response - cbw.dir = TUSB_DIR_IN_MASK; - cbw.cmd_len = sizeof(scsi_request_sense_t); - - scsi_request_sense_t const cmd_request_sense = { - .cmd_code = SCSI_CMD_REQUEST_SENSE, - .alloc_length = 18 - }; - memcpy(cbw.command, &cmd_request_sense, cbw.cmd_len); - - return tuh_msc_scsi_command(dev_addr, &cbw, response, complete_cb, arg); -} - -bool tuh_msc_read10(uint8_t dev_addr, uint8_t lun, void* buffer, uint32_t lba, uint16_t block_count, - tuh_msc_complete_cb_t complete_cb, uintptr_t arg) { - msch_interface_t* p_msc = get_itf(dev_addr); - TU_VERIFY(p_msc->mounted); - - msc_cbw_t cbw; - cbw_init(&cbw, lun); - - cbw.total_bytes = block_count * p_msc->capacity[lun].block_size; - cbw.dir = TUSB_DIR_IN_MASK; - cbw.cmd_len = sizeof(scsi_read10_t); - - scsi_read10_t const cmd_read10 = { - .cmd_code = SCSI_CMD_READ_10, - .lba = tu_htonl(lba), - .block_count = tu_htons(block_count) - }; - memcpy(cbw.command, &cmd_read10, cbw.cmd_len); - - return tuh_msc_scsi_command(dev_addr, &cbw, buffer, complete_cb, arg); -} - -bool tuh_msc_write10(uint8_t dev_addr, uint8_t lun, void const* buffer, uint32_t lba, uint16_t block_count, - tuh_msc_complete_cb_t complete_cb, uintptr_t arg) { - msch_interface_t* p_msc = get_itf(dev_addr); - TU_VERIFY(p_msc->mounted); - - msc_cbw_t cbw; - cbw_init(&cbw, lun); - - cbw.total_bytes = block_count * p_msc->capacity[lun].block_size; - cbw.dir = TUSB_DIR_OUT; - cbw.cmd_len = sizeof(scsi_write10_t); - - scsi_write10_t const cmd_write10 = { - .cmd_code = SCSI_CMD_WRITE_10, - .lba = tu_htonl(lba), - .block_count = tu_htons(block_count) - }; - memcpy(cbw.command, &cmd_write10, cbw.cmd_len); - - return tuh_msc_scsi_command(dev_addr, &cbw, (void*) (uintptr_t) buffer, complete_cb, arg); -} - -#if 0 -// MSC interface Reset (not used now) -bool tuh_msc_reset(uint8_t dev_addr) { - tusb_control_request_t const new_request = { - .bmRequestType_bit = { - .recipient = TUSB_REQ_RCPT_INTERFACE, - .type = TUSB_REQ_TYPE_CLASS, - .direction = TUSB_DIR_OUT - }, - .bRequest = MSC_REQ_RESET, - .wValue = 0, - .wIndex = p_msc->itf_num, - .wLength = 0 - }; - TU_ASSERT( usbh_control_xfer( dev_addr, &new_request, NULL ) ); -} -#endif - -//--------------------------------------------------------------------+ -// CLASS-USBH API -//--------------------------------------------------------------------+ -void msch_init(void) { - tu_memclr(_msch_itf, sizeof(_msch_itf)); -} - -void msch_close(uint8_t dev_addr) { - TU_VERIFY(dev_addr <= CFG_TUH_DEVICE_MAX,); - msch_interface_t* p_msc = get_itf(dev_addr); - TU_VERIFY(p_msc->configured,); - - TU_LOG_DRV(" MSCh close addr = %d\r\n", dev_addr); - - // invoke Application Callback - if (p_msc->mounted) { - if (tuh_msc_umount_cb) tuh_msc_umount_cb(dev_addr); - } - - tu_memclr(p_msc, sizeof(msch_interface_t)); -} - -bool msch_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes) { - msch_interface_t* p_msc = get_itf(dev_addr); - msc_cbw_t const * cbw = &p_msc->cbw; - msc_csw_t * csw = &p_msc->csw; - - switch (p_msc->stage) { - case MSC_STAGE_CMD: - // Must be Command Block - TU_ASSERT(ep_addr == p_msc->ep_out && event == XFER_RESULT_SUCCESS && xferred_bytes == sizeof(msc_cbw_t)); - - if (cbw->total_bytes && p_msc->buffer) { - // Data stage if any - p_msc->stage = MSC_STAGE_DATA; - uint8_t const ep_data = (cbw->dir & TUSB_DIR_IN_MASK) ? p_msc->ep_in : p_msc->ep_out; - TU_ASSERT(usbh_edpt_xfer(dev_addr, ep_data, p_msc->buffer, (uint16_t) cbw->total_bytes)); - } else { - // Status stage - p_msc->stage = MSC_STAGE_STATUS; - TU_ASSERT(usbh_edpt_xfer(dev_addr, p_msc->ep_in, (uint8_t*) &p_msc->csw, (uint16_t) sizeof(msc_csw_t))); - } - break; - - case MSC_STAGE_DATA: - // Status stage - p_msc->stage = MSC_STAGE_STATUS; - TU_ASSERT(usbh_edpt_xfer(dev_addr, p_msc->ep_in, (uint8_t*) &p_msc->csw, (uint16_t) sizeof(msc_csw_t))); - break; - - case MSC_STAGE_STATUS: - // SCSI op is complete - p_msc->stage = MSC_STAGE_IDLE; - - if (p_msc->complete_cb) { - tuh_msc_complete_data_t const cb_data = { - .cbw = cbw, - .csw = csw, - .scsi_data = p_msc->buffer, - .user_arg = p_msc->complete_arg - }; - p_msc->complete_cb(dev_addr, &cb_data); - } - break; - - // unknown state - default: - break; - } - - return true; -} - -//--------------------------------------------------------------------+ -// MSC Enumeration -//--------------------------------------------------------------------+ -static void config_get_maxlun_complete(tuh_xfer_t* xfer); -static bool config_test_unit_ready_complete(uint8_t dev_addr, tuh_msc_complete_data_t const* cb_data); -static bool config_request_sense_complete(uint8_t dev_addr, tuh_msc_complete_data_t const* cb_data); -static bool config_read_capacity_complete(uint8_t dev_addr, tuh_msc_complete_data_t const* cb_data); - -bool msch_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const* desc_itf, uint16_t max_len) { - (void) rhport; - TU_VERIFY (MSC_SUBCLASS_SCSI == desc_itf->bInterfaceSubClass && - MSC_PROTOCOL_BOT == desc_itf->bInterfaceProtocol); - - // msc driver length is fixed - uint16_t const drv_len = (uint16_t) (sizeof(tusb_desc_interface_t) + - desc_itf->bNumEndpoints * sizeof(tusb_desc_endpoint_t)); - TU_ASSERT(drv_len <= max_len); - - msch_interface_t* p_msc = get_itf(dev_addr); - tusb_desc_endpoint_t const* ep_desc = (tusb_desc_endpoint_t const*) tu_desc_next(desc_itf); - - for (uint32_t i = 0; i < 2; i++) { - TU_ASSERT(TUSB_DESC_ENDPOINT == ep_desc->bDescriptorType && TUSB_XFER_BULK == ep_desc->bmAttributes.xfer); - TU_ASSERT(tuh_edpt_open(dev_addr, ep_desc)); - - if (TUSB_DIR_IN == tu_edpt_dir(ep_desc->bEndpointAddress)) { - p_msc->ep_in = ep_desc->bEndpointAddress; - } else { - p_msc->ep_out = ep_desc->bEndpointAddress; - } - - ep_desc = (tusb_desc_endpoint_t const*) tu_desc_next(ep_desc); - } - - p_msc->itf_num = desc_itf->bInterfaceNumber; - - return true; -} - -bool msch_set_config(uint8_t dev_addr, uint8_t itf_num) { - msch_interface_t* p_msc = get_itf(dev_addr); - TU_ASSERT(p_msc->itf_num == itf_num); - - p_msc->configured = true; - - //------------- Get Max Lun -------------// - TU_LOG_DRV("MSC Get Max Lun\r\n"); - tusb_control_request_t const request = { - .bmRequestType_bit = { - .recipient = TUSB_REQ_RCPT_INTERFACE, - .type = TUSB_REQ_TYPE_CLASS, - .direction = TUSB_DIR_IN - }, - .bRequest = MSC_REQ_GET_MAX_LUN, - .wValue = 0, - .wIndex = itf_num, - .wLength = 1 - }; - - tuh_xfer_t xfer = { - .daddr = dev_addr, - .ep_addr = 0, - .setup = &request, - .buffer = _msch_buffer, - .complete_cb = config_get_maxlun_complete, - .user_data = 0 - }; - TU_ASSERT(tuh_control_xfer(&xfer)); - - return true; -} - -static void config_get_maxlun_complete(tuh_xfer_t* xfer) { - uint8_t const daddr = xfer->daddr; - msch_interface_t* p_msc = get_itf(daddr); - - // STALL means zero - p_msc->max_lun = (XFER_RESULT_SUCCESS == xfer->result) ? _msch_buffer[0] : 0; - p_msc->max_lun++; // MAX LUN is minus 1 by specs - - TU_LOG_DRV(" Max LUN = %u\r\n", p_msc->max_lun); - - // TODO multiple LUN support - TU_LOG_DRV("SCSI Test Unit Ready\r\n"); - uint8_t const lun = 0; - tuh_msc_test_unit_ready(daddr, lun, config_test_unit_ready_complete, 0); -} - -static bool config_test_unit_ready_complete(uint8_t dev_addr, tuh_msc_complete_data_t const* cb_data) { - msc_cbw_t const* cbw = cb_data->cbw; - msc_csw_t const* csw = cb_data->csw; - - if (csw->status == 0) { - // Unit is ready, read its capacity - TU_LOG_DRV("SCSI Read Capacity\r\n"); - tuh_msc_read_capacity(dev_addr, cbw->lun, (scsi_read_capacity10_resp_t*) ((void*) _msch_buffer), - config_read_capacity_complete, 0); - } else { - // Note: During enumeration, some device fails Test Unit Ready and require a few retries - // with Request Sense to start working !! - // TODO limit number of retries - TU_LOG_DRV("SCSI Request Sense\r\n"); - TU_ASSERT(tuh_msc_request_sense(dev_addr, cbw->lun, _msch_buffer, config_request_sense_complete, 0)); - } - - return true; -} - -static bool config_request_sense_complete(uint8_t dev_addr, tuh_msc_complete_data_t const* cb_data) { - msc_cbw_t const* cbw = cb_data->cbw; - msc_csw_t const* csw = cb_data->csw; - - TU_ASSERT(csw->status == 0); - TU_ASSERT(tuh_msc_test_unit_ready(dev_addr, cbw->lun, config_test_unit_ready_complete, 0)); - return true; -} - -static bool config_read_capacity_complete(uint8_t dev_addr, tuh_msc_complete_data_t const* cb_data) { - msc_cbw_t const* cbw = cb_data->cbw; - msc_csw_t const* csw = cb_data->csw; - - TU_ASSERT(csw->status == 0); - - msch_interface_t* p_msc = get_itf(dev_addr); - - // Capacity response field: Block size and Last LBA are both Big-Endian - scsi_read_capacity10_resp_t* resp = (scsi_read_capacity10_resp_t*) ((void*) _msch_buffer); - p_msc->capacity[cbw->lun].block_count = tu_ntohl(resp->last_lba) + 1; - p_msc->capacity[cbw->lun].block_size = tu_ntohl(resp->block_size); - - // Mark enumeration is complete - p_msc->mounted = true; - if (tuh_msc_mount_cb) tuh_msc_mount_cb(dev_addr); - - // notify usbh that driver enumeration is complete - usbh_driver_set_config_complete(dev_addr, p_msc->itf_num); - - return true; -} - -#endif diff --git a/pico-sdk/lib/tinyusb/src/class/msc/msc_host.h b/pico-sdk/lib/tinyusb/src/class/msc/msc_host.h deleted file mode 100644 index 9ca1b47..0000000 --- a/pico-sdk/lib/tinyusb/src/class/msc/msc_host.h +++ /dev/null @@ -1,126 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#ifndef TUSB_MSC_HOST_H_ -#define TUSB_MSC_HOST_H_ - -#include "msc.h" - -#ifdef __cplusplus - extern "C" { -#endif - -//--------------------------------------------------------------------+ -// Class Driver Configuration -//--------------------------------------------------------------------+ - -#ifndef CFG_TUH_MSC_MAXLUN -#define CFG_TUH_MSC_MAXLUN 4 -#endif - -typedef struct { - msc_cbw_t const* cbw; // SCSI command - msc_csw_t const* csw; // SCSI status - void* scsi_data; // SCSI Data - uintptr_t user_arg; // user argument -}tuh_msc_complete_data_t; - -typedef bool (*tuh_msc_complete_cb_t)(uint8_t dev_addr, tuh_msc_complete_data_t const* cb_data); - -//--------------------------------------------------------------------+ -// Application API -//--------------------------------------------------------------------+ - -// Check if device supports MassStorage interface. -// This function true after tuh_msc_mounted_cb() and false after tuh_msc_unmounted_cb() -bool tuh_msc_mounted(uint8_t dev_addr); - -// Check if the interface is currently ready or busy transferring data -bool tuh_msc_ready(uint8_t dev_addr); - -// Get Max Lun -uint8_t tuh_msc_get_maxlun(uint8_t dev_addr); - -// Get number of block -uint32_t tuh_msc_get_block_count(uint8_t dev_addr, uint8_t lun); - -// Get block size in bytes -uint32_t tuh_msc_get_block_size(uint8_t dev_addr, uint8_t lun); - -// Perform a full SCSI command (cbw, data, csw) in non-blocking manner. -// Complete callback is invoked when SCSI op is complete. -// return true if success, false if there is already pending operation. -bool tuh_msc_scsi_command(uint8_t daddr, msc_cbw_t const* cbw, void* data, tuh_msc_complete_cb_t complete_cb, uintptr_t arg); - -// Perform SCSI Inquiry command -// Complete callback is invoked when SCSI op is complete. -bool tuh_msc_inquiry(uint8_t dev_addr, uint8_t lun, scsi_inquiry_resp_t* response, tuh_msc_complete_cb_t complete_cb, uintptr_t arg); - -// Perform SCSI Test Unit Ready command -// Complete callback is invoked when SCSI op is complete. -bool tuh_msc_test_unit_ready(uint8_t dev_addr, uint8_t lun, tuh_msc_complete_cb_t complete_cb, uintptr_t arg); - -// Perform SCSI Request Sense 10 command -// Complete callback is invoked when SCSI op is complete. -bool tuh_msc_request_sense(uint8_t dev_addr, uint8_t lun, void *response, tuh_msc_complete_cb_t complete_cb, uintptr_t arg); - -// Perform SCSI Read 10 command. Read n blocks starting from LBA to buffer -// Complete callback is invoked when SCSI op is complete. -bool tuh_msc_read10(uint8_t dev_addr, uint8_t lun, void * buffer, uint32_t lba, uint16_t block_count, tuh_msc_complete_cb_t complete_cb, uintptr_t arg); - -// Perform SCSI Write 10 command. Write n blocks starting from LBA to device -// Complete callback is invoked when SCSI op is complete. -bool tuh_msc_write10(uint8_t dev_addr, uint8_t lun, void const * buffer, uint32_t lba, uint16_t block_count, tuh_msc_complete_cb_t complete_cb, uintptr_t arg); - -// Perform SCSI Read Capacity 10 command -// Complete callback is invoked when SCSI op is complete. -// Note: during enumeration, host stack already carried out this request. Application can retrieve capacity by -// simply call tuh_msc_get_block_count() and tuh_msc_get_block_size() -bool tuh_msc_read_capacity(uint8_t dev_addr, uint8_t lun, scsi_read_capacity10_resp_t* response, tuh_msc_complete_cb_t complete_cb, uintptr_t arg); - -//------------- Application Callback -------------// - -// Invoked when a device with MassStorage interface is mounted -TU_ATTR_WEAK void tuh_msc_mount_cb(uint8_t dev_addr); - -// Invoked when a device with MassStorage interface is unmounted -TU_ATTR_WEAK void tuh_msc_umount_cb(uint8_t dev_addr); - -//--------------------------------------------------------------------+ -// Internal Class Driver API -//--------------------------------------------------------------------+ - -void msch_init (void); -bool msch_open (uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *desc_itf, uint16_t max_len); -bool msch_set_config (uint8_t dev_addr, uint8_t itf_num); -void msch_close (uint8_t dev_addr); -bool msch_xfer_cb (uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); - -#ifdef __cplusplus - } -#endif - -#endif diff --git a/pico-sdk/lib/tinyusb/src/class/vendor/vendor_device.c b/pico-sdk/lib/tinyusb/src/class/vendor/vendor_device.c deleted file mode 100644 index 389a296..0000000 --- a/pico-sdk/lib/tinyusb/src/class/vendor/vendor_device.c +++ /dev/null @@ -1,287 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#include "tusb_option.h" - -#if (CFG_TUD_ENABLED && CFG_TUD_VENDOR) - -#include "device/usbd.h" -#include "device/usbd_pvt.h" - -#include "vendor_device.h" - -//--------------------------------------------------------------------+ -// MACRO CONSTANT TYPEDEF -//--------------------------------------------------------------------+ -typedef struct -{ - uint8_t itf_num; - uint8_t ep_in; - uint8_t ep_out; - - /*------------- From this point, data is not cleared by bus reset -------------*/ - tu_fifo_t rx_ff; - tu_fifo_t tx_ff; - - uint8_t rx_ff_buf[CFG_TUD_VENDOR_RX_BUFSIZE]; - uint8_t tx_ff_buf[CFG_TUD_VENDOR_TX_BUFSIZE]; - -#if CFG_FIFO_MUTEX - osal_mutex_def_t rx_ff_mutex; - osal_mutex_def_t tx_ff_mutex; -#endif - - // Endpoint Transfer buffer - CFG_TUSB_MEM_ALIGN uint8_t epout_buf[CFG_TUD_VENDOR_EPSIZE]; - CFG_TUSB_MEM_ALIGN uint8_t epin_buf[CFG_TUD_VENDOR_EPSIZE]; -} vendord_interface_t; - -CFG_TUD_MEM_SECTION tu_static vendord_interface_t _vendord_itf[CFG_TUD_VENDOR]; - -#define ITF_MEM_RESET_SIZE offsetof(vendord_interface_t, rx_ff) - - -bool tud_vendor_n_mounted (uint8_t itf) -{ - return _vendord_itf[itf].ep_in && _vendord_itf[itf].ep_out; -} - -uint32_t tud_vendor_n_available (uint8_t itf) -{ - return tu_fifo_count(&_vendord_itf[itf].rx_ff); -} - -bool tud_vendor_n_peek(uint8_t itf, uint8_t* u8) -{ - return tu_fifo_peek(&_vendord_itf[itf].rx_ff, u8); -} - -//--------------------------------------------------------------------+ -// Read API -//--------------------------------------------------------------------+ -static void _prep_out_transaction (vendord_interface_t* p_itf) -{ - uint8_t const rhport = 0; - - // claim endpoint - TU_VERIFY(usbd_edpt_claim(rhport, p_itf->ep_out), ); - - // Prepare for incoming data but only allow what we can store in the ring buffer. - uint16_t max_read = tu_fifo_remaining(&p_itf->rx_ff); - if ( max_read >= CFG_TUD_VENDOR_EPSIZE ) - { - usbd_edpt_xfer(rhport, p_itf->ep_out, p_itf->epout_buf, CFG_TUD_VENDOR_EPSIZE); - } - else - { - // Release endpoint since we don't make any transfer - usbd_edpt_release(rhport, p_itf->ep_out); - } -} - -uint32_t tud_vendor_n_read (uint8_t itf, void* buffer, uint32_t bufsize) -{ - vendord_interface_t* p_itf = &_vendord_itf[itf]; - uint32_t num_read = tu_fifo_read_n(&p_itf->rx_ff, buffer, (uint16_t) bufsize); - _prep_out_transaction(p_itf); - return num_read; -} - -void tud_vendor_n_read_flush (uint8_t itf) -{ - vendord_interface_t* p_itf = &_vendord_itf[itf]; - tu_fifo_clear(&p_itf->rx_ff); - _prep_out_transaction(p_itf); -} - -//--------------------------------------------------------------------+ -// Write API -//--------------------------------------------------------------------+ -uint32_t tud_vendor_n_write (uint8_t itf, void const* buffer, uint32_t bufsize) -{ - vendord_interface_t* p_itf = &_vendord_itf[itf]; - uint16_t ret = tu_fifo_write_n(&p_itf->tx_ff, buffer, (uint16_t) bufsize); - - // flush if queue more than packet size - if (tu_fifo_count(&p_itf->tx_ff) >= CFG_TUD_VENDOR_EPSIZE) { - tud_vendor_n_write_flush(itf); - } - return ret; -} - -uint32_t tud_vendor_n_write_flush (uint8_t itf) -{ - vendord_interface_t* p_itf = &_vendord_itf[itf]; - - // Skip if usb is not ready yet - TU_VERIFY( tud_ready(), 0 ); - - // No data to send - if ( !tu_fifo_count(&p_itf->tx_ff) ) return 0; - - uint8_t const rhport = 0; - - // Claim the endpoint - TU_VERIFY( usbd_edpt_claim(rhport, p_itf->ep_in), 0 ); - - // Pull data from FIFO - uint16_t const count = tu_fifo_read_n(&p_itf->tx_ff, p_itf->epin_buf, sizeof(p_itf->epin_buf)); - - if ( count ) - { - TU_ASSERT( usbd_edpt_xfer(rhport, p_itf->ep_in, p_itf->epin_buf, count), 0 ); - return count; - }else - { - // Release endpoint since we don't make any transfer - // Note: data is dropped if terminal is not connected - usbd_edpt_release(rhport, p_itf->ep_in); - return 0; - } -} - -uint32_t tud_vendor_n_write_available (uint8_t itf) -{ - return tu_fifo_remaining(&_vendord_itf[itf].tx_ff); -} - -//--------------------------------------------------------------------+ -// USBD Driver API -//--------------------------------------------------------------------+ -void vendord_init(void) -{ - tu_memclr(_vendord_itf, sizeof(_vendord_itf)); - - for(uint8_t i=0; irx_ff, p_itf->rx_ff_buf, CFG_TUD_VENDOR_RX_BUFSIZE, 1, false); - tu_fifo_config(&p_itf->tx_ff, p_itf->tx_ff_buf, CFG_TUD_VENDOR_TX_BUFSIZE, 1, false); - -#if CFG_FIFO_MUTEX - tu_fifo_config_mutex(&p_itf->rx_ff, NULL, osal_mutex_create(&p_itf->rx_ff_mutex)); - tu_fifo_config_mutex(&p_itf->tx_ff, osal_mutex_create(&p_itf->tx_ff_mutex), NULL); -#endif - } -} - -void vendord_reset(uint8_t rhport) -{ - (void) rhport; - - for(uint8_t i=0; irx_ff); - tu_fifo_clear(&p_itf->tx_ff); - } -} - -uint16_t vendord_open(uint8_t rhport, tusb_desc_interface_t const * desc_itf, uint16_t max_len) -{ - TU_VERIFY(TUSB_CLASS_VENDOR_SPECIFIC == desc_itf->bInterfaceClass, 0); - - uint8_t const * p_desc = tu_desc_next(desc_itf); - uint8_t const * desc_end = p_desc + max_len; - - // Find available interface - vendord_interface_t* p_vendor = NULL; - for(uint8_t i=0; iitf_num = desc_itf->bInterfaceNumber; - if (desc_itf->bNumEndpoints) - { - // skip non-endpoint descriptors - while ( (TUSB_DESC_ENDPOINT != tu_desc_type(p_desc)) && (p_desc < desc_end) ) - { - p_desc = tu_desc_next(p_desc); - } - - // Open endpoint pair with usbd helper - TU_ASSERT(usbd_open_edpt_pair(rhport, p_desc, desc_itf->bNumEndpoints, TUSB_XFER_BULK, &p_vendor->ep_out, &p_vendor->ep_in), 0); - - p_desc += desc_itf->bNumEndpoints*sizeof(tusb_desc_endpoint_t); - - // Prepare for incoming data - if ( p_vendor->ep_out ) - { - _prep_out_transaction(p_vendor); - } - - if ( p_vendor->ep_in ) tud_vendor_n_write_flush((uint8_t)(p_vendor - _vendord_itf)); - } - - return (uint16_t) ((uintptr_t) p_desc - (uintptr_t) desc_itf); -} - -bool vendord_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) -{ - (void) rhport; - (void) result; - - uint8_t itf = 0; - vendord_interface_t* p_itf = _vendord_itf; - - for ( ; ; itf++, p_itf++) - { - if (itf >= TU_ARRAY_SIZE(_vendord_itf)) return false; - - if ( ( ep_addr == p_itf->ep_out ) || ( ep_addr == p_itf->ep_in ) ) break; - } - - if ( ep_addr == p_itf->ep_out ) - { - // Receive new data - tu_fifo_write_n(&p_itf->rx_ff, p_itf->epout_buf, (uint16_t) xferred_bytes); - - // Invoked callback if any - if (tud_vendor_rx_cb) tud_vendor_rx_cb(itf); - - _prep_out_transaction(p_itf); - } - else if ( ep_addr == p_itf->ep_in ) - { - if (tud_vendor_tx_cb) tud_vendor_tx_cb(itf, (uint16_t) xferred_bytes); - // Send complete, try to send more if possible - tud_vendor_n_write_flush(itf); - } - - return true; -} - -#endif diff --git a/pico-sdk/lib/tinyusb/src/class/vendor/vendor_device.h b/pico-sdk/lib/tinyusb/src/class/vendor/vendor_device.h deleted file mode 100644 index d239406..0000000 --- a/pico-sdk/lib/tinyusb/src/class/vendor/vendor_device.h +++ /dev/null @@ -1,150 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#ifndef _TUSB_VENDOR_DEVICE_H_ -#define _TUSB_VENDOR_DEVICE_H_ - -#include "common/tusb_common.h" - -#ifndef CFG_TUD_VENDOR_EPSIZE -#define CFG_TUD_VENDOR_EPSIZE 64 -#endif - -#ifdef __cplusplus - extern "C" { -#endif - -//--------------------------------------------------------------------+ -// Application API (Multiple Interfaces) -//--------------------------------------------------------------------+ -bool tud_vendor_n_mounted (uint8_t itf); - -uint32_t tud_vendor_n_available (uint8_t itf); -uint32_t tud_vendor_n_read (uint8_t itf, void* buffer, uint32_t bufsize); -bool tud_vendor_n_peek (uint8_t itf, uint8_t* ui8); -void tud_vendor_n_read_flush (uint8_t itf); - -uint32_t tud_vendor_n_write (uint8_t itf, void const* buffer, uint32_t bufsize); -uint32_t tud_vendor_n_write_flush (uint8_t itf); -uint32_t tud_vendor_n_write_available (uint8_t itf); - -static inline uint32_t tud_vendor_n_write_str (uint8_t itf, char const* str); - -// backward compatible -#define tud_vendor_n_flush(itf) tud_vendor_n_write_flush(itf) - -//--------------------------------------------------------------------+ -// Application API (Single Port) -//--------------------------------------------------------------------+ -static inline bool tud_vendor_mounted (void); -static inline uint32_t tud_vendor_available (void); -static inline uint32_t tud_vendor_read (void* buffer, uint32_t bufsize); -static inline bool tud_vendor_peek (uint8_t* ui8); -static inline void tud_vendor_read_flush (void); -static inline uint32_t tud_vendor_write (void const* buffer, uint32_t bufsize); -static inline uint32_t tud_vendor_write_str (char const* str); -static inline uint32_t tud_vendor_write_available (void); -static inline uint32_t tud_vendor_write_flush (void); - -// backward compatible -#define tud_vendor_flush() tud_vendor_write_flush() - -//--------------------------------------------------------------------+ -// Application Callback API (weak is optional) -//--------------------------------------------------------------------+ - -// Invoked when received new data -TU_ATTR_WEAK void tud_vendor_rx_cb(uint8_t itf); -// Invoked when last rx transfer finished -TU_ATTR_WEAK void tud_vendor_tx_cb(uint8_t itf, uint32_t sent_bytes); - -//--------------------------------------------------------------------+ -// Inline Functions -//--------------------------------------------------------------------+ - -static inline uint32_t tud_vendor_n_write_str (uint8_t itf, char const* str) -{ - return tud_vendor_n_write(itf, str, strlen(str)); -} - -static inline bool tud_vendor_mounted (void) -{ - return tud_vendor_n_mounted(0); -} - -static inline uint32_t tud_vendor_available (void) -{ - return tud_vendor_n_available(0); -} - -static inline uint32_t tud_vendor_read (void* buffer, uint32_t bufsize) -{ - return tud_vendor_n_read(0, buffer, bufsize); -} - -static inline bool tud_vendor_peek (uint8_t* ui8) -{ - return tud_vendor_n_peek(0, ui8); -} - -static inline void tud_vendor_read_flush(void) -{ - tud_vendor_n_read_flush(0); -} - -static inline uint32_t tud_vendor_write (void const* buffer, uint32_t bufsize) -{ - return tud_vendor_n_write(0, buffer, bufsize); -} - -static inline uint32_t tud_vendor_write_flush (void) -{ - return tud_vendor_n_write_flush(0); -} - -static inline uint32_t tud_vendor_write_str (char const* str) -{ - return tud_vendor_n_write_str(0, str); -} - -static inline uint32_t tud_vendor_write_available (void) -{ - return tud_vendor_n_write_available(0); -} - -//--------------------------------------------------------------------+ -// Internal Class Driver API -//--------------------------------------------------------------------+ -void vendord_init(void); -void vendord_reset(uint8_t rhport); -uint16_t vendord_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); -bool vendord_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); - -#ifdef __cplusplus - } -#endif - -#endif /* _TUSB_VENDOR_DEVICE_H_ */ diff --git a/pico-sdk/lib/tinyusb/src/class/vendor/vendor_host.c b/pico-sdk/lib/tinyusb/src/class/vendor/vendor_host.c deleted file mode 100644 index e66c500..0000000 --- a/pico-sdk/lib/tinyusb/src/class/vendor/vendor_host.c +++ /dev/null @@ -1,146 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#include "tusb_option.h" - -#if (CFG_TUH_ENABLED && CFG_TUH_VENDOR) - -//--------------------------------------------------------------------+ -// INCLUDE -//--------------------------------------------------------------------+ -#include "host/usbh.h" -#include "vendor_host.h" - -//--------------------------------------------------------------------+ -// MACRO CONSTANT TYPEDEF -//--------------------------------------------------------------------+ - -//--------------------------------------------------------------------+ -// INTERNAL OBJECT & FUNCTION DECLARATION -//--------------------------------------------------------------------+ -custom_interface_info_t custom_interface[CFG_TUH_DEVICE_MAX]; - -static tusb_error_t cush_validate_paras(uint8_t dev_addr, uint16_t vendor_id, uint16_t product_id, void * p_buffer, uint16_t length) -{ - if ( !tusbh_custom_is_mounted(dev_addr, vendor_id, product_id) ) - { - return TUSB_ERROR_DEVICE_NOT_READY; - } - - TU_ASSERT( p_buffer != NULL && length != 0, TUSB_ERROR_INVALID_PARA); - - return TUSB_ERROR_NONE; -} -//--------------------------------------------------------------------+ -// APPLICATION API (need to check parameters) -//--------------------------------------------------------------------+ -tusb_error_t tusbh_custom_read(uint8_t dev_addr, uint16_t vendor_id, uint16_t product_id, void * p_buffer, uint16_t length) -{ - TU_ASSERT_ERR( cush_validate_paras(dev_addr, vendor_id, product_id, p_buffer, length) ); - - if ( !hcd_pipe_is_idle(custom_interface[dev_addr-1].pipe_in) ) - { - return TUSB_ERROR_INTERFACE_IS_BUSY; - } - - (void) usbh_edpt_xfer( custom_interface[dev_addr-1].pipe_in, p_buffer, length); - - return TUSB_ERROR_NONE; -} - -tusb_error_t tusbh_custom_write(uint8_t dev_addr, uint16_t vendor_id, uint16_t product_id, void const * p_data, uint16_t length) -{ - TU_ASSERT_ERR( cush_validate_paras(dev_addr, vendor_id, product_id, p_data, length) ); - - if ( !hcd_pipe_is_idle(custom_interface[dev_addr-1].pipe_out) ) - { - return TUSB_ERROR_INTERFACE_IS_BUSY; - } - - (void) usbh_edpt_xfer( custom_interface[dev_addr-1].pipe_out, p_data, length); - - return TUSB_ERROR_NONE; -} - -//--------------------------------------------------------------------+ -// USBH-CLASS API -//--------------------------------------------------------------------+ -void cush_init(void) -{ - tu_memclr(&custom_interface, sizeof(custom_interface_info_t) * CFG_TUH_DEVICE_MAX); -} - -tusb_error_t cush_open_subtask(uint8_t dev_addr, tusb_desc_interface_t const *p_interface_desc, uint16_t *p_length) -{ - // FIXME quick hack to test lpc1k custom class with 2 bulk endpoints - uint8_t const *p_desc = (uint8_t const *) p_interface_desc; - p_desc = tu_desc_next(p_desc); - - //------------- Bulk Endpoints Descriptor -------------// - for(uint32_t i=0; i<2; i++) - { - tusb_desc_endpoint_t const *p_endpoint = (tusb_desc_endpoint_t const *) p_desc; - TU_ASSERT(TUSB_DESC_ENDPOINT == p_endpoint->bDescriptorType, TUSB_ERROR_INVALID_PARA); - - pipe_handle_t * p_pipe_hdl = ( p_endpoint->bEndpointAddress & TUSB_DIR_IN_MASK ) ? - &custom_interface[dev_addr-1].pipe_in : &custom_interface[dev_addr-1].pipe_out; - *p_pipe_hdl = usbh_edpt_open(dev_addr, p_endpoint, TUSB_CLASS_VENDOR_SPECIFIC); - TU_ASSERT ( pipehandle_is_valid(*p_pipe_hdl), TUSB_ERROR_HCD_OPEN_PIPE_FAILED ); - - p_desc = tu_desc_next(p_desc); - } - - (*p_length) = sizeof(tusb_desc_interface_t) + 2*sizeof(tusb_desc_endpoint_t); - return TUSB_ERROR_NONE; -} - -void cush_isr(pipe_handle_t pipe_hdl, xfer_result_t event) -{ - -} - -void cush_close(uint8_t dev_addr) -{ - tusb_error_t err1, err2; - custom_interface_info_t * p_interface = &custom_interface[dev_addr-1]; - - // TODO re-consider to check pipe valid before calling pipe_close - if( pipehandle_is_valid( p_interface->pipe_in ) ) - { - err1 = hcd_pipe_close( p_interface->pipe_in ); - } - - if ( pipehandle_is_valid( p_interface->pipe_out ) ) - { - err2 = hcd_pipe_close( p_interface->pipe_out ); - } - - tu_memclr(p_interface, sizeof(custom_interface_info_t)); - - TU_ASSERT(err1 == TUSB_ERROR_NONE && err2 == TUSB_ERROR_NONE, (void) 0 ); -} - -#endif diff --git a/pico-sdk/lib/tinyusb/src/class/vendor/vendor_host.h b/pico-sdk/lib/tinyusb/src/class/vendor/vendor_host.h deleted file mode 100644 index acfebe7..0000000 --- a/pico-sdk/lib/tinyusb/src/class/vendor/vendor_host.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#ifndef _TUSB_VENDOR_HOST_H_ -#define _TUSB_VENDOR_HOST_H_ - -#include "common/tusb_common.h" - -#ifdef __cplusplus - extern "C" { -#endif - -typedef struct { - pipe_handle_t pipe_in; - pipe_handle_t pipe_out; -}custom_interface_info_t; - -//--------------------------------------------------------------------+ -// USBH-CLASS DRIVER API -//--------------------------------------------------------------------+ -static inline bool tusbh_custom_is_mounted(uint8_t dev_addr, uint16_t vendor_id, uint16_t product_id) -{ - (void) vendor_id; // TODO check this later - (void) product_id; -// return (tusbh_device_get_mounted_class_flag(dev_addr) & TU_BIT(TUSB_CLASS_MAPPED_INDEX_END-1) ) != 0; - return false; -} - -bool tusbh_custom_read(uint8_t dev_addr, uint16_t vendor_id, uint16_t product_id, void * p_buffer, uint16_t length); -bool tusbh_custom_write(uint8_t dev_addr, uint16_t vendor_id, uint16_t product_id, void const * p_data, uint16_t length); - -//--------------------------------------------------------------------+ -// Internal Class Driver API -//--------------------------------------------------------------------+ -void cush_init(void); -bool cush_open_subtask(uint8_t dev_addr, tusb_desc_interface_t const *p_interface_desc, uint16_t *p_length); -void cush_isr(pipe_handle_t pipe_hdl, xfer_result_t event); -void cush_close(uint8_t dev_addr); - -#ifdef __cplusplus - } -#endif - -#endif /* _TUSB_VENDOR_HOST_H_ */ diff --git a/pico-sdk/lib/tinyusb/src/common/tusb_common.h b/pico-sdk/lib/tinyusb/src/common/tusb_common.h index caeb5f2..0d4082c 100644 --- a/pico-sdk/lib/tinyusb/src/common/tusb_common.h +++ b/pico-sdk/lib/tinyusb/src/common/tusb_common.h @@ -37,6 +37,7 @@ #define TU_ARRAY_SIZE(_arr) ( sizeof(_arr) / sizeof(_arr[0]) ) #define TU_MIN(_x, _y) ( ( (_x) < (_y) ) ? (_x) : (_y) ) #define TU_MAX(_x, _y) ( ( (_x) > (_y) ) ? (_x) : (_y) ) +#define TU_DIV_CEIL(n, d) (((n) + (d) - 1) / (d)) #define TU_U16(_high, _low) ((uint16_t) (((_high) << 8) | (_low))) #define TU_U16_HIGH(_u16) ((uint8_t) (((_u16) >> 8) & 0x00ff)) @@ -64,6 +65,7 @@ // Standard Headers #include #include +#include #include #include #include diff --git a/pico-sdk/lib/tinyusb/src/common/tusb_compiler.h b/pico-sdk/lib/tinyusb/src/common/tusb_compiler.h index 6f07bdd..0d5570b 100644 --- a/pico-sdk/lib/tinyusb/src/common/tusb_compiler.h +++ b/pico-sdk/lib/tinyusb/src/common/tusb_compiler.h @@ -56,7 +56,7 @@ #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L #define TU_VERIFY_STATIC _Static_assert #elif defined(__CCRX__) - #define TU_VERIFY_STATIC(const_expr, _mess) typedef char TU_XSTRCAT(Line, __LINE__)[(const_expr) ? 1 : 0]; + #define TU_VERIFY_STATIC(const_expr, _mess) typedef char TU_XSTRCAT(_verify_static_, _TU_COUNTER_)[(const_expr) ? 1 : 0]; #else #define TU_VERIFY_STATIC(const_expr, _mess) enum { TU_XSTRCAT(_verify_static_, _TU_COUNTER_) = 1/(!!(const_expr)) } #endif diff --git a/pico-sdk/lib/tinyusb/src/common/tusb_debug.h b/pico-sdk/lib/tinyusb/src/common/tusb_debug.h index 0f4dc93..2e9f1d9 100644 --- a/pico-sdk/lib/tinyusb/src/common/tusb_debug.h +++ b/pico-sdk/lib/tinyusb/src/common/tusb_debug.h @@ -60,6 +60,7 @@ void tu_print_mem(void const *buf, uint32_t count, uint8_t indent); static inline void tu_print_buf(uint8_t const* buf, uint32_t bufsize) { for(uint32_t i=0; i= 2 diff --git a/pico-sdk/lib/tinyusb/src/common/tusb_fifo.c b/pico-sdk/lib/tinyusb/src/common/tusb_fifo.c index d6c3db4..8a0fd44 100644 --- a/pico-sdk/lib/tinyusb/src/common/tusb_fifo.c +++ b/pico-sdk/lib/tinyusb/src/common/tusb_fifo.c @@ -62,7 +62,9 @@ TU_ATTR_ALWAYS_INLINE static inline void _ff_unlock(osal_mutex_t mutex) typedef enum { TU_FIFO_COPY_INC, ///< Copy from/to an increasing source/destination address - default mode +#ifdef TUP_MEM_CONST_ADDR TU_FIFO_COPY_CST_FULL_WORDS, ///< Copy from/to a constant source/destination address - required for e.g. STM32 to write into USB hardware FIFO +#endif } tu_fifo_copy_mode_t; bool tu_fifo_config(tu_fifo_t *f, void* buffer, uint16_t depth, uint16_t item_size, bool overwritable) @@ -92,6 +94,7 @@ bool tu_fifo_config(tu_fifo_t *f, void* buffer, uint16_t depth, uint16_t item_si // Pull & Push //--------------------------------------------------------------------+ +#ifdef TUP_MEM_CONST_ADDR // Intended to be used to read from hardware USB FIFO in e.g. STM32 where all data is read from a constant address // Code adapted from dcd_synopsys.c // TODO generalize with configurable 1 byte or 4 byte each read @@ -140,6 +143,7 @@ static void _ff_pull_const_addr(void * app_buf, const uint8_t * ff_buf, uint16_t *reg_tx = tmp32; } } +#endif // send one item to fifo WITHOUT updating write pointer static inline void _ff_push(tu_fifo_t* f, void const * app_buf, uint16_t rel) @@ -179,7 +183,7 @@ static void _ff_push_n(tu_fifo_t* f, void const * app_buf, uint16_t n, uint16_t memcpy(f->buffer, ((uint8_t const*) app_buf) + lin_bytes, wrap_bytes); } break; - +#ifdef TUP_MEM_CONST_ADDR case TU_FIFO_COPY_CST_FULL_WORDS: // Intended for hardware buffers from which it can be read word by word only if(n <= lin_count) @@ -224,6 +228,8 @@ static void _ff_push_n(tu_fifo_t* f, void const * app_buf, uint16_t n, uint16_t if (wrap_bytes > 0) _ff_push_const_addr(ff_buf, app_buf, wrap_bytes); } break; +#endif + default: break; } } @@ -264,7 +270,7 @@ static void _ff_pull_n(tu_fifo_t* f, void* app_buf, uint16_t n, uint16_t rd_ptr, memcpy((uint8_t*) app_buf + lin_bytes, f->buffer, wrap_bytes); } break; - +#ifdef TUP_MEM_CONST_ADDR case TU_FIFO_COPY_CST_FULL_WORDS: if ( n <= lin_count ) { @@ -309,6 +315,7 @@ static void _ff_pull_n(tu_fifo_t* f, void* app_buf, uint16_t n, uint16_t rd_ptr, // Read data wrapped part if (wrap_bytes > 0) _ff_pull_const_addr(app_buf, ff_buf, wrap_bytes); } +#endif break; default: break; @@ -726,10 +733,29 @@ uint16_t tu_fifo_read_n(tu_fifo_t* f, void * buffer, uint16_t n) return _tu_fifo_read_n(f, buffer, n, TU_FIFO_COPY_INC); } +#ifdef TUP_MEM_CONST_ADDR +/******************************************************************************/ +/*! + @brief This function will read n elements from the array index specified by + the read pointer and increment the read index. + This function checks for an overflow and corrects read pointer if required. + The dest address will not be incremented which is useful for writing to registers. + + @param[in] f + Pointer to the FIFO buffer to manipulate + @param[in] buffer + The pointer to data location + @param[in] n + Number of element that buffer can afford + + @returns number of items read from the FIFO + */ +/******************************************************************************/ uint16_t tu_fifo_read_n_const_addr_full_words(tu_fifo_t* f, void * buffer, uint16_t n) { return _tu_fifo_read_n(f, buffer, n, TU_FIFO_COPY_CST_FULL_WORDS); } +#endif /******************************************************************************/ /*! @@ -838,6 +864,7 @@ uint16_t tu_fifo_write_n(tu_fifo_t* f, const void * data, uint16_t n) return _tu_fifo_write_n(f, data, n, TU_FIFO_COPY_INC); } +#ifdef TUP_MEM_CONST_ADDR /******************************************************************************/ /*! @brief This function will write n elements into the array index specified by @@ -857,6 +884,7 @@ uint16_t tu_fifo_write_n_const_addr_full_words(tu_fifo_t* f, const void * data, { return _tu_fifo_write_n(f, data, n, TU_FIFO_COPY_CST_FULL_WORDS); } +#endif /******************************************************************************/ /*! diff --git a/pico-sdk/lib/tinyusb/src/common/tusb_fifo.h b/pico-sdk/lib/tinyusb/src/common/tusb_fifo.h index 2f60ec2..6c0efb5 100644 --- a/pico-sdk/lib/tinyusb/src/common/tusb_fifo.h +++ b/pico-sdk/lib/tinyusb/src/common/tusb_fifo.h @@ -102,10 +102,8 @@ extern "C" { * | * ------------------------- * | R | 1 | 2 | W | 4 | 5 | - */ -typedef struct -{ +typedef struct { uint8_t* buffer ; // buffer pointer uint16_t depth ; // max items @@ -124,16 +122,14 @@ typedef struct } tu_fifo_t; -typedef struct -{ +typedef struct { uint16_t len_lin ; ///< linear length in item size uint16_t len_wrap ; ///< wrapped length in item size void * ptr_lin ; ///< linear part start pointer void * ptr_wrap ; ///< wrapped part start pointer } tu_fifo_buffer_info_t; -#define TU_FIFO_INIT(_buffer, _depth, _type, _overwritable) \ -{ \ +#define TU_FIFO_INIT(_buffer, _depth, _type, _overwritable){\ .buffer = _buffer, \ .depth = _depth, \ .item_size = sizeof(_type), \ @@ -144,32 +140,31 @@ typedef struct uint8_t _name##_buf[_depth*sizeof(_type)]; \ tu_fifo_t _name = TU_FIFO_INIT(_name##_buf, _depth, _type, _overwritable) - bool tu_fifo_set_overwritable(tu_fifo_t *f, bool overwritable); bool tu_fifo_clear(tu_fifo_t *f); bool tu_fifo_config(tu_fifo_t *f, void* buffer, uint16_t depth, uint16_t item_size, bool overwritable); #if OSAL_MUTEX_REQUIRED TU_ATTR_ALWAYS_INLINE static inline -void tu_fifo_config_mutex(tu_fifo_t *f, osal_mutex_t wr_mutex, osal_mutex_t rd_mutex) -{ +void tu_fifo_config_mutex(tu_fifo_t *f, osal_mutex_t wr_mutex, osal_mutex_t rd_mutex) { f->mutex_wr = wr_mutex; f->mutex_rd = rd_mutex; } - #else - #define tu_fifo_config_mutex(_f, _wr_mutex, _rd_mutex) - #endif bool tu_fifo_write (tu_fifo_t* f, void const * p_data); uint16_t tu_fifo_write_n (tu_fifo_t* f, void const * p_data, uint16_t n); +#ifdef TUP_MEM_CONST_ADDR uint16_t tu_fifo_write_n_const_addr_full_words (tu_fifo_t* f, const void * data, uint16_t n); +#endif bool tu_fifo_read (tu_fifo_t* f, void * p_buffer); uint16_t tu_fifo_read_n (tu_fifo_t* f, void * p_buffer, uint16_t n); +#ifdef TUP_MEM_CONST_ADDR uint16_t tu_fifo_read_n_const_addr_full_words (tu_fifo_t* f, void * buffer, uint16_t n); +#endif bool tu_fifo_peek (tu_fifo_t* f, void * p_buffer); uint16_t tu_fifo_peek_n (tu_fifo_t* f, void * p_buffer, uint16_t n); @@ -182,8 +177,7 @@ bool tu_fifo_overflowed (tu_fifo_t* f); void tu_fifo_correct_read_pointer (tu_fifo_t* f); TU_ATTR_ALWAYS_INLINE static inline -uint16_t tu_fifo_depth(tu_fifo_t* f) -{ +uint16_t tu_fifo_depth(tu_fifo_t* f) { return f->depth; } @@ -198,7 +192,6 @@ void tu_fifo_advance_read_pointer (tu_fifo_t *f, uint16_t n); void tu_fifo_get_read_info (tu_fifo_t *f, tu_fifo_buffer_info_t *info); void tu_fifo_get_write_info(tu_fifo_t *f, tu_fifo_buffer_info_t *info); - #ifdef __cplusplus } #endif diff --git a/pico-sdk/lib/tinyusb/src/common/tusb_mcu.h b/pico-sdk/lib/tinyusb/src/common/tusb_mcu.h index e589c4c..abfed28 100644 --- a/pico-sdk/lib/tinyusb/src/common/tusb_mcu.h +++ b/pico-sdk/lib/tinyusb/src/common/tusb_mcu.h @@ -100,6 +100,13 @@ #define TUP_DCD_ENDPOINT_MAX 8 #define TUP_RHPORT_HIGHSPEED 1 +#elif TU_CHECK_MCU(OPT_MCU_MCXA15) + // USB0 is chipidea FS + #define TUP_USBIP_CHIPIDEA_FS + #define TUP_USBIP_CHIPIDEA_FS_MCX + + #define TUP_DCD_ENDPOINT_MAX 16 + #elif TU_CHECK_MCU(OPT_MCU_MIMXRT1XXX) #define TUP_USBIP_CHIPIDEA_HS #define TUP_USBIP_EHCI @@ -107,7 +114,7 @@ #define TUP_DCD_ENDPOINT_MAX 8 #define TUP_RHPORT_HIGHSPEED 1 -#elif TU_CHECK_MCU(OPT_MCU_KINETIS_KL, OPT_MCU_KINETIS_K32L) +#elif TU_CHECK_MCU(OPT_MCU_KINETIS_KL, OPT_MCU_KINETIS_K32L, OPT_MCU_KINETIS_K) #define TUP_USBIP_CHIPIDEA_FS #define TUP_USBIP_CHIPIDEA_FS_KINETIS #define TUP_DCD_ENDPOINT_MAX 16 @@ -188,6 +195,7 @@ #elif TU_CHECK_MCU(OPT_MCU_STM32F4) #define TUP_USBIP_DWC2 #define TUP_USBIP_DWC2_STM32 + #define TUP_USBIP_DWC2_TEST_MODE // For most mcu, FS has 4, HS has 6. TODO 446/469/479 HS has 9 #define TUP_DCD_ENDPOINT_MAX 6 @@ -202,6 +210,7 @@ // MCU with on-chip HS Phy #if defined(STM32F723xx) || defined(STM32F730xx) || defined(STM32F733xx) #define TUP_RHPORT_HIGHSPEED 1 // Port0: FS, Port1: HS + #define TUP_USBIP_DWC2_TEST_MODE #endif #elif TU_CHECK_MCU(OPT_MCU_STM32H7) @@ -270,6 +279,7 @@ defined(STM32U5F7xx) || defined(STM32U5F9xx) || defined(STM32U5G7xx) || defined(STM32U5G9xx) #define TUP_DCD_ENDPOINT_MAX 9 #define TUP_RHPORT_HIGHSPEED 1 + #define TUP_USBIP_DWC2_TEST_MODE #else #define TUP_DCD_ENDPOINT_MAX 6 #endif @@ -322,6 +332,9 @@ #define TUP_USBIP_DWC2 #define TUP_DCD_ENDPOINT_MAX 6 +#elif TU_CHECK_MCU(OPT_MCU_ESP32, OPT_MCU_ESP32C2, OPT_MCU_ESP32C3, OPT_MCU_ESP32C6, OPT_MCU_ESP32H2) && (CFG_TUD_ENABLED || !(defined(CFG_TUH_MAX3421) && CFG_TUH_MAX3421)) + #error "MCUs are only supported with CFG_TUH_MAX3421 enabled" + //--------------------------------------------------------------------+ // Dialog //--------------------------------------------------------------------+ @@ -391,12 +404,24 @@ //------------- WCH -------------// #elif TU_CHECK_MCU(OPT_MCU_CH32V307) - #define TUP_DCD_ENDPOINT_MAX 16 - #define TUP_RHPORT_HIGHSPEED 1 + // v307 support both FS and HS + #define TUP_USBIP_WCH_USBHS + #define TUP_USBIP_WCH_USBFS + + #define TUP_RHPORT_HIGHSPEED 1 // default to highspeed + #define TUP_DCD_ENDPOINT_MAX (CFG_TUD_MAX_SPEED == OPT_MODE_HIGH_SPEED ? 16 : 8) #elif TU_CHECK_MCU(OPT_MCU_CH32F20X) - #define TUP_DCD_ENDPOINT_MAX 16 - #define TUP_RHPORT_HIGHSPEED 1 + #define TUP_USBIP_WCH_USBHS + #define TUP_USBIP_WCH_USBFS + + #define TUP_RHPORT_HIGHSPEED 1 // default to highspeed + #define TUP_DCD_ENDPOINT_MAX (CFG_TUD_MAX_SPEED == OPT_MODE_HIGH_SPEED ? 16 : 8) + +#elif TU_CHECK_MCU(OPT_MCU_CH32V20X) + #define TUP_USBIP_WCH_USBFS + #define TUP_DCD_ENDPOINT_MAX 8 + #endif @@ -419,7 +444,7 @@ #define TUP_MCU_MULTIPLE_CORE 0 #endif -#ifndef TUP_DCD_ENDPOINT_MAX +#if !defined(TUP_DCD_ENDPOINT_MAX) && defined(CFG_TUD_ENABLED) && CFG_TUD_ENABLED #warning "TUP_DCD_ENDPOINT_MAX is not defined for this MCU, default to 8" #define TUP_DCD_ENDPOINT_MAX 8 #endif @@ -434,4 +459,12 @@ #define TU_ATTR_FAST_FUNC #endif +#if defined(TUP_USBIP_DWC2) || defined(TUP_USBIP_FSDEV) + #define TUP_DCD_EDPT_ISO_ALLOC +#endif + +#if defined(TUP_USBIP_DWC2) + #define TUP_MEM_CONST_ADDR +#endif + #endif diff --git a/pico-sdk/lib/tinyusb/src/common/tusb_private.h b/pico-sdk/lib/tinyusb/src/common/tusb_private.h index db1ba97..373a502 100644 --- a/pico-sdk/lib/tinyusb/src/common/tusb_private.h +++ b/pico-sdk/lib/tinyusb/src/common/tusb_private.h @@ -60,7 +60,7 @@ typedef struct { tu_fifo_t ff; // mutex: read if ep rx, write if e tx - OSAL_MUTEX_DEF(ff_mutex); + OSAL_MUTEX_DEF(ff_mutexdef); }tu_edpt_stream_t; @@ -87,15 +87,17 @@ bool tu_edpt_release(tu_edpt_state_t* ep_state, osal_mutex_t mutex); // Endpoint Stream //--------------------------------------------------------------------+ -// Init an stream, should only be called once +// Init an endpoint stream bool tu_edpt_stream_init(tu_edpt_stream_t* s, bool is_host, bool is_tx, bool overwritable, void* ff_buf, uint16_t ff_bufsize, uint8_t* ep_buf, uint16_t ep_bufsize); +// Deinit an endpoint stream +bool tu_edpt_stream_deinit(tu_edpt_stream_t* s); + // Open an stream for an endpoint // hwid is either device address (host mode) or rhport (device mode) TU_ATTR_ALWAYS_INLINE static inline -void tu_edpt_stream_open(tu_edpt_stream_t* s, uint8_t hwid, tusb_desc_endpoint_t const *desc_ep) -{ +void tu_edpt_stream_open(tu_edpt_stream_t* s, uint8_t hwid, tusb_desc_endpoint_t const *desc_ep) { tu_fifo_clear(&s->ff); s->hwid = hwid; s->ep_addr = desc_ep->bEndpointAddress; @@ -103,16 +105,14 @@ void tu_edpt_stream_open(tu_edpt_stream_t* s, uint8_t hwid, tusb_desc_endpoint_t } TU_ATTR_ALWAYS_INLINE static inline -void tu_edpt_stream_close(tu_edpt_stream_t* s) -{ +void tu_edpt_stream_close(tu_edpt_stream_t* s) { s->hwid = 0; s->ep_addr = 0; } // Clear fifo TU_ATTR_ALWAYS_INLINE static inline -bool tu_edpt_stream_clear(tu_edpt_stream_t* s) -{ +bool tu_edpt_stream_clear(tu_edpt_stream_t* s) { return tu_fifo_clear(&s->ff); } @@ -131,8 +131,7 @@ bool tu_edpt_stream_write_zlp_if_needed(tu_edpt_stream_t* s, uint32_t last_xferr // Get the number of bytes available for writing TU_ATTR_ALWAYS_INLINE static inline -uint32_t tu_edpt_stream_write_available(tu_edpt_stream_t* s) -{ +uint32_t tu_edpt_stream_write_available(tu_edpt_stream_t* s) { return (uint32_t) tu_fifo_remaining(&s->ff); } diff --git a/pico-sdk/lib/tinyusb/src/common/tusb_types.h b/pico-sdk/lib/tinyusb/src/common/tusb_types.h index fab6809..b571f9b 100644 --- a/pico-sdk/lib/tinyusb/src/common/tusb_types.h +++ b/pico-sdk/lib/tinyusb/src/common/tusb_types.h @@ -24,12 +24,8 @@ * This file is part of the TinyUSB stack. */ -/** \ingroup group_usb_definitions - * \defgroup USBDef_Type USB Types - * @{ */ - -#ifndef _TUSB_TYPES_H_ -#define _TUSB_TYPES_H_ +#ifndef TUSB_TYPES_H_ +#define TUSB_TYPES_H_ #include #include @@ -44,43 +40,38 @@ *------------------------------------------------------------------*/ /// defined base on EHCI specs value for Endpoint Speed -typedef enum -{ +typedef enum { TUSB_SPEED_FULL = 0, TUSB_SPEED_LOW = 1, TUSB_SPEED_HIGH = 2, TUSB_SPEED_INVALID = 0xff, -}tusb_speed_t; +} tusb_speed_t; /// defined base on USB Specs Endpoint's bmAttributes -typedef enum -{ +typedef enum { TUSB_XFER_CONTROL = 0 , TUSB_XFER_ISOCHRONOUS , TUSB_XFER_BULK , TUSB_XFER_INTERRUPT -}tusb_xfer_type_t; +} tusb_xfer_type_t; -typedef enum -{ +typedef enum { TUSB_DIR_OUT = 0, TUSB_DIR_IN = 1, TUSB_DIR_IN_MASK = 0x80 -}tusb_dir_t; +} tusb_dir_t; -enum -{ +enum { TUSB_EPSIZE_BULK_FS = 64, - TUSB_EPSIZE_BULK_HS= 512, + TUSB_EPSIZE_BULK_HS = 512, TUSB_EPSIZE_ISO_FS_MAX = 1023, TUSB_EPSIZE_ISO_HS_MAX = 1024, }; -/// Isochronous End Point Attributes -typedef enum -{ +/// Isochronous Endpoint Attributes +typedef enum { TUSB_ISO_EP_ATT_NO_SYNC = 0x00, TUSB_ISO_EP_ATT_ASYNCHRONOUS = 0x04, TUSB_ISO_EP_ATT_ADAPTIVE = 0x08, @@ -88,11 +79,10 @@ typedef enum TUSB_ISO_EP_ATT_DATA = 0x00, ///< Data End Point TUSB_ISO_EP_ATT_EXPLICIT_FB = 0x10, ///< Feedback End Point TUSB_ISO_EP_ATT_IMPLICIT_FB = 0x20, ///< Data endpoint that also serves as an implicit feedback -}tusb_iso_ep_attribute_t; +} tusb_iso_ep_attribute_t; /// USB Descriptor Types -typedef enum -{ +typedef enum { TUSB_DESC_DEVICE = 0x01, TUSB_DESC_CONFIGURATION = 0x02, TUSB_DESC_STRING = 0x03, @@ -119,10 +109,9 @@ typedef enum TUSB_DESC_SUPERSPEED_ENDPOINT_COMPANION = 0x30, TUSB_DESC_SUPERSPEED_ISO_ENDPOINT_COMPANION = 0x31 -}tusb_desc_type_t; +} tusb_desc_type_t; -typedef enum -{ +typedef enum { TUSB_REQ_GET_STATUS = 0 , TUSB_REQ_CLEAR_FEATURE = 1 , TUSB_REQ_RESERVED = 2 , @@ -136,25 +125,22 @@ typedef enum TUSB_REQ_GET_INTERFACE = 10 , TUSB_REQ_SET_INTERFACE = 11 , TUSB_REQ_SYNCH_FRAME = 12 -}tusb_request_code_t; +} tusb_request_code_t; -typedef enum -{ +typedef enum { TUSB_REQ_FEATURE_EDPT_HALT = 0, TUSB_REQ_FEATURE_REMOTE_WAKEUP = 1, TUSB_REQ_FEATURE_TEST_MODE = 2 -}tusb_request_feature_selector_t; +} tusb_request_feature_selector_t; -typedef enum -{ +typedef enum { TUSB_REQ_TYPE_STANDARD = 0, TUSB_REQ_TYPE_CLASS, TUSB_REQ_TYPE_VENDOR, TUSB_REQ_TYPE_INVALID } tusb_request_type_t; -typedef enum -{ +typedef enum { TUSB_REQ_RCPT_DEVICE =0, TUSB_REQ_RCPT_INTERFACE, TUSB_REQ_RCPT_ENDPOINT, @@ -162,8 +148,7 @@ typedef enum } tusb_request_recipient_t; // https://www.usb.org/defined-class-codes -typedef enum -{ +typedef enum { TUSB_CLASS_UNSPECIFIED = 0 , TUSB_CLASS_AUDIO = 1 , TUSB_CLASS_CDC = 2 , @@ -187,26 +172,23 @@ typedef enum TUSB_CLASS_MISC = 0xEF , TUSB_CLASS_APPLICATION_SPECIFIC = 0xFE , TUSB_CLASS_VENDOR_SPECIFIC = 0xFF -}tusb_class_code_t; +} tusb_class_code_t; typedef enum { MISC_SUBCLASS_COMMON = 2 }misc_subclass_type_t; -typedef enum -{ +typedef enum { MISC_PROTOCOL_IAD = 1 -}misc_protocol_type_t; +} misc_protocol_type_t; -typedef enum -{ +typedef enum { APP_SUBCLASS_USBTMC = 0x03, APP_SUBCLASS_DFU_RUNTIME = 0x01 } app_subclass_type_t; -typedef enum -{ +typedef enum { DEVICE_CAPABILITY_WIRELESS_USB = 0x01, DEVICE_CAPABILITY_USB20_EXTENSION = 0x02, DEVICE_CAPABILITY_SUPERSPEED_USB = 0x03, @@ -223,11 +205,11 @@ typedef enum DEVICE_CAPABILITY_AUTHENTICATION = 0x0E, DEVICE_CAPABILITY_BILLBOARD_EX = 0x0F, DEVICE_CAPABILITY_CONFIGURATION_SUMMARY = 0x10 -}device_capability_type_t; +} device_capability_type_t; enum { - TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP = TU_BIT(5), - TUSB_DESC_CONFIG_ATT_SELF_POWERED = TU_BIT(6), + TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP = 1u << 5, + TUSB_DESC_CONFIG_ATT_SELF_POWERED = 1u << 6, }; #define TUSB_DESC_CONFIG_POWER_MA(x) ((x)/2) @@ -235,28 +217,25 @@ enum { //--------------------------------------------------------------------+ // //--------------------------------------------------------------------+ -typedef enum -{ +typedef enum { XFER_RESULT_SUCCESS = 0, XFER_RESULT_FAILED, XFER_RESULT_STALLED, XFER_RESULT_TIMEOUT, XFER_RESULT_INVALID -}xfer_result_t; +} xfer_result_t; -enum // TODO remove -{ +// TODO remove +enum { DESC_OFFSET_LEN = 0, DESC_OFFSET_TYPE = 1 }; -enum -{ +enum { INTERFACE_INVALID_NUMBER = 0xff }; -typedef enum -{ +typedef enum { MS_OS_20_SET_HEADER_DESCRIPTOR = 0x00, MS_OS_20_SUBSET_HEADER_CONFIGURATION = 0x01, MS_OS_20_SUBSET_HEADER_FUNCTION = 0x02, @@ -268,16 +247,14 @@ typedef enum MS_OS_20_FEATURE_VENDOR_REVISION = 0x08 } microsoft_os_20_type_t; -enum -{ +enum { CONTROL_STAGE_IDLE, CONTROL_STAGE_SETUP, CONTROL_STAGE_DATA, CONTROL_STAGE_ACK }; -enum -{ +enum { TUSB_INDEX_INVALID_8 = 0xFFu }; @@ -290,15 +267,14 @@ TU_ATTR_PACKED_BEGIN TU_ATTR_BIT_FIELD_ORDER_BEGIN /// USB Device Descriptor -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of this descriptor in bytes. uint8_t bDescriptorType ; ///< DEVICE Descriptor Type. - uint16_t bcdUSB ; ///< BUSB Specification Release Number in Binary-Coded Decimal (i.e., 2.10 is 210H). This field identifies the release of the USB Specification with which the device and its descriptors are compliant. + uint16_t bcdUSB ; ///< BUSB Specification Release Number in Binary-Coded Decimal (i.e., 2.10 is 210H). - uint8_t bDeviceClass ; ///< Class code (assigned by the USB-IF). \li If this field is reset to zero, each interface within a configuration specifies its own class information and the various interfaces operate independently. \li If this field is set to a value between 1 and FEH, the device supports different class specifications on different interfaces and the interfaces may not operate independently. This value identifies the class definition used for the aggregate interfaces. \li If this field is set to FFH, the device class is vendor-specific. - uint8_t bDeviceSubClass ; ///< Subclass code (assigned by the USB-IF). These codes are qualified by the value of the bDeviceClass field. \li If the bDeviceClass field is reset to zero, this field must also be reset to zero. \li If the bDeviceClass field is not set to FFH, all values are reserved for assignment by the USB-IF. - uint8_t bDeviceProtocol ; ///< Protocol code (assigned by the USB-IF). These codes are qualified by the value of the bDeviceClass and the bDeviceSubClass fields. If a device supports class-specific protocols on a device basis as opposed to an interface basis, this code identifies the protocols that the device uses as defined by the specification of the device class. \li If this field is reset to zero, the device does not use class-specific protocols on a device basis. However, it may use classspecific protocols on an interface basis. \li If this field is set to FFH, the device uses a vendor-specific protocol on a device basis. + uint8_t bDeviceClass ; ///< Class code (assigned by the USB-IF). + uint8_t bDeviceSubClass ; ///< Subclass code (assigned by the USB-IF). + uint8_t bDeviceProtocol ; ///< Protocol code (assigned by the USB-IF). uint8_t bMaxPacketSize0 ; ///< Maximum packet size for endpoint zero (only 8, 16, 32, or 64 are valid). For HS devices is fixed to 64. uint16_t idVendor ; ///< Vendor ID (assigned by the USB-IF). @@ -314,8 +290,7 @@ typedef struct TU_ATTR_PACKED TU_VERIFY_STATIC( sizeof(tusb_desc_device_t) == 18, "size is not correct"); // USB Binary Device Object Store (BOS) Descriptor -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of this descriptor in bytes uint8_t bDescriptorType ; ///< CONFIGURATION Descriptor Type uint16_t wTotalLength ; ///< Total length of data returned for this descriptor @@ -325,8 +300,7 @@ typedef struct TU_ATTR_PACKED TU_VERIFY_STATIC( sizeof(tusb_desc_bos_t) == 5, "size is not correct"); /// USB Configuration Descriptor -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of this descriptor in bytes uint8_t bDescriptorType ; ///< CONFIGURATION Descriptor Type uint16_t wTotalLength ; ///< Total length of data returned for this configuration. Includes the combined length of all descriptors (configuration, interface, endpoint, and class- or vendor-specific) returned for this configuration. @@ -341,8 +315,7 @@ typedef struct TU_ATTR_PACKED TU_VERIFY_STATIC( sizeof(tusb_desc_configuration_t) == 9, "size is not correct"); /// USB Interface Descriptor -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of this descriptor in bytes uint8_t bDescriptorType ; ///< INTERFACE Descriptor Type @@ -358,8 +331,7 @@ typedef struct TU_ATTR_PACKED TU_VERIFY_STATIC( sizeof(tusb_desc_interface_t) == 9, "size is not correct"); /// USB Endpoint Descriptor -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; // Size of this descriptor in bytes uint8_t bDescriptorType ; // ENDPOINT Descriptor Type @@ -379,8 +351,7 @@ typedef struct TU_ATTR_PACKED TU_VERIFY_STATIC( sizeof(tusb_desc_endpoint_t) == 7, "size is not correct"); /// USB Other Speed Configuration Descriptor -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of descriptor uint8_t bDescriptorType ; ///< Other_speed_Configuration Type uint16_t wTotalLength ; ///< Total length of data returned @@ -393,8 +364,7 @@ typedef struct TU_ATTR_PACKED } tusb_desc_other_speed_t; /// USB Device Qualifier Descriptor -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of descriptor uint8_t bDescriptorType ; ///< Device Qualifier Type uint16_t bcdUSB ; ///< USB specification version number (e.g., 0200H for V2.00) @@ -411,8 +381,7 @@ typedef struct TU_ATTR_PACKED TU_VERIFY_STATIC( sizeof(tusb_desc_device_qualifier_t) == 10, "size is not correct"); /// USB Interface Association Descriptor (IAD ECN) -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of descriptor uint8_t bDescriptorType ; ///< Other_speed_Configuration Type @@ -426,17 +395,17 @@ typedef struct TU_ATTR_PACKED uint8_t iFunction ; ///< Index of the string descriptor describing the interface association. } tusb_desc_interface_assoc_t; +TU_VERIFY_STATIC( sizeof(tusb_desc_interface_assoc_t) == 8, "size is not correct"); + // USB String Descriptor -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength ; ///< Size of this descriptor in bytes uint8_t bDescriptorType ; ///< Descriptor Type uint16_t unicode_string[]; } tusb_desc_string_t; // USB Binary Device Object Store (BOS) -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength; uint8_t bDescriptorType ; uint8_t bDevCapabilityType; @@ -445,9 +414,8 @@ typedef struct TU_ATTR_PACKED uint8_t CapabilityData[]; } tusb_desc_bos_platform_t; -// USB WebuSB URL Descriptor -typedef struct TU_ATTR_PACKED -{ +// USB WebUSB URL Descriptor +typedef struct TU_ATTR_PACKED { uint8_t bLength; uint8_t bDescriptorType; uint8_t bScheme; @@ -455,8 +423,7 @@ typedef struct TU_ATTR_PACKED } tusb_desc_webusb_url_t; // DFU Functional Descriptor -typedef struct TU_ATTR_PACKED -{ +typedef struct TU_ATTR_PACKED { uint8_t bLength; uint8_t bDescriptorType; @@ -481,7 +448,7 @@ typedef struct TU_ATTR_PACKED // //--------------------------------------------------------------------+ -typedef struct TU_ATTR_PACKED{ +typedef struct TU_ATTR_PACKED { union { struct TU_ATTR_PACKED { uint8_t recipient : 5; ///< Recipient type tusb_request_recipient_t. @@ -500,7 +467,6 @@ typedef struct TU_ATTR_PACKED{ TU_VERIFY_STATIC( sizeof(tusb_control_request_t) == 8, "size is not correct"); - TU_ATTR_PACKED_END // End of all packed definitions TU_ATTR_BIT_FIELD_ORDER_END @@ -509,36 +475,25 @@ TU_ATTR_BIT_FIELD_ORDER_END //--------------------------------------------------------------------+ // Get direction from Endpoint address -TU_ATTR_ALWAYS_INLINE static inline tusb_dir_t tu_edpt_dir(uint8_t addr) -{ +TU_ATTR_ALWAYS_INLINE static inline tusb_dir_t tu_edpt_dir(uint8_t addr) { return (addr & TUSB_DIR_IN_MASK) ? TUSB_DIR_IN : TUSB_DIR_OUT; } // Get Endpoint number from address -TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_edpt_number(uint8_t addr) -{ +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_edpt_number(uint8_t addr) { return (uint8_t)(addr & (~TUSB_DIR_IN_MASK)); } -TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_edpt_addr(uint8_t num, uint8_t dir) -{ +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_edpt_addr(uint8_t num, uint8_t dir) { return (uint8_t)(num | (dir ? TUSB_DIR_IN_MASK : 0)); } -TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_edpt_packet_size(tusb_desc_endpoint_t const* desc_ep) -{ - return tu_le16toh(desc_ep->wMaxPacketSize) & TU_GENMASK(10, 0); +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_edpt_packet_size(tusb_desc_endpoint_t const* desc_ep) { + return tu_le16toh(desc_ep->wMaxPacketSize) & 0x7FF; } #if CFG_TUSB_DEBUG -TU_ATTR_ALWAYS_INLINE static inline const char *tu_edpt_dir_str(tusb_dir_t dir) -{ - tu_static const char *str[] = {"out", "in"}; - return str[dir]; -} - -TU_ATTR_ALWAYS_INLINE static inline const char *tu_edpt_type_str(tusb_xfer_type_t t) -{ +TU_ATTR_ALWAYS_INLINE static inline const char *tu_edpt_type_str(tusb_xfer_type_t t) { tu_static const char *str[] = {"control", "isochronous", "bulk", "interrupt"}; return str[t]; } @@ -549,21 +504,18 @@ TU_ATTR_ALWAYS_INLINE static inline const char *tu_edpt_type_str(tusb_xfer_type_ //--------------------------------------------------------------------+ // return next descriptor -TU_ATTR_ALWAYS_INLINE static inline uint8_t const * tu_desc_next(void const* desc) -{ +TU_ATTR_ALWAYS_INLINE static inline uint8_t const * tu_desc_next(void const* desc) { uint8_t const* desc8 = (uint8_t const*) desc; return desc8 + desc8[DESC_OFFSET_LEN]; } // get descriptor type -TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_desc_type(void const* desc) -{ +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_desc_type(void const* desc) { return ((uint8_t const*) desc)[DESC_OFFSET_TYPE]; } // get descriptor length -TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_desc_len(void const* desc) -{ +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_desc_len(void const* desc) { return ((uint8_t const*) desc)[DESC_OFFSET_LEN]; } @@ -580,6 +532,4 @@ uint8_t const * tu_desc_find3(uint8_t const* desc, uint8_t const* end, uint8_t b } #endif -#endif /* _TUSB_TYPES_H_ */ - -/** @} */ +#endif // TUSB_TYPES_H_ diff --git a/pico-sdk/lib/tinyusb/src/common/tusb_verify.h b/pico-sdk/lib/tinyusb/src/common/tusb_verify.h index 8aa66b4..dde0550 100644 --- a/pico-sdk/lib/tinyusb/src/common/tusb_verify.h +++ b/pico-sdk/lib/tinyusb/src/common/tusb_verify.h @@ -76,14 +76,14 @@ #endif // Halt CPU (breakpoint) when hitting error, only apply for Cortex M3, M4, M7, M33. M55 -#if defined(__ARM_ARCH_7M__) || defined (__ARM_ARCH_7EM__) || defined(__ARM_ARCH_8M_MAIN__) || defined(__ARM_ARCH_8_1M_MAIN__) - #define TU_BREAKPOINT() do \ - { \ +#if defined(__ARM_ARCH_7M__) || defined (__ARM_ARCH_7EM__) || defined(__ARM_ARCH_8M_MAIN__) || defined(__ARM_ARCH_8_1M_MAIN__) || \ + defined(__ARM7M__) || defined (__ARM7EM__) || defined(__ARM8M_MAINLINE__) || defined(__ARM8EM_MAINLINE__) + #define TU_BREAKPOINT() do { \ volatile uint32_t* ARM_CM_DHCSR = ((volatile uint32_t*) 0xE000EDF0UL); /* Cortex M CoreDebug->DHCSR */ \ if ( (*ARM_CM_DHCSR) & 1UL ) __asm("BKPT #0\n"); /* Only halt mcu if debugger is attached */ \ } while(0) -#elif defined(__riscv) +#elif defined(__riscv) && !TUP_MCU_ESPRESSIF #define TU_BREAKPOINT() do { __asm("ebreak\n"); } while(0) #elif defined(_mips) diff --git a/pico-sdk/lib/tinyusb/src/device/dcd.h b/pico-sdk/lib/tinyusb/src/device/dcd.h index 69c26bc..9447d6d 100644 --- a/pico-sdk/lib/tinyusb/src/device/dcd.h +++ b/pico-sdk/lib/tinyusb/src/device/dcd.h @@ -97,6 +97,14 @@ typedef struct TU_ATTR_ALIGNED(4) { }; } dcd_event_t; +typedef enum { + TEST_J = 1, + TEST_K, + TEST_SE0_NAK, + TEST_PACKET, + TEST_FORCE_ENABLE, +} test_mode_t; + //TU_VERIFY_STATIC(sizeof(dcd_event_t) <= 12, "size is not correct"); //--------------------------------------------------------------------+ @@ -122,6 +130,9 @@ void dcd_dcache_clean_invalidate(void const* addr, uint32_t data_size) TU_ATTR_W // Initialize controller to device mode void dcd_init(uint8_t rhport); +// Deinitialize controller, unset device mode. +bool dcd_deinit(uint8_t rhport); + // Interrupt Handler void dcd_int_handler(uint8_t rhport); @@ -146,6 +157,13 @@ void dcd_disconnect(uint8_t rhport) TU_ATTR_WEAK; // Enable/Disable Start-of-frame interrupt. Default is disabled void dcd_sof_enable(uint8_t rhport, bool en); +#if CFG_TUD_TEST_MODE +// Check if the test mode is supported, returns true is test mode selector is supported +bool dcd_check_test_mode_support(test_mode_t test_selector) TU_ATTR_WEAK; + +// Put device into a test mode (needs power cycle to quit) +void dcd_enter_test_mode(uint8_t rhport, test_mode_t test_selector) TU_ATTR_WEAK; +#endif //--------------------------------------------------------------------+ // Endpoint API //--------------------------------------------------------------------+ diff --git a/pico-sdk/lib/tinyusb/src/device/usbd.c b/pico-sdk/lib/tinyusb/src/device/usbd.c index 5c94ebc..83100ef 100644 --- a/pico-sdk/lib/tinyusb/src/device/usbd.c +++ b/pico-sdk/lib/tinyusb/src/device/usbd.c @@ -39,18 +39,27 @@ // USBD Configuration //--------------------------------------------------------------------+ #ifndef CFG_TUD_TASK_QUEUE_SZ - #define CFG_TUD_TASK_QUEUE_SZ 16 + #define CFG_TUD_TASK_QUEUE_SZ 32 #endif //--------------------------------------------------------------------+ -// Callback weak stubs (called if application does not provide) +// Weak stubs: invoked if no strong implementation is available //--------------------------------------------------------------------+ +TU_ATTR_WEAK bool dcd_deinit(uint8_t rhport) { + (void) rhport; + return false; +} + TU_ATTR_WEAK void tud_event_hook_cb(uint8_t rhport, uint32_t eventid, bool in_isr) { (void)rhport; (void)eventid; (void)in_isr; } +TU_ATTR_WEAK void tud_sof_cb(uint32_t frame_count) { + (void)frame_count; +} + //--------------------------------------------------------------------+ // Device Data //--------------------------------------------------------------------+ @@ -68,9 +77,10 @@ typedef struct { uint8_t remote_wakeup_support : 1; // configuration descriptor's attribute uint8_t self_powered : 1; // configuration descriptor's attribute }; - volatile uint8_t cfg_num; // current active configuration (0x00 is not configured) uint8_t speed; + volatile uint8_t setup_count; + volatile uint8_t sof_consumer; uint8_t itf2drv[CFG_TUD_INTERFACE_MAX]; // map interface number to driver (0xff is invalid) uint8_t ep2drv[CFG_TUD_ENDPPOINT_MAX][2]; // map endpoint to driver ( 0xff is invalid ), can use only 4-bit each @@ -85,17 +95,18 @@ tu_static usbd_device_t _usbd_dev; // Class Driver //--------------------------------------------------------------------+ #if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL - #define DRIVER_NAME(_name) .name = _name, + #define DRIVER_NAME(_name) _name #else - #define DRIVER_NAME(_name) + #define DRIVER_NAME(_name) NULL #endif // Built-in class drivers tu_static usbd_class_driver_t const _usbd_driver[] = { #if CFG_TUD_CDC { - DRIVER_NAME("CDC") + .name = DRIVER_NAME("CDC"), .init = cdcd_init, + .deinit = cdcd_deinit, .reset = cdcd_reset, .open = cdcd_open, .control_xfer_cb = cdcd_control_xfer_cb, @@ -106,8 +117,9 @@ tu_static usbd_class_driver_t const _usbd_driver[] = { #if CFG_TUD_MSC { - DRIVER_NAME("MSC") + .name = DRIVER_NAME("MSC"), .init = mscd_init, + .deinit = NULL, .reset = mscd_reset, .open = mscd_open, .control_xfer_cb = mscd_control_xfer_cb, @@ -118,121 +130,131 @@ tu_static usbd_class_driver_t const _usbd_driver[] = { #if CFG_TUD_HID { - DRIVER_NAME("HID") - .init = hidd_init, - .reset = hidd_reset, - .open = hidd_open, - .control_xfer_cb = hidd_control_xfer_cb, - .xfer_cb = hidd_xfer_cb, - .sof = NULL + .name = DRIVER_NAME("HID"), + .init = hidd_init, + .deinit = hidd_deinit, + .reset = hidd_reset, + .open = hidd_open, + .control_xfer_cb = hidd_control_xfer_cb, + .xfer_cb = hidd_xfer_cb, + .sof = NULL }, #endif #if CFG_TUD_AUDIO { - DRIVER_NAME("AUDIO") - .init = audiod_init, - .reset = audiod_reset, - .open = audiod_open, - .control_xfer_cb = audiod_control_xfer_cb, - .xfer_cb = audiod_xfer_cb, - .sof = audiod_sof_isr + .name = DRIVER_NAME("AUDIO"), + .init = audiod_init, + .deinit = audiod_deinit, + .reset = audiod_reset, + .open = audiod_open, + .control_xfer_cb = audiod_control_xfer_cb, + .xfer_cb = audiod_xfer_cb, + .sof = audiod_sof_isr }, #endif #if CFG_TUD_VIDEO { - DRIVER_NAME("VIDEO") - .init = videod_init, - .reset = videod_reset, - .open = videod_open, - .control_xfer_cb = videod_control_xfer_cb, - .xfer_cb = videod_xfer_cb, - .sof = NULL + .name = DRIVER_NAME("VIDEO"), + .init = videod_init, + .deinit = videod_deinit, + .reset = videod_reset, + .open = videod_open, + .control_xfer_cb = videod_control_xfer_cb, + .xfer_cb = videod_xfer_cb, + .sof = NULL }, #endif #if CFG_TUD_MIDI { - DRIVER_NAME("MIDI") - .init = midid_init, - .open = midid_open, - .reset = midid_reset, - .control_xfer_cb = midid_control_xfer_cb, - .xfer_cb = midid_xfer_cb, - .sof = NULL + .name = DRIVER_NAME("MIDI"), + .init = midid_init, + .deinit = midid_deinit, + .open = midid_open, + .reset = midid_reset, + .control_xfer_cb = midid_control_xfer_cb, + .xfer_cb = midid_xfer_cb, + .sof = NULL }, #endif #if CFG_TUD_VENDOR { - DRIVER_NAME("VENDOR") - .init = vendord_init, - .reset = vendord_reset, - .open = vendord_open, - .control_xfer_cb = tud_vendor_control_xfer_cb, - .xfer_cb = vendord_xfer_cb, - .sof = NULL + .name = DRIVER_NAME("VENDOR"), + .init = vendord_init, + .deinit = vendord_deinit, + .reset = vendord_reset, + .open = vendord_open, + .control_xfer_cb = tud_vendor_control_xfer_cb, + .xfer_cb = vendord_xfer_cb, + .sof = NULL }, #endif #if CFG_TUD_USBTMC { - DRIVER_NAME("TMC") - .init = usbtmcd_init_cb, - .reset = usbtmcd_reset_cb, - .open = usbtmcd_open_cb, - .control_xfer_cb = usbtmcd_control_xfer_cb, - .xfer_cb = usbtmcd_xfer_cb, - .sof = NULL + .name = DRIVER_NAME("TMC"), + .init = usbtmcd_init_cb, + .deinit = usbtmcd_deinit, + .reset = usbtmcd_reset_cb, + .open = usbtmcd_open_cb, + .control_xfer_cb = usbtmcd_control_xfer_cb, + .xfer_cb = usbtmcd_xfer_cb, + .sof = NULL }, #endif #if CFG_TUD_DFU_RUNTIME { - DRIVER_NAME("DFU-RUNTIME") - .init = dfu_rtd_init, - .reset = dfu_rtd_reset, - .open = dfu_rtd_open, - .control_xfer_cb = dfu_rtd_control_xfer_cb, - .xfer_cb = NULL, - .sof = NULL + .name = DRIVER_NAME("DFU-RUNTIME"), + .init = dfu_rtd_init, + .deinit = dfu_rtd_deinit, + .reset = dfu_rtd_reset, + .open = dfu_rtd_open, + .control_xfer_cb = dfu_rtd_control_xfer_cb, + .xfer_cb = NULL, + .sof = NULL }, #endif #if CFG_TUD_DFU { - DRIVER_NAME("DFU") - .init = dfu_moded_init, - .reset = dfu_moded_reset, - .open = dfu_moded_open, - .control_xfer_cb = dfu_moded_control_xfer_cb, - .xfer_cb = NULL, - .sof = NULL + .name = DRIVER_NAME("DFU"), + .init = dfu_moded_init, + .deinit = dfu_moded_deinit, + .reset = dfu_moded_reset, + .open = dfu_moded_open, + .control_xfer_cb = dfu_moded_control_xfer_cb, + .xfer_cb = NULL, + .sof = NULL }, #endif #if CFG_TUD_ECM_RNDIS || CFG_TUD_NCM { - DRIVER_NAME("NET") - .init = netd_init, - .reset = netd_reset, - .open = netd_open, - .control_xfer_cb = netd_control_xfer_cb, - .xfer_cb = netd_xfer_cb, - .sof = NULL, + .name = DRIVER_NAME("NET"), + .init = netd_init, + .deinit = netd_deinit, + .reset = netd_reset, + .open = netd_open, + .control_xfer_cb = netd_control_xfer_cb, + .xfer_cb = netd_xfer_cb, + .sof = NULL, }, #endif #if CFG_TUD_BTH { - DRIVER_NAME("BTH") - .init = btd_init, - .reset = btd_reset, - .open = btd_open, - .control_xfer_cb = btd_control_xfer_cb, - .xfer_cb = btd_xfer_cb, - .sof = NULL + .name = DRIVER_NAME("BTH"), + .init = btd_init, + .deinit = btd_deinit, + .reset = btd_reset, + .open = btd_open, + .control_xfer_cb = btd_control_xfer_cb, + .xfer_cb = btd_xfer_cb, + .sof = NULL }, #endif }; @@ -258,6 +280,7 @@ TU_ATTR_ALWAYS_INLINE static inline usbd_class_driver_t const * get_driver(uint8 return driver; } + //--------------------------------------------------------------------+ // DCD Event //--------------------------------------------------------------------+ @@ -279,9 +302,9 @@ tu_static osal_queue_t _usbd_q; #endif TU_ATTR_ALWAYS_INLINE static inline bool queue_event(dcd_event_t const * event, bool in_isr) { - bool ret = osal_queue_send(_usbd_q, event, in_isr); + TU_ASSERT(osal_queue_send(_usbd_q, event, in_isr)); tud_event_hook_cb(event->rhport, event->event_id, in_isr); - return ret; + return true; } //--------------------------------------------------------------------+ @@ -290,7 +313,9 @@ TU_ATTR_ALWAYS_INLINE static inline bool queue_event(dcd_event_t const * event, static bool process_control_request(uint8_t rhport, tusb_control_request_t const * p_request); static bool process_set_config(uint8_t rhport, uint8_t cfg_num); static bool process_get_descriptor(uint8_t rhport, tusb_control_request_t const * p_request); - +#if CFG_TUD_TEST_MODE +static bool process_test_mode_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); +#endif // from usbd_control.c void usbd_control_reset(void); void usbd_control_set_request(tusb_control_request_t const *request); @@ -319,7 +344,7 @@ void usbd_driver_print_control_complete_name(usbd_control_xfer_cb_t callback) { for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++) { usbd_class_driver_t const* driver = get_driver(i); if (driver && driver->control_xfer_cb == callback) { - TU_LOG_USBD(" %s control complete\r\n", driver->name); + TU_LOG_USBD("%s control complete\r\n", driver->name); return; } } @@ -365,6 +390,12 @@ bool tud_connect(void) { return true; } +bool tud_sof_cb_enable(bool en) +{ + usbd_sof_enable(_usbd_rhport, SOF_CONSUMER_USER, en); + return true; +} + //--------------------------------------------------------------------+ // USBD Task //--------------------------------------------------------------------+ @@ -372,13 +403,13 @@ bool tud_inited(void) { return _usbd_rhport != RHPORT_INVALID; } -bool tud_init (uint8_t rhport) -{ +bool tud_init(uint8_t rhport) { // skip if already initialized - if ( tud_inited() ) return true; + if (tud_inited()) return true; TU_LOG_USBD("USBD init on controller %u\r\n", rhport); TU_LOG_INT(CFG_TUD_LOG_LEVEL, sizeof(usbd_device_t)); + TU_LOG_INT(CFG_TUD_LOG_LEVEL, sizeof(dcd_event_t)); TU_LOG_INT(CFG_TUD_LOG_LEVEL, sizeof(tu_fifo_t)); TU_LOG_INT(CFG_TUD_LOG_LEVEL, sizeof(tu_edpt_stream_t)); @@ -395,16 +426,14 @@ bool tud_init (uint8_t rhport) TU_ASSERT(_usbd_q); // Get application driver if available - if ( usbd_app_driver_get_cb ) - { + if (usbd_app_driver_get_cb) { _app_driver = usbd_app_driver_get_cb(&_app_driver_count); } // Init class drivers - for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++) - { - usbd_class_driver_t const * driver = get_driver(i); - TU_ASSERT(driver); + for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++) { + usbd_class_driver_t const* driver = get_driver(i); + TU_ASSERT(driver && driver->init); TU_LOG_USBD("%s init\r\n", driver->name); driver->init(); } @@ -418,31 +447,61 @@ bool tud_init (uint8_t rhport) return true; } -static void configuration_reset(uint8_t rhport) -{ - for ( uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++ ) - { - usbd_class_driver_t const * driver = get_driver(i); - TU_ASSERT(driver, ); +bool tud_deinit(uint8_t rhport) { + // skip if not initialized + if (!tud_inited()) return true; + + TU_LOG_USBD("USBD deinit on controller %u\r\n", rhport); + + // Deinit device controller driver + dcd_int_disable(rhport); + dcd_disconnect(rhport); + dcd_deinit(rhport); + + // Deinit class drivers + for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++) { + usbd_class_driver_t const* driver = get_driver(i); + if(driver && driver->deinit) { + TU_LOG_USBD("%s deinit\r\n", driver->name); + driver->deinit(); + } + } + + // Deinit device queue & task + osal_queue_delete(_usbd_q); + _usbd_q = NULL; + +#if OSAL_MUTEX_REQUIRED + // TODO make sure there is no task waiting on this mutex + osal_mutex_delete(_usbd_mutex); + _usbd_mutex = NULL; +#endif + + _usbd_rhport = RHPORT_INVALID; + + return true; +} + +static void configuration_reset(uint8_t rhport) { + for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++) { + usbd_class_driver_t const* driver = get_driver(i); + TU_ASSERT(driver,); driver->reset(rhport); } tu_varclr(&_usbd_dev); memset(_usbd_dev.itf2drv, DRVID_INVALID, sizeof(_usbd_dev.itf2drv)); // invalid mapping - memset(_usbd_dev.ep2drv , DRVID_INVALID, sizeof(_usbd_dev.ep2drv )); // invalid mapping + memset(_usbd_dev.ep2drv, DRVID_INVALID, sizeof(_usbd_dev.ep2drv)); // invalid mapping } -static void usbd_reset(uint8_t rhport) -{ +static void usbd_reset(uint8_t rhport) { configuration_reset(rhport); usbd_control_reset(); } -bool tud_task_event_ready(void) -{ +bool tud_task_event_ready(void) { // Skip if stack is not initialized - if ( !tud_inited() ) return false; - + if (!tud_inited()) return false; return !osal_queue_empty(_usbd_q); } @@ -450,57 +509,52 @@ bool tud_task_event_ready(void) * This top level thread manages all device controller event and delegates events to class-specific drivers. * This should be called periodically within the mainloop or rtos thread. * - @code - int main(void) - { + int main(void) { application_init(); tusb_init(); - while(1) // the mainloop - { + while(1) { // the mainloop application_code(); tud_task(); // tinyusb device task } } - @endcode */ -void tud_task_ext(uint32_t timeout_ms, bool in_isr) -{ +void tud_task_ext(uint32_t timeout_ms, bool in_isr) { (void) in_isr; // not implemented yet // Skip if stack is not initialized - if ( !tud_inited() ) return; + if (!tud_inited()) return; // Loop until there is no more events in the queue - while (1) - { + while (1) { dcd_event_t event; - if ( !osal_queue_receive(_usbd_q, &event, timeout_ms) ) return; + if (!osal_queue_receive(_usbd_q, &event, timeout_ms)) return; #if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL if (event.event_id == DCD_EVENT_SETUP_RECEIVED) TU_LOG_USBD("\r\n"); // extra line for setup TU_LOG_USBD("USBD %s ", event.event_id < DCD_EVENT_COUNT ? _usbd_event_str[event.event_id] : "CORRUPTED"); #endif - switch ( event.event_id ) - { + switch (event.event_id) { case DCD_EVENT_BUS_RESET: TU_LOG_USBD(": %s Speed\r\n", tu_str_speed[event.bus_reset.speed]); usbd_reset(event.rhport); _usbd_dev.speed = event.bus_reset.speed; - break; + break; case DCD_EVENT_UNPLUGGED: TU_LOG_USBD("\r\n"); usbd_reset(event.rhport); - - // invoke callback if (tud_umount_cb) tud_umount_cb(); - break; + break; case DCD_EVENT_SETUP_RECEIVED: + _usbd_dev.setup_count--; TU_LOG_BUF(CFG_TUD_LOG_LEVEL, &event.setup_received, 8); - TU_LOG_USBD("\r\n"); + if (_usbd_dev.setup_count) { + TU_LOG_USBD(" Skipped since there is other SETUP in queue\r\n"); + break; + } // Mark as connected after receiving 1st setup packet. // But it is easier to set it every time instead of wasting time to check then set @@ -509,87 +563,82 @@ void tud_task_ext(uint32_t timeout_ms, bool in_isr) // mark both in & out control as free _usbd_dev.ep_status[0][TUSB_DIR_OUT].busy = 0; _usbd_dev.ep_status[0][TUSB_DIR_OUT].claimed = 0; - _usbd_dev.ep_status[0][TUSB_DIR_IN ].busy = 0; - _usbd_dev.ep_status[0][TUSB_DIR_IN ].claimed = 0; + _usbd_dev.ep_status[0][TUSB_DIR_IN].busy = 0; + _usbd_dev.ep_status[0][TUSB_DIR_IN].claimed = 0; // Process control request - if ( !process_control_request(event.rhport, &event.setup_received) ) - { + if (!process_control_request(event.rhport, &event.setup_received)) { TU_LOG_USBD(" Stall EP0\r\n"); // Failed -> stall both control endpoint IN and OUT dcd_edpt_stall(event.rhport, 0); dcd_edpt_stall(event.rhport, 0 | TUSB_DIR_IN_MASK); } - break; + break; - case DCD_EVENT_XFER_COMPLETE: - { + case DCD_EVENT_XFER_COMPLETE: { // Invoke the class callback associated with the endpoint address uint8_t const ep_addr = event.xfer_complete.ep_addr; - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const ep_dir = tu_edpt_dir(ep_addr); + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const ep_dir = tu_edpt_dir(ep_addr); TU_LOG_USBD("on EP %02X with %u bytes\r\n", ep_addr, (unsigned int) event.xfer_complete.len); _usbd_dev.ep_status[epnum][ep_dir].busy = 0; _usbd_dev.ep_status[epnum][ep_dir].claimed = 0; - if ( 0 == epnum ) - { - usbd_control_xfer_cb(event.rhport, ep_addr, (xfer_result_t) event.xfer_complete.result, event.xfer_complete - .len); - } - else - { - usbd_class_driver_t const * driver = get_driver( _usbd_dev.ep2drv[epnum][ep_dir] ); - TU_ASSERT(driver, ); + if (0 == epnum) { + usbd_control_xfer_cb(event.rhport, ep_addr, (xfer_result_t) event.xfer_complete.result, + event.xfer_complete.len); + } else { + usbd_class_driver_t const* driver = get_driver(_usbd_dev.ep2drv[epnum][ep_dir]); + TU_ASSERT(driver,); TU_LOG_USBD(" %s xfer callback\r\n", driver->name); driver->xfer_cb(event.rhport, ep_addr, (xfer_result_t) event.xfer_complete.result, event.xfer_complete.len); } + break; } - break; case DCD_EVENT_SUSPEND: // NOTE: When plugging/unplugging device, the D+/D- state are unstable and // can accidentally meet the SUSPEND condition ( Bus Idle for 3ms ), which result in a series of event // e.g suspend -> resume -> unplug/plug. Skip suspend/resume if not connected - if ( _usbd_dev.connected ) - { + if (_usbd_dev.connected) { TU_LOG_USBD(": Remote Wakeup = %u\r\n", _usbd_dev.remote_wakeup_en); if (tud_suspend_cb) tud_suspend_cb(_usbd_dev.remote_wakeup_en); - }else - { + } else { TU_LOG_USBD(" Skipped\r\n"); } - break; + break; case DCD_EVENT_RESUME: - if ( _usbd_dev.connected ) - { + if (_usbd_dev.connected) { TU_LOG_USBD("\r\n"); if (tud_resume_cb) tud_resume_cb(); - }else - { + } else { TU_LOG_USBD(" Skipped\r\n"); } - break; + break; case USBD_EVENT_FUNC_CALL: TU_LOG_USBD("\r\n"); - if ( event.func_call.func ) event.func_call.func(event.func_call.param); - break; + if (event.func_call.func) event.func_call.func(event.func_call.param); + break; case DCD_EVENT_SOF: + if (tu_bit_test(_usbd_dev.sof_consumer, SOF_CONSUMER_USER)) { + TU_LOG_USBD("\r\n"); + tud_sof_cb(event.sof.frame_count); + } + break; + default: TU_BREAKPOINT(); - break; + break; } -#if CFG_TUSB_OS != OPT_OS_NONE && CFG_TUSB_OS != OPT_OS_PICO // return if there is no more events, for application to run other background if (osal_queue_empty(_usbd_q)) return; -#endif } } @@ -598,24 +647,20 @@ void tud_task_ext(uint32_t timeout_ms, bool in_isr) //--------------------------------------------------------------------+ // Helper to invoke class driver control request handler -static bool invoke_class_control(uint8_t rhport, usbd_class_driver_t const * driver, tusb_control_request_t const * request) -{ +static bool invoke_class_control(uint8_t rhport, usbd_class_driver_t const * driver, tusb_control_request_t const * request) { usbd_control_set_complete_callback(driver->control_xfer_cb); TU_LOG_USBD(" %s control request\r\n", driver->name); return driver->control_xfer_cb(rhport, CONTROL_STAGE_SETUP, request); } // This handles the actual request and its response. -// return false will cause its caller to stall control endpoint -static bool process_control_request(uint8_t rhport, tusb_control_request_t const * p_request) -{ +// Returns false if unable to complete the request, causing caller to stall control endpoints. +static bool process_control_request(uint8_t rhport, tusb_control_request_t const * p_request) { usbd_control_set_complete_callback(NULL); - TU_ASSERT(p_request->bmRequestType_bit.type < TUSB_REQ_TYPE_INVALID); // Vendor request - if ( p_request->bmRequestType_bit.type == TUSB_REQ_TYPE_VENDOR ) - { + if ( p_request->bmRequestType_bit.type == TUSB_REQ_TYPE_VENDOR ) { TU_VERIFY(tud_vendor_control_xfer_cb); usbd_control_set_complete_callback(tud_vendor_control_xfer_cb); @@ -623,19 +668,16 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const } #if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL - if (TUSB_REQ_TYPE_STANDARD == p_request->bmRequestType_bit.type && p_request->bRequest <= TUSB_REQ_SYNCH_FRAME) - { + if (TUSB_REQ_TYPE_STANDARD == p_request->bmRequestType_bit.type && p_request->bRequest <= TUSB_REQ_SYNCH_FRAME) { TU_LOG_USBD(" %s", tu_str_std_request[p_request->bRequest]); if (TUSB_REQ_GET_DESCRIPTOR != p_request->bRequest) TU_LOG_USBD("\r\n"); } #endif - switch ( p_request->bmRequestType_bit.recipient ) - { + switch ( p_request->bmRequestType_bit.recipient ) { //------------- Device Requests e.g in enumeration -------------// case TUSB_REQ_RCPT_DEVICE: - if ( TUSB_REQ_TYPE_CLASS == p_request->bmRequestType_bit.type ) - { + if ( TUSB_REQ_TYPE_CLASS == p_request->bmRequestType_bit.type ) { uint8_t const itf = tu_u16_low(p_request->wIndex); TU_VERIFY(itf < TU_ARRAY_SIZE(_usbd_dev.itf2drv)); @@ -646,15 +688,13 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const return invoke_class_control(rhport, driver, p_request); } - if ( TUSB_REQ_TYPE_STANDARD != p_request->bmRequestType_bit.type ) - { + if ( TUSB_REQ_TYPE_STANDARD != p_request->bmRequestType_bit.type ) { // Non standard request is not supported TU_BREAKPOINT(); return false; } - switch ( p_request->bRequest ) - { + switch ( p_request->bRequest ) { case TUSB_REQ_SET_ADDRESS: // Depending on mcu, status phase could be sent either before or after changing device address, // or even require stack to not response with status at all @@ -665,25 +705,24 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const _usbd_dev.addressed = 1; break; - case TUSB_REQ_GET_CONFIGURATION: - { + case TUSB_REQ_GET_CONFIGURATION: { uint8_t cfg_num = _usbd_dev.cfg_num; tud_control_xfer(rhport, p_request, &cfg_num, 1); } break; - case TUSB_REQ_SET_CONFIGURATION: - { + case TUSB_REQ_SET_CONFIGURATION: { uint8_t const cfg_num = (uint8_t) p_request->wValue; // Only process if new configure is different - if (_usbd_dev.cfg_num != cfg_num) - { - if ( _usbd_dev.cfg_num ) - { + if (_usbd_dev.cfg_num != cfg_num) { + if ( _usbd_dev.cfg_num ) { // already configured: need to clear all endpoints and driver first TU_LOG_USBD(" Clear current Configuration (%u) before switching\r\n", _usbd_dev.cfg_num); + // disable SOF + dcd_sof_enable(rhport, false); + // close all non-control endpoints, cancel all pending transfers if any dcd_edpt_close_all(rhport); @@ -695,15 +734,11 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const } // Handle the new configuration and execute the corresponding callback - if ( cfg_num ) - { + if ( cfg_num ) { // switch to new configuration if not zero TU_ASSERT( process_set_config(rhport, cfg_num) ); - if ( tud_mount_cb ) tud_mount_cb(); - } - else - { + } else { if ( tud_umount_cb ) tud_umount_cb(); } } @@ -718,14 +753,47 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const break; case TUSB_REQ_SET_FEATURE: - // Only support remote wakeup for device feature - TU_VERIFY(TUSB_REQ_FEATURE_REMOTE_WAKEUP == p_request->wValue); + // Handle the feature selector + switch(p_request->wValue) + { + // Support for remote wakeup + case TUSB_REQ_FEATURE_REMOTE_WAKEUP: + TU_LOG_USBD(" Enable Remote Wakeup\r\n"); - TU_LOG_USBD(" Enable Remote Wakeup\r\n"); + // Host may enable remote wake up before suspending especially HID device + _usbd_dev.remote_wakeup_en = true; + tud_control_status(rhport, p_request); + break; - // Host may enable remote wake up before suspending especially HID device - _usbd_dev.remote_wakeup_en = true; - tud_control_status(rhport, p_request); +#if CFG_TUD_TEST_MODE + // Support for TEST_MODE + case TUSB_REQ_FEATURE_TEST_MODE: { + // Only handle the test mode if supported and valid + TU_VERIFY(dcd_enter_test_mode && dcd_check_test_mode_support && 0 == tu_u16_low(p_request->wIndex)); + + uint8_t selector = tu_u16_high(p_request->wIndex); + + // Stall request if the selected test mode isn't supported + if (!dcd_check_test_mode_support((test_mode_t)selector)) + { + TU_LOG_USBD(" Unsupported Test Mode (test selector index: %d)\r\n", selector); + + return false; + } + + // Acknowledge request + tud_control_status(rhport, p_request); + + TU_LOG_USBD(" Enter Test Mode (test selector index: %d)\r\n", selector); + + usbd_control_set_complete_callback(process_test_mode_cb); + break; + } +#endif /* CFG_TUD_TEST_MODE */ + + // Stall unsupported feature selector + default: return false; + } break; case TUSB_REQ_CLEAR_FEATURE: @@ -739,15 +807,14 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const tud_control_status(rhport, p_request); break; - case TUSB_REQ_GET_STATUS: - { + case TUSB_REQ_GET_STATUS: { // Device status bit mask // - Bit 0: Self Powered // - Bit 1: Remote Wakeup enabled uint16_t status = (uint16_t) ((_usbd_dev.self_powered ? 1u : 0u) | (_usbd_dev.remote_wakeup_en ? 2u : 0u)); tud_control_xfer(rhport, p_request, &status, 2); + break; } - break; // Unknown/Unsupported request default: TU_BREAKPOINT(); return false; @@ -755,8 +822,7 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const break; //------------- Class/Interface Specific Request -------------// - case TUSB_REQ_RCPT_INTERFACE: - { + case TUSB_REQ_RCPT_INTERFACE: { uint8_t const itf = tu_u16_low(p_request->wIndex); TU_VERIFY(itf < TU_ARRAY_SIZE(_usbd_dev.itf2drv)); @@ -765,25 +831,21 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const // all requests to Interface (STD or Class) is forwarded to class driver. // notable requests are: GET HID REPORT DESCRIPTOR, SET_INTERFACE, GET_INTERFACE - if ( !invoke_class_control(rhport, driver, p_request) ) - { + if ( !invoke_class_control(rhport, driver, p_request) ) { // For GET_INTERFACE and SET_INTERFACE, it is mandatory to respond even if the class // driver doesn't use alternate settings or implement this TU_VERIFY(TUSB_REQ_TYPE_STANDARD == p_request->bmRequestType_bit.type); - switch(p_request->bRequest) - { + switch(p_request->bRequest) { case TUSB_REQ_GET_INTERFACE: case TUSB_REQ_SET_INTERFACE: // Clear complete callback if driver set since it can also stall the request. usbd_control_set_complete_callback(NULL); - if (TUSB_REQ_GET_INTERFACE == p_request->bRequest) - { + if (TUSB_REQ_GET_INTERFACE == p_request->bRequest) { uint8_t alternate = 0; tud_control_xfer(rhport, p_request, &alternate, 1); - }else - { + }else { tud_control_status(rhport, p_request); } break; @@ -791,54 +853,42 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const default: return false; } } + break; } - break; //------------- Endpoint Request -------------// - case TUSB_REQ_RCPT_ENDPOINT: - { + case TUSB_REQ_RCPT_ENDPOINT: { uint8_t const ep_addr = tu_u16_low(p_request->wIndex); uint8_t const ep_num = tu_edpt_number(ep_addr); uint8_t const ep_dir = tu_edpt_dir(ep_addr); TU_ASSERT(ep_num < TU_ARRAY_SIZE(_usbd_dev.ep2drv) ); - usbd_class_driver_t const * driver = get_driver(_usbd_dev.ep2drv[ep_num][ep_dir]); - if ( TUSB_REQ_TYPE_STANDARD != p_request->bmRequestType_bit.type ) - { + if ( TUSB_REQ_TYPE_STANDARD != p_request->bmRequestType_bit.type ) { // Forward class request to its driver TU_VERIFY(driver); return invoke_class_control(rhport, driver, p_request); - } - else - { + } else { // Handle STD request to endpoint - switch ( p_request->bRequest ) - { - case TUSB_REQ_GET_STATUS: - { + switch ( p_request->bRequest ) { + case TUSB_REQ_GET_STATUS: { uint16_t status = usbd_edpt_stalled(rhport, ep_addr) ? 0x0001 : 0x0000; tud_control_xfer(rhport, p_request, &status, 2); } break; case TUSB_REQ_CLEAR_FEATURE: - case TUSB_REQ_SET_FEATURE: - { - if ( TUSB_REQ_FEATURE_EDPT_HALT == p_request->wValue ) - { - if ( TUSB_REQ_CLEAR_FEATURE == p_request->bRequest ) - { + case TUSB_REQ_SET_FEATURE: { + if ( TUSB_REQ_FEATURE_EDPT_HALT == p_request->wValue ) { + if ( TUSB_REQ_CLEAR_FEATURE == p_request->bRequest ) { usbd_edpt_clear_stall(rhport, ep_addr); - }else - { + }else { usbd_edpt_stall(rhport, ep_addr); } } - if (driver) - { + if (driver) { // Some classes such as USBTMC needs to clear/re-init its buffer when receiving CLEAR_FEATURE request // We will also forward std request targeted endpoint to class drivers as well @@ -854,14 +904,18 @@ static bool process_control_request(uint8_t rhport, tusb_control_request_t const break; // Unknown/Unsupported request - default: TU_BREAKPOINT(); return false; + default: + TU_BREAKPOINT(); + return false; } } } break; // Unknown recipient - default: TU_BREAKPOINT(); return false; + default: + TU_BREAKPOINT(); + return false; } return true; @@ -1067,6 +1121,20 @@ static bool process_get_descriptor(uint8_t rhport, tusb_control_request_t const } } +#if CFG_TUD_TEST_MODE +static bool process_test_mode_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) +{ + // At this point it should already be ensured that dcd_enter_test_mode() is defined + + // Only enter the test mode after the request for it has completed + TU_VERIFY(CONTROL_STAGE_ACK == stage); + + dcd_enter_test_mode(rhport, (test_mode_t)tu_u16_high(request->wIndex)); + + return true; +} +#endif /* CFG_TUD_TEST_MODE */ + //--------------------------------------------------------------------+ // DCD Event Handler //--------------------------------------------------------------------+ @@ -1101,6 +1169,14 @@ TU_ATTR_FAST_FUNC void dcd_event_handler(dcd_event_t const* event, bool in_isr) break; case DCD_EVENT_SOF: + // SOF driver handler in ISR context + for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++) { + usbd_class_driver_t const* driver = get_driver(i); + if (driver && driver->sof) { + driver->sof(event->rhport, event->sof.frame_count); + } + } + // Some MCUs after running dcd_remote_wakeup() does not have way to detect the end of remote wakeup // which last 1-15 ms. DCD can use SOF as a clear indicator that bus is back to operational if (_usbd_dev.suspended) { @@ -1110,15 +1186,15 @@ TU_ATTR_FAST_FUNC void dcd_event_handler(dcd_event_t const* event, bool in_isr) queue_event(&event_resume, in_isr); } - // SOF driver handler in ISR context - for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++) { - usbd_class_driver_t const* driver = get_driver(i); - if (driver && driver->sof) { - driver->sof(event->rhport, event->sof.frame_count); - } + if (tu_bit_test(_usbd_dev.sof_consumer, SOF_CONSUMER_USER)) { + dcd_event_t const event_sof = {.rhport = event->rhport, .event_id = DCD_EVENT_SOF}; + queue_event(&event_sof, in_isr); } + break; - // skip osal queue for SOF in usbd task + case DCD_EVENT_SETUP_RECEIVED: + _usbd_dev.setup_count++; + send = true; break; default: @@ -1186,8 +1262,7 @@ void usbd_defer_func(osal_task_func_t func, void* param, bool in_isr) { // USBD Endpoint API //--------------------------------------------------------------------+ -bool usbd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * desc_ep) -{ +bool usbd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const* desc_ep) { rhport = _usbd_rhport; TU_ASSERT(tu_edpt_number(desc_ep->bEndpointAddress) < CFG_TUD_ENDPPOINT_MAX); @@ -1196,42 +1271,44 @@ bool usbd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * desc_ep) return dcd_edpt_open(rhport, desc_ep); } -bool usbd_edpt_claim(uint8_t rhport, uint8_t ep_addr) -{ +bool usbd_edpt_claim(uint8_t rhport, uint8_t ep_addr) { (void) rhport; // TODO add this check later, also make sure we don't starve an out endpoint while suspending // TU_VERIFY(tud_ready()); - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); tu_edpt_state_t* ep_state = &_usbd_dev.ep_status[epnum][dir]; return tu_edpt_claim(ep_state, _usbd_mutex); } -bool usbd_edpt_release(uint8_t rhport, uint8_t ep_addr) -{ +bool usbd_edpt_release(uint8_t rhport, uint8_t ep_addr) { (void) rhport; - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); tu_edpt_state_t* ep_state = &_usbd_dev.ep_status[epnum][dir]; return tu_edpt_release(ep_state, _usbd_mutex); } -bool usbd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes) -{ +bool usbd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes) { rhport = _usbd_rhport; uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); // TODO skip ready() check for now since enumeration also use this API // TU_VERIFY(tud_ready()); TU_LOG_USBD(" Queue EP %02X with %u bytes ...\r\n", ep_addr, total_bytes); +#if CFG_TUD_LOG_LEVEL >= 3 + if(dir == TUSB_DIR_IN) { + TU_LOG_MEM(CFG_TUD_LOG_LEVEL, buffer, total_bytes, 2); + } +#endif // Attempt to transfer on a busy endpoint, sound like an race condition ! TU_ASSERT(_usbd_dev.ep_status[epnum][dir].busy == 0); @@ -1240,11 +1317,9 @@ bool usbd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t // could return and USBD task can preempt and clear the busy _usbd_dev.ep_status[epnum][dir].busy = 1; - if ( dcd_edpt_xfer(rhport, ep_addr, buffer, total_bytes) ) - { + if (dcd_edpt_xfer(rhport, ep_addr, buffer, total_bytes)) { return true; - }else - { + } else { // DCD error, mark endpoint as ready to allow next transfer _usbd_dev.ep_status[epnum][dir].busy = 0; _usbd_dev.ep_status[epnum][dir].claimed = 0; @@ -1258,12 +1333,11 @@ bool usbd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t // bytes should be written and second to keep the return value free to give back a boolean // success message. If total_bytes is too big, the FIFO will copy only what is available // into the USB buffer! -bool usbd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes) -{ +bool usbd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t* ff, uint16_t total_bytes) { rhport = _usbd_rhport; uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); TU_LOG_USBD(" Queue ISO EP %02X with %u bytes ... ", ep_addr, total_bytes); @@ -1274,12 +1348,10 @@ bool usbd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16 // and usbd task can preempt and clear the busy _usbd_dev.ep_status[epnum][dir].busy = 1; - if (dcd_edpt_xfer_fifo(rhport, ep_addr, ff, total_bytes)) - { + if (dcd_edpt_xfer_fifo(rhport, ep_addr, ff, total_bytes)) { TU_LOG_USBD("OK\r\n"); return true; - }else - { + } else { // DCD error, mark endpoint as ready to allow next transfer _usbd_dev.ep_status[epnum][dir].busy = 0; _usbd_dev.ep_status[epnum][dir].claimed = 0; @@ -1289,75 +1361,62 @@ bool usbd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16 } } -bool usbd_edpt_busy(uint8_t rhport, uint8_t ep_addr) -{ +bool usbd_edpt_busy(uint8_t rhport, uint8_t ep_addr) { (void) rhport; uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); return _usbd_dev.ep_status[epnum][dir].busy; } -void usbd_edpt_stall(uint8_t rhport, uint8_t ep_addr) -{ +void usbd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { rhport = _usbd_rhport; uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); // only stalled if currently cleared - if ( !_usbd_dev.ep_status[epnum][dir].stalled ) - { - TU_LOG_USBD(" Stall EP %02X\r\n", ep_addr); - dcd_edpt_stall(rhport, ep_addr); - _usbd_dev.ep_status[epnum][dir].stalled = 1; - _usbd_dev.ep_status[epnum][dir].busy = 1; - } + TU_LOG_USBD(" Stall EP %02X\r\n", ep_addr); + dcd_edpt_stall(rhport, ep_addr); + _usbd_dev.ep_status[epnum][dir].stalled = 1; + _usbd_dev.ep_status[epnum][dir].busy = 1; } -void usbd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) -{ +void usbd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { rhport = _usbd_rhport; uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); // only clear if currently stalled - if ( _usbd_dev.ep_status[epnum][dir].stalled ) - { - TU_LOG_USBD(" Clear Stall EP %02X\r\n", ep_addr); - dcd_edpt_clear_stall(rhport, ep_addr); - _usbd_dev.ep_status[epnum][dir].stalled = 0; - _usbd_dev.ep_status[epnum][dir].busy = 0; - } + TU_LOG_USBD(" Clear Stall EP %02X\r\n", ep_addr); + dcd_edpt_clear_stall(rhport, ep_addr); + _usbd_dev.ep_status[epnum][dir].stalled = 0; + _usbd_dev.ep_status[epnum][dir].busy = 0; } -bool usbd_edpt_stalled(uint8_t rhport, uint8_t ep_addr) -{ +bool usbd_edpt_stalled(uint8_t rhport, uint8_t ep_addr) { (void) rhport; uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); return _usbd_dev.ep_status[epnum][dir].stalled; } /** * usbd_edpt_close will disable an endpoint. - * * In progress transfers on this EP may be delivered after this call. - * */ -void usbd_edpt_close(uint8_t rhport, uint8_t ep_addr) -{ +void usbd_edpt_close(uint8_t rhport, uint8_t ep_addr) { rhport = _usbd_rhport; TU_ASSERT(dcd_edpt_close, /**/); TU_LOG_USBD(" CLOSING Endpoint: 0x%02X\r\n", ep_addr); uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); dcd_edpt_close(rhport, ep_addr); _usbd_dev.ep_status[epnum][dir].stalled = 0; @@ -1367,17 +1426,24 @@ void usbd_edpt_close(uint8_t rhport, uint8_t ep_addr) return; } -void usbd_sof_enable(uint8_t rhport, bool en) -{ +void usbd_sof_enable(uint8_t rhport, sof_consumer_t consumer, bool en) { rhport = _usbd_rhport; - // TODO: Check needed if all drivers including the user sof_cb does not need an active SOF ISR any more. - // Only if all drivers switched off SOF calls the SOF interrupt may be disabled - dcd_sof_enable(rhport, en); + uint8_t consumer_old = _usbd_dev.sof_consumer; + // Keep track how many class instances need the SOF interrupt + if (en) { + _usbd_dev.sof_consumer |= (uint8_t)(1 << consumer); + } else { + _usbd_dev.sof_consumer &= (uint8_t)(~(1 << consumer)); + } + + // Test logically unequal + if(!_usbd_dev.sof_consumer != !consumer_old) { + dcd_sof_enable(rhport, _usbd_dev.sof_consumer); + } } -bool usbd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) -{ +bool usbd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packet_size) { rhport = _usbd_rhport; TU_ASSERT(dcd_edpt_iso_alloc); @@ -1386,12 +1452,11 @@ bool usbd_edpt_iso_alloc(uint8_t rhport, uint8_t ep_addr, uint16_t largest_packe return dcd_edpt_iso_alloc(rhport, ep_addr, largest_packet_size); } -bool usbd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const * desc_ep) -{ +bool usbd_edpt_iso_activate(uint8_t rhport, tusb_desc_endpoint_t const* desc_ep) { rhport = _usbd_rhport; uint8_t const epnum = tu_edpt_number(desc_ep->bEndpointAddress); - uint8_t const dir = tu_edpt_dir(desc_ep->bEndpointAddress); + uint8_t const dir = tu_edpt_dir(desc_ep->bEndpointAddress); TU_ASSERT(dcd_edpt_iso_activate); TU_ASSERT(epnum < CFG_TUD_ENDPPOINT_MAX); diff --git a/pico-sdk/lib/tinyusb/src/device/usbd.h b/pico-sdk/lib/tinyusb/src/device/usbd.h index 3ab6c81..cba94fd 100644 --- a/pico-sdk/lib/tinyusb/src/device/usbd.h +++ b/pico-sdk/lib/tinyusb/src/device/usbd.h @@ -37,9 +37,12 @@ extern "C" { // Application API //--------------------------------------------------------------------+ -// Init device stack +// Init device stack on roothub port bool tud_init (uint8_t rhport); +// Deinit device stack on roothub port +bool tud_deinit(uint8_t rhport); + // Check if device stack is already initialized bool tud_inited(void); @@ -94,6 +97,9 @@ bool tud_disconnect(void); // Return false on unsupported MCUs bool tud_connect(void); +// Enable or disable the Start Of Frame callback support +bool tud_sof_cb_enable(bool en); + // Carry out Data and Status stage of control transfer // - If len = 0, it is equivalent to sending status only // - If len > wLength : it will be truncated @@ -149,6 +155,9 @@ TU_ATTR_WEAK void tud_resume_cb(void); // Invoked when there is a new usb event, which need to be processed by tud_task()/tud_task_ext() void tud_event_hook_cb(uint8_t rhport, uint32_t eventid, bool in_isr); +// Invoked when a new (micro) frame started +void tud_sof_cb(uint32_t frame_count); + // Invoked when received control request with VENDOR TYPE TU_ATTR_WEAK bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); @@ -218,8 +227,8 @@ TU_ATTR_WEAK bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_HEADER, U16_TO_U8S_LE(0x0120),\ /* CDC Call */\ 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_CALL_MANAGEMENT, 0, (uint8_t)((_itfnum) + 1),\ - /* CDC ACM: support line request */\ - 4, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_ABSTRACT_CONTROL_MANAGEMENT, 2,\ + /* CDC ACM: support line request + send break */\ + 4, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_ABSTRACT_CONTROL_MANAGEMENT, 6,\ /* CDC Union */\ 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_UNION, _itfnum, (uint8_t)((_itfnum) + 1),\ /* Endpoint Notification */\ @@ -393,6 +402,11 @@ TU_ATTR_WEAK bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb // For more channels, add definitions here +/* Standard AC Interrupt Endpoint Descriptor(4.8.2.1) */ +#define TUD_AUDIO_DESC_STD_AC_INT_EP_LEN 7 +#define TUD_AUDIO_DESC_STD_AC_INT_EP(_ep, _interval) \ + TUD_AUDIO_DESC_STD_AC_INT_EP_LEN, TUSB_DESC_ENDPOINT, _ep, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(6), _interval + /* Standard AS Interface Descriptor(4.9.1) */ #define TUD_AUDIO_DESC_STD_AS_INT_LEN 9 #define TUD_AUDIO_DESC_STD_AS_INT(_itfnum, _altset, _nEPs, _stridx) \ @@ -421,7 +435,7 @@ TU_ATTR_WEAK bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb /* Standard AS Isochronous Feedback Endpoint Descriptor(4.10.2.1) */ #define TUD_AUDIO_DESC_STD_AS_ISO_FB_EP_LEN 7 #define TUD_AUDIO_DESC_STD_AS_ISO_FB_EP(_ep, _interval) \ - TUD_AUDIO_DESC_STD_AS_ISO_FB_EP_LEN, TUSB_DESC_ENDPOINT, _ep, (uint8_t) (TUSB_XFER_ISOCHRONOUS | TUSB_ISO_EP_ATT_NO_SYNC | TUSB_ISO_EP_ATT_EXPLICIT_FB), U16_TO_U8S_LE(4), _interval + TUD_AUDIO_DESC_STD_AS_ISO_FB_EP_LEN, TUSB_DESC_ENDPOINT, _ep, (uint8_t) ((uint8_t)TUSB_XFER_ISOCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_NO_SYNC | (uint8_t)TUSB_ISO_EP_ATT_EXPLICIT_FB), U16_TO_U8S_LE(4), _interval // AUDIO simple descriptor (UAC2) for 1 microphone input // - 1 Input Terminal, 1 Feature Unit (Mute and Volume Control), 1 Output Terminal, 1 Clock Source @@ -468,7 +482,7 @@ TU_ATTR_WEAK bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb /* Type I Format Type Descriptor(2.3.1.6 - Audio Formats) */\ TUD_AUDIO_DESC_TYPE_I_FORMAT(_nBytesPerSample, _nBitsUsedPerSample),\ /* Standard AS Isochronous Audio Data Endpoint Descriptor(4.10.1.1) */\ - TUD_AUDIO_DESC_STD_AS_ISO_EP(/*_ep*/ _epin, /*_attr*/ (uint8_t) (TUSB_XFER_ISOCHRONOUS | TUSB_ISO_EP_ATT_ASYNCHRONOUS | TUSB_ISO_EP_ATT_DATA), /*_maxEPsize*/ _epsize, /*_interval*/ 0x01),\ + TUD_AUDIO_DESC_STD_AS_ISO_EP(/*_ep*/ _epin, /*_attr*/ (uint8_t) ((uint8_t)TUSB_XFER_ISOCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_ASYNCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_DATA), /*_maxEPsize*/ _epsize, /*_interval*/ 0x01),\ /* Class-Specific AS Isochronous Audio Data Endpoint Descriptor(4.10.1.2) */\ TUD_AUDIO_DESC_CS_AS_ISO_EP(/*_attr*/ AUDIO_CS_AS_ISO_DATA_EP_ATT_NON_MAX_PACKETS_OK, /*_ctrl*/ AUDIO_CTRL_NONE, /*_lockdelayunit*/ AUDIO_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_UNDEFINED, /*_lockdelay*/ 0x0000) @@ -517,7 +531,7 @@ TU_ATTR_WEAK bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb /* Type I Format Type Descriptor(2.3.1.6 - Audio Formats) */\ TUD_AUDIO_DESC_TYPE_I_FORMAT(_nBytesPerSample, _nBitsUsedPerSample),\ /* Standard AS Isochronous Audio Data Endpoint Descriptor(4.10.1.1) */\ - TUD_AUDIO_DESC_STD_AS_ISO_EP(/*_ep*/ _epin, /*_attr*/ (uint8_t) (TUSB_XFER_ISOCHRONOUS | TUSB_ISO_EP_ATT_ASYNCHRONOUS | TUSB_ISO_EP_ATT_DATA), /*_maxEPsize*/ _epsize, /*_interval*/ 0x01),\ + TUD_AUDIO_DESC_STD_AS_ISO_EP(/*_ep*/ _epin, /*_attr*/ (uint8_t) ((uint8_t)TUSB_XFER_ISOCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_ASYNCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_DATA), /*_maxEPsize*/ _epsize, /*_interval*/ 0x01),\ /* Class-Specific AS Isochronous Audio Data Endpoint Descriptor(4.10.1.2) */\ TUD_AUDIO_DESC_CS_AS_ISO_EP(/*_attr*/ AUDIO_CS_AS_ISO_DATA_EP_ATT_NON_MAX_PACKETS_OK, /*_ctrl*/ AUDIO_CTRL_NONE, /*_lockdelayunit*/ AUDIO_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_UNDEFINED, /*_lockdelay*/ 0x0000) @@ -565,7 +579,7 @@ TU_ATTR_WEAK bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb /* Type I Format Type Descriptor(2.3.1.6 - Audio Formats) */\ TUD_AUDIO_DESC_TYPE_I_FORMAT(_nBytesPerSample, _nBitsUsedPerSample),\ /* Standard AS Isochronous Audio Data Endpoint Descriptor(4.10.1.1) */\ - TUD_AUDIO_DESC_STD_AS_ISO_EP(/*_ep*/ _epout, /*_attr*/ (uint8_t) (TUSB_XFER_ISOCHRONOUS | TUSB_ISO_EP_ATT_ASYNCHRONOUS | TUSB_ISO_EP_ATT_DATA), /*_maxEPsize*/ _epsize, /*_interval*/ 0x01),\ + TUD_AUDIO_DESC_STD_AS_ISO_EP(/*_ep*/ _epout, /*_attr*/ (uint8_t) ((uint8_t)TUSB_XFER_ISOCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_ASYNCHRONOUS | (uint8_t)TUSB_ISO_EP_ATT_DATA), /*_maxEPsize*/ _epsize, /*_interval*/ 0x01),\ /* Class-Specific AS Isochronous Audio Data Endpoint Descriptor(4.10.1.2) */\ TUD_AUDIO_DESC_CS_AS_ISO_EP(/*_attr*/ AUDIO_CS_AS_ISO_DATA_EP_ATT_NON_MAX_PACKETS_OK, /*_ctrl*/ AUDIO_CTRL_NONE, /*_lockdelayunit*/ AUDIO_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_UNDEFINED, /*_lockdelay*/ 0x0000),\ /* Standard AS Isochronous Feedback Endpoint Descriptor(4.10.2.1) */\ diff --git a/pico-sdk/lib/tinyusb/src/device/usbd_pvt.h b/pico-sdk/lib/tinyusb/src/device/usbd_pvt.h index 9039bc9..335d46c 100644 --- a/pico-sdk/lib/tinyusb/src/device/usbd_pvt.h +++ b/pico-sdk/lib/tinyusb/src/device/usbd_pvt.h @@ -23,8 +23,8 @@ * * This file is part of the TinyUSB stack. */ -#ifndef _TUSB_USBD_PVT_H_ -#define _TUSB_USBD_PVT_H_ +#ifndef TUSB_USBD_PVT_H_ +#define TUSB_USBD_PVT_H_ #include "osal/osal.h" #include "common/tusb_fifo.h" @@ -35,16 +35,23 @@ #define TU_LOG_USBD(...) TU_LOG(CFG_TUD_LOG_LEVEL, __VA_ARGS__) +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF PROTYPES +//--------------------------------------------------------------------+ + +typedef enum { + SOF_CONSUMER_USER = 0, + SOF_CONSUMER_AUDIO, +} sof_consumer_t; + //--------------------------------------------------------------------+ // Class Driver API //--------------------------------------------------------------------+ typedef struct { - #if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL char const* name; - #endif - void (* init ) (void); + bool (* deinit ) (void); void (* reset ) (uint8_t rhport); uint16_t (* open ) (uint8_t rhport, tusb_desc_interface_t const * desc_intf, uint16_t max_len); bool (* control_xfer_cb ) (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); @@ -110,7 +117,7 @@ bool usbd_edpt_ready(uint8_t rhport, uint8_t ep_addr) { } // Enable SOF interrupt -void usbd_sof_enable(uint8_t rhport, bool en); +void usbd_sof_enable(uint8_t rhport, sof_consumer_t consumer, bool en); /*------------------------------------------------------------------*/ /* Helper diff --git a/pico-sdk/lib/tinyusb/src/host/hcd.h b/pico-sdk/lib/tinyusb/src/host/hcd.h index 2bde289..5547c7c 100644 --- a/pico-sdk/lib/tinyusb/src/host/hcd.h +++ b/pico-sdk/lib/tinyusb/src/host/hcd.h @@ -125,11 +125,14 @@ bool hcd_dcache_clean_invalidate(void const* addr, uint32_t data_size) TU_ATTR_W //--------------------------------------------------------------------+ // optional hcd configuration, called by tuh_configure() -bool hcd_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param) TU_ATTR_WEAK; +bool hcd_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param); // Initialize controller to host mode bool hcd_init(uint8_t rhport); +// De-initialize controller +bool hcd_deinit(uint8_t rhport); + // Interrupt Handler void hcd_int_handler(uint8_t rhport, bool in_isr); diff --git a/pico-sdk/lib/tinyusb/src/host/hub.c b/pico-sdk/lib/tinyusb/src/host/hub.c index 3bac186..e970144 100644 --- a/pico-sdk/lib/tinyusb/src/host/hub.c +++ b/pico-sdk/lib/tinyusb/src/host/hub.c @@ -182,9 +182,13 @@ bool hub_port_get_status(uint8_t hub_addr, uint8_t hub_port, void* resp, //--------------------------------------------------------------------+ // CLASS-USBH API (don't require to verify parameters) //--------------------------------------------------------------------+ -void hub_init(void) -{ +bool hub_init(void) { tu_memclr(hub_data, sizeof(hub_data)); + return true; +} + +bool hub_deinit(void) { + return true; } bool hub_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *itf_desc, uint16_t max_len) diff --git a/pico-sdk/lib/tinyusb/src/host/hub.h b/pico-sdk/lib/tinyusb/src/host/hub.h index 390740e..385efe6 100644 --- a/pico-sdk/lib/tinyusb/src/host/hub.h +++ b/pico-sdk/lib/tinyusb/src/host/hub.h @@ -187,16 +187,14 @@ bool hub_port_get_status (uint8_t hub_addr, uint8_t hub_port, void* resp, bool hub_edpt_status_xfer(uint8_t dev_addr); // Reset a port -static inline bool hub_port_reset(uint8_t hub_addr, uint8_t hub_port, - tuh_xfer_cb_t complete_cb, uintptr_t user_data) -{ +TU_ATTR_ALWAYS_INLINE static inline +bool hub_port_reset(uint8_t hub_addr, uint8_t hub_port, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { return hub_port_set_feature(hub_addr, hub_port, HUB_FEATURE_PORT_RESET, complete_cb, user_data); } // Clear Reset Change -static inline bool hub_port_clear_reset_change(uint8_t hub_addr, uint8_t hub_port, - tuh_xfer_cb_t complete_cb, uintptr_t user_data) -{ +TU_ATTR_ALWAYS_INLINE static inline +bool hub_port_clear_reset_change(uint8_t hub_addr, uint8_t hub_port, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { return hub_port_clear_feature(hub_addr, hub_port, HUB_FEATURE_PORT_RESET_CHANGE, complete_cb, user_data); } @@ -204,7 +202,8 @@ static inline bool hub_port_clear_reset_change(uint8_t hub_addr, uint8_t hub_por //--------------------------------------------------------------------+ // Internal Class Driver API //--------------------------------------------------------------------+ -void hub_init (void); +bool hub_init (void); +bool hub_deinit (void); bool hub_open (uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *itf_desc, uint16_t max_len); bool hub_set_config (uint8_t dev_addr, uint8_t itf_num); bool hub_xfer_cb (uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); diff --git a/pico-sdk/lib/tinyusb/src/host/usbh.c b/pico-sdk/lib/tinyusb/src/host/usbh.c index 3da296f..53b8527 100644 --- a/pico-sdk/lib/tinyusb/src/host/usbh.c +++ b/pico-sdk/lib/tinyusb/src/host/usbh.c @@ -37,7 +37,7 @@ // USBH Configuration //--------------------------------------------------------------------+ #ifndef CFG_TUH_TASK_QUEUE_SZ - #define CFG_TUH_TASK_QUEUE_SZ 16 + #define CFG_TUH_TASK_QUEUE_SZ 32 #endif #ifndef CFG_TUH_INTERFACE_MAX @@ -45,8 +45,20 @@ #endif //--------------------------------------------------------------------+ -// Callback weak stubs (called if application does not provide) +// Weak stubs: invoked if no strong implementation is available //--------------------------------------------------------------------+ +TU_ATTR_WEAK bool hcd_deinit(uint8_t rhport) { + (void) rhport; + return false; +} + +TU_ATTR_WEAK bool hcd_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param) { + (void) rhport; + (void) cfg_id; + (void) cfg_param; + return false; +} + TU_ATTR_WEAK void tuh_event_hook_cb(uint8_t rhport, uint32_t eventid, bool in_isr) { (void) rhport; (void) eventid; @@ -119,16 +131,17 @@ typedef struct { // MACRO CONSTANT TYPEDEF //--------------------------------------------------------------------+ #if CFG_TUSB_DEBUG >= CFG_TUH_LOG_LEVEL - #define DRIVER_NAME(_name) .name = _name, + #define DRIVER_NAME(_name) _name #else - #define DRIVER_NAME(_name) + #define DRIVER_NAME(_name) NULL #endif static usbh_class_driver_t const usbh_class_drivers[] = { #if CFG_TUH_CDC { - DRIVER_NAME("CDC") + .name = DRIVER_NAME("CDC"), .init = cdch_init, + .deinit = cdch_deinit, .open = cdch_open, .set_config = cdch_set_config, .xfer_cb = cdch_xfer_cb, @@ -138,8 +151,9 @@ static usbh_class_driver_t const usbh_class_drivers[] = { #if CFG_TUH_MSC { - DRIVER_NAME("MSC") + .name = DRIVER_NAME("MSC"), .init = msch_init, + .deinit = msch_deinit, .open = msch_open, .set_config = msch_set_config, .xfer_cb = msch_xfer_cb, @@ -149,8 +163,9 @@ static usbh_class_driver_t const usbh_class_drivers[] = { #if CFG_TUH_HID { - DRIVER_NAME("HID") + .name = DRIVER_NAME("HID"), .init = hidh_init, + .deinit = hidh_deinit, .open = hidh_open, .set_config = hidh_set_config, .xfer_cb = hidh_xfer_cb, @@ -160,8 +175,9 @@ static usbh_class_driver_t const usbh_class_drivers[] = { #if CFG_TUH_HUB { - DRIVER_NAME("HUB") + .name = DRIVER_NAME("HUB"), .init = hub_init, + .deinit = hub_deinit, .open = hub_open, .set_config = hub_set_config, .xfer_cb = hub_xfer_cb, @@ -171,9 +187,11 @@ static usbh_class_driver_t const usbh_class_drivers[] = { #if CFG_TUH_VENDOR { - DRIVER_NAME("VENDOR") + .name = DRIVER_NAME("VENDOR"), .init = cush_init, - .open = cush_open_subtask, + .deinit = cush_deinit, + .open = cush_open, + .set_config = cush_set_config, .xfer_cb = cush_isr, .close = cush_close } @@ -270,9 +288,9 @@ TU_ATTR_WEAK void osal_task_delay(uint32_t msec) { #endif TU_ATTR_ALWAYS_INLINE static inline bool queue_event(hcd_event_t const * event, bool in_isr) { - bool ret = osal_queue_send(_usbh_q, event, in_isr); + TU_ASSERT(osal_queue_send(_usbh_q, event, in_isr)); tuh_event_hook_cb(event->rhport, event->event_id, in_isr); - return ret; + return true; } //--------------------------------------------------------------------+ @@ -321,11 +339,7 @@ bool tuh_rhport_reset_bus(uint8_t rhport, bool active) { //--------------------------------------------------------------------+ bool tuh_configure(uint8_t rhport, uint32_t cfg_id, const void *cfg_param) { - if ( hcd_configure ) { - return hcd_configure(rhport, cfg_id, cfg_param); - } else { - return false; - } + return hcd_configure(rhport, cfg_id, cfg_param); } static void clear_device(usbh_device_t* dev) { @@ -338,55 +352,94 @@ bool tuh_inited(void) { return _usbh_controller != TUSB_INDEX_INVALID_8; } -bool tuh_init(uint8_t controller_id) { +bool tuh_init(uint8_t rhport) { // skip if already initialized - if ( tuh_inited() ) return true; + if (tuh_rhport_is_active(rhport)) return true; - TU_LOG_USBH("USBH init on controller %u\r\n", controller_id); - TU_LOG_INT(CFG_TUH_LOG_LEVEL, sizeof(usbh_device_t)); - TU_LOG_INT(CFG_TUH_LOG_LEVEL, sizeof(hcd_event_t)); - TU_LOG_INT(CFG_TUH_LOG_LEVEL, sizeof(_ctrl_xfer)); - TU_LOG_INT(CFG_TUH_LOG_LEVEL, sizeof(tuh_xfer_t)); - TU_LOG_INT(CFG_TUH_LOG_LEVEL, sizeof(tu_fifo_t)); - TU_LOG_INT(CFG_TUH_LOG_LEVEL, sizeof(tu_edpt_stream_t)); + TU_LOG_USBH("USBH init on controller %u\r\n", rhport); - // Event queue - _usbh_q = osal_queue_create( &_usbh_qdef ); - TU_ASSERT(_usbh_q != NULL); + // Init host stack if not already + if (!tuh_inited()) { + TU_LOG_INT_USBH(sizeof(usbh_device_t)); + TU_LOG_INT_USBH(sizeof(hcd_event_t)); + TU_LOG_INT_USBH(sizeof(_ctrl_xfer)); + TU_LOG_INT_USBH(sizeof(tuh_xfer_t)); + TU_LOG_INT_USBH(sizeof(tu_fifo_t)); + TU_LOG_INT_USBH(sizeof(tu_edpt_stream_t)); + + // Event queue + _usbh_q = osal_queue_create(&_usbh_qdef); + TU_ASSERT(_usbh_q != NULL); #if OSAL_MUTEX_REQUIRED - // Init mutex - _usbh_mutex = osal_mutex_create(&_usbh_mutexdef); - TU_ASSERT(_usbh_mutex); + // Init mutex + _usbh_mutex = osal_mutex_create(&_usbh_mutexdef); + TU_ASSERT(_usbh_mutex); #endif - // Get application driver if available - if ( usbh_app_driver_get_cb ) { - _app_driver = usbh_app_driver_get_cb(&_app_driver_count); - } + // Get application driver if available + if (usbh_app_driver_get_cb) { + _app_driver = usbh_app_driver_get_cb(&_app_driver_count); + } - // Device - tu_memclr(&_dev0, sizeof(_dev0)); - tu_memclr(_usbh_devices, sizeof(_usbh_devices)); - tu_memclr(&_ctrl_xfer, sizeof(_ctrl_xfer)); + // Device + tu_memclr(&_dev0, sizeof(_dev0)); + tu_memclr(_usbh_devices, sizeof(_usbh_devices)); + tu_memclr(&_ctrl_xfer, sizeof(_ctrl_xfer)); - for(uint8_t i=0; iname); - driver->init(); + // Class drivers + for (uint8_t drv_id = 0; drv_id < TOTAL_DRIVER_COUNT; drv_id++) { + usbh_class_driver_t const* driver = get_driver(drv_id); + if (driver) { + TU_LOG_USBH("%s init\r\n", driver->name); + driver->init(); + } } } - _usbh_controller = controller_id;; + // Init host controller + _usbh_controller = rhport;; + TU_ASSERT(hcd_init(rhport)); + hcd_int_enable(rhport); - TU_ASSERT(hcd_init(controller_id)); - hcd_int_enable(controller_id); + return true; +} + +bool tuh_deinit(uint8_t rhport) { + if (!tuh_rhport_is_active(rhport)) return true; + + // deinit host controller + hcd_int_disable(rhport); + hcd_deinit(rhport); + _usbh_controller = TUSB_INDEX_INVALID_8; + + // "unplug" all devices on this rhport (hub_addr = 0, hub_port = 0) + process_removing_device(rhport, 0, 0); + + // deinit host stack if no controller is active + if (!tuh_inited()) { + // Class drivers + for (uint8_t drv_id = 0; drv_id < TOTAL_DRIVER_COUNT; drv_id++) { + usbh_class_driver_t const* driver = get_driver(drv_id); + if (driver && driver->deinit) { + TU_LOG_USBH("%s deinit\r\n", driver->name); + driver->deinit(); + } + } + + osal_queue_delete(_usbh_q); + _usbh_q = NULL; + + #if OSAL_MUTEX_REQUIRED + // TODO make sure there is no task waiting on this mutex + osal_mutex_delete(_usbh_mutex); + _usbh_mutex = NULL; + #endif + } return true; } @@ -420,20 +473,18 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) { (void) in_isr; // not implemented yet // Skip if stack is not initialized - if ( !tuh_inited() ) return; + if (!tuh_inited()) return; // Loop until there is no more events in the queue - while (1) - { + while (1) { hcd_event_t event; - if ( !osal_queue_receive(_usbh_q, &event, timeout_ms) ) return; + if (!osal_queue_receive(_usbh_q, &event, timeout_ms)) return; - switch (event.event_id) - { + switch (event.event_id) { case HCD_EVENT_DEVICE_ATTACH: // due to the shared _usbh_ctrl_buf, we must complete enumerating one device before enumerating another one. // TODO better to have an separated queue for newly attached devices - if ( _dev0.enumerating ) { + if (_dev0.enumerating) { TU_LOG_USBH("[%u:] USBH Defer Attach until current enumeration complete\r\n", event.rhport); bool is_empty = osal_queue_empty(_usbh_q); @@ -443,12 +494,12 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) { // Exit if this is the only event in the queue, otherwise we may loop forever return; } - }else { + } else { TU_LOG_USBH("[%u:] USBH DEVICE ATTACH\r\n", event.rhport); _dev0.enumerating = 1; enum_new_device(&event); } - break; + break; case HCD_EVENT_DEVICE_REMOVE: TU_LOG_USBH("[%u:%u:%u] USBH DEVICE REMOVED\r\n", event.rhport, event.connection.hub_addr, event.connection.hub_port); @@ -456,37 +507,34 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) { #if CFG_TUH_HUB // TODO remove - if ( event.connection.hub_addr != 0 && event.connection.hub_port != 0) { + if (event.connection.hub_addr != 0 && event.connection.hub_port != 0) { // done with hub, waiting for next data on status pipe - (void) hub_edpt_status_xfer( event.connection.hub_addr ); + (void) hub_edpt_status_xfer(event.connection.hub_addr); } #endif - break; + break; - case HCD_EVENT_XFER_COMPLETE: - { + case HCD_EVENT_XFER_COMPLETE: { uint8_t const ep_addr = event.xfer_complete.ep_addr; - uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const ep_dir = tu_edpt_dir(ep_addr); + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const ep_dir = (uint8_t) tu_edpt_dir(ep_addr); - // TU_LOG_USBH("on EP %02X with %u bytes: %s\r\n", ep_addr, (unsigned int) event.xfer_complete.len, - // tu_str_xfer_result[event.xfer_complete.result]); + TU_LOG_USBH("on EP %02X with %u bytes: %s\r\n", ep_addr, (unsigned int) event.xfer_complete.len, tu_str_xfer_result[event.xfer_complete.result]); if (event.dev_addr == 0) { // device 0 only has control endpoint - TU_ASSERT(epnum == 0, ); + TU_ASSERT(epnum == 0,); usbh_control_xfer_cb(event.dev_addr, ep_addr, (xfer_result_t) event.xfer_complete.result, event.xfer_complete.len); } else { usbh_device_t* dev = get_device(event.dev_addr); - TU_VERIFY(dev && dev->connected, ); + TU_VERIFY(dev && dev->connected,); - dev->ep_status[epnum][ep_dir].busy = 0; + dev->ep_status[epnum][ep_dir].busy = 0; dev->ep_status[epnum][ep_dir].claimed = 0; - if ( 0 == epnum ) { - usbh_control_xfer_cb(event.dev_addr, ep_addr, (xfer_result_t) event.xfer_complete.result, - event.xfer_complete.len); - }else { + if (0 == epnum) { + usbh_control_xfer_cb(event.dev_addr, ep_addr, (xfer_result_t) event.xfer_complete.result, event.xfer_complete.len); + } else { // Prefer application callback over built-in one if available. This occurs when tuh_edpt_xfer() is used // with enabled driver e.g HID endpoint #if CFG_TUH_API_EDPT_XFER @@ -503,41 +551,36 @@ void tuh_task_ext(uint32_t timeout_ms, bool in_isr) { .complete_cb = complete_cb, .user_data = dev->ep_callback[epnum][ep_dir].user_data }; - complete_cb(&xfer); }else #endif { uint8_t drv_id = dev->ep2drv[epnum][ep_dir]; - usbh_class_driver_t const * driver = get_driver(drv_id); - if ( driver ) - { - // TU_LOG_USBH("%s xfer callback\r\n", driver->name); + usbh_class_driver_t const* driver = get_driver(drv_id); + if (driver) { + TU_LOG_USBH("%s xfer callback\r\n", driver->name); driver->xfer_cb(event.dev_addr, ep_addr, (xfer_result_t) event.xfer_complete.result, event.xfer_complete.len); - } - else - { + } else { // no driver/callback responsible for this transfer TU_ASSERT(false,); } } } } + break; } - break; case USBH_EVENT_FUNC_CALL: - if ( event.func_call.func ) event.func_call.func(event.func_call.param); - break; + if (event.func_call.func) event.func_call.func(event.func_call.param); + break; - default: break; + default: + break; } -#if CFG_TUSB_OS != OPT_OS_NONE && CFG_TUSB_OS != OPT_OS_PICO // return if there is no more events, for application to run other background if (osal_queue_empty(_usbh_q)) return; -#endif } } @@ -588,8 +631,7 @@ bool tuh_control_xfer (tuh_xfer_t* xfer) { TU_LOG_USBH("[%u:%u] %s: ", rhport, daddr, (xfer->setup->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && xfer->setup->bRequest <= TUSB_REQ_SYNCH_FRAME) ? tu_str_std_request[xfer->setup->bRequest] : "Class Request"); - TU_LOG_BUF(CFG_TUH_LOG_LEVEL, xfer->setup, 8); - TU_LOG_USBH("\r\n"); + TU_LOG_BUF_USBH(xfer->setup, 8); if (xfer->complete_cb) { TU_ASSERT( hcd_setup_send(rhport, daddr, (uint8_t const*) &_ctrl_xfer.request) ); @@ -630,7 +672,7 @@ TU_ATTR_ALWAYS_INLINE static inline void _set_control_xfer_stage(uint8_t stage) (void) osal_mutex_unlock(_usbh_mutex); } -static void _xfer_complete(uint8_t daddr, xfer_result_t result) { +static void _control_xfer_complete(uint8_t daddr, xfer_result_t result) { TU_LOG_USBH("\r\n"); // duplicate xfer since user can execute control transfer within callback @@ -653,46 +695,56 @@ static void _xfer_complete(uint8_t daddr, xfer_result_t result) { } } -static bool usbh_control_xfer_cb (uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { +static bool usbh_control_xfer_cb (uint8_t daddr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { (void) ep_addr; - const uint8_t rhport = usbh_get_rhport(dev_addr); + const uint8_t rhport = usbh_get_rhport(daddr); tusb_control_request_t const * request = &_ctrl_xfer.request; if (XFER_RESULT_SUCCESS != result) { - TU_LOG1("[%u:%u] Control %s, xferred_bytes = %lu\r\n", rhport, dev_addr, result == XFER_RESULT_STALLED ? "STALLED" : "FAILED", xferred_bytes); - TU_LOG1_BUF(request, 8); - TU_LOG1("\r\n"); + TU_LOG_USBH("[%u:%u] Control %s, xferred_bytes = %" PRIu32 "\r\n", rhport, daddr, result == XFER_RESULT_STALLED ? "STALLED" : "FAILED", xferred_bytes); + TU_LOG_BUF_USBH(request, 8); // terminate transfer if any stage failed - _xfer_complete(dev_addr, result); + _control_xfer_complete(daddr, result); }else { switch(_ctrl_xfer.stage) { case CONTROL_STAGE_SETUP: if (request->wLength) { // DATA stage: initial data toggle is always 1 _set_control_xfer_stage(CONTROL_STAGE_DATA); - TU_ASSERT( hcd_edpt_xfer(rhport, dev_addr, tu_edpt_addr(0, request->bmRequestType_bit.direction), _ctrl_xfer.buffer, request->wLength) ); + TU_ASSERT( hcd_edpt_xfer(rhport, daddr, tu_edpt_addr(0, request->bmRequestType_bit.direction), _ctrl_xfer.buffer, request->wLength) ); return true; } TU_ATTR_FALLTHROUGH; case CONTROL_STAGE_DATA: if (request->wLength) { - TU_LOG_USBH("[%u:%u] Control data:\r\n", rhport, dev_addr); - TU_LOG_MEM(CFG_TUH_LOG_LEVEL, _ctrl_xfer.buffer, xferred_bytes, 2); + TU_LOG_USBH("[%u:%u] Control data:\r\n", rhport, daddr); + TU_LOG_MEM_USBH(_ctrl_xfer.buffer, xferred_bytes, 2); } _ctrl_xfer.actual_len = (uint16_t) xferred_bytes; // ACK stage: toggle is always 1 _set_control_xfer_stage(CONTROL_STAGE_ACK); - TU_ASSERT( hcd_edpt_xfer(rhport, dev_addr, tu_edpt_addr(0, 1-request->bmRequestType_bit.direction), NULL, 0) ); - break; + TU_ASSERT( hcd_edpt_xfer(rhport, daddr, tu_edpt_addr(0, 1 - request->bmRequestType_bit.direction), NULL, 0) ); + break; - case CONTROL_STAGE_ACK: - _xfer_complete(dev_addr, result); - break; + case CONTROL_STAGE_ACK: { + // Abort all pending transfers if SET_CONFIGURATION request + // NOTE: should we force closing all non-control endpoints in the future? + if (request->bRequest == TUSB_REQ_SET_CONFIGURATION && request->bmRequestType == 0x00) { + for(uint8_t epnum=1; epnumep_addr; TU_VERIFY(daddr && ep_addr); - TU_VERIFY(usbh_edpt_claim(daddr, ep_addr)); if (!usbh_edpt_xfer_with_callback(daddr, ep_addr, xfer->buffer, (uint16_t) xfer->buflen, @@ -743,7 +794,7 @@ bool tuh_edpt_abort_xfer(uint8_t daddr, uint8_t ep_addr) { TU_VERIFY(hcd_edpt_abort_xfer(dev->rhport, daddr, ep_addr)); // mark as ready and release endpoint if transfer is aborted dev->ep_status[epnum][dir].busy = false; - usbh_edpt_release(daddr, ep_addr); + tu_edpt_release(&dev->ep_status[epnum][dir], _usbh_mutex); } return true; @@ -785,30 +836,28 @@ void usbh_defer_func(osal_task_func_t func, void *param, bool in_isr) { //--------------------------------------------------------------------+ // Claim an endpoint for transfer -bool usbh_edpt_claim(uint8_t dev_addr, uint8_t ep_addr) -{ +bool usbh_edpt_claim(uint8_t dev_addr, uint8_t ep_addr) { // Note: addr0 only use tuh_control_xfer usbh_device_t* dev = get_device(dev_addr); TU_ASSERT(dev && dev->connected); uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); TU_VERIFY(tu_edpt_claim(&dev->ep_status[epnum][dir], _usbh_mutex)); - // TU_LOG_USBH("[%u] Claimed EP 0x%02x\r\n", dev_addr, ep_addr); + TU_LOG_USBH("[%u] Claimed EP 0x%02x\r\n", dev_addr, ep_addr); return true; } // Release an claimed endpoint due to failed transfer attempt -bool usbh_edpt_release(uint8_t dev_addr, uint8_t ep_addr) -{ +bool usbh_edpt_release(uint8_t dev_addr, uint8_t ep_addr) { // Note: addr0 only use tuh_control_xfer usbh_device_t* dev = get_device(dev_addr); TU_VERIFY(dev && dev->connected); uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); TU_VERIFY(tu_edpt_release(&dev->ep_status[epnum][dir], _usbh_mutex)); TU_LOG_USBH("[%u] Released EP 0x%02x\r\n", dev_addr, ep_addr); @@ -818,9 +867,8 @@ bool usbh_edpt_release(uint8_t dev_addr, uint8_t ep_addr) // Submit an transfer // TODO call usbh_edpt_release if failed -bool usbh_edpt_xfer_with_callback(uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes, - tuh_xfer_cb_t complete_cb, uintptr_t user_data) -{ +bool usbh_edpt_xfer_with_callback(uint8_t dev_addr, uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes, + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { (void) complete_cb; (void) user_data; @@ -828,10 +876,10 @@ bool usbh_edpt_xfer_with_callback(uint8_t dev_addr, uint8_t ep_addr, uint8_t * b TU_VERIFY(dev); uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); tu_edpt_state_t* ep_state = &dev->ep_status[epnum][dir]; - // TU_LOG_USBH(" Queue EP %02X with %u bytes ... \r\n", ep_addr, total_bytes); + TU_LOG_USBH(" Queue EP %02X with %u bytes ... \r\n", ep_addr, total_bytes); // Attempt to transfer on a busy endpoint, sound like an race condition ! TU_ASSERT(ep_state->busy == 0); @@ -845,14 +893,12 @@ bool usbh_edpt_xfer_with_callback(uint8_t dev_addr, uint8_t ep_addr, uint8_t * b dev->ep_callback[epnum][dir].user_data = user_data; #endif - if ( hcd_edpt_xfer(dev->rhport, dev_addr, ep_addr, buffer, total_bytes) ) - { - // TU_LOG_USBH("OK\r\n"); + if (hcd_edpt_xfer(dev->rhport, dev_addr, ep_addr, buffer, total_bytes)) { + TU_LOG_USBH("OK\r\n"); return true; - }else - { + } else { // HCD error, mark endpoint as ready to allow next transfer - ep_state->busy = 0; + ep_state->busy = 0; ep_state->claimed = 0; TU_LOG1("Failed\r\n"); // TU_BREAKPOINT(); @@ -860,12 +906,9 @@ bool usbh_edpt_xfer_with_callback(uint8_t dev_addr, uint8_t ep_addr, uint8_t * b } } -static bool usbh_edpt_control_open(uint8_t dev_addr, uint8_t max_packet_size) -{ +static bool usbh_edpt_control_open(uint8_t dev_addr, uint8_t max_packet_size) { TU_LOG_USBH("[%u:%u] Open EP0 with Size = %u\r\n", usbh_get_rhport(dev_addr), dev_addr, max_packet_size); - - tusb_desc_endpoint_t ep0_desc = - { + tusb_desc_endpoint_t ep0_desc = { .bLength = sizeof(tusb_desc_endpoint_t), .bDescriptorType = TUSB_DESC_ENDPOINT, .bEndpointAddress = 0, @@ -877,10 +920,8 @@ static bool usbh_edpt_control_open(uint8_t dev_addr, uint8_t max_packet_size) return hcd_edpt_open(usbh_get_rhport(dev_addr), dev_addr, &ep0_desc); } -bool tuh_edpt_open(uint8_t dev_addr, tusb_desc_endpoint_t const * desc_ep) -{ - TU_ASSERT( tu_edpt_validate(desc_ep, tuh_speed_get(dev_addr)) ); - +bool tuh_edpt_open(uint8_t dev_addr, tusb_desc_endpoint_t const* desc_ep) { + TU_ASSERT(tu_edpt_validate(desc_ep, tuh_speed_get(dev_addr))); return hcd_edpt_open(usbh_get_rhport(dev_addr), dev_addr, desc_ep); } @@ -889,7 +930,7 @@ bool usbh_edpt_busy(uint8_t dev_addr, uint8_t ep_addr) { TU_VERIFY(dev); uint8_t const epnum = tu_edpt_number(ep_addr); - uint8_t const dir = tu_edpt_dir(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); return dev->ep_status[epnum][dir].busy; } @@ -898,22 +939,18 @@ bool usbh_edpt_busy(uint8_t dev_addr, uint8_t ep_addr) { // HCD Event Handler //--------------------------------------------------------------------+ -void hcd_devtree_get_info(uint8_t dev_addr, hcd_devtree_info_t* devtree_info) -{ +void hcd_devtree_get_info(uint8_t dev_addr, hcd_devtree_info_t* devtree_info) { usbh_device_t const* dev = get_device(dev_addr); - - if (dev) - { - devtree_info->rhport = dev->rhport; + if (dev) { + devtree_info->rhport = dev->rhport; devtree_info->hub_addr = dev->hub_addr; devtree_info->hub_port = dev->hub_port; - devtree_info->speed = dev->speed; - }else - { - devtree_info->rhport = _dev0.rhport; + devtree_info->speed = dev->speed; + } else { + devtree_info->rhport = _dev0.rhport; devtree_info->hub_addr = _dev0.hub_addr; devtree_info->hub_port = _dev0.hub_port; - devtree_info->speed = _dev0.speed; + devtree_info->speed = _dev0.speed; } } @@ -943,12 +980,9 @@ TU_ATTR_FAST_FUNC void hcd_event_handler(hcd_event_t const* event, bool in_isr) // generic helper to get a descriptor // if blocking, user_data is pointed to xfer_result static bool _get_descriptor(uint8_t daddr, uint8_t type, uint8_t index, uint16_t language_id, void* buffer, uint16_t len, - tuh_xfer_cb_t complete_cb, uintptr_t user_data) -{ - tusb_control_request_t const request = - { - .bmRequestType_bit = - { + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { + tusb_control_request_t const request = { + .bmRequestType_bit = { .recipient = TUSB_REQ_RCPT_DEVICE, .type = TUSB_REQ_TYPE_STANDARD, .direction = TUSB_DIR_IN @@ -958,9 +992,7 @@ static bool _get_descriptor(uint8_t daddr, uint8_t type, uint8_t index, uint16_t .wIndex = tu_htole16(language_id), .wLength = tu_htole16(len) }; - - tuh_xfer_t xfer = - { + tuh_xfer_t xfer = { .daddr = daddr, .ep_addr = 0, .setup = &request, @@ -973,29 +1005,25 @@ static bool _get_descriptor(uint8_t daddr, uint8_t type, uint8_t index, uint16_t } bool tuh_descriptor_get(uint8_t daddr, uint8_t type, uint8_t index, void* buffer, uint16_t len, - tuh_xfer_cb_t complete_cb, uintptr_t user_data) -{ + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { return _get_descriptor(daddr, type, index, 0x0000, buffer, len, complete_cb, user_data); } bool tuh_descriptor_get_device(uint8_t daddr, void* buffer, uint16_t len, - tuh_xfer_cb_t complete_cb, uintptr_t user_data) -{ + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { len = tu_min16(len, sizeof(tusb_desc_device_t)); return tuh_descriptor_get(daddr, TUSB_DESC_DEVICE, 0, buffer, len, complete_cb, user_data); } bool tuh_descriptor_get_configuration(uint8_t daddr, uint8_t index, void* buffer, uint16_t len, - tuh_xfer_cb_t complete_cb, uintptr_t user_data) -{ + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { return tuh_descriptor_get(daddr, TUSB_DESC_CONFIGURATION, index, buffer, len, complete_cb, user_data); } //------------- String Descriptor -------------// bool tuh_descriptor_get_string(uint8_t daddr, uint8_t index, uint16_t language_id, void* buffer, uint16_t len, - tuh_xfer_cb_t complete_cb, uintptr_t user_data) -{ + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { return _get_descriptor(daddr, TUSB_DESC_STRING, index, language_id, buffer, len, complete_cb, user_data); } @@ -1010,8 +1038,7 @@ bool tuh_descriptor_get_manufacturer_string(uint8_t daddr, uint16_t language_id, // Get product string descriptor bool tuh_descriptor_get_product_string(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len, - tuh_xfer_cb_t complete_cb, uintptr_t user_data) -{ + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { usbh_device_t const* dev = get_device(daddr); TU_VERIFY(dev && dev->i_product); return tuh_descriptor_get_string(daddr, dev->i_product, language_id, buffer, len, complete_cb, user_data); @@ -1019,8 +1046,7 @@ bool tuh_descriptor_get_product_string(uint8_t daddr, uint16_t language_id, void // Get serial string descriptor bool tuh_descriptor_get_serial_string(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len, - tuh_xfer_cb_t complete_cb, uintptr_t user_data) -{ + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { usbh_device_t const* dev = get_device(daddr); TU_VERIFY(dev && dev->i_serial); return tuh_descriptor_get_string(daddr, dev->i_serial, language_id, buffer, len, complete_cb, user_data); @@ -1029,95 +1055,78 @@ bool tuh_descriptor_get_serial_string(uint8_t daddr, uint16_t language_id, void* // Get HID report descriptor // if blocking, user_data is pointed to xfer_result bool tuh_descriptor_get_hid_report(uint8_t daddr, uint8_t itf_num, uint8_t desc_type, uint8_t index, void* buffer, uint16_t len, - tuh_xfer_cb_t complete_cb, uintptr_t user_data) -{ + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { TU_LOG_USBH("HID Get Report Descriptor\r\n"); - tusb_control_request_t const request = - { - .bmRequestType_bit = - { - .recipient = TUSB_REQ_RCPT_INTERFACE, - .type = TUSB_REQ_TYPE_STANDARD, - .direction = TUSB_DIR_IN - }, - .bRequest = TUSB_REQ_GET_DESCRIPTOR, - .wValue = tu_htole16(TU_U16(desc_type, index)), - .wIndex = tu_htole16((uint16_t) itf_num), - .wLength = len + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_STANDARD, + .direction = TUSB_DIR_IN + }, + .bRequest = TUSB_REQ_GET_DESCRIPTOR, + .wValue = tu_htole16(TU_U16(desc_type, index)), + .wIndex = tu_htole16((uint16_t) itf_num), + .wLength = len }; - - tuh_xfer_t xfer = - { - .daddr = daddr, - .ep_addr = 0, - .setup = &request, - .buffer = buffer, - .complete_cb = complete_cb, - .user_data = user_data + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = buffer, + .complete_cb = complete_cb, + .user_data = user_data }; return tuh_control_xfer(&xfer); } bool tuh_configuration_set(uint8_t daddr, uint8_t config_num, - tuh_xfer_cb_t complete_cb, uintptr_t user_data) -{ + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { TU_LOG_USBH("Set Configuration = %d\r\n", config_num); - - tusb_control_request_t const request = - { - .bmRequestType_bit = - { - .recipient = TUSB_REQ_RCPT_DEVICE, - .type = TUSB_REQ_TYPE_STANDARD, - .direction = TUSB_DIR_OUT - }, - .bRequest = TUSB_REQ_SET_CONFIGURATION, - .wValue = tu_htole16(config_num), - .wIndex = 0, - .wLength = 0 + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_DEVICE, + .type = TUSB_REQ_TYPE_STANDARD, + .direction = TUSB_DIR_OUT + }, + .bRequest = TUSB_REQ_SET_CONFIGURATION, + .wValue = tu_htole16(config_num), + .wIndex = 0, + .wLength = 0 }; - - tuh_xfer_t xfer = - { - .daddr = daddr, - .ep_addr = 0, - .setup = &request, - .buffer = NULL, - .complete_cb = complete_cb, - .user_data = user_data + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = NULL, + .complete_cb = complete_cb, + .user_data = user_data }; return tuh_control_xfer(&xfer); } bool tuh_interface_set(uint8_t daddr, uint8_t itf_num, uint8_t itf_alt, - tuh_xfer_cb_t complete_cb, uintptr_t user_data) -{ + tuh_xfer_cb_t complete_cb, uintptr_t user_data) { TU_LOG_USBH("Set Interface %u Alternate %u\r\n", itf_num, itf_alt); - - tusb_control_request_t const request = - { - .bmRequestType_bit = - { - .recipient = TUSB_REQ_RCPT_DEVICE, - .type = TUSB_REQ_TYPE_STANDARD, - .direction = TUSB_DIR_OUT - }, - .bRequest = TUSB_REQ_SET_INTERFACE, - .wValue = tu_htole16(itf_alt), - .wIndex = tu_htole16(itf_num), - .wLength = 0 + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_DEVICE, + .type = TUSB_REQ_TYPE_STANDARD, + .direction = TUSB_DIR_OUT + }, + .bRequest = TUSB_REQ_SET_INTERFACE, + .wValue = tu_htole16(itf_alt), + .wIndex = tu_htole16(itf_num), + .wLength = 0 }; - - tuh_xfer_t xfer = - { - .daddr = daddr, - .ep_addr = 0, - .setup = &request, - .buffer = NULL, - .complete_cb = complete_cb, - .user_data = user_data + tuh_xfer_t xfer = { + .daddr = daddr, + .ep_addr = 0, + .setup = &request, + .buffer = NULL, + .complete_cb = complete_cb, + .user_data = user_data }; return tuh_control_xfer(&xfer); @@ -1132,43 +1141,42 @@ bool tuh_interface_set(uint8_t daddr, uint8_t itf_num, uint8_t itf_alt, TU_VERIFY(_async_func(__VA_ARGS__, NULL, (uintptr_t) &result), XFER_RESULT_TIMEOUT); \ return (uint8_t) result -uint8_t tuh_descriptor_get_sync(uint8_t daddr, uint8_t type, uint8_t index, void* buffer, uint16_t len) -{ +uint8_t tuh_descriptor_get_sync(uint8_t daddr, uint8_t type, uint8_t index, + void* buffer, uint16_t len) { _CONTROL_SYNC_API(tuh_descriptor_get, daddr, type, index, buffer, len); } -uint8_t tuh_descriptor_get_device_sync(uint8_t daddr, void* buffer, uint16_t len) -{ +uint8_t tuh_descriptor_get_device_sync(uint8_t daddr, void* buffer, uint16_t len) { _CONTROL_SYNC_API(tuh_descriptor_get_device, daddr, buffer, len); } -uint8_t tuh_descriptor_get_configuration_sync(uint8_t daddr, uint8_t index, void* buffer, uint16_t len) -{ +uint8_t tuh_descriptor_get_configuration_sync(uint8_t daddr, uint8_t index, + void* buffer, uint16_t len) { _CONTROL_SYNC_API(tuh_descriptor_get_configuration, daddr, index, buffer, len); } -uint8_t tuh_descriptor_get_hid_report_sync(uint8_t daddr, uint8_t itf_num, uint8_t desc_type, uint8_t index, void* buffer, uint16_t len) -{ +uint8_t tuh_descriptor_get_hid_report_sync(uint8_t daddr, uint8_t itf_num, uint8_t desc_type, uint8_t index, + void* buffer, uint16_t len) { _CONTROL_SYNC_API(tuh_descriptor_get_hid_report, daddr, itf_num, desc_type, index, buffer, len); } -uint8_t tuh_descriptor_get_string_sync(uint8_t daddr, uint8_t index, uint16_t language_id, void* buffer, uint16_t len) -{ +uint8_t tuh_descriptor_get_string_sync(uint8_t daddr, uint8_t index, uint16_t language_id, + void* buffer, uint16_t len) { _CONTROL_SYNC_API(tuh_descriptor_get_string, daddr, index, language_id, buffer, len); } -uint8_t tuh_descriptor_get_manufacturer_string_sync(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len) -{ +uint8_t tuh_descriptor_get_manufacturer_string_sync(uint8_t daddr, uint16_t language_id, + void* buffer, uint16_t len) { _CONTROL_SYNC_API(tuh_descriptor_get_manufacturer_string, daddr, language_id, buffer, len); } -uint8_t tuh_descriptor_get_product_string_sync(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len) -{ +uint8_t tuh_descriptor_get_product_string_sync(uint8_t daddr, uint16_t language_id, + void* buffer, uint16_t len) { _CONTROL_SYNC_API(tuh_descriptor_get_product_string, daddr, language_id, buffer, len); } -uint8_t tuh_descriptor_get_serial_string_sync(uint8_t daddr, uint16_t language_id, void* buffer, uint16_t len) -{ +uint8_t tuh_descriptor_get_serial_string_sync(uint8_t daddr, uint16_t language_id, + void* buffer, uint16_t len) { _CONTROL_SYNC_API(tuh_descriptor_get_serial_string, daddr, language_id, buffer, len); } @@ -1176,9 +1184,7 @@ uint8_t tuh_descriptor_get_serial_string_sync(uint8_t daddr, uint16_t language_i // Detaching //--------------------------------------------------------------------+ -TU_ATTR_ALWAYS_INLINE -static inline bool is_hub_addr(uint8_t daddr) -{ +TU_ATTR_ALWAYS_INLINE static inline bool is_hub_addr(uint8_t daddr) { return (CFG_TUH_HUB > 0) && (daddr > CFG_TUH_DEVICE_MAX); } @@ -1203,62 +1209,63 @@ static inline bool is_hub_addr(uint8_t daddr) //} // a device unplugged from rhport:hub_addr:hub_port -static void process_removing_device(uint8_t rhport, uint8_t hub_addr, uint8_t hub_port) -{ +static void process_removing_device(uint8_t rhport, uint8_t hub_addr, uint8_t hub_port) { //------------- find the all devices (star-network) under port that is unplugged -------------// // TODO mark as disconnected in ISR, also handle dev0 + uint32_t removing_hubs = 0; + do { + for (uint8_t dev_id = 0; dev_id < TOTAL_DEVICES; dev_id++) { + usbh_device_t* dev = &_usbh_devices[dev_id]; + uint8_t const daddr = dev_id + 1; -#if 0 - // index as hub addr, value is hub port (0xFF for invalid) - uint8_t removing_hubs[CFG_TUH_HUB]; - memset(removing_hubs, TUSB_INDEX_INVALID_8, sizeof(removing_hubs)); + // hub_addr = 0 means roothub, hub_port = 0 means all devices of downstream hub + if (dev->rhport == rhport && dev->connected && + (hub_addr == 0 || dev->hub_addr == hub_addr) && + (hub_port == 0 || dev->hub_port == hub_port)) { + TU_LOG_USBH("[%u:%u:%u] unplugged address = %u\r\n", rhport, hub_addr, hub_port, daddr); - removing_hubs[hub_addr-CFG_TUH_DEVICE_MAX] = hub_port; + if (is_hub_addr(daddr)) { + TU_LOG_USBH(" is a HUB device %u\r\n", daddr); + removing_hubs |= TU_BIT(dev_id - CFG_TUH_DEVICE_MAX); + } else { + // Invoke callback before closing driver (maybe call it later ?) + if (tuh_umount_cb) tuh_umount_cb(daddr); + } - // consecutive non-removing hub - uint8_t nop_count = 0; -#endif + // Close class driver + for (uint8_t drv_id = 0; drv_id < TOTAL_DRIVER_COUNT; drv_id++) { + usbh_class_driver_t const* driver = get_driver(drv_id); + if (driver) driver->close(daddr); + } - for (uint8_t dev_id = 0; dev_id < TOTAL_DEVICES; dev_id++) - { - usbh_device_t *dev = &_usbh_devices[dev_id]; - uint8_t const daddr = dev_id + 1; + hcd_device_close(rhport, daddr); + clear_device(dev); - // hub_addr = 0 means roothub, hub_port = 0 means all devices of downstream hub - if (dev->rhport == rhport && dev->connected && - (hub_addr == 0 || dev->hub_addr == hub_addr) && - (hub_port == 0 || dev->hub_port == hub_port)) { - TU_LOG_USBH("Device unplugged address = %u\r\n", daddr); - - if (is_hub_addr(daddr)) { - TU_LOG_USBH(" is a HUB device %u\r\n", daddr); - - // Submit removed event If the device itself is a hub (un-rolled recursive) - // TODO a better to unroll recursrive is using array of removing_hubs and mark it here - hcd_event_t event; - event.rhport = rhport; - event.event_id = HCD_EVENT_DEVICE_REMOVE; - event.connection.hub_addr = daddr; - event.connection.hub_port = 0; - - hcd_event_handler(&event, false); - } else { - // Invoke callback before closing driver (maybe call it later ?) - if (tuh_umount_cb) tuh_umount_cb(daddr); + // abort on-going control xfer on this device if any + if (_ctrl_xfer.daddr == daddr) _set_control_xfer_stage(CONTROL_STAGE_IDLE); } - - // Close class driver - for (uint8_t drv_id = 0; drv_id < TOTAL_DRIVER_COUNT; drv_id++) { - usbh_class_driver_t const * driver = get_driver(drv_id); - if ( driver ) driver->close(daddr); - } - - hcd_device_close(rhport, daddr); - clear_device(dev); - // abort on-going control xfer if any - if (_ctrl_xfer.daddr == daddr) _set_control_xfer_stage(CONTROL_STAGE_IDLE); } - } + + // if removing a hub, we need to remove its downstream devices + #if CFG_TUH_HUB + if (removing_hubs == 0) break; + + // find a marked hub to process + for (uint8_t h_id = 0; h_id < CFG_TUH_HUB; h_id++) { + if (tu_bit_test(removing_hubs, h_id)) { + removing_hubs &= ~TU_BIT(h_id); + + // update hub_addr and hub_port for next loop + hub_addr = h_id + 1 + CFG_TUH_DEVICE_MAX; + hub_port = 0; + break; + } + } + #else + (void) removing_hubs; + break; + #endif + } while(1); } //--------------------------------------------------------------------+ @@ -1327,227 +1334,221 @@ static void process_enumeration(tuh_xfer_t* xfer) { uint8_t const daddr = xfer->daddr; uintptr_t const state = xfer->user_data; - switch(state) - { -#if CFG_TUH_HUB + switch (state) { + #if CFG_TUH_HUB //case ENUM_HUB_GET_STATUS_1: break; - case ENUM_HUB_CLEAR_RESET_1: - { + case ENUM_HUB_CLEAR_RESET_1: { hub_port_status_response_t port_status; memcpy(&port_status, _usbh_ctrl_buf, sizeof(hub_port_status_response_t)); - if ( !port_status.status.connection ) - { + if (!port_status.status.connection) { // device unplugged while delaying, nothing else to do enum_full_complete(); return; } _dev0.speed = (port_status.status.high_speed) ? TUSB_SPEED_HIGH : - (port_status.status.low_speed ) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL; + (port_status.status.low_speed) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL; // Acknowledge Port Reset Change - if (port_status.change.reset) - { - hub_port_clear_reset_change(_dev0.hub_addr, _dev0.hub_port, process_enumeration, ENUM_ADDR0_DEVICE_DESC); + if (port_status.change.reset) { + hub_port_clear_reset_change(_dev0.hub_addr, _dev0.hub_port, + process_enumeration, ENUM_ADDR0_DEVICE_DESC); } + break; } - break; case ENUM_HUB_GET_STATUS_2: osal_task_delay(ENUM_RESET_DELAY); - TU_ASSERT( hub_port_get_status(_dev0.hub_addr, _dev0.hub_port, _usbh_ctrl_buf, process_enumeration, ENUM_HUB_CLEAR_RESET_2), ); - break; + TU_ASSERT(hub_port_get_status(_dev0.hub_addr, _dev0.hub_port, _usbh_ctrl_buf, + process_enumeration, ENUM_HUB_CLEAR_RESET_2),); + break; - case ENUM_HUB_CLEAR_RESET_2: - { + case ENUM_HUB_CLEAR_RESET_2: { hub_port_status_response_t port_status; memcpy(&port_status, _usbh_ctrl_buf, sizeof(hub_port_status_response_t)); // Acknowledge Port Reset Change if Reset Successful - if (port_status.change.reset) - { - TU_ASSERT( hub_port_clear_reset_change(_dev0.hub_addr, _dev0.hub_port, process_enumeration, ENUM_SET_ADDR), ); + if (port_status.change.reset) { + TU_ASSERT(hub_port_clear_reset_change(_dev0.hub_addr, _dev0.hub_port, + process_enumeration, ENUM_SET_ADDR),); } + break; } - break; -#endif + #endif - case ENUM_ADDR0_DEVICE_DESC: - { + case ENUM_ADDR0_DEVICE_DESC: { // TODO probably doesn't need to open/close each enumeration uint8_t const addr0 = 0; - TU_ASSERT( usbh_edpt_control_open(addr0, 8), ); + TU_ASSERT(usbh_edpt_control_open(addr0, 8),); // Get first 8 bytes of device descriptor for Control Endpoint size TU_LOG_USBH("Get 8 byte of Device Descriptor\r\n"); - TU_ASSERT(tuh_descriptor_get_device(addr0, _usbh_ctrl_buf, 8, process_enumeration, ENUM_SET_ADDR), ); + TU_ASSERT(tuh_descriptor_get_device(addr0, _usbh_ctrl_buf, 8, + process_enumeration, ENUM_SET_ADDR),); + break; } - break; #if 0 - case ENUM_RESET_2: - // TODO not used by now, but may be needed for some devices !? - // Reset device again before Set Address - TU_LOG_USBH("Port reset2 \r\n"); - if (_dev0.hub_addr == 0) - { - // connected directly to roothub - hcd_port_reset( _dev0.rhport ); - osal_task_delay(RESET_DELAY); // TODO may not work for no-OS on MCU that require reset_end() since - // sof of controller may not running while resetting - hcd_port_reset_end(_dev0.rhport); - // TODO: fall through to SET ADDRESS, refactor later - } - #if CFG_TUH_HUB - else - { - // after RESET_DELAY the hub_port_reset() already complete - TU_ASSERT( hub_port_reset(_dev0.hub_addr, _dev0.hub_port, process_enumeration, ENUM_HUB_GET_STATUS_2), ); - break; - } - #endif - TU_ATTR_FALLTHROUGH; + case ENUM_RESET_2: + // TODO not used by now, but may be needed for some devices !? + // Reset device again before Set Address + TU_LOG_USBH("Port reset2 \r\n"); + if (_dev0.hub_addr == 0) { + // connected directly to roothub + hcd_port_reset( _dev0.rhport ); + osal_task_delay(RESET_DELAY); // TODO may not work for no-OS on MCU that require reset_end() since + // sof of controller may not running while resetting + hcd_port_reset_end(_dev0.rhport); + // TODO: fall through to SET ADDRESS, refactor later + } +#if CFG_TUH_HUB + else { + // after RESET_DELAY the hub_port_reset() already complete + TU_ASSERT( hub_port_reset(_dev0.hub_addr, _dev0.hub_port, + process_enumeration, ENUM_HUB_GET_STATUS_2), ); + break; + } +#endif + TU_ATTR_FALLTHROUGH; #endif case ENUM_SET_ADDR: enum_request_set_addr(); - break; + break; - case ENUM_GET_DEVICE_DESC: - { + case ENUM_GET_DEVICE_DESC: { uint8_t const new_addr = (uint8_t) tu_le16toh(xfer->setup->wValue); usbh_device_t* new_dev = get_device(new_addr); - TU_ASSERT(new_dev, ); + TU_ASSERT(new_dev,); new_dev->addressed = 1; // Close device 0 hcd_device_close(_dev0.rhport, 0); // open control pipe for new address - TU_ASSERT( usbh_edpt_control_open(new_addr, new_dev->ep0_size), ); + TU_ASSERT(usbh_edpt_control_open(new_addr, new_dev->ep0_size),); // Get full device descriptor TU_LOG_USBH("Get Device Descriptor\r\n"); - TU_ASSERT(tuh_descriptor_get_device(new_addr, _usbh_ctrl_buf, sizeof(tusb_desc_device_t), process_enumeration, ENUM_GET_9BYTE_CONFIG_DESC), ); + TU_ASSERT(tuh_descriptor_get_device(new_addr, _usbh_ctrl_buf, sizeof(tusb_desc_device_t), + process_enumeration, ENUM_GET_9BYTE_CONFIG_DESC),); + break; } - break; - case ENUM_GET_9BYTE_CONFIG_DESC: - { - tusb_desc_device_t const * desc_device = (tusb_desc_device_t const*) _usbh_ctrl_buf; + case ENUM_GET_9BYTE_CONFIG_DESC: { + tusb_desc_device_t const* desc_device = (tusb_desc_device_t const*) _usbh_ctrl_buf; usbh_device_t* dev = get_device(daddr); - TU_ASSERT(dev, ); + TU_ASSERT(dev,); - dev->vid = desc_device->idVendor; - dev->pid = desc_device->idProduct; + dev->vid = desc_device->idVendor; + dev->pid = desc_device->idProduct; dev->i_manufacturer = desc_device->iManufacturer; - dev->i_product = desc_device->iProduct; - dev->i_serial = desc_device->iSerialNumber; + dev->i_product = desc_device->iProduct; + dev->i_serial = desc_device->iSerialNumber; - // if (tuh_attach_cb) tuh_attach_cb((tusb_desc_device_t*) _usbh_ctrl_buf); + // if (tuh_attach_cb) tuh_attach_cb((tusb_desc_device_t*) _usbh_ctrl_buf); // Get 9-byte for total length uint8_t const config_idx = CONFIG_NUM - 1; TU_LOG_USBH("Get Configuration[0] Descriptor (9 bytes)\r\n"); - TU_ASSERT( tuh_descriptor_get_configuration(daddr, config_idx, _usbh_ctrl_buf, 9, process_enumeration, ENUM_GET_FULL_CONFIG_DESC), ); + TU_ASSERT(tuh_descriptor_get_configuration(daddr, config_idx, _usbh_ctrl_buf, 9, + process_enumeration, ENUM_GET_FULL_CONFIG_DESC),); + break; } - break; - case ENUM_GET_FULL_CONFIG_DESC: - { - uint8_t const * desc_config = _usbh_ctrl_buf; + case ENUM_GET_FULL_CONFIG_DESC: { + uint8_t const* desc_config = _usbh_ctrl_buf; // Use offsetof to avoid pointer to the odd/misaligned address - uint16_t const total_len = tu_le16toh( tu_unaligned_read16(desc_config + offsetof(tusb_desc_configuration_t, wTotalLength)) ); + uint16_t const total_len = tu_le16toh( + tu_unaligned_read16(desc_config + offsetof(tusb_desc_configuration_t, wTotalLength))); // TODO not enough buffer to hold configuration descriptor - TU_ASSERT(total_len <= CFG_TUH_ENUMERATION_BUFSIZE, ); + TU_ASSERT(total_len <= CFG_TUH_ENUMERATION_BUFSIZE,); // Get full configuration descriptor uint8_t const config_idx = CONFIG_NUM - 1; TU_LOG_USBH("Get Configuration[0] Descriptor\r\n"); - TU_ASSERT( tuh_descriptor_get_configuration(daddr, config_idx, _usbh_ctrl_buf, total_len, process_enumeration, ENUM_SET_CONFIG), ); + TU_ASSERT(tuh_descriptor_get_configuration(daddr, config_idx, _usbh_ctrl_buf, total_len, + process_enumeration, ENUM_SET_CONFIG),); + break; } - break; case ENUM_SET_CONFIG: - // Parse configuration & set up drivers - // Driver open aren't allowed to make any usb transfer yet - TU_ASSERT( _parse_configuration_descriptor(daddr, (tusb_desc_configuration_t*) _usbh_ctrl_buf), ); + TU_ASSERT(tuh_configuration_set(daddr, CONFIG_NUM, process_enumeration, ENUM_CONFIG_DRIVER),); + break; - TU_ASSERT( tuh_configuration_set(daddr, CONFIG_NUM, process_enumeration, ENUM_CONFIG_DRIVER), ); - break; - - case ENUM_CONFIG_DRIVER: - { + case ENUM_CONFIG_DRIVER: { TU_LOG_USBH("Device configured\r\n"); usbh_device_t* dev = get_device(daddr); - TU_ASSERT(dev, ); + TU_ASSERT(dev,); dev->configured = 1; + // Parse configuration & set up drivers + // driver_open() must not make any usb transfer + TU_ASSERT(_parse_configuration_descriptor(daddr, (tusb_desc_configuration_t*) _usbh_ctrl_buf),); + // Start the Set Configuration process for interfaces (itf = TUSB_INDEX_INVALID_8) // Since driver can perform control transfer within its set_config, this is done asynchronously. // The process continue with next interface when class driver complete its sequence with usbh_driver_set_config_complete() // TODO use separated API instead of using TUSB_INDEX_INVALID_8 usbh_driver_set_config_complete(daddr, TUSB_INDEX_INVALID_8); + break; } - break; default: // stop enumeration if unknown state enum_full_complete(); - break; + break; } } -static bool enum_new_device(hcd_event_t* event) -{ - _dev0.rhport = event->rhport; +static bool enum_new_device(hcd_event_t* event) { + _dev0.rhport = event->rhport; _dev0.hub_addr = event->connection.hub_addr; _dev0.hub_port = event->connection.hub_port; - if (_dev0.hub_addr == 0) - { + if (_dev0.hub_addr == 0) { // connected/disconnected directly with roothub hcd_port_reset(_dev0.rhport); osal_task_delay(ENUM_RESET_DELAY); // TODO may not work for no-OS on MCU that require reset_end() since - // sof of controller may not running while resetting - hcd_port_reset_end( _dev0.rhport); + // sof of controller may not running while resetting + hcd_port_reset_end(_dev0.rhport); // wait until device connection is stable TODO non blocking osal_task_delay(ENUM_CONTACT_DEBOUNCING_DELAY); // device unplugged while delaying - if ( !hcd_port_connect_status(_dev0.rhport) ) { + if (!hcd_port_connect_status(_dev0.rhport)) { enum_full_complete(); return true; } - _dev0.speed = hcd_port_speed_get(_dev0.rhport ); + _dev0.speed = hcd_port_speed_get(_dev0.rhport); TU_LOG_USBH("%s Speed\r\n", tu_str_speed[_dev0.speed]); // fake transfer to kick-off the enumeration process tuh_xfer_t xfer; - xfer.daddr = 0; - xfer.result = XFER_RESULT_SUCCESS; + xfer.daddr = 0; + xfer.result = XFER_RESULT_SUCCESS; xfer.user_data = ENUM_ADDR0_DEVICE_DESC; process_enumeration(&xfer); } #if CFG_TUH_HUB - else - { + else { // connected/disconnected via external hub // wait until device connection is stable TODO non blocking osal_task_delay(ENUM_CONTACT_DEBOUNCING_DELAY); // ENUM_HUB_GET_STATUS //TU_ASSERT( hub_port_get_status(_dev0.hub_addr, _dev0.hub_port, _usbh_ctrl_buf, enum_hub_get_status0_complete, 0) ); - TU_ASSERT( hub_port_get_status(_dev0.hub_addr, _dev0.hub_port, _usbh_ctrl_buf, process_enumeration, ENUM_HUB_CLEAR_RESET_1) ); + TU_ASSERT(hub_port_get_status(_dev0.hub_addr, _dev0.hub_port, _usbh_ctrl_buf, + process_enumeration, ENUM_HUB_CLEAR_RESET_1)); } #endif // hub @@ -1573,58 +1574,48 @@ static uint8_t get_new_address(bool is_hub) { return 0; // invalid address } -static bool enum_request_set_addr(void) -{ - tusb_desc_device_t const * desc_device = (tusb_desc_device_t const*) _usbh_ctrl_buf; +static bool enum_request_set_addr(void) { + tusb_desc_device_t const* desc_device = (tusb_desc_device_t const*) _usbh_ctrl_buf; // Get new address uint8_t const new_addr = get_new_address(desc_device->bDeviceClass == TUSB_CLASS_HUB); TU_ASSERT(new_addr != 0); - TU_LOG_USBH("Set Address = %d\r\n", new_addr); usbh_device_t* new_dev = get_device(new_addr); - - new_dev->rhport = _dev0.rhport; - new_dev->hub_addr = _dev0.hub_addr; - new_dev->hub_port = _dev0.hub_port; - new_dev->speed = _dev0.speed; + new_dev->rhport = _dev0.rhport; + new_dev->hub_addr = _dev0.hub_addr; + new_dev->hub_port = _dev0.hub_port; + new_dev->speed = _dev0.speed; new_dev->connected = 1; - new_dev->ep0_size = desc_device->bMaxPacketSize0; + new_dev->ep0_size = desc_device->bMaxPacketSize0; - tusb_control_request_t const request = - { - .bmRequestType_bit = - { - .recipient = TUSB_REQ_RCPT_DEVICE, - .type = TUSB_REQ_TYPE_STANDARD, - .direction = TUSB_DIR_OUT - }, - .bRequest = TUSB_REQ_SET_ADDRESS, - .wValue = tu_htole16(new_addr), - .wIndex = 0, - .wLength = 0 + tusb_control_request_t const request = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_DEVICE, + .type = TUSB_REQ_TYPE_STANDARD, + .direction = TUSB_DIR_OUT + }, + .bRequest = TUSB_REQ_SET_ADDRESS, + .wValue = tu_htole16(new_addr), + .wIndex = 0, + .wLength = 0 + }; + tuh_xfer_t xfer = { + .daddr = 0, // dev0 + .ep_addr = 0, + .setup = &request, + .buffer = NULL, + .complete_cb = process_enumeration, + .user_data = ENUM_GET_DEVICE_DESC }; - tuh_xfer_t xfer = - { - .daddr = 0, // dev0 - .ep_addr = 0, - .setup = &request, - .buffer = NULL, - .complete_cb = process_enumeration, - .user_data = ENUM_GET_DEVICE_DESC - }; - - TU_ASSERT( tuh_control_xfer(&xfer) ); - + TU_ASSERT(tuh_control_xfer(&xfer)); return true; } -static bool _parse_configuration_descriptor(uint8_t dev_addr, tusb_desc_configuration_t const* desc_cfg) -{ +static bool _parse_configuration_descriptor(uint8_t dev_addr, tusb_desc_configuration_t const* desc_cfg) { usbh_device_t* dev = get_device(dev_addr); - uint16_t const total_len = tu_le16toh(desc_cfg->wTotalLength); uint8_t const* desc_end = ((uint8_t const*) desc_cfg) + total_len; uint8_t const* p_desc = tu_desc_next(desc_cfg); @@ -1632,13 +1623,11 @@ static bool _parse_configuration_descriptor(uint8_t dev_addr, tusb_desc_configur TU_LOG_USBH("Parsing Configuration descriptor (wTotalLength = %u)\r\n", total_len); // parse each interfaces - while( p_desc < desc_end ) - { + while( p_desc < desc_end ) { uint8_t assoc_itf_count = 1; // Class will always starts with Interface Association (if any) and then Interface descriptor - if ( TUSB_DESC_INTERFACE_ASSOCIATION == tu_desc_type(p_desc) ) - { + if ( TUSB_DESC_INTERFACE_ASSOCIATION == tu_desc_type(p_desc) ) { tusb_desc_interface_assoc_t const * desc_iad = (tusb_desc_interface_assoc_t const *) p_desc; assoc_itf_count = desc_iad->bInterfaceCount; @@ -1658,8 +1647,7 @@ static bool _parse_configuration_descriptor(uint8_t dev_addr, tusb_desc_configur if (1 == assoc_itf_count && TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass && AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass && - AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_itf->bInterfaceProtocol) - { + AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_itf->bInterfaceProtocol) { assoc_itf_count = 2; } #endif @@ -1669,8 +1657,7 @@ static bool _parse_configuration_descriptor(uint8_t dev_addr, tusb_desc_configur // manually force associated count = 2 if (1 == assoc_itf_count && TUSB_CLASS_CDC == desc_itf->bInterfaceClass && - CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL == desc_itf->bInterfaceSubClass) - { + CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL == desc_itf->bInterfaceSubClass) { assoc_itf_count = 2; } #endif @@ -1679,18 +1666,14 @@ static bool _parse_configuration_descriptor(uint8_t dev_addr, tusb_desc_configur TU_ASSERT(drv_len >= sizeof(tusb_desc_interface_t)); // Find driver for this interface - for (uint8_t drv_id = 0; drv_id < TOTAL_DRIVER_COUNT; drv_id++) - { + for (uint8_t drv_id = 0; drv_id < TOTAL_DRIVER_COUNT; drv_id++) { usbh_class_driver_t const * driver = get_driver(drv_id); - - if (driver && driver->open(dev->rhport, dev_addr, desc_itf, drv_len) ) - { + if (driver && driver->open(dev->rhport, dev_addr, desc_itf, drv_len) ) { // open successfully TU_LOG_USBH(" %s opened\r\n", driver->name); // bind (associated) interfaces to found driver - for(uint8_t i=0; ibInterfaceNumber+i; // Interface number must not be used already @@ -1704,8 +1687,7 @@ static bool _parse_configuration_descriptor(uint8_t dev_addr, tusb_desc_configur break; // exit driver find loop } - if ( drv_id == TOTAL_DRIVER_COUNT - 1 ) - { + if ( drv_id == TOTAL_DRIVER_COUNT - 1 ) { TU_LOG_USBH("[%u:%u] Interface %u: class = %u subclass = %u protocol = %u is not supported\r\n", dev->rhport, dev_addr, desc_itf->bInterfaceNumber, desc_itf->bInterfaceClass, desc_itf->bInterfaceSubClass, desc_itf->bInterfaceProtocol); } diff --git a/pico-sdk/lib/tinyusb/src/host/usbh.h b/pico-sdk/lib/tinyusb/src/host/usbh.h index 9ff1185..3596841 100644 --- a/pico-sdk/lib/tinyusb/src/host/usbh.h +++ b/pico-sdk/lib/tinyusb/src/host/usbh.h @@ -73,11 +73,25 @@ typedef struct { tusb_desc_interface_t desc; } tuh_itf_info_t; -// ConfigID for tuh_config() +// ConfigID for tuh_configure() enum { - TUH_CFGID_RPI_PIO_USB_CONFIGURATION = OPT_MCU_RP2040 << 8 // cfg_param: pio_usb_configuration_t + TUH_CFGID_INVALID = 0, + TUH_CFGID_RPI_PIO_USB_CONFIGURATION = 100, // cfg_param: pio_usb_configuration_t + TUH_CFGID_MAX3421 = 200, }; +typedef struct { + uint8_t max_nak; // max NAK per endpoint per frame + uint8_t cpuctl; // R16: CPU Control Register + uint8_t pinctl; // R17: Pin Control Register. FDUPSPI bit is ignored +} tuh_configure_max3421_t; + +typedef union { + // For TUH_CFGID_RPI_PIO_USB_CONFIGURATION use pio_usb_configuration_t + + tuh_configure_max3421_t max3421; +} tuh_configure_param_t; + //--------------------------------------------------------------------+ // APPLICATION CALLBACK //--------------------------------------------------------------------+ @@ -109,7 +123,11 @@ bool tuh_configure(uint8_t rhport, uint32_t cfg_id, const void* cfg_param); // Init host stack bool tuh_init(uint8_t rhport); +// Deinit host stack on rhport +bool tuh_deinit(uint8_t rhport); + // Check if host stack is already initialized with any roothub ports +// To check if an rhport is initialized, use tuh_rhport_is_active() bool tuh_inited(void); // Task function should be called in main/rtos loop, extended version of tuh_task() diff --git a/pico-sdk/lib/tinyusb/src/host/usbh_pvt.h b/pico-sdk/lib/tinyusb/src/host/usbh_pvt.h index 4a97a1c..95de915 100644 --- a/pico-sdk/lib/tinyusb/src/host/usbh_pvt.h +++ b/pico-sdk/lib/tinyusb/src/host/usbh_pvt.h @@ -35,7 +35,11 @@ extern "C" { #endif -#define TU_LOG_USBH(...) TU_LOG(CFG_TUH_LOG_LEVEL, __VA_ARGS__) +#define TU_LOG_USBH(...) TU_LOG(CFG_TUH_LOG_LEVEL, __VA_ARGS__) +#define TU_LOG_MEM_USBH(...) TU_LOG_MEM(CFG_TUH_LOG_LEVEL, __VA_ARGS__) +#define TU_LOG_BUF_USBH(...) TU_LOG_BUF(CFG_TUH_LOG_LEVEL, __VA_ARGS__) +#define TU_LOG_INT_USBH(...) TU_LOG_INT(CFG_TUH_LOG_LEVEL, __VA_ARGS__) +#define TU_LOG_HEX_USBH(...) TU_LOG_HEX(CFG_TUH_LOG_LEVEL, __VA_ARGS__) enum { USBH_EPSIZE_BULK_MAX = (TUH_OPT_HIGH_SPEED ? TUSB_EPSIZE_BULK_HS : TUSB_EPSIZE_BULK_FS) @@ -46,11 +50,9 @@ enum { //--------------------------------------------------------------------+ typedef struct { - #if CFG_TUSB_DEBUG >= CFG_TUH_LOG_LEVEL char const* name; - #endif - - void (* const init )(void); + bool (* const init )(void); + bool (* const deinit )(void); bool (* const open )(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const * itf_desc, uint16_t max_len); bool (* const set_config )(uint8_t dev_addr, uint8_t itf_num); bool (* const xfer_cb )(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); diff --git a/pico-sdk/lib/tinyusb/src/osal/osal.h b/pico-sdk/lib/tinyusb/src/osal/osal.h index f092e8f..8f45ea5 100644 --- a/pico-sdk/lib/tinyusb/src/osal/osal.h +++ b/pico-sdk/lib/tinyusb/src/osal/osal.h @@ -74,15 +74,18 @@ typedef void (*osal_task_func_t)( void * ); // Should be implemented as static inline function in osal_port.h header /* osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef); + bool osal_semaphore_delete(osal_semaphore_t semd_hdl); bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr); bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec); void osal_semaphore_reset(osal_semaphore_t sem_hdl); // TODO removed osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef); + bool osal_mutex_delete(osal_mutex_t mutex_hdl) bool osal_mutex_lock (osal_mutex_t sem_hdl, uint32_t msec); bool osal_mutex_unlock(osal_mutex_t mutex_hdl); osal_queue_t osal_queue_create(osal_queue_def_t* qdef); + bool osal_queue_delete(osal_queue_t qhdl); bool osal_queue_receive(osal_queue_t qhdl, void* data, uint32_t msec); bool osal_queue_send(osal_queue_t qhdl, void const * data, bool in_isr); bool osal_queue_empty(osal_queue_t qhdl); diff --git a/pico-sdk/lib/tinyusb/src/osal/osal_freertos.h b/pico-sdk/lib/tinyusb/src/osal/osal_freertos.h deleted file mode 100644 index 501e0bd..0000000 --- a/pico-sdk/lib/tinyusb/src/osal/osal_freertos.h +++ /dev/null @@ -1,214 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#ifndef _TUSB_OSAL_FREERTOS_H_ -#define _TUSB_OSAL_FREERTOS_H_ - -// FreeRTOS Headers -#include TU_INCLUDE_PATH(CFG_TUSB_OS_INC_PATH,FreeRTOS.h) -#include TU_INCLUDE_PATH(CFG_TUSB_OS_INC_PATH,semphr.h) -#include TU_INCLUDE_PATH(CFG_TUSB_OS_INC_PATH,queue.h) -#include TU_INCLUDE_PATH(CFG_TUSB_OS_INC_PATH,task.h) - -#ifdef __cplusplus -extern "C" { -#endif - -//--------------------------------------------------------------------+ -// MACRO CONSTANT TYPEDEF PROTYPES -//--------------------------------------------------------------------+ - -#if configSUPPORT_STATIC_ALLOCATION - typedef StaticSemaphore_t osal_semaphore_def_t; - typedef StaticSemaphore_t osal_mutex_def_t; -#else - // not used therefore defined to smallest possible type to save space - typedef uint8_t osal_semaphore_def_t; - typedef uint8_t osal_mutex_def_t; -#endif - -typedef SemaphoreHandle_t osal_semaphore_t; -typedef SemaphoreHandle_t osal_mutex_t; -typedef QueueHandle_t osal_queue_t; - -typedef struct -{ - uint16_t depth; - uint16_t item_sz; - void* buf; - -#if defined(configQUEUE_REGISTRY_SIZE) && (configQUEUE_REGISTRY_SIZE>0) - char const* name; -#endif - -#if configSUPPORT_STATIC_ALLOCATION - StaticQueue_t sq; -#endif -} osal_queue_def_t; - -#if defined(configQUEUE_REGISTRY_SIZE) && (configQUEUE_REGISTRY_SIZE>0) - #define _OSAL_Q_NAME(_name) .name = #_name -#else - #define _OSAL_Q_NAME(_name) -#endif - -// _int_set is not used with an RTOS -#define OSAL_QUEUE_DEF(_int_set, _name, _depth, _type) \ - static _type _name##_##buf[_depth];\ - osal_queue_def_t _name = { .depth = _depth, .item_sz = sizeof(_type), .buf = _name##_##buf, _OSAL_Q_NAME(_name) }; - -//--------------------------------------------------------------------+ -// TASK API -//--------------------------------------------------------------------+ - -TU_ATTR_ALWAYS_INLINE static inline uint32_t _osal_ms2tick(uint32_t msec) { - if ( msec == OSAL_TIMEOUT_WAIT_FOREVER ) return portMAX_DELAY; - if ( msec == 0 ) return 0; - - uint32_t ticks = pdMS_TO_TICKS(msec); - - // configTICK_RATE_HZ is less than 1000 and 1 tick > 1 ms - // we still need to delay at least 1 tick - if ( ticks == 0 ) ticks = 1; - - return ticks; -} - -TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { - vTaskDelay(pdMS_TO_TICKS(msec)); -} - -//--------------------------------------------------------------------+ -// Semaphore API -//--------------------------------------------------------------------+ - -TU_ATTR_ALWAYS_INLINE static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t *semdef) { -#if configSUPPORT_STATIC_ALLOCATION - return xSemaphoreCreateBinaryStatic(semdef); -#else - (void) semdef; - return xSemaphoreCreateBinary(); -#endif -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) { - if ( !in_isr ) { - return xSemaphoreGive(sem_hdl) != 0; - } else { - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - BaseType_t res = xSemaphoreGiveFromISR(sem_hdl, &xHigherPriorityTaskWoken); - -#if CFG_TUSB_MCU == OPT_MCU_ESP32S2 || CFG_TUSB_MCU == OPT_MCU_ESP32S3 - // not needed after https://github.com/espressif/esp-idf/commit/c5fd79547ac9b7bae06fa660e9f814d18d3390b7 - if ( xHigherPriorityTaskWoken ) portYIELD_FROM_ISR(); -#else - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); -#endif - - return res != 0; - } -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec) { - return xSemaphoreTake(sem_hdl, _osal_ms2tick(msec)); -} - -TU_ATTR_ALWAYS_INLINE static inline void osal_semaphore_reset(osal_semaphore_t const sem_hdl) { - xQueueReset(sem_hdl); -} - -//--------------------------------------------------------------------+ -// MUTEX API (priority inheritance) -//--------------------------------------------------------------------+ - -TU_ATTR_ALWAYS_INLINE static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t *mdef) { -#if configSUPPORT_STATIC_ALLOCATION - return xSemaphoreCreateMutexStatic(mdef); -#else - (void) mdef; - return xSemaphoreCreateMutex(); -#endif -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_lock(osal_mutex_t mutex_hdl, uint32_t msec) { - return osal_semaphore_wait(mutex_hdl, msec); -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl) { - return xSemaphoreGive(mutex_hdl); -} - -//--------------------------------------------------------------------+ -// QUEUE API -//--------------------------------------------------------------------+ - -TU_ATTR_ALWAYS_INLINE static inline osal_queue_t osal_queue_create(osal_queue_def_t* qdef) { - osal_queue_t q; - -#if configSUPPORT_STATIC_ALLOCATION - q = xQueueCreateStatic(qdef->depth, qdef->item_sz, (uint8_t*) qdef->buf, &qdef->sq); -#else - q = xQueueCreate(qdef->depth, qdef->item_sz); -#endif - -#if defined(configQUEUE_REGISTRY_SIZE) && (configQUEUE_REGISTRY_SIZE>0) - vQueueAddToRegistry(q, qdef->name); -#endif - - return q; -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_receive(osal_queue_t qhdl, void* data, uint32_t msec) { - return xQueueReceive(qhdl, data, _osal_ms2tick(msec)); -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_send(osal_queue_t qhdl, void const *data, bool in_isr) { - if ( !in_isr ) { - return xQueueSendToBack(qhdl, data, OSAL_TIMEOUT_WAIT_FOREVER) != 0; - } else { - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - BaseType_t res = xQueueSendToBackFromISR(qhdl, data, &xHigherPriorityTaskWoken); - -#if CFG_TUSB_MCU == OPT_MCU_ESP32S2 || CFG_TUSB_MCU == OPT_MCU_ESP32S3 - // not needed after https://github.com/espressif/esp-idf/commit/c5fd79547ac9b7bae06fa660e9f814d18d3390b7 (IDF v5) - if ( xHigherPriorityTaskWoken ) portYIELD_FROM_ISR(); -#else - portYIELD_FROM_ISR(xHigherPriorityTaskWoken); -#endif - - return res != 0; - } -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_empty(osal_queue_t qhdl) { - return uxQueueMessagesWaiting(qhdl) == 0; -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/pico-sdk/lib/tinyusb/src/osal/osal_mynewt.h b/pico-sdk/lib/tinyusb/src/osal/osal_mynewt.h deleted file mode 100644 index b8ea208..0000000 --- a/pico-sdk/lib/tinyusb/src/osal/osal_mynewt.h +++ /dev/null @@ -1,176 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#ifndef OSAL_MYNEWT_H_ -#define OSAL_MYNEWT_H_ - -#include "os/os.h" - -#ifdef __cplusplus - extern "C" { -#endif - -//--------------------------------------------------------------------+ -// TASK API -//--------------------------------------------------------------------+ -TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) -{ - os_time_delay( os_time_ms_to_ticks32(msec) ); -} - -//--------------------------------------------------------------------+ -// Semaphore API -//--------------------------------------------------------------------+ -typedef struct os_sem osal_semaphore_def_t; -typedef struct os_sem* osal_semaphore_t; - -TU_ATTR_ALWAYS_INLINE static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef) -{ - return (os_sem_init(semdef, 0) == OS_OK) ? (osal_semaphore_t) semdef : NULL; -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) -{ - (void) in_isr; - return os_sem_release(sem_hdl) == OS_OK; -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec) -{ - uint32_t const ticks = (msec == OSAL_TIMEOUT_WAIT_FOREVER) ? OS_TIMEOUT_NEVER : os_time_ms_to_ticks32(msec); - return os_sem_pend(sem_hdl, ticks) == OS_OK; -} - -static inline void osal_semaphore_reset(osal_semaphore_t sem_hdl) -{ - // TODO implement later -} - -//--------------------------------------------------------------------+ -// MUTEX API (priority inheritance) -//--------------------------------------------------------------------+ -typedef struct os_mutex osal_mutex_def_t; -typedef struct os_mutex* osal_mutex_t; - -TU_ATTR_ALWAYS_INLINE static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef) -{ - return (os_mutex_init(mdef) == OS_OK) ? (osal_mutex_t) mdef : NULL; -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_lock(osal_mutex_t mutex_hdl, uint32_t msec) -{ - uint32_t const ticks = (msec == OSAL_TIMEOUT_WAIT_FOREVER) ? OS_TIMEOUT_NEVER : os_time_ms_to_ticks32(msec); - return os_mutex_pend(mutex_hdl, ticks) == OS_OK; -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl) -{ - return os_mutex_release(mutex_hdl) == OS_OK; -} - -//--------------------------------------------------------------------+ -// QUEUE API -//--------------------------------------------------------------------+ - -// role device/host is used by OS NONE for mutex (disable usb isr) only -#define OSAL_QUEUE_DEF(_int_set, _name, _depth, _type) \ - static _type _name##_##buf[_depth];\ - static struct os_event _name##_##evbuf[_depth];\ - osal_queue_def_t _name = { .depth = _depth, .item_sz = sizeof(_type), .buf = _name##_##buf, .evbuf = _name##_##evbuf};\ - -typedef struct -{ - uint16_t depth; - uint16_t item_sz; - void* buf; - void* evbuf; - - struct os_mempool mpool; - struct os_mempool epool; - - struct os_eventq evq; -}osal_queue_def_t; - -typedef osal_queue_def_t* osal_queue_t; - -TU_ATTR_ALWAYS_INLINE static inline osal_queue_t osal_queue_create(osal_queue_def_t* qdef) -{ - if ( OS_OK != os_mempool_init(&qdef->mpool, qdef->depth, qdef->item_sz, qdef->buf, "usbd queue") ) return NULL; - if ( OS_OK != os_mempool_init(&qdef->epool, qdef->depth, sizeof(struct os_event), qdef->evbuf, "usbd evqueue") ) return NULL; - - os_eventq_init(&qdef->evq); - return (osal_queue_t) qdef; -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_receive(osal_queue_t qhdl, void* data, uint32_t msec) -{ - (void) msec; // os_eventq_get() does not take timeout, always behave as msec = WAIT_FOREVER - - struct os_event* ev; - ev = os_eventq_get(&qhdl->evq); - - memcpy(data, ev->ev_arg, qhdl->item_sz); // copy message - os_memblock_put(&qhdl->mpool, ev->ev_arg); // put back mem block - os_memblock_put(&qhdl->epool, ev); // put back ev block - - return true; -} - -static inline bool osal_queue_send(osal_queue_t qhdl, void const * data, bool in_isr) -{ - (void) in_isr; - - // get a block from mem pool for data - void* ptr = os_memblock_get(&qhdl->mpool); - if (!ptr) return false; - memcpy(ptr, data, qhdl->item_sz); - - // get a block from event pool to put into queue - struct os_event* ev = (struct os_event*) os_memblock_get(&qhdl->epool); - if (!ev) - { - os_memblock_put(&qhdl->mpool, ptr); - return false; - } - tu_memclr(ev, sizeof(struct os_event)); - ev->ev_arg = ptr; - - os_eventq_put(&qhdl->evq, ev); - - return true; -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_empty(osal_queue_t qhdl) -{ - return STAILQ_EMPTY(&qhdl->evq.evq_list); -} - - -#ifdef __cplusplus - } -#endif - -#endif /* OSAL_MYNEWT_H_ */ diff --git a/pico-sdk/lib/tinyusb/src/osal/osal_none.h b/pico-sdk/lib/tinyusb/src/osal/osal_none.h index a07d398..c93f7a8 100644 --- a/pico-sdk/lib/tinyusb/src/osal/osal_none.h +++ b/pico-sdk/lib/tinyusb/src/osal/osal_none.h @@ -54,6 +54,12 @@ TU_ATTR_ALWAYS_INLINE static inline osal_semaphore_t osal_semaphore_create(osal_ return semdef; } +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_delete(osal_semaphore_t semd_hdl) { + (void) semd_hdl; + return true; // nothing to do +} + + TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) { (void) in_isr; sem_hdl->count++; @@ -90,6 +96,11 @@ TU_ATTR_ALWAYS_INLINE static inline osal_mutex_t osal_mutex_create(osal_mutex_de return mdef; } +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_delete(osal_mutex_t mutex_hdl) { + (void) mutex_hdl; + return true; // nothing to do +} + TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_lock (osal_mutex_t mutex_hdl, uint32_t msec) { return osal_semaphore_wait(mutex_hdl, msec); } @@ -143,6 +154,11 @@ TU_ATTR_ALWAYS_INLINE static inline osal_queue_t osal_queue_create(osal_queue_de return (osal_queue_t) qdef; } +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_delete(osal_queue_t qhdl) { + (void) qhdl; + return true; // nothing to do +} + TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_receive(osal_queue_t qhdl, void* data, uint32_t msec) { (void) msec; // not used, always behave as msec = 0 @@ -164,7 +180,6 @@ TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_send(osal_queue_t qhdl, void _osal_q_unlock(qhdl); } - TU_ASSERT(success); return success; } diff --git a/pico-sdk/lib/tinyusb/src/osal/osal_pico.h b/pico-sdk/lib/tinyusb/src/osal/osal_pico.h index e6efa09..315de09 100644 --- a/pico-sdk/lib/tinyusb/src/osal/osal_pico.h +++ b/pico-sdk/lib/tinyusb/src/osal/osal_pico.h @@ -24,8 +24,8 @@ * This file is part of the TinyUSB stack. */ -#ifndef _TUSB_OSAL_PICO_H_ -#define _TUSB_OSAL_PICO_H_ +#ifndef TUSB_OSAL_PICO_H_ +#define TUSB_OSAL_PICO_H_ #include "pico/time.h" #include "pico/sem.h" @@ -33,42 +33,42 @@ #include "pico/critical_section.h" #ifdef __cplusplus - extern "C" { +extern "C" { #endif //--------------------------------------------------------------------+ // TASK API //--------------------------------------------------------------------+ -TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) -{ +TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { sleep_ms(msec); } //--------------------------------------------------------------------+ // Binary Semaphore API //--------------------------------------------------------------------+ -typedef struct semaphore osal_semaphore_def_t, *osal_semaphore_t; +typedef struct semaphore osal_semaphore_def_t, * osal_semaphore_t; -TU_ATTR_ALWAYS_INLINE static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef) -{ +TU_ATTR_ALWAYS_INLINE static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef) { sem_init(semdef, 0, 255); return semdef; } -TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) -{ +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_delete(osal_semaphore_t semd_hdl) { + (void) semd_hdl; + return true; // nothing to do +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) { (void) in_isr; sem_release(sem_hdl); return true; } -TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_wait (osal_semaphore_t sem_hdl, uint32_t msec) -{ +TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec) { return sem_acquire_timeout_ms(sem_hdl, msec); } -TU_ATTR_ALWAYS_INLINE static inline void osal_semaphore_reset(osal_semaphore_t sem_hdl) -{ +TU_ATTR_ALWAYS_INLINE static inline void osal_semaphore_reset(osal_semaphore_t sem_hdl) { sem_reset(sem_hdl, 0); } @@ -76,21 +76,23 @@ TU_ATTR_ALWAYS_INLINE static inline void osal_semaphore_reset(osal_semaphore_t s // MUTEX API // Within tinyusb, mutex is never used in ISR context //--------------------------------------------------------------------+ -typedef struct mutex osal_mutex_def_t, *osal_mutex_t; +typedef struct mutex osal_mutex_def_t, * osal_mutex_t; -TU_ATTR_ALWAYS_INLINE static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef) -{ +TU_ATTR_ALWAYS_INLINE static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef) { mutex_init(mdef); return mdef; } -TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_lock (osal_mutex_t mutex_hdl, uint32_t msec) -{ +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_delete(osal_mutex_t mutex_hdl) { + (void) mutex_hdl; + return true; // nothing to do +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_lock(osal_mutex_t mutex_hdl, uint32_t msec) { return mutex_enter_timeout_ms(mutex_hdl, msec); } -TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl) -{ +TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl) { mutex_exit(mutex_hdl); return true; } @@ -100,75 +102,53 @@ TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_unlock(osal_mutex_t mutex_hd //--------------------------------------------------------------------+ #include "common/tusb_fifo.h" -typedef struct -{ - tu_fifo_t ff; - struct critical_section critsec; // osal_queue may be used in IRQs, so need critical section +typedef struct { + tu_fifo_t ff; + struct critical_section critsec; // osal_queue may be used in IRQs, so need critical section } osal_queue_def_t; typedef osal_queue_def_t* osal_queue_t; // role device/host is used by OS NONE for mutex (disable usb isr) only -#define OSAL_QUEUE_DEF(_int_set, _name, _depth, _type) \ +#define OSAL_QUEUE_DEF(_int_set, _name, _depth, _type) \ uint8_t _name##_buf[_depth*sizeof(_type)]; \ osal_queue_def_t _name = { \ .ff = TU_FIFO_INIT(_name##_buf, _depth, _type, false) \ } -// lock queue by disable USB interrupt -TU_ATTR_ALWAYS_INLINE static inline void _osal_q_lock(osal_queue_t qhdl) -{ - critical_section_enter_blocking(&qhdl->critsec); -} - -// unlock queue -TU_ATTR_ALWAYS_INLINE static inline void _osal_q_unlock(osal_queue_t qhdl) -{ - critical_section_exit(&qhdl->critsec); -} - -TU_ATTR_ALWAYS_INLINE static inline osal_queue_t osal_queue_create(osal_queue_def_t* qdef) -{ +TU_ATTR_ALWAYS_INLINE static inline osal_queue_t osal_queue_create(osal_queue_def_t* qdef) { critical_section_init(&qdef->critsec); tu_fifo_clear(&qdef->ff); return (osal_queue_t) qdef; } -TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_receive(osal_queue_t qhdl, void* data, uint32_t msec) -{ +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_delete(osal_queue_t qhdl) { + osal_queue_def_t* qdef = (osal_queue_def_t*) qhdl; + critical_section_deinit(&qdef->critsec); + return true; +} + +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_receive(osal_queue_t qhdl, void* data, uint32_t msec) { (void) msec; // not used, always behave as msec = 0 - // TODO: revisit... docs say that mutexes are never used from IRQ context, - // however osal_queue_recieve may be. therefore my assumption is that - // the fifo mutex is not populated for queues used from an IRQ context - //assert(!qhdl->ff.mutex); - - _osal_q_lock(qhdl); + critical_section_enter_blocking(&qhdl->critsec); bool success = tu_fifo_read(&qhdl->ff, data); - _osal_q_unlock(qhdl); + critical_section_exit(&qhdl->critsec); return success; } -TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_send(osal_queue_t qhdl, void const * data, bool in_isr) -{ - // TODO: revisit... docs say that mutexes are never used from IRQ context, - // however osal_queue_recieve may be. therefore my assumption is that - // the fifo mutex is not populated for queues used from an IRQ context - //assert(!qhdl->ff.mutex); +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_send(osal_queue_t qhdl, void const* data, bool in_isr) { (void) in_isr; - _osal_q_lock(qhdl); + critical_section_enter_blocking(&qhdl->critsec); bool success = tu_fifo_write(&qhdl->ff, data); - _osal_q_unlock(qhdl); - - TU_ASSERT(success); + critical_section_exit(&qhdl->critsec); return success; } -TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_empty(osal_queue_t qhdl) -{ +TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_empty(osal_queue_t qhdl) { // TODO: revisit; whether this is true or not currently, tu_fifo_empty is a single // volatile read. @@ -178,7 +158,7 @@ TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_empty(osal_queue_t qhdl) } #ifdef __cplusplus - } +} #endif -#endif /* _TUSB_OSAL_PICO_H_ */ +#endif diff --git a/pico-sdk/lib/tinyusb/src/osal/osal_rtthread.h b/pico-sdk/lib/tinyusb/src/osal/osal_rtthread.h deleted file mode 100644 index 18eb9c6..0000000 --- a/pico-sdk/lib/tinyusb/src/osal/osal_rtthread.h +++ /dev/null @@ -1,132 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2020 tfx2001 (2479727366@qq.com) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#ifndef _TUSB_OSAL_RTTHREAD_H_ -#define _TUSB_OSAL_RTTHREAD_H_ - -// RT-Thread Headers -#include "rtthread.h" - -#ifdef __cplusplus -extern "C" { -#endif - -//--------------------------------------------------------------------+ -// TASK API -//--------------------------------------------------------------------+ -TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) { - rt_thread_mdelay(msec); -} - -//--------------------------------------------------------------------+ -// Semaphore API -//--------------------------------------------------------------------+ -typedef struct rt_semaphore osal_semaphore_def_t; -typedef rt_sem_t osal_semaphore_t; - -TU_ATTR_ALWAYS_INLINE static inline osal_semaphore_t -osal_semaphore_create(osal_semaphore_def_t *semdef) { - rt_sem_init(semdef, "tusb", 0, RT_IPC_FLAG_PRIO); - return semdef; -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) { - (void) in_isr; - return rt_sem_release(sem_hdl) == RT_EOK; -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec) { - return rt_sem_take(sem_hdl, rt_tick_from_millisecond((rt_int32_t) msec)) == RT_EOK; -} - -TU_ATTR_ALWAYS_INLINE static inline void osal_semaphore_reset(osal_semaphore_t const sem_hdl) { - rt_sem_control(sem_hdl, RT_IPC_CMD_RESET, 0); -} - -//--------------------------------------------------------------------+ -// MUTEX API (priority inheritance) -//--------------------------------------------------------------------+ -typedef struct rt_mutex osal_mutex_def_t; -typedef rt_mutex_t osal_mutex_t; - -TU_ATTR_ALWAYS_INLINE static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t *mdef) { - rt_mutex_init(mdef, "tusb", RT_IPC_FLAG_PRIO); - return mdef; -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_lock(osal_mutex_t mutex_hdl, uint32_t msec) { - return rt_mutex_take(mutex_hdl, rt_tick_from_millisecond((rt_int32_t) msec)) == RT_EOK; -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl) { - return rt_mutex_release(mutex_hdl) == RT_EOK; -} - -//--------------------------------------------------------------------+ -// QUEUE API -//--------------------------------------------------------------------+ - -// role device/host is used by OS NONE for mutex (disable usb isr) only -#define OSAL_QUEUE_DEF(_int_set, _name, _depth, _type) \ - static _type _name##_##buf[_depth]; \ - osal_queue_def_t _name = { .depth = _depth, .item_sz = sizeof(_type), .buf = _name##_##buf }; - -typedef struct { - uint16_t depth; - uint16_t item_sz; - void *buf; - - struct rt_messagequeue sq; -} osal_queue_def_t; - -typedef rt_mq_t osal_queue_t; - -TU_ATTR_ALWAYS_INLINE static inline osal_queue_t osal_queue_create(osal_queue_def_t *qdef) { - rt_mq_init(&(qdef->sq), "tusb", qdef->buf, qdef->item_sz, - qdef->item_sz * qdef->depth, RT_IPC_FLAG_PRIO); - return &(qdef->sq); -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_receive(osal_queue_t qhdl, void *data, uint32_t msec) { - - rt_tick_t tick = rt_tick_from_millisecond((rt_int32_t) msec); - return rt_mq_recv(qhdl, data, qhdl->msg_size, tick) == RT_EOK; -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_send(osal_queue_t qhdl, void const *data, bool in_isr) { - (void) in_isr; - return rt_mq_send(qhdl, (void *)data, qhdl->msg_size) == RT_EOK; -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_empty(osal_queue_t qhdl) { - return (qhdl->entry) == 0; -} - -#ifdef __cplusplus -} -#endif - -#endif /* _TUSB_OSAL_RTTHREAD_H_ */ diff --git a/pico-sdk/lib/tinyusb/src/osal/osal_rtx4.h b/pico-sdk/lib/tinyusb/src/osal/osal_rtx4.h deleted file mode 100644 index e443135..0000000 --- a/pico-sdk/lib/tinyusb/src/osal/osal_rtx4.h +++ /dev/null @@ -1,170 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2021 Tian Yunhao (t123yh) - * Copyright (c) 2019 Ha Thach (tinyusb.org) - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * This file is part of the TinyUSB stack. - */ - -#ifndef _TUSB_OSAL_RTX4_H_ -#define _TUSB_OSAL_RTX4_H_ - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -//--------------------------------------------------------------------+ -// TASK API -//--------------------------------------------------------------------+ -TU_ATTR_ALWAYS_INLINE static inline void osal_task_delay(uint32_t msec) -{ - uint16_t hi = msec >> 16; - uint16_t lo = msec; - while (hi--) { - os_dly_wait(0xFFFE); - } - os_dly_wait(lo); -} - -TU_ATTR_ALWAYS_INLINE static inline uint16_t msec2wait(uint32_t msec) { - if (msec == OSAL_TIMEOUT_WAIT_FOREVER) - return 0xFFFF; - else if (msec >= 0xFFFE) - return 0xFFFE; - else - return msec; -} - -//--------------------------------------------------------------------+ -// Semaphore API -//--------------------------------------------------------------------+ -typedef OS_SEM osal_semaphore_def_t; -typedef OS_ID osal_semaphore_t; - -TU_ATTR_ALWAYS_INLINE static inline OS_ID osal_semaphore_create(osal_semaphore_def_t* semdef) { - os_sem_init(semdef, 0); - return semdef; -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) { - if ( !in_isr ) { - os_sem_send(sem_hdl); - } else { - isr_sem_send(sem_hdl); - } - return true; -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_semaphore_wait (osal_semaphore_t sem_hdl, uint32_t msec) { - return os_sem_wait(sem_hdl, msec2wait(msec)) != OS_R_TMO; -} - -TU_ATTR_ALWAYS_INLINE static inline void osal_semaphore_reset(osal_semaphore_t const sem_hdl) { - // TODO: implement -} - -//--------------------------------------------------------------------+ -// MUTEX API (priority inheritance) -//--------------------------------------------------------------------+ -typedef OS_MUT osal_mutex_def_t; -typedef OS_ID osal_mutex_t; - -TU_ATTR_ALWAYS_INLINE static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef) -{ - os_mut_init(mdef); - return mdef; -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_lock (osal_mutex_t mutex_hdl, uint32_t msec) -{ - return os_mut_wait(mutex_hdl, msec2wait(msec)) != OS_R_TMO; -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl) -{ - return os_mut_release(mutex_hdl) == OS_R_OK; -} - -//--------------------------------------------------------------------+ -// QUEUE API -//--------------------------------------------------------------------+ - -// role device/host is used by OS NONE for mutex (disable usb isr) only -#define OSAL_QUEUE_DEF(_int_set, _name, _depth, _type) \ - os_mbx_declare(_name##__mbox, _depth); \ - _declare_box(_name##__pool, sizeof(_type), _depth); \ - osal_queue_def_t _name = { .depth = _depth, .item_sz = sizeof(_type), .pool = _name##__pool, .mbox = _name##__mbox }; - - -typedef struct -{ - uint16_t depth; - uint16_t item_sz; - U32* pool; - U32* mbox; -}osal_queue_def_t; - -typedef osal_queue_def_t* osal_queue_t; - -TU_ATTR_ALWAYS_INLINE static inline osal_queue_t osal_queue_create(osal_queue_def_t* qdef) -{ - os_mbx_init(qdef->mbox, (qdef->depth + 4) * 4); - _init_box(qdef->pool, ((qdef->item_sz+3)/4)*(qdef->depth) + 3, qdef->item_sz); - return qdef; -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_receive(osal_queue_t qhdl, void* data, uint32_t msec) -{ - void* buf; - os_mbx_wait(qhdl->mbox, &buf, msec2wait(msec)); - memcpy(data, buf, qhdl->item_sz); - _free_box(qhdl->pool, buf); - return true; -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_send(osal_queue_t qhdl, void const * data, bool in_isr) -{ - void* buf = _alloc_box(qhdl->pool); - memcpy(buf, data, qhdl->item_sz); - if ( !in_isr ) - { - os_mbx_send(qhdl->mbox, buf, 0xFFFF); - } - else - { - isr_mbx_send(qhdl->mbox, buf); - } - return true; -} - -TU_ATTR_ALWAYS_INLINE static inline bool osal_queue_empty(osal_queue_t qhdl) -{ - return os_mbx_check(qhdl->mbox) == qhdl->depth; -} - -#ifdef __cplusplus - } -#endif - -#endif diff --git a/pico-sdk/lib/tinyusb/src/portable/raspberrypi/rp2040/dcd_rp2040.c b/pico-sdk/lib/tinyusb/src/portable/raspberrypi/rp2040/dcd_rp2040.c index e8cee73..bc0deee 100644 --- a/pico-sdk/lib/tinyusb/src/portable/raspberrypi/rp2040/dcd_rp2040.c +++ b/pico-sdk/lib/tinyusb/src/portable/raspberrypi/rp2040/dcd_rp2040.c @@ -48,7 +48,7 @@ *------------------------------------------------------------------*/ // Init these in dcd_init -static uint8_t *next_buffer_ptr; +static uint8_t* next_buffer_ptr; // USB_MAX_ENDPOINTS Endpoints, direction TUSB_DIR_OUT for out and TUSB_DIR_IN for in. static struct hw_endpoint hw_endpoints[USB_MAX_ENDPOINTS][2]; @@ -56,79 +56,70 @@ static struct hw_endpoint hw_endpoints[USB_MAX_ENDPOINTS][2]; // SOF may be used by remote wakeup as RESUME, this indicate whether SOF is actually used by usbd static bool _sof_enable = false; -TU_ATTR_ALWAYS_INLINE static inline struct hw_endpoint *hw_endpoint_get_by_num(uint8_t num, tusb_dir_t dir) -{ +TU_ATTR_ALWAYS_INLINE static inline struct hw_endpoint* hw_endpoint_get_by_num(uint8_t num, tusb_dir_t dir) { return &hw_endpoints[num][dir]; } -static struct hw_endpoint *hw_endpoint_get_by_addr(uint8_t ep_addr) -{ +TU_ATTR_ALWAYS_INLINE static inline struct hw_endpoint* hw_endpoint_get_by_addr(uint8_t ep_addr) { uint8_t num = tu_edpt_number(ep_addr); tusb_dir_t dir = tu_edpt_dir(ep_addr); return hw_endpoint_get_by_num(num, dir); } -static void _hw_endpoint_alloc(struct hw_endpoint *ep, uint8_t transfer_type) -{ +static void _hw_endpoint_alloc(struct hw_endpoint* ep, uint8_t transfer_type) { // size must be multiple of 64 uint size = tu_div_ceil(ep->wMaxPacketSize, 64) * 64u; // double buffered Bulk endpoint - if ( transfer_type == TUSB_XFER_BULK ) - { + if (transfer_type == TUSB_XFER_BULK) { size *= 2u; } ep->hw_data_buf = next_buffer_ptr; next_buffer_ptr += size; - assert(((uintptr_t )next_buffer_ptr & 0b111111u) == 0); + assert(((uintptr_t) next_buffer_ptr & 0b111111u) == 0); uint dpram_offset = hw_data_offset(ep->hw_data_buf); hard_assert(hw_data_offset(next_buffer_ptr) <= USB_DPRAM_MAX); pico_info(" Allocated %d bytes at offset 0x%x (0x%p)\r\n", size, dpram_offset, ep->hw_data_buf); // Fill in endpoint control register with buffer offset - uint32_t const reg = EP_CTRL_ENABLE_BITS | ((uint)transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | dpram_offset; + uint32_t const reg = EP_CTRL_ENABLE_BITS | ((uint) transfer_type << EP_CTRL_BUFFER_TYPE_LSB) | dpram_offset; *ep->endpoint_control = reg; } -static void _hw_endpoint_close(struct hw_endpoint *ep) -{ - // Clear hardware registers and then zero the struct - // Clears endpoint enable - *ep->endpoint_control = 0; - // Clears buffer available, etc - *ep->buffer_control = 0; - // Clear any endpoint state - memset(ep, 0, sizeof(struct hw_endpoint)); +static void _hw_endpoint_close(struct hw_endpoint* ep) { + // Clear hardware registers and then zero the struct + // Clears endpoint enable + *ep->endpoint_control = 0; + // Clears buffer available, etc + *ep->buffer_control = 0; + // Clear any endpoint state + memset(ep, 0, sizeof(struct hw_endpoint)); - // Reclaim buffer space if all endpoints are closed - bool reclaim_buffers = true; - for ( uint8_t i = 1; i < USB_MAX_ENDPOINTS; i++ ) - { - if (hw_endpoint_get_by_num(i, TUSB_DIR_OUT)->hw_data_buf != NULL || hw_endpoint_get_by_num(i, TUSB_DIR_IN)->hw_data_buf != NULL) - { - reclaim_buffers = false; - break; - } - } - if (reclaim_buffers) - { - next_buffer_ptr = &usb_dpram->epx_data[0]; + // Reclaim buffer space if all endpoints are closed + bool reclaim_buffers = true; + for (uint8_t i = 1; i < USB_MAX_ENDPOINTS; i++) { + if (hw_endpoint_get_by_num(i, TUSB_DIR_OUT)->hw_data_buf != NULL || + hw_endpoint_get_by_num(i, TUSB_DIR_IN)->hw_data_buf != NULL) { + reclaim_buffers = false; + break; } + } + if (reclaim_buffers) { + next_buffer_ptr = &usb_dpram->epx_data[0]; + } } -static void hw_endpoint_close(uint8_t ep_addr) -{ - struct hw_endpoint *ep = hw_endpoint_get_by_addr(ep_addr); - _hw_endpoint_close(ep); +static void hw_endpoint_close(uint8_t ep_addr) { + struct hw_endpoint* ep = hw_endpoint_get_by_addr(ep_addr); + _hw_endpoint_close(ep); } -static void hw_endpoint_init(uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type) -{ - struct hw_endpoint *ep = hw_endpoint_get_by_addr(ep_addr); +static void hw_endpoint_init(uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t transfer_type) { + struct hw_endpoint* ep = hw_endpoint_get_by_addr(ep_addr); const uint8_t num = tu_edpt_number(ep_addr); const tusb_dir_t dir = tu_edpt_dir(ep_addr); @@ -143,35 +134,26 @@ static void hw_endpoint_init(uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t t ep->transfer_type = transfer_type; // Every endpoint has a buffer control register in dpram - if ( dir == TUSB_DIR_IN ) - { + if (dir == TUSB_DIR_IN) { ep->buffer_control = &usb_dpram->ep_buf_ctrl[num].in; - } - else - { + } else { ep->buffer_control = &usb_dpram->ep_buf_ctrl[num].out; } // Clear existing buffer control state *ep->buffer_control = 0; - if ( num == 0 ) - { + if (num == 0) { // EP0 has no endpoint control register because the buffer offsets are fixed ep->endpoint_control = NULL; // Buffer offset is fixed (also double buffered) ep->hw_data_buf = (uint8_t*) &usb_dpram->ep0_buf_a[0]; - } - else - { + } else { // Set the endpoint control register (starts at EP1, hence num-1) - if ( dir == TUSB_DIR_IN ) - { + if (dir == TUSB_DIR_IN) { ep->endpoint_control = &usb_dpram->ep_ctrl[num - 1].in; - } - else - { + } else { ep->endpoint_control = &usb_dpram->ep_ctrl[num - 1].out; } @@ -180,76 +162,82 @@ static void hw_endpoint_init(uint8_t ep_addr, uint16_t wMaxPacketSize, uint8_t t } } -static void hw_endpoint_xfer(uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes) -{ - struct hw_endpoint *ep = hw_endpoint_get_by_addr(ep_addr); - hw_endpoint_xfer_start(ep, buffer, total_bytes); +static void hw_endpoint_xfer(uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes) { + struct hw_endpoint* ep = hw_endpoint_get_by_addr(ep_addr); + hw_endpoint_xfer_start(ep, buffer, total_bytes); } -static void __tusb_irq_path_func(hw_handle_buff_status)(void) -{ - uint32_t remaining_buffers = usb_hw->buf_status; - pico_trace("buf_status = 0x%08lx\r\n", remaining_buffers); - uint bit = 1u; - for (uint8_t i = 0; remaining_buffers && i < USB_MAX_ENDPOINTS * 2; i++) - { - if (remaining_buffers & bit) - { - // clear this in advance - usb_hw_clear->buf_status = bit; +static void __tusb_irq_path_func(hw_handle_buff_status)(void) { + uint32_t remaining_buffers = usb_hw->buf_status; + pico_trace("buf_status = 0x%08lx\r\n", remaining_buffers); + uint bit = 1u; + for (uint8_t i = 0; remaining_buffers && i < USB_MAX_ENDPOINTS * 2; i++) { + if (remaining_buffers & bit) { + // clear this in advance + usb_hw_clear->buf_status = bit; - // IN transfer for even i, OUT transfer for odd i - struct hw_endpoint *ep = hw_endpoint_get_by_num(i >> 1u, (i & 1u) ? TUSB_DIR_OUT : TUSB_DIR_IN); + // IN transfer for even i, OUT transfer for odd i + struct hw_endpoint* ep = hw_endpoint_get_by_num(i >> 1u, (i & 1u) ? TUSB_DIR_OUT : TUSB_DIR_IN); - // Continue xfer - bool done = hw_endpoint_xfer_continue(ep); - if (done) - { - // Notify - dcd_event_xfer_complete(0, ep->ep_addr, ep->xferred_len, XFER_RESULT_SUCCESS, true); - hw_endpoint_reset_transfer(ep); - } - remaining_buffers &= ~bit; - } - bit <<= 1u; + // Continue xfer + bool done = hw_endpoint_xfer_continue(ep); + if (done) { + // Notify + dcd_event_xfer_complete(0, ep->ep_addr, ep->xferred_len, XFER_RESULT_SUCCESS, true); + hw_endpoint_reset_transfer(ep); + } + remaining_buffers &= ~bit; } + bit <<= 1u; + } } -TU_ATTR_ALWAYS_INLINE static inline void reset_ep0_pid(void) -{ - // If we have finished this transfer on EP0 set pid back to 1 for next - // setup transfer. Also clear a stall in case - uint8_t addrs[] = {0x0, 0x80}; - for (uint i = 0 ; i < TU_ARRAY_SIZE(addrs); i++) - { - struct hw_endpoint *ep = hw_endpoint_get_by_addr(addrs[i]); - ep->next_pid = 1u; +TU_ATTR_ALWAYS_INLINE static inline void reset_ep0(void) { + // If we have finished this transfer on EP0 set pid back to 1 for next + // setup transfer. Also clear a stall in case + for (uint8_t dir = 0; dir < 2; dir++) { + struct hw_endpoint* ep = hw_endpoint_get_by_num(0, dir); + if (ep->active) { + // Abort any pending transfer from a prior control transfer per USB specs + // Due to Errata RP2040-E2: ABORT flag is only applicable for B2 and later (unusable for B0, B1). + // Which means we are not guaranteed to safely abort pending transfer on B0 and B1. + uint32_t const abort_mask = (dir ? USB_EP_ABORT_EP0_IN_BITS : USB_EP_ABORT_EP0_OUT_BITS); + if (rp2040_chip_version() >= 2) { + usb_hw_set->abort = abort_mask; + while ((usb_hw->abort_done & abort_mask) != abort_mask) {} + } + + _hw_endpoint_buffer_control_set_value32(ep, USB_BUF_CTRL_DATA1_PID | USB_BUF_CTRL_SEL); + hw_endpoint_reset_transfer(ep); + + if (rp2040_chip_version() >= 2) { + usb_hw_clear->abort_done = abort_mask; + usb_hw_clear->abort = abort_mask; + } } + ep->next_pid = 1u; + } } -static void __tusb_irq_path_func(reset_non_control_endpoints)(void) -{ +static void __tusb_irq_path_func(reset_non_control_endpoints)(void) { // Disable all non-control - for ( uint8_t i = 0; i < USB_MAX_ENDPOINTS-1; i++ ) - { + for (uint8_t i = 0; i < USB_MAX_ENDPOINTS - 1; i++) { usb_dpram->ep_ctrl[i].in = 0; usb_dpram->ep_ctrl[i].out = 0; } // clear non-control hw endpoints - tu_memclr(hw_endpoints[1], sizeof(hw_endpoints) - 2*sizeof(hw_endpoint_t)); + tu_memclr(hw_endpoints[1], sizeof(hw_endpoints) - 2 * sizeof(hw_endpoint_t)); // reclaim buffer space next_buffer_ptr = &usb_dpram->epx_data[0]; } -static void __tusb_irq_path_func(dcd_rp2040_irq)(void) -{ +static void __tusb_irq_path_func(dcd_rp2040_irq)(void) { uint32_t const status = usb_hw->ints; uint32_t handled = 0; - if ( status & USB_INTF_DEV_SOF_BITS ) - { + if (status & USB_INTF_DEV_SOF_BITS) { bool keep_sof_alive = false; handled |= USB_INTF_DEV_SOF_BITS; @@ -258,20 +246,17 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) // Errata 15 workaround for Device Bulk-In endpoint e15_last_sof = time_us_32(); - for ( uint8_t i = 0; i < USB_MAX_ENDPOINTS; i++ ) - { - struct hw_endpoint * ep = hw_endpoint_get_by_num(i, TUSB_DIR_IN); + for (uint8_t i = 0; i < USB_MAX_ENDPOINTS; i++) { + struct hw_endpoint* ep = hw_endpoint_get_by_num(i, TUSB_DIR_IN); // Active Bulk IN endpoint requires SOF - if ( (ep->transfer_type == TUSB_XFER_BULK) && ep->active ) - { + if ((ep->transfer_type == TUSB_XFER_BULK) && ep->active) { keep_sof_alive = true; hw_endpoint_lock_update(ep, 1); // Deferred enable? - if ( ep->pending ) - { + if (ep->pending) { ep->pending = 0; hw_endpoint_start_next_buffer(ep); } @@ -282,26 +267,24 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) #endif // disable SOF interrupt if it is used for RESUME in remote wakeup - if ( !keep_sof_alive && !_sof_enable ) usb_hw_clear->inte = USB_INTS_DEV_SOF_BITS; + if (!keep_sof_alive && !_sof_enable) usb_hw_clear->inte = USB_INTS_DEV_SOF_BITS; dcd_event_sof(0, usb_hw->sof_rd & USB_SOF_RD_BITS, true); } // xfer events are handled before setup req. So if a transfer completes immediately // before closing the EP, the events will be delivered in same order. - if ( status & USB_INTS_BUFF_STATUS_BITS ) - { + if (status & USB_INTS_BUFF_STATUS_BITS) { handled |= USB_INTS_BUFF_STATUS_BITS; hw_handle_buff_status(); } - if ( status & USB_INTS_SETUP_REQ_BITS ) - { + if (status & USB_INTS_SETUP_REQ_BITS) { handled |= USB_INTS_SETUP_REQ_BITS; - uint8_t const * setup = remove_volatile_cast(uint8_t const*, &usb_dpram->setup_packet); + uint8_t const* setup = remove_volatile_cast(uint8_t const*, &usb_dpram->setup_packet); // reset pid to both 1 (data and ack) - reset_ep0_pid(); + reset_ep0(); // Pass setup packet to tiny usb dcd_event_setup_received(0, setup, true); @@ -329,8 +312,7 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) #endif // SE0 for 2.5 us or more (will last at least 10ms) - if ( status & USB_INTS_BUS_RESET_BITS ) - { + if (status & USB_INTS_BUS_RESET_BITS) { pico_trace("BUS RESET\r\n"); handled |= USB_INTS_BUS_RESET_BITS; @@ -342,7 +324,7 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) #if TUD_OPT_RP2040_USB_DEVICE_ENUMERATION_FIX // Only run enumeration workaround if pull up is enabled - if ( usb_hw->sie_ctrl & USB_SIE_CTRL_PULLUP_EN_BITS ) rp2040_usb_device_enumeration_fix(); + if (usb_hw->sie_ctrl & USB_SIE_CTRL_PULLUP_EN_BITS) rp2040_usb_device_enumeration_fix(); #endif } @@ -354,22 +336,19 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) * because without VBUS detection, it is impossible to tell the difference between * being disconnected and suspended. */ - if ( status & USB_INTS_DEV_SUSPEND_BITS ) - { + if (status & USB_INTS_DEV_SUSPEND_BITS) { handled |= USB_INTS_DEV_SUSPEND_BITS; dcd_event_bus_signal(0, DCD_EVENT_SUSPEND, true); usb_hw_clear->sie_status = USB_SIE_STATUS_SUSPENDED_BITS; } - if ( status & USB_INTS_DEV_RESUME_FROM_HOST_BITS ) - { + if (status & USB_INTS_DEV_RESUME_FROM_HOST_BITS) { handled |= USB_INTS_DEV_RESUME_FROM_HOST_BITS; dcd_event_bus_signal(0, DCD_EVENT_RESUME, true); usb_hw_clear->sie_status = USB_SIE_STATUS_RESUME_BITS; } - if ( status ^ handled ) - { + if (status ^ handled) { panic("Unhandled IRQ 0x%x\n", (uint) (status ^ handled)); } } @@ -390,10 +369,11 @@ static void __tusb_irq_path_func(dcd_rp2040_irq)(void) #define PICO_SHARED_IRQ_HANDLER_HIGHEST_ORDER_PRIORITY 0xff #endif -void dcd_init (uint8_t rhport) -{ +void dcd_init(uint8_t rhport) { assert(rhport == 0); + TU_LOG(2, "Chip Version B%u\r\n", rp2040_chip_version()); + // Reset hardware to default state rp2040_usb_init(); @@ -405,7 +385,7 @@ void dcd_init (uint8_t rhport) irq_add_shared_handler(USBCTRL_IRQ, dcd_rp2040_irq, PICO_SHARED_IRQ_HANDLER_HIGHEST_ORDER_PRIORITY); // Init control endpoints - tu_memclr(hw_endpoints[0], 2*sizeof(hw_endpoint_t)); + tu_memclr(hw_endpoints[0], 2 * sizeof(hw_endpoint_t)); hw_endpoint_init(0x0, 64, TUSB_XFER_CONTROL); hw_endpoint_init(0x80, 64, TUSB_XFER_CONTROL); @@ -420,27 +400,37 @@ void dcd_init (uint8_t rhport) // for the global interrupt enable... // Note: Force VBUS detect cause disconnection not detectable usb_hw->sie_ctrl = USB_SIE_CTRL_EP0_INT_1BUF_BITS; - usb_hw->inte = USB_INTS_BUFF_STATUS_BITS | USB_INTS_BUS_RESET_BITS | USB_INTS_SETUP_REQ_BITS | - USB_INTS_DEV_SUSPEND_BITS | USB_INTS_DEV_RESUME_FROM_HOST_BITS | - (FORCE_VBUS_DETECT ? 0 : USB_INTS_DEV_CONN_DIS_BITS); + usb_hw->inte = USB_INTS_BUFF_STATUS_BITS | USB_INTS_BUS_RESET_BITS | USB_INTS_SETUP_REQ_BITS | + USB_INTS_DEV_SUSPEND_BITS | USB_INTS_DEV_RESUME_FROM_HOST_BITS | + (FORCE_VBUS_DETECT ? 0 : USB_INTS_DEV_CONN_DIS_BITS); dcd_connect(rhport); } -void dcd_int_enable(__unused uint8_t rhport) -{ - assert(rhport == 0); - irq_set_enabled(USBCTRL_IRQ, true); +bool dcd_deinit(uint8_t rhport) { + (void) rhport; + + reset_non_control_endpoints(); + irq_remove_handler(USBCTRL_IRQ, dcd_rp2040_irq); + + // reset usb hardware into initial state + reset_block(RESETS_RESET_USBCTRL_BITS); + unreset_block_wait(RESETS_RESET_USBCTRL_BITS); + + return true; } -void dcd_int_disable(__unused uint8_t rhport) -{ - assert(rhport == 0); - irq_set_enabled(USBCTRL_IRQ, false); +void dcd_int_enable(__unused uint8_t rhport) { + assert(rhport == 0); + irq_set_enabled(USBCTRL_IRQ, true); } -void dcd_set_address (__unused uint8_t rhport, __unused uint8_t dev_addr) -{ +void dcd_int_disable(__unused uint8_t rhport) { + assert(rhport == 0); + irq_set_enabled(USBCTRL_IRQ, false); +} + +void dcd_set_address(__unused uint8_t rhport, __unused uint8_t dev_addr) { assert(rhport == 0); // Can't set device address in hardware until status xfer has complete @@ -448,8 +438,7 @@ void dcd_set_address (__unused uint8_t rhport, __unused uint8_t dev_addr) hw_endpoint_xfer(0x80, NULL, 0); } -void dcd_remote_wakeup(__unused uint8_t rhport) -{ +void dcd_remote_wakeup(__unused uint8_t rhport) { pico_info("dcd_remote_wakeup %d\n", rhport); assert(rhport == 0); @@ -460,100 +449,88 @@ void dcd_remote_wakeup(__unused uint8_t rhport) } // disconnect by disabling internal pull-up resistor on D+/D- -void dcd_disconnect(__unused uint8_t rhport) -{ +void dcd_disconnect(__unused uint8_t rhport) { (void) rhport; usb_hw_clear->sie_ctrl = USB_SIE_CTRL_PULLUP_EN_BITS; } // connect by enabling internal pull-up resistor on D+/D- -void dcd_connect(__unused uint8_t rhport) -{ +void dcd_connect(__unused uint8_t rhport) { (void) rhport; usb_hw_set->sie_ctrl = USB_SIE_CTRL_PULLUP_EN_BITS; } -void dcd_sof_enable(uint8_t rhport, bool en) -{ +void dcd_sof_enable(uint8_t rhport, bool en) { (void) rhport; _sof_enable = en; - if (en) - { + if (en) { usb_hw_set->inte = USB_INTS_DEV_SOF_BITS; - }else - { + } +#if !TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX + else { // Don't clear immediately if the SOF workaround is in use. // The SOF handler will conditionally disable the interrupt. -#if !TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX usb_hw_clear->inte = USB_INTS_DEV_SOF_BITS; -#endif } +#endif } /*------------------------------------------------------------------*/ /* DCD Endpoint port *------------------------------------------------------------------*/ -void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const * request) -{ +void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const* request) { (void) rhport; - if ( request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE && - request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && - request->bRequest == TUSB_REQ_SET_ADDRESS ) - { + if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE && + request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD && + request->bRequest == TUSB_REQ_SET_ADDRESS) { usb_hw->dev_addr_ctrl = (uint8_t) request->wValue; } } -bool dcd_edpt_open (__unused uint8_t rhport, tusb_desc_endpoint_t const * desc_edpt) -{ - assert(rhport == 0); - hw_endpoint_init(desc_edpt->bEndpointAddress, tu_edpt_packet_size(desc_edpt), desc_edpt->bmAttributes.xfer); - return true; +bool dcd_edpt_open(__unused uint8_t rhport, tusb_desc_endpoint_t const* desc_edpt) { + assert(rhport == 0); + hw_endpoint_init(desc_edpt->bEndpointAddress, tu_edpt_packet_size(desc_edpt), desc_edpt->bmAttributes.xfer); + return true; } -void dcd_edpt_close_all (uint8_t rhport) -{ +void dcd_edpt_close_all(uint8_t rhport) { (void) rhport; // may need to use EP Abort reset_non_control_endpoints(); } -bool dcd_edpt_xfer(__unused uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes) -{ - assert(rhport == 0); - hw_endpoint_xfer(ep_addr, buffer, total_bytes); - return true; +bool dcd_edpt_xfer(__unused uint8_t rhport, uint8_t ep_addr, uint8_t* buffer, uint16_t total_bytes) { + assert(rhport == 0); + hw_endpoint_xfer(ep_addr, buffer, total_bytes); + return true; } -void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) -{ +void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) { (void) rhport; - if ( tu_edpt_number(ep_addr) == 0 ) - { + if (tu_edpt_number(ep_addr) == 0) { // A stall on EP0 has to be armed so it can be cleared on the next setup packet - usb_hw_set->ep_stall_arm = (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) ? USB_EP_STALL_ARM_EP0_IN_BITS : USB_EP_STALL_ARM_EP0_OUT_BITS; + usb_hw_set->ep_stall_arm = (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) ? USB_EP_STALL_ARM_EP0_IN_BITS + : USB_EP_STALL_ARM_EP0_OUT_BITS; } - struct hw_endpoint *ep = hw_endpoint_get_by_addr(ep_addr); + struct hw_endpoint* ep = hw_endpoint_get_by_addr(ep_addr); // stall and clear current pending buffer // may need to use EP_ABORT _hw_endpoint_buffer_control_set_value32(ep, USB_BUF_CTRL_STALL); } -void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) -{ +void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) { (void) rhport; - if (tu_edpt_number(ep_addr)) - { - struct hw_endpoint *ep = hw_endpoint_get_by_addr(ep_addr); + if (tu_edpt_number(ep_addr)) { + struct hw_endpoint* ep = hw_endpoint_get_by_addr(ep_addr); // clear stall also reset toggle to DATA0, ready for next transfer ep->next_pid = 0; @@ -561,16 +538,13 @@ void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) } } -void dcd_edpt_close (uint8_t rhport, uint8_t ep_addr) -{ - (void) rhport; - - pico_trace("dcd_edpt_close %02x\r\n", ep_addr); - hw_endpoint_close(ep_addr); +void dcd_edpt_close(uint8_t rhport, uint8_t ep_addr) { + (void) rhport; + pico_trace("dcd_edpt_close %02x\r\n", ep_addr); + hw_endpoint_close(ep_addr); } -void __tusb_irq_path_func(dcd_int_handler)(uint8_t rhport) -{ +void __tusb_irq_path_func(dcd_int_handler)(uint8_t rhport) { (void) rhport; dcd_rp2040_irq(); } diff --git a/pico-sdk/lib/tinyusb/src/portable/raspberrypi/rp2040/hcd_rp2040.c b/pico-sdk/lib/tinyusb/src/portable/raspberrypi/rp2040/hcd_rp2040.c index 08aef93..222dbbb 100644 --- a/pico-sdk/lib/tinyusb/src/portable/raspberrypi/rp2040/hcd_rp2040.c +++ b/pico-sdk/lib/tinyusb/src/portable/raspberrypi/rp2040/hcd_rp2040.c @@ -113,7 +113,7 @@ static void __tusb_irq_path_func(_handle_buff_status_bit)(uint bit, struct hw_en static void __tusb_irq_path_func(hw_handle_buff_status)(void) { uint32_t remaining_buffers = usb_hw->buf_status; - pico_trace("buf_status 0x%08x\n", remaining_buffers); + pico_trace("buf_status 0x%08lx\n", remaining_buffers); // Check EPX first uint bit = 0b1; @@ -325,10 +325,8 @@ static void _hw_endpoint_init(struct hw_endpoint *ep, uint8_t dev_addr, uint8_t ep->wMaxPacketSize = wMaxPacketSize; ep->transfer_type = transfer_type; - pico_trace("hw_endpoint_init dev %d ep %d %s xfer %d\n", ep->dev_addr, tu_edpt_number(ep->ep_addr), - ep_dir_string[tu_edpt_dir(ep->ep_addr)], ep->transfer_type); - pico_trace("dev %d ep %d %s setup buffer @ 0x%p\n", ep->dev_addr, tu_edpt_number(ep->ep_addr), - ep_dir_string[tu_edpt_dir(ep->ep_addr)], ep->hw_data_buf); + pico_trace("hw_endpoint_init dev %d ep %02X xfer %d\n", ep->dev_addr, ep->ep_addr, ep->transfer_type); + pico_trace("dev %d ep %02X setup buffer @ 0x%p\n", ep->dev_addr, ep->ep_addr, ep->hw_data_buf); uint dpram_offset = hw_data_offset(ep->hw_data_buf); // Bits 0-5 should be 0 assert(!(dpram_offset & 0b111111)); @@ -343,7 +341,7 @@ static void _hw_endpoint_init(struct hw_endpoint *ep, uint8_t dev_addr, uint8_t ep_reg |= (uint32_t) ((bmInterval - 1) << EP_CTRL_HOST_INTERRUPT_INTERVAL_LSB); } *ep->endpoint_control = ep_reg; - pico_trace("endpoint control (0x%p) <- 0x%x\n", ep->endpoint_control, ep_reg); + pico_trace("endpoint control (0x%p) <- 0x%lx\n", ep->endpoint_control, ep_reg); ep->configured = true; if ( ep != &epx ) @@ -411,6 +409,16 @@ bool hcd_init(uint8_t rhport) return true; } +bool hcd_deinit(uint8_t rhport) { + (void) rhport; + + irq_remove_handler(USBCTRL_IRQ, hcd_rp2040_irq); + reset_block(RESETS_RESET_USBCTRL_BITS); + unreset_block_wait(RESETS_RESET_USBCTRL_BITS); + + return true; +} + void hcd_port_reset(uint8_t rhport) { (void) rhport; diff --git a/pico-sdk/lib/tinyusb/src/portable/raspberrypi/rp2040/rp2040_usb.c b/pico-sdk/lib/tinyusb/src/portable/raspberrypi/rp2040/rp2040_usb.c index a512dc3..1ca711c 100644 --- a/pico-sdk/lib/tinyusb/src/portable/raspberrypi/rp2040/rp2040_usb.c +++ b/pico-sdk/lib/tinyusb/src/portable/raspberrypi/rp2040/rp2040_usb.c @@ -35,26 +35,18 @@ //--------------------------------------------------------------------+ // MACRO CONSTANT TYPEDEF PROTOTYPE //--------------------------------------------------------------------+ - -// Direction strings for debug -const char *ep_dir_string[] = { - "out", - "in", -}; - -static void _hw_endpoint_xfer_sync(struct hw_endpoint *ep); +static void _hw_endpoint_xfer_sync(struct hw_endpoint* ep); #if TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX - static bool e15_is_bulkin_ep(struct hw_endpoint *ep); - static bool e15_is_critical_frame_period(struct hw_endpoint *ep); + static bool e15_is_bulkin_ep(struct hw_endpoint* ep); + static bool e15_is_critical_frame_period(struct hw_endpoint* ep); #else #define e15_is_bulkin_ep(x) (false) #define e15_is_critical_frame_period(x) (false) #endif // if usb hardware is in host mode -TU_ATTR_ALWAYS_INLINE static inline bool is_host_mode(void) -{ +TU_ATTR_ALWAYS_INLINE static inline bool is_host_mode(void) { return (usb_hw->main_ctrl & USB_MAIN_CTRL_HOST_NDEVICE_BITS) ? true : false; } @@ -62,8 +54,7 @@ TU_ATTR_ALWAYS_INLINE static inline bool is_host_mode(void) // Implementation //--------------------------------------------------------------------+ -void rp2040_usb_init(void) -{ +void rp2040_usb_init(void) { // Reset usb controller reset_block(RESETS_RESET_USBCTRL_BITS); unreset_block_wait(RESETS_RESET_USBCTRL_BITS); @@ -88,46 +79,33 @@ void rp2040_usb_init(void) TU_LOG2_INT(sizeof(hw_endpoint_t)); } -void __tusb_irq_path_func(hw_endpoint_reset_transfer)(struct hw_endpoint *ep) -{ +void __tusb_irq_path_func(hw_endpoint_reset_transfer)(struct hw_endpoint* ep) { ep->active = false; ep->remaining_len = 0; ep->xferred_len = 0; ep->user_buf = 0; } -void __tusb_irq_path_func(_hw_endpoint_buffer_control_update32)(struct hw_endpoint *ep, uint32_t and_mask, uint32_t or_mask) -{ +void __tusb_irq_path_func(_hw_endpoint_buffer_control_update32)(struct hw_endpoint* ep, uint32_t and_mask, + uint32_t or_mask) { uint32_t value = 0; - if ( and_mask ) - { + if (and_mask) { value = *ep->buffer_control & and_mask; } - if ( or_mask ) - { + if (or_mask) { value |= or_mask; - if ( or_mask & USB_BUF_CTRL_AVAIL ) - { - if ( *ep->buffer_control & USB_BUF_CTRL_AVAIL ) - { - panic("ep %d %s was already available", tu_edpt_number(ep->ep_addr), ep_dir_string[tu_edpt_dir(ep->ep_addr)]); + if (or_mask & USB_BUF_CTRL_AVAIL) { + if (*ep->buffer_control & USB_BUF_CTRL_AVAIL) { + panic("ep %02X was already available", ep->ep_addr); } *ep->buffer_control = value & ~USB_BUF_CTRL_AVAIL; - // 12 cycle delay.. (should be good for 48*12Mhz = 576Mhz) + // 4.1.2.5.1 Con-current access: 12 cycles (should be good for 48*12Mhz = 576Mhz) after write to buffer control // Don't need delay in host mode as host is in charge -#if !CFG_TUH_ENABLED - __asm volatile ( - "b 1f\n" - "1: b 1f\n" - "1: b 1f\n" - "1: b 1f\n" - "1: b 1f\n" - "1: b 1f\n" - "1:\n" - : : : "memory"); -#endif + if ( !is_host_mode()) { + busy_wait_at_least_cycles(12); + } } } @@ -135,10 +113,9 @@ void __tusb_irq_path_func(_hw_endpoint_buffer_control_update32)(struct hw_endpoi } // prepare buffer, return buffer control -static uint32_t __tusb_irq_path_func(prepare_ep_buffer)(struct hw_endpoint *ep, uint8_t buf_id) -{ +static uint32_t __tusb_irq_path_func(prepare_ep_buffer)(struct hw_endpoint* ep, uint8_t buf_id) { uint16_t const buflen = tu_min16(ep->remaining_len, ep->wMaxPacketSize); - ep->remaining_len = (uint16_t)(ep->remaining_len - buflen); + ep->remaining_len = (uint16_t) (ep->remaining_len - buflen); uint32_t buf_ctrl = buflen | USB_BUF_CTRL_AVAIL; @@ -146,10 +123,9 @@ static uint32_t __tusb_irq_path_func(prepare_ep_buffer)(struct hw_endpoint *ep, buf_ctrl |= ep->next_pid ? USB_BUF_CTRL_DATA1_PID : USB_BUF_CTRL_DATA0_PID; ep->next_pid ^= 1u; - if ( !ep->rx ) - { + if (!ep->rx) { // Copy data from user buffer to hw buffer - memcpy(ep->hw_data_buf + buf_id*64, ep->user_buf, buflen); + memcpy(ep->hw_data_buf + buf_id * 64, ep->user_buf, buflen); ep->user_buf += buflen; // Mark as full @@ -159,8 +135,7 @@ static uint32_t __tusb_irq_path_func(prepare_ep_buffer)(struct hw_endpoint *ep, // Is this the last buffer? Only really matters for host mode. Will trigger // the trans complete irq but also stop it polling. We only really care about // trans complete for setup packets being sent - if (ep->remaining_len == 0) - { + if (ep->remaining_len == 0) { buf_ctrl |= USB_BUF_CTRL_LAST; } @@ -170,8 +145,7 @@ static uint32_t __tusb_irq_path_func(prepare_ep_buffer)(struct hw_endpoint *ep, } // Prepare buffer control register value -void __tusb_irq_path_func(hw_endpoint_start_next_buffer)(struct hw_endpoint *ep) -{ +void __tusb_irq_path_func(hw_endpoint_start_next_buffer)(struct hw_endpoint* ep) { uint32_t ep_ctrl = *ep->endpoint_control; // always compute and start with buffer 0 @@ -186,8 +160,7 @@ void __tusb_irq_path_func(hw_endpoint_start_next_buffer)(struct hw_endpoint *ep) bool const force_single = (!is_host && !tu_edpt_dir(ep->ep_addr)) || (is_host && tu_edpt_number(ep->ep_addr) != 0); - if(ep->remaining_len && !force_single) - { + if (ep->remaining_len && !force_single) { // Use buffer 1 (double buffered) if there is still data // TODO: Isochronous for buffer1 bit-field is different than CBI (control bulk, interrupt) @@ -196,8 +169,7 @@ void __tusb_irq_path_func(hw_endpoint_start_next_buffer)(struct hw_endpoint *ep) // Set endpoint control double buffered bit if needed ep_ctrl &= ~EP_CTRL_INTERRUPT_PER_BUFFER; ep_ctrl |= EP_CTRL_DOUBLE_BUFFERED_BITS | EP_CTRL_INTERRUPT_PER_DOUBLE_BUFFER; - }else - { + } else { // Single buffered since 1 is enough ep_ctrl &= ~(EP_CTRL_DOUBLE_BUFFERED_BITS | EP_CTRL_INTERRUPT_PER_DOUBLE_BUFFER); ep_ctrl |= EP_CTRL_INTERRUPT_PER_BUFFER; @@ -212,35 +184,28 @@ void __tusb_irq_path_func(hw_endpoint_start_next_buffer)(struct hw_endpoint *ep) _hw_endpoint_buffer_control_set_value32(ep, buf_ctrl); } -void hw_endpoint_xfer_start(struct hw_endpoint *ep, uint8_t *buffer, uint16_t total_len) -{ +void hw_endpoint_xfer_start(struct hw_endpoint* ep, uint8_t* buffer, uint16_t total_len) { hw_endpoint_lock_update(ep, 1); - if ( ep->active ) - { + if (ep->active) { // TODO: Is this acceptable for interrupt packets? - TU_LOG(1, "WARN: starting new transfer on already active ep %d %s\r\n", tu_edpt_number(ep->ep_addr), - ep_dir_string[tu_edpt_dir(ep->ep_addr)]); - + TU_LOG(1, "WARN: starting new transfer on already active ep %02X\r\n", ep->ep_addr); hw_endpoint_reset_transfer(ep); } // Fill in info now that we're kicking off the hw ep->remaining_len = total_len; - ep->xferred_len = 0; - ep->active = true; - ep->user_buf = buffer; + ep->xferred_len = 0; + ep->active = true; + ep->user_buf = buffer; - if ( e15_is_bulkin_ep(ep) ) - { + if (e15_is_bulkin_ep(ep)) { usb_hw_set->inte = USB_INTS_DEV_SOF_BITS; } - if ( e15_is_critical_frame_period(ep) ) - { + if (e15_is_critical_frame_period(ep)) { ep->pending = 1; - } else - { + } else { hw_endpoint_start_next_buffer(ep); } @@ -248,34 +213,30 @@ void hw_endpoint_xfer_start(struct hw_endpoint *ep, uint8_t *buffer, uint16_t to } // sync endpoint buffer and return transferred bytes -static uint16_t __tusb_irq_path_func(sync_ep_buffer)(struct hw_endpoint *ep, uint8_t buf_id) -{ +static uint16_t __tusb_irq_path_func(sync_ep_buffer)(struct hw_endpoint* ep, uint8_t buf_id) { uint32_t buf_ctrl = _hw_endpoint_buffer_control_get_value32(ep); - if (buf_id) buf_ctrl = buf_ctrl >> 16; + if (buf_id) buf_ctrl = buf_ctrl >> 16; uint16_t xferred_bytes = buf_ctrl & USB_BUF_CTRL_LEN_MASK; - if ( !ep->rx ) - { + if (!ep->rx) { // We are continuing a transfer here. If we are TX, we have successfully // sent some data can increase the length we have sent assert(!(buf_ctrl & USB_BUF_CTRL_FULL)); - ep->xferred_len = (uint16_t)(ep->xferred_len + xferred_bytes); - }else - { + ep->xferred_len = (uint16_t) (ep->xferred_len + xferred_bytes); + } else { // If we have received some data, so can increase the length // we have received AFTER we have copied it to the user buffer at the appropriate offset assert(buf_ctrl & USB_BUF_CTRL_FULL); - memcpy(ep->user_buf, ep->hw_data_buf + buf_id*64, xferred_bytes); - ep->xferred_len = (uint16_t)(ep->xferred_len + xferred_bytes); + memcpy(ep->user_buf, ep->hw_data_buf + buf_id * 64, xferred_bytes); + ep->xferred_len = (uint16_t) (ep->xferred_len + xferred_bytes); ep->user_buf += xferred_bytes; } // Short packet - if (xferred_bytes < ep->wMaxPacketSize) - { + if (xferred_bytes < ep->wMaxPacketSize) { pico_trace(" Short packet on buffer %d with %u bytes\r\n", buf_id, xferred_bytes); // Reduce total length as this is last packet ep->remaining_len = 0; @@ -284,8 +245,7 @@ static uint16_t __tusb_irq_path_func(sync_ep_buffer)(struct hw_endpoint *ep, uin return xferred_bytes; } -static void __tusb_irq_path_func(_hw_endpoint_xfer_sync) (struct hw_endpoint *ep) -{ +static void __tusb_irq_path_func(_hw_endpoint_xfer_sync)(struct hw_endpoint* ep) { // Update hw endpoint struct with info from hardware // after a buff status interrupt @@ -296,14 +256,11 @@ static void __tusb_irq_path_func(_hw_endpoint_xfer_sync) (struct hw_endpoint *ep uint16_t buf0_bytes = sync_ep_buffer(ep, 0); // sync buffer 1 if double buffered - if ( (*ep->endpoint_control) & EP_CTRL_DOUBLE_BUFFERED_BITS ) - { - if (buf0_bytes == ep->wMaxPacketSize) - { + if ((*ep->endpoint_control) & EP_CTRL_DOUBLE_BUFFERED_BITS) { + if (buf0_bytes == ep->wMaxPacketSize) { // sync buffer 1 if not short packet sync_ep_buffer(ep, 1); - }else - { + } else { // short packet on buffer 0 // TODO couldn't figure out how to handle this case which happen with net_lwip_webserver example // At this time (currently trigger per 2 buffer), the buffer1 is probably filled with data from @@ -335,14 +292,12 @@ static void __tusb_irq_path_func(_hw_endpoint_xfer_sync) (struct hw_endpoint *ep } // Returns true if transfer is complete -bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint *ep) -{ +bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint* ep) { hw_endpoint_lock_update(ep, 1); // Part way through a transfer - if (!ep->active) - { - panic("Can't continue xfer on inactive ep %d %s", tu_edpt_number(ep->ep_addr), ep_dir_string[tu_edpt_dir(ep->ep_addr)]); + if (!ep->active) { + panic("Can't continue xfer on inactive ep %02X", ep->ep_addr); } // Update EP struct from hardware state @@ -350,21 +305,15 @@ bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint *ep) // Now we have synced our state with the hardware. Is there more data to transfer? // If we are done then notify tinyusb - if (ep->remaining_len == 0) - { - pico_trace("Completed transfer of %d bytes on ep %d %s\r\n", - ep->xferred_len, tu_edpt_number(ep->ep_addr), ep_dir_string[tu_edpt_dir(ep->ep_addr)]); + if (ep->remaining_len == 0) { + pico_trace("Completed transfer of %d bytes on ep %02X\r\n", ep->xferred_len, ep->ep_addr); // Notify caller we are done so it can notify the tinyusb stack hw_endpoint_lock_update(ep, -1); return true; - } - else - { - if ( e15_is_critical_frame_period(ep) ) - { + } else { + if (e15_is_critical_frame_period(ep)) { ep->pending = 1; - } else - { + } else { hw_endpoint_start_next_buffer(ep); } } @@ -399,16 +348,14 @@ bool __tusb_irq_path_func(hw_endpoint_xfer_continue)(struct hw_endpoint *ep) volatile uint32_t e15_last_sof = 0; // check if Errata 15 is needed for this endpoint i.e device bulk-in -static bool __tusb_irq_path_func(e15_is_bulkin_ep) (struct hw_endpoint *ep) -{ +static bool __tusb_irq_path_func(e15_is_bulkin_ep)(struct hw_endpoint* ep) { return (!is_host_mode() && tu_edpt_dir(ep->ep_addr) == TUSB_DIR_IN && ep->transfer_type == TUSB_XFER_BULK); } // check if we need to apply Errata 15 workaround : i.e // Endpoint is BULK IN and is currently in critical frame period i.e 20% of last usb frame -static bool __tusb_irq_path_func(e15_is_critical_frame_period) (struct hw_endpoint *ep) -{ +static bool __tusb_irq_path_func(e15_is_critical_frame_period)(struct hw_endpoint* ep) { TU_VERIFY(e15_is_bulkin_ep(ep)); /* Avoid the last 200us (uframe 6.5-7) of a frame, up to the EOF2 point. @@ -419,11 +366,10 @@ static bool __tusb_irq_path_func(e15_is_critical_frame_period) (struct hw_endpoi if (delta < 800 || delta > 998) { return false; } - TU_LOG(3, "Avoiding sof %lu now %lu last %lu\r\n", (usb_hw->sof_rd + 1) & USB_SOF_RD_BITS, time_us_32(), e15_last_sof); + TU_LOG(3, "Avoiding sof %lu now %lu last %lu\r\n", (usb_hw->sof_rd + 1) & USB_SOF_RD_BITS, time_us_32(), + e15_last_sof); return true; } -#endif - - +#endif // TUD_OPT_RP2040_USB_DEVICE_UFRAME_FIX #endif diff --git a/pico-sdk/lib/tinyusb/src/tinyusb.mk b/pico-sdk/lib/tinyusb/src/tinyusb.mk index 84974ad..3d7c644 100644 --- a/pico-sdk/lib/tinyusb/src/tinyusb.mk +++ b/pico-sdk/lib/tinyusb/src/tinyusb.mk @@ -4,11 +4,15 @@ TINYUSB_SRC_C += \ src/common/tusb_fifo.c \ src/device/usbd.c \ src/device/usbd_control.c \ + src/class/msc/msc_device.c \ src/typec/usbc.c \ + src/class/audio/audio_device.c \ src/class/cdc/cdc_device.c \ + src/class/dfu/dfu_device.c \ + src/class/dfu/dfu_rt_device.c \ src/class/hid/hid_device.c \ + src/class/usbtmc/usbtmc_device.c \ src/host/usbh.c \ src/host/hub.c \ - src/class/cdc/cdc_host.c \ src/class/hid/hid_host.c \ src/typec/usbc.c \ diff --git a/pico-sdk/lib/tinyusb/src/tusb.c b/pico-sdk/lib/tinyusb/src/tusb.c index 7943ea5..0092267 100644 --- a/pico-sdk/lib/tinyusb/src/tusb.c +++ b/pico-sdk/lib/tinyusb/src/tusb.c @@ -43,32 +43,30 @@ // Public API //--------------------------------------------------------------------+ -bool tusb_init(void) -{ -#if CFG_TUD_ENABLED && defined(TUD_OPT_RHPORT) +bool tusb_init(void) { + #if CFG_TUD_ENABLED && defined(TUD_OPT_RHPORT) // init device stack CFG_TUSB_RHPORTx_MODE must be defined TU_ASSERT ( tud_init(TUD_OPT_RHPORT) ); -#endif + #endif -#if CFG_TUH_ENABLED && defined(TUH_OPT_RHPORT) + #if CFG_TUH_ENABLED && defined(TUH_OPT_RHPORT) // init host stack CFG_TUSB_RHPORTx_MODE must be defined TU_ASSERT( tuh_init(TUH_OPT_RHPORT) ); -#endif + #endif return true; } -bool tusb_inited(void) -{ +bool tusb_inited(void) { bool ret = false; -#if CFG_TUD_ENABLED + #if CFG_TUD_ENABLED ret = ret || tud_inited(); -#endif + #endif -#if CFG_TUH_ENABLED + #if CFG_TUH_ENABLED ret = ret || tuh_inited(); -#endif + #endif return ret; } @@ -77,43 +75,35 @@ bool tusb_inited(void) // Descriptor helper //--------------------------------------------------------------------+ -uint8_t const * tu_desc_find(uint8_t const* desc, uint8_t const* end, uint8_t byte1) -{ - while(desc+1 < end) - { - if ( desc[1] == byte1 ) return desc; +uint8_t const* tu_desc_find(uint8_t const* desc, uint8_t const* end, uint8_t byte1) { + while (desc + 1 < end) { + if (desc[1] == byte1) return desc; desc += desc[DESC_OFFSET_LEN]; } return NULL; } -uint8_t const * tu_desc_find2(uint8_t const* desc, uint8_t const* end, uint8_t byte1, uint8_t byte2) -{ - while(desc+2 < end) - { - if ( desc[1] == byte1 && desc[2] == byte2) return desc; +uint8_t const* tu_desc_find2(uint8_t const* desc, uint8_t const* end, uint8_t byte1, uint8_t byte2) { + while (desc + 2 < end) { + if (desc[1] == byte1 && desc[2] == byte2) return desc; desc += desc[DESC_OFFSET_LEN]; } return NULL; } -uint8_t const * tu_desc_find3(uint8_t const* desc, uint8_t const* end, uint8_t byte1, uint8_t byte2, uint8_t byte3) -{ - while(desc+3 < end) - { +uint8_t const* tu_desc_find3(uint8_t const* desc, uint8_t const* end, uint8_t byte1, uint8_t byte2, uint8_t byte3) { + while (desc + 3 < end) { if (desc[1] == byte1 && desc[2] == byte2 && desc[3] == byte3) return desc; desc += desc[DESC_OFFSET_LEN]; } return NULL; } - //--------------------------------------------------------------------+ // Endpoint Helper for both Host and Device stack //--------------------------------------------------------------------+ -bool tu_edpt_claim(tu_edpt_state_t* ep_state, osal_mutex_t mutex) -{ +bool tu_edpt_claim(tu_edpt_state_t* ep_state, osal_mutex_t mutex) { (void) mutex; // pre-check to help reducing mutex lock @@ -122,111 +112,93 @@ bool tu_edpt_claim(tu_edpt_state_t* ep_state, osal_mutex_t mutex) // can only claim the endpoint if it is not busy and not claimed yet. bool const available = (ep_state->busy == 0) && (ep_state->claimed == 0); - if (available) - { + if (available) { ep_state->claimed = 1; } (void) osal_mutex_unlock(mutex); - return available; } -bool tu_edpt_release(tu_edpt_state_t* ep_state, osal_mutex_t mutex) -{ +bool tu_edpt_release(tu_edpt_state_t* ep_state, osal_mutex_t mutex) { (void) mutex; - (void) osal_mutex_lock(mutex, OSAL_TIMEOUT_WAIT_FOREVER); // can only release the endpoint if it is claimed and not busy bool const ret = (ep_state->claimed == 1) && (ep_state->busy == 0); - if (ret) - { + if (ret) { ep_state->claimed = 0; } (void) osal_mutex_unlock(mutex); - return ret; } -bool tu_edpt_validate(tusb_desc_endpoint_t const * desc_ep, tusb_speed_t speed) -{ +bool tu_edpt_validate(tusb_desc_endpoint_t const* desc_ep, tusb_speed_t speed) { uint16_t const max_packet_size = tu_edpt_packet_size(desc_ep); TU_LOG2(" Open EP %02X with Size = %u\r\n", desc_ep->bEndpointAddress, max_packet_size); - switch (desc_ep->bmAttributes.xfer) - { - case TUSB_XFER_ISOCHRONOUS: - { + switch (desc_ep->bmAttributes.xfer) { + case TUSB_XFER_ISOCHRONOUS: { uint16_t const spec_size = (speed == TUSB_SPEED_HIGH ? 1024 : 1023); TU_ASSERT(max_packet_size <= spec_size); + break; } - break; case TUSB_XFER_BULK: - if (speed == TUSB_SPEED_HIGH) - { + if (speed == TUSB_SPEED_HIGH) { // Bulk highspeed must be EXACTLY 512 TU_ASSERT(max_packet_size == 512); - }else - { + } else { // TODO Bulk fullspeed can only be 8, 16, 32, 64 TU_ASSERT(max_packet_size <= 64); } - break; + break; - case TUSB_XFER_INTERRUPT: - { + case TUSB_XFER_INTERRUPT: { uint16_t const spec_size = (speed == TUSB_SPEED_HIGH ? 1024 : 64); TU_ASSERT(max_packet_size <= spec_size); + break; } - break; - default: return false; + default: + return false; } return true; } -void tu_edpt_bind_driver(uint8_t ep2drv[][2], tusb_desc_interface_t const* desc_itf, uint16_t desc_len, uint8_t driver_id) -{ +void tu_edpt_bind_driver(uint8_t ep2drv[][2], tusb_desc_interface_t const* desc_itf, uint16_t desc_len, + uint8_t driver_id) { uint8_t const* p_desc = (uint8_t const*) desc_itf; uint8_t const* desc_end = p_desc + desc_len; - while( p_desc < desc_end ) - { - if ( TUSB_DESC_ENDPOINT == tu_desc_type(p_desc) ) - { + while (p_desc < desc_end) { + if (TUSB_DESC_ENDPOINT == tu_desc_type(p_desc)) { uint8_t const ep_addr = ((tusb_desc_endpoint_t const*) p_desc)->bEndpointAddress; - TU_LOG(2, " Bind EP %02x to driver id %u\r\n", ep_addr, driver_id); ep2drv[tu_edpt_number(ep_addr)][tu_edpt_dir(ep_addr)] = driver_id; } - p_desc = tu_desc_next(p_desc); } } -uint16_t tu_desc_get_interface_total_len(tusb_desc_interface_t const* desc_itf, uint8_t itf_count, uint16_t max_len) -{ +uint16_t tu_desc_get_interface_total_len(tusb_desc_interface_t const* desc_itf, uint8_t itf_count, uint16_t max_len) { uint8_t const* p_desc = (uint8_t const*) desc_itf; uint16_t len = 0; - while (itf_count--) - { + while (itf_count--) { // Next on interface desc len += tu_desc_len(desc_itf); p_desc = tu_desc_next(p_desc); - while (len < max_len) - { + while (len < max_len) { // return on IAD regardless of itf count - if ( tu_desc_type(p_desc) == TUSB_DESC_INTERFACE_ASSOCIATION ) return len; - - if ( (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE) && - ((tusb_desc_interface_t const*) p_desc)->bAlternateSetting == 0 ) - { + if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE_ASSOCIATION) { + return len; + } + if ((tu_desc_type(p_desc) == TUSB_DESC_INTERFACE) && + ((tusb_desc_interface_t const*) p_desc)->bAlternateSetting == 0) { break; } @@ -243,9 +215,8 @@ uint16_t tu_desc_get_interface_total_len(tusb_desc_interface_t const* desc_itf, //--------------------------------------------------------------------+ bool tu_edpt_stream_init(tu_edpt_stream_t* s, bool is_host, bool is_tx, bool overwritable, - void* ff_buf, uint16_t ff_bufsize, uint8_t* ep_buf, uint16_t ep_bufsize) -{ - osal_mutex_t new_mutex = osal_mutex_create(&s->ff_mutex); + void* ff_buf, uint16_t ff_bufsize, uint8_t* ep_buf, uint16_t ep_bufsize) { + osal_mutex_t new_mutex = osal_mutex_create(&s->ff_mutexdef); (void) new_mutex; (void) is_tx; @@ -259,92 +230,82 @@ bool tu_edpt_stream_init(tu_edpt_stream_t* s, bool is_host, bool is_tx, bool ove return true; } +bool tu_edpt_stream_deinit(tu_edpt_stream_t* s) { + (void) s; + #if OSAL_MUTEX_REQUIRED + if (s->ff.mutex_wr) osal_mutex_delete(s->ff.mutex_wr); + if (s->ff.mutex_rd) osal_mutex_delete(s->ff.mutex_rd); + #endif + return true; +} + TU_ATTR_ALWAYS_INLINE static inline -bool stream_claim(tu_edpt_stream_t* s) -{ - if (s->is_host) - { +bool stream_claim(tu_edpt_stream_t* s) { + if (s->is_host) { #if CFG_TUH_ENABLED return usbh_edpt_claim(s->daddr, s->ep_addr); #endif - }else - { + } else { #if CFG_TUD_ENABLED return usbd_edpt_claim(s->rhport, s->ep_addr); #endif } - return false; } TU_ATTR_ALWAYS_INLINE static inline -bool stream_xfer(tu_edpt_stream_t* s, uint16_t count) -{ - if (s->is_host) - { +bool stream_xfer(tu_edpt_stream_t* s, uint16_t count) { + if (s->is_host) { #if CFG_TUH_ENABLED return usbh_edpt_xfer(s->daddr, s->ep_addr, count ? s->ep_buf : NULL, count); #endif - }else - { + } else { #if CFG_TUD_ENABLED return usbd_edpt_xfer(s->rhport, s->ep_addr, count ? s->ep_buf : NULL, count); #endif } - return false; } TU_ATTR_ALWAYS_INLINE static inline -bool stream_release(tu_edpt_stream_t* s) -{ - if (s->is_host) - { +bool stream_release(tu_edpt_stream_t* s) { + if (s->is_host) { #if CFG_TUH_ENABLED return usbh_edpt_release(s->daddr, s->ep_addr); #endif - }else - { + } else { #if CFG_TUD_ENABLED return usbd_edpt_release(s->rhport, s->ep_addr); #endif } - return false; } //--------------------------------------------------------------------+ // Stream Write //--------------------------------------------------------------------+ - -bool tu_edpt_stream_write_zlp_if_needed(tu_edpt_stream_t* s, uint32_t last_xferred_bytes) -{ +bool tu_edpt_stream_write_zlp_if_needed(tu_edpt_stream_t* s, uint32_t last_xferred_bytes) { // ZLP condition: no pending data, last transferred bytes is multiple of packet size - TU_VERIFY( !tu_fifo_count(&s->ff) && last_xferred_bytes && (0 == (last_xferred_bytes & (s->ep_packetsize-1))) ); - - TU_VERIFY( stream_claim(s) ); - TU_ASSERT( stream_xfer(s, 0) ); - + TU_VERIFY(!tu_fifo_count(&s->ff) && last_xferred_bytes && (0 == (last_xferred_bytes & (s->ep_packetsize - 1)))); + TU_VERIFY(stream_claim(s)); + TU_ASSERT(stream_xfer(s, 0)); return true; } -uint32_t tu_edpt_stream_write_xfer(tu_edpt_stream_t* s) -{ +uint32_t tu_edpt_stream_write_xfer(tu_edpt_stream_t* s) { // skip if no data - TU_VERIFY( tu_fifo_count(&s->ff), 0 ); + TU_VERIFY(tu_fifo_count(&s->ff), 0); // Claim the endpoint - TU_VERIFY( stream_claim(s), 0 ); + TU_VERIFY(stream_claim(s), 0); // Pull data from FIFO -> EP buf uint16_t const count = tu_fifo_read_n(&s->ff, s->ep_buf, s->ep_bufsize); - if ( count ) - { - TU_ASSERT( stream_xfer(s, count), 0 ); + if (count) { + TU_ASSERT(stream_xfer(s, count), 0); return count; - }else - { + } else { // Release endpoint since we don't make any transfer // Note: data is dropped if terminal is not connected stream_release(s); @@ -352,16 +313,13 @@ uint32_t tu_edpt_stream_write_xfer(tu_edpt_stream_t* s) } } -uint32_t tu_edpt_stream_write(tu_edpt_stream_t* s, void const *buffer, uint32_t bufsize) -{ +uint32_t tu_edpt_stream_write(tu_edpt_stream_t* s, void const* buffer, uint32_t bufsize) { TU_VERIFY(bufsize); // TODO support ZLP - uint16_t ret = tu_fifo_write_n(&s->ff, buffer, (uint16_t) bufsize); // flush if fifo has more than packet size or // in rare case: fifo depth is configured too small (which never reach packet size) - if ( (tu_fifo_count(&s->ff) >= s->ep_packetsize) || (tu_fifo_depth(&s->ff) < s->ep_packetsize) ) - { + if ((tu_fifo_count(&s->ff) >= s->ep_packetsize) || (tu_fifo_depth(&s->ff) < s->ep_packetsize)) { tu_edpt_stream_write_xfer(s); } @@ -371,9 +329,7 @@ uint32_t tu_edpt_stream_write(tu_edpt_stream_t* s, void const *buffer, uint32_t //--------------------------------------------------------------------+ // Stream Read //--------------------------------------------------------------------+ - -uint32_t tu_edpt_stream_read_xfer(tu_edpt_stream_t* s) -{ +uint32_t tu_edpt_stream_read_xfer(tu_edpt_stream_t* s) { uint16_t available = tu_fifo_remaining(&s->ff); // Prepare for incoming data but only allow what we can store in the ring buffer. @@ -388,25 +344,21 @@ uint32_t tu_edpt_stream_read_xfer(tu_edpt_stream_t* s) // get available again since fifo can be changed before endpoint is claimed available = tu_fifo_remaining(&s->ff); - if ( available >= s->ep_packetsize ) - { + if (available >= s->ep_packetsize) { // multiple of packet size limit by ep bufsize - uint16_t count = (uint16_t) (available & ~(s->ep_packetsize -1)); + uint16_t count = (uint16_t) (available & ~(s->ep_packetsize - 1)); count = tu_min16(count, s->ep_bufsize); - TU_ASSERT( stream_xfer(s, count), 0 ); - + TU_ASSERT(stream_xfer(s, count), 0); return count; - }else - { + } else { // Release endpoint since we don't make any transfer stream_release(s); return 0; } } -uint32_t tu_edpt_stream_read(tu_edpt_stream_t* s, void* buffer, uint32_t bufsize) -{ +uint32_t tu_edpt_stream_read(tu_edpt_stream_t* s, void* buffer, uint32_t bufsize) { uint32_t num_read = tu_fifo_read_n(&s->ff, buffer, (uint16_t) bufsize); tu_edpt_stream_read_xfer(s); return num_read; @@ -420,42 +372,35 @@ uint32_t tu_edpt_stream_read(tu_edpt_stream_t* s, void* buffer, uint32_t bufsize #include #if CFG_TUSB_DEBUG >= CFG_TUH_LOG_LEVEL || CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL - -char const* const tu_str_speed[] = { "Full", "Low", "High" }; -char const* const tu_str_std_request[] = -{ - "Get Status" , - "Clear Feature" , - "Reserved" , - "Set Feature" , - "Reserved" , - "Set Address" , - "Get Descriptor" , - "Set Descriptor" , - "Get Configuration" , - "Set Configuration" , - "Get Interface" , - "Set Interface" , - "Synch Frame" +char const* const tu_str_speed[] = {"Full", "Low", "High"}; +char const* const tu_str_std_request[] = { + "Get Status", + "Clear Feature", + "Reserved", + "Set Feature", + "Reserved", + "Set Address", + "Get Descriptor", + "Set Descriptor", + "Get Configuration", + "Set Configuration", + "Get Interface", + "Set Interface", + "Synch Frame" }; char const* const tu_str_xfer_result[] = { "OK", "FAILED", "STALLED", "TIMEOUT" }; - #endif -static void dump_str_line(uint8_t const* buf, uint16_t count) -{ +static void dump_str_line(uint8_t const* buf, uint16_t count) { tu_printf(" |"); - // each line is 16 bytes - for(uint16_t i=0; i= 900 && CFG_TUSB_MCU < 1000) // check if Espressif MCU // Dialog #define OPT_MCU_DA1469X 1000 ///< Dialog Semiconductor DA1469x @@ -125,6 +137,7 @@ #define OPT_MCU_KINETIS_KL 1200 ///< NXP KL series #define OPT_MCU_KINETIS_K32L 1201 ///< NXP K32L series #define OPT_MCU_KINETIS_K32 1201 ///< Alias to K32L +#define OPT_MCU_KINETIS_K 1202 ///< NXP K series #define OPT_MCU_MKL25ZXX 1200 ///< Alias to KL (obsolete) #define OPT_MCU_K32L2BXX 1201 ///< Alias to K32 (obsolete) @@ -138,7 +151,6 @@ #define OPT_MCU_RX72N 1402 ///< Renesas RX72N #define OPT_MCU_RAXXX 1403 ///< Renesas RAxxx families - // Mind Motion #define OPT_MCU_MM32F327X 1500 ///< Mind Motion MM32F327 @@ -171,10 +183,12 @@ // WCH #define OPT_MCU_CH32V307 2200 ///< WCH CH32V307 #define OPT_MCU_CH32F20X 2210 ///< WCH CH32F20x +#define OPT_MCU_CH32V20X 2220 ///< WCH CH32V20X // NXP LPC MCX #define OPT_MCU_MCXN9 2300 ///< NXP MCX N9 Series +#define OPT_MCU_MCXA15 2301 ///< NXP MCX A15 Series // Check if configured MCU is one of listed // Apply _TU_CHECK_MCU with || as separator to list of input @@ -353,6 +367,11 @@ #define CFG_TUD_INTERFACE_MAX 16 #endif +// USB 2.0 compliance test mode support +#ifndef CFG_TUD_TEST_MODE + #define CFG_TUD_TEST_MODE 0 +#endif + //------------- Device Class Driver -------------// #ifndef CFG_TUD_BTH #define CFG_TUD_BTH 0 @@ -472,6 +491,23 @@ {0x10C4, 0xEA60}, {0x10C4, 0xEA70} #endif +#ifndef CFG_TUH_CDC_CH34X + // CH34X is not part of CDC class, only to re-use CDC driver API + #define CFG_TUH_CDC_CH34X 0 +#endif + +#ifndef CFG_TUH_CDC_CH34X_VID_PID_LIST + // List of product IDs that can use the CH34X CDC driver + #define CFG_TUH_CDC_CH34X_VID_PID_LIST \ + { 0x1a86, 0x5523 }, /* ch341 chip */ \ + { 0x1a86, 0x7522 }, /* ch340k chip */ \ + { 0x1a86, 0x7523 }, /* ch340 chip */ \ + { 0x1a86, 0xe523 }, /* ch330 chip */ \ + { 0x4348, 0x5523 }, /* ch340 custom chip */ \ + { 0x2184, 0x0057 }, /* overtaken from Linux Kernel driver /drivers/usb/serial/ch341.c */ \ + { 0x9986, 0x7523 } /* overtaken from Linux Kernel driver /drivers/usb/serial/ch341.c */ +#endif + #ifndef CFG_TUH_HID #define CFG_TUH_HID 0 #endif diff --git a/pico-sdk/src/rp2_common/CMakeLists.txt b/pico-sdk/src/rp2_common/CMakeLists.txt index 82f56f8..15b3435 100644 --- a/pico-sdk/src/rp2_common/CMakeLists.txt +++ b/pico-sdk/src/rp2_common/CMakeLists.txt @@ -61,14 +61,7 @@ if (NOT PICO_BARE_METAL) pico_add_subdirectory(pico_stdio_usb) pico_add_subdirectory(pico_i2c_slave) - # networking libraries - note dependency order is important pico_add_subdirectory(pico_async_context) - pico_add_subdirectory(pico_btstack) - pico_add_subdirectory(pico_cyw43_driver) - pico_add_subdirectory(pico_lwip) - pico_add_subdirectory(pico_cyw43_arch) - pico_add_subdirectory(pico_mbedtls) - pico_add_subdirectory(pico_stdlib) pico_add_subdirectory(pico_cxx_options) @@ -85,4 +78,4 @@ set(CMAKE_EXECUTABLE_SUFFIX "${CMAKE_EXECUTABLE_SUFFIX}" PARENT_SCOPE) pico_add_doxygen(${CMAKE_CURRENT_LIST_DIR}) pico_add_doxygen_exclude(${CMAKE_CURRENT_LIST_DIR}/cmsis) -pico_promote_common_scope_vars() \ No newline at end of file +pico_promote_common_scope_vars() diff --git a/pico-sdk/src/rp2_common/pico_btstack/CMakeLists.txt b/pico-sdk/src/rp2_common/pico_btstack/CMakeLists.txt deleted file mode 100644 index 6ead74d..0000000 --- a/pico-sdk/src/rp2_common/pico_btstack/CMakeLists.txt +++ /dev/null @@ -1,357 +0,0 @@ -if (DEFINED ENV{PICO_BTSTACK_PATH} AND (NOT PICO_BTSTACK_PATH)) - set(PICO_BTSTACK_PATH $ENV{PICO_BTSTACK_PATH}) - message("Using PICO_BTSTACK_PATH from environment ('${PICO_BTSTACK_PATH}')") -endif () - -set(BTSTACK_TEST_PATH "src/bluetooth.h") -if (NOT PICO_BTSTACK_PATH) - set(PICO_BTSTACK_PATH ${PROJECT_SOURCE_DIR}/lib/btstack) - if (PICO_CYW43_SUPPORTED AND NOT EXISTS ${PICO_BTSTACK_PATH}/${BTSTACK_TEST_PATH}) - message(WARNING "btstack submodule has not been initialized; Pico W BLE support will be unavailable. - hint: try 'git submodule update --init' from your SDK directory (${PICO_SDK_PATH}).") - endif() -elseif (NOT EXISTS ${PICO_BTSTACK_PATH}/${BTSTACK_TEST_PATH}) - message(WARNING "PICO_BTSTACK_PATH specified but content not present.") -endif() - -if (EXISTS ${PICO_BTSTACK_PATH}/${BTSTACK_TEST_PATH}) - message("BTstack available at ${PICO_BTSTACK_PATH}") - - pico_register_common_scope_var(PICO_BTSTACK_PATH) - - pico_add_library(pico_btstack_base NOFLAG) - target_include_directories(pico_btstack_base_headers INTERFACE - ${PICO_BTSTACK_PATH}/src - ${PICO_BTSTACK_PATH}/platform/embedded - ) - - target_sources(pico_btstack_base INTERFACE - ${PICO_BTSTACK_PATH}/3rd-party/micro-ecc/uECC.c - ${PICO_BTSTACK_PATH}/3rd-party/rijndael/rijndael.c - ${PICO_BTSTACK_PATH}/3rd-party/segger-rtt/SEGGER_RTT.c - ${PICO_BTSTACK_PATH}/3rd-party/segger-rtt/SEGGER_RTT_printf.c - ${PICO_BTSTACK_PATH}/platform/embedded/btstack_tlv_flash_bank.c - ${PICO_BTSTACK_PATH}/platform/embedded/hci_dump_embedded_stdout.c - ${PICO_BTSTACK_PATH}/platform/embedded/hci_dump_segger_rtt_stdout.c - ${PICO_BTSTACK_PATH}/src/ad_parser.c - ${PICO_BTSTACK_PATH}/src/btstack_audio.c - ${PICO_BTSTACK_PATH}/src/btstack_base64_decoder.c - ${PICO_BTSTACK_PATH}/src/btstack_crypto.c - ${PICO_BTSTACK_PATH}/src/btstack_hid_parser.c - ${PICO_BTSTACK_PATH}/src/btstack_linked_list.c - ${PICO_BTSTACK_PATH}/src/btstack_memory.c - ${PICO_BTSTACK_PATH}/src/btstack_memory_pool.c - ${PICO_BTSTACK_PATH}/src/btstack_resample.c - ${PICO_BTSTACK_PATH}/src/btstack_ring_buffer.c - ${PICO_BTSTACK_PATH}/src/btstack_run_loop.c - ${PICO_BTSTACK_PATH}/src/btstack_run_loop_base.c - ${PICO_BTSTACK_PATH}/src/btstack_slip.c - ${PICO_BTSTACK_PATH}/src/btstack_tlv.c - ${PICO_BTSTACK_PATH}/src/btstack_tlv_none.c - ${PICO_BTSTACK_PATH}/src/btstack_util.c - ${PICO_BTSTACK_PATH}/src/hci.c - ${PICO_BTSTACK_PATH}/src/hci_cmd.c - ${PICO_BTSTACK_PATH}/src/hci_dump.c - ${PICO_BTSTACK_PATH}/src/hci_event.c - ${PICO_BTSTACK_PATH}/src/l2cap.c - ${PICO_BTSTACK_PATH}/src/l2cap_signaling.c - ${PICO_BTSTACK_PATH}/src/mesh/gatt-service/mesh_provisioning_service_server.c - ${PICO_BTSTACK_PATH}/src/mesh/gatt-service/mesh_proxy_service_server.c - ${PICO_BTSTACK_PATH}/3rd-party/md5/md5.c - ${PICO_BTSTACK_PATH}/3rd-party/yxml/yxml.c - ${CMAKE_CURRENT_LIST_DIR}/btstack_stdin_pico.c - ) - target_include_directories(pico_btstack_base_headers INTERFACE - ${PICO_BTSTACK_PATH}/ - ${PICO_BTSTACK_PATH}/3rd-party/md5 - ${PICO_BTSTACK_PATH}/3rd-party/yxml - ${PICO_BTSTACK_PATH}/3rd-party/rijndael - ${PICO_BTSTACK_PATH}/3rd-party/micro-ecc - ${PICO_BTSTACK_PATH}/3rd-party/segger-rtt - ) - - pico_add_library(pico_btstack_ble) - target_sources(pico_btstack_ble INTERFACE - ${PICO_BTSTACK_PATH}/src/ble/att_db.c - ${PICO_BTSTACK_PATH}/src/ble/att_db_util.c - ${PICO_BTSTACK_PATH}/src/ble/att_dispatch.c - ${PICO_BTSTACK_PATH}/src/ble/att_server.c - ${PICO_BTSTACK_PATH}/src/ble/gatt-service/battery_service_server.c - ${PICO_BTSTACK_PATH}/src/ble/gatt-service/battery_service_client.c - ${PICO_BTSTACK_PATH}/src/ble/gatt-service/cycling_power_service_server.c - ${PICO_BTSTACK_PATH}/src/ble/gatt-service/cycling_speed_and_cadence_service_server.c - ${PICO_BTSTACK_PATH}/src/ble/gatt-service/device_information_service_server.c - ${PICO_BTSTACK_PATH}/src/ble/gatt-service/device_information_service_client.c - ${PICO_BTSTACK_PATH}/src/ble/gatt-service/heart_rate_service_server.c - ${PICO_BTSTACK_PATH}/src/ble/gatt-service/hids_client.c - ${PICO_BTSTACK_PATH}/src/ble/gatt-service/hids_device.c - ${PICO_BTSTACK_PATH}/src/ble/gatt-service/nordic_spp_service_server.c - ${PICO_BTSTACK_PATH}/src/ble/gatt-service/ublox_spp_service_server.c - ${PICO_BTSTACK_PATH}/src/ble/gatt-service/ancs_client.c - ${PICO_BTSTACK_PATH}/src/ble/gatt_client.c - ${PICO_BTSTACK_PATH}/src/ble/le_device_db_memory.c - ${PICO_BTSTACK_PATH}/src/ble/le_device_db_tlv.c - ${PICO_BTSTACK_PATH}/src/ble/sm.c - ) - pico_mirrored_target_link_libraries(pico_btstack_ble INTERFACE - pico_btstack_base - ) - target_compile_definitions(pico_btstack_ble_headers INTERFACE - ENABLE_BLE=1 - ) - - pico_add_library(pico_btstack_classic) - target_sources(pico_btstack_classic INTERFACE - ${PICO_BTSTACK_PATH}/src/classic/a2dp.c - ${PICO_BTSTACK_PATH}/src/classic/a2dp_sink.c - ${PICO_BTSTACK_PATH}/src/classic/a2dp_source.c - ${PICO_BTSTACK_PATH}/src/classic/avdtp.c - ${PICO_BTSTACK_PATH}/src/classic/avdtp_acceptor.c - ${PICO_BTSTACK_PATH}/src/classic/avdtp_initiator.c - ${PICO_BTSTACK_PATH}/src/classic/avdtp_sink.c - ${PICO_BTSTACK_PATH}/src/classic/avdtp_source.c - ${PICO_BTSTACK_PATH}/src/classic/avdtp_util.c - ${PICO_BTSTACK_PATH}/src/classic/avrcp.c - ${PICO_BTSTACK_PATH}/src/classic/avrcp_browsing.c - ${PICO_BTSTACK_PATH}/src/classic/avrcp_browsing_controller.c - ${PICO_BTSTACK_PATH}/src/classic/avrcp_browsing_target.c - ${PICO_BTSTACK_PATH}/src/classic/avrcp_controller.c - ${PICO_BTSTACK_PATH}/src/classic/avrcp_cover_art_client.c - ${PICO_BTSTACK_PATH}/src/classic/avrcp_media_item_iterator.c - ${PICO_BTSTACK_PATH}/src/classic/avrcp_target.c - ${PICO_BTSTACK_PATH}/src/classic/btstack_cvsd_plc.c - ${PICO_BTSTACK_PATH}/src/classic/btstack_link_key_db_tlv.c - ${PICO_BTSTACK_PATH}/src/classic/btstack_sbc_plc.c - ${PICO_BTSTACK_PATH}/src/classic/device_id_server.c - ${PICO_BTSTACK_PATH}/src/classic/gatt_sdp.c - ${PICO_BTSTACK_PATH}/src/classic/goep_client.c - ${PICO_BTSTACK_PATH}/src/classic/goep_server.c - ${PICO_BTSTACK_PATH}/src/classic/hfp.c - ${PICO_BTSTACK_PATH}/src/classic/hfp_ag.c - ${PICO_BTSTACK_PATH}/src/classic/hfp_gsm_model.c - ${PICO_BTSTACK_PATH}/src/classic/hfp_hf.c - ${PICO_BTSTACK_PATH}/src/classic/hfp_msbc.c - ${PICO_BTSTACK_PATH}/src/classic/hid_device.c - ${PICO_BTSTACK_PATH}/src/classic/hid_host.c - ${PICO_BTSTACK_PATH}/src/classic/hsp_ag.c - ${PICO_BTSTACK_PATH}/src/classic/hsp_hs.c - ${PICO_BTSTACK_PATH}/src/classic/obex_iterator.c - ${PICO_BTSTACK_PATH}/src/classic/obex_message_builder.c - ${PICO_BTSTACK_PATH}/src/classic/obex_parser.c - ${PICO_BTSTACK_PATH}/src/classic/pan.c - ${PICO_BTSTACK_PATH}/src/classic/pbap_client.c - ${PICO_BTSTACK_PATH}/src/classic/rfcomm.c - ${PICO_BTSTACK_PATH}/src/classic/sdp_client.c - ${PICO_BTSTACK_PATH}/src/classic/sdp_client_rfcomm.c - ${PICO_BTSTACK_PATH}/src/classic/sdp_server.c - ${PICO_BTSTACK_PATH}/src/classic/sdp_util.c - ${PICO_BTSTACK_PATH}/src/classic/spp_server.c - ) - pico_mirrored_target_link_libraries(pico_btstack_classic INTERFACE - pico_btstack_base - ) - target_compile_definitions(pico_btstack_classic_headers INTERFACE - ENABLE_CLASSIC=1 - ) - - pico_add_library(pico_btstack_flash_bank) - target_sources(pico_btstack_flash_bank INTERFACE - ${CMAKE_CURRENT_LIST_DIR}/btstack_flash_bank.c - ) - target_include_directories(pico_btstack_flash_bank_headers INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) - pico_mirrored_target_link_libraries(pico_btstack_flash_bank INTERFACE pico_btstack_base pico_flash) - - pico_add_library(pico_btstack_run_loop_async_context NOFLAG) - target_sources(pico_btstack_run_loop_async_context INTERFACE - ${CMAKE_CURRENT_LIST_DIR}/btstack_run_loop_async_context.c - ) - target_include_directories(pico_btstack_run_loop_async_context_headers INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) - pico_mirrored_target_link_libraries(pico_btstack_run_loop_async_context INTERFACE pico_btstack_base pico_async_context_base) - - pico_add_library(pico_btstack_sbc_encoder NOFLAG) - target_sources(pico_btstack_sbc_encoder INTERFACE - # SBC codec for A2DP and HFP demos - ${PICO_BTSTACK_PATH}/src/classic/btstack_sbc_encoder_bluedroid.c - ${PICO_BTSTACK_PATH}/3rd-party/bluedroid/encoder/srce/sbc_analysis.c - ${PICO_BTSTACK_PATH}/3rd-party/bluedroid/encoder/srce/sbc_dct.c - ${PICO_BTSTACK_PATH}/3rd-party/bluedroid/encoder/srce/sbc_dct_coeffs.c - ${PICO_BTSTACK_PATH}/3rd-party/bluedroid/encoder/srce/sbc_enc_bit_alloc_mono.c - ${PICO_BTSTACK_PATH}/3rd-party/bluedroid/encoder/srce/sbc_enc_bit_alloc_ste.c - ${PICO_BTSTACK_PATH}/3rd-party/bluedroid/encoder/srce/sbc_enc_coeffs.c - ${PICO_BTSTACK_PATH}/3rd-party/bluedroid/encoder/srce/sbc_encoder.c - ${PICO_BTSTACK_PATH}/3rd-party/bluedroid/encoder/srce/sbc_packing.c - ) - target_include_directories(pico_btstack_sbc_encoder_headers INTERFACE - ${PICO_BTSTACK_PATH}/3rd-party/bluedroid/encoder/include - ) - - pico_add_library(pico_btstack_sbc_decoder NOFLAG) - target_sources(pico_btstack_sbc_decoder INTERFACE - ${PICO_BTSTACK_PATH}/src/classic/btstack_sbc_decoder_bluedroid.c - ${PICO_BTSTACK_PATH}/3rd-party/bluedroid/decoder/srce/alloc.c - ${PICO_BTSTACK_PATH}/3rd-party/bluedroid/decoder/srce/bitalloc.c - ${PICO_BTSTACK_PATH}/3rd-party/bluedroid/decoder/srce/bitalloc-sbc.c - ${PICO_BTSTACK_PATH}/3rd-party/bluedroid/decoder/srce/bitstream-decode.c - ${PICO_BTSTACK_PATH}/3rd-party/bluedroid/decoder/srce/decoder-oina.c - ${PICO_BTSTACK_PATH}/3rd-party/bluedroid/decoder/srce/decoder-private.c - ${PICO_BTSTACK_PATH}/3rd-party/bluedroid/decoder/srce/decoder-sbc.c - ${PICO_BTSTACK_PATH}/3rd-party/bluedroid/decoder/srce/dequant.c - ${PICO_BTSTACK_PATH}/3rd-party/bluedroid/decoder/srce/framing.c - ${PICO_BTSTACK_PATH}/3rd-party/bluedroid/decoder/srce/framing-sbc.c - ${PICO_BTSTACK_PATH}/3rd-party/bluedroid/decoder/srce/oi_codec_version.c - ${PICO_BTSTACK_PATH}/3rd-party/bluedroid/decoder/srce/synthesis-sbc.c - ${PICO_BTSTACK_PATH}/3rd-party/bluedroid/decoder/srce/synthesis-dct8.c - ${PICO_BTSTACK_PATH}/3rd-party/bluedroid/decoder/srce/synthesis-8-generated.c - ) - target_include_directories(pico_btstack_sbc_decoder_headers INTERFACE - ${PICO_BTSTACK_PATH}/3rd-party/bluedroid/decoder/include - ) - - pico_add_library(pico_btstack_bnep_lwip NOFLAG) - target_sources(pico_btstack_bnep_lwip INTERFACE - ${PICO_BTSTACK_PATH}/src/classic/bnep.c - ${PICO_BTSTACK_PATH}/platform/lwip/bnep_lwip.c - ) - target_include_directories(pico_btstack_bnep_lwip_headers INTERFACE - ${PICO_BTSTACK_PATH}/platform/lwip - ) - - pico_add_library(pico_btstack_bnep_lwip_sys_freertos NOFLAG) - target_include_directories(pico_btstack_bnep_lwip_sys_freertos INTERFACE - ${PICO_BTSTACK_PATH}/platform/freertos - ) - pico_mirrored_target_link_libraries(pico_btstack_bnep_lwip_sys_freertos INTERFACE - pico_btstack_bnep_lwip - ) - target_compile_definitions(pico_btstack_bnep_lwip_sys_freertos INTERFACE - LWIP_PROVIDE_ERRNO=1 - PICO_LWIP_CUSTOM_LOCK_TCPIP_CORE=1 - ) - - pico_promote_common_scope_vars() - - # Make a GATT header file from a BTstack GATT file - # Pass the target library name library type and path to the GATT input file - function(pico_btstack_make_gatt_header TARGET_LIB TARGET_TYPE GATT_FILE) - find_package (Python3 REQUIRED COMPONENTS Interpreter) - get_filename_component(GATT_NAME "${GATT_FILE}" NAME_WE) - get_filename_component(GATT_PATH "${GATT_FILE}" PATH) - set(GATT_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated") - set(GATT_HEADER "${GATT_BINARY_DIR}/${GATT_NAME}.h") - set(TARGET_GATT "${TARGET_LIB}_gatt_header") - - add_custom_target(${TARGET_GATT} DEPENDS ${GATT_HEADER}) - add_custom_command( - OUTPUT ${GATT_HEADER} - DEPENDS ${GATT_FILE} - WORKING_DIRECTORY ${GATT_PATH} - COMMAND ${CMAKE_COMMAND} -E make_directory ${GATT_BINARY_DIR} && - ${Python3_EXECUTABLE} ${PICO_SDK_PATH}/lib/btstack/tool/compile_gatt.py ${GATT_FILE} ${GATT_HEADER} - VERBATIM) - add_dependencies(${TARGET_LIB} - ${TARGET_GATT} - ) - target_include_directories(${TARGET_LIB} ${TARGET_TYPE} - ${GATT_BINARY_DIR} - ) - endfunction() - - function(suppress_btstack_warnings) - set_source_files_properties( - ${PICO_BTSTACK_PATH}/src/ble/att_server.c - ${PICO_BTSTACK_PATH}/src/ble/gatt-service/device_information_service_server.c - ${PICO_BTSTACK_PATH}/src/ble/gatt-service/hids_client.c - ${PICO_BTSTACK_PATH}/src/btstack_util.c - ${PICO_BTSTACK_PATH}/src/btstack_crypto.c - ${PICO_BTSTACK_PATH}/src/classic/a2dp.c - ${PICO_BTSTACK_PATH}/src/classic/a2dp_sink.c - ${PICO_BTSTACK_PATH}/src/classic/a2dp_source.c - ${PICO_BTSTACK_PATH}/src/classic/avdtp.c - ${PICO_BTSTACK_PATH}/src/classic/avdtp_source.c - ${PICO_BTSTACK_PATH}/src/classic/avrcp.c - ${PICO_BTSTACK_PATH}/src/classic/avrcp_controller.c - ${PICO_BTSTACK_PATH}/src/classic/btstack_sbc_decoder_bluedroid.c - ${PICO_BTSTACK_PATH}/src/classic/avrcp_target.c - ${PICO_BTSTACK_PATH}/src/classic/hid_device.c - ${PICO_BTSTACK_PATH}/src/classic/hsp_ag.c - ${PICO_BTSTACK_PATH}/src/classic/hsp_hs.c - ${PICO_BTSTACK_PATH}/src/classic/pan.c - ${PICO_BTSTACK_PATH}/src/classic/pbap_client.c - ${PICO_BTSTACK_PATH}/src/classic/rfcomm.c - ${PICO_BTSTACK_PATH}/src/classic/sdp_client_rfcomm.c - ${PICO_BTSTACK_PATH}/src/classic/sdp_server.c - ${PICO_BTSTACK_PATH}/src/classic/spp_server.c - PROPERTIES - COMPILE_OPTIONS "-Wno-cast-qual" - ) - set_source_files_properties( - ${PICO_BTSTACK_PATH}/src/ble/sm.c - ${PICO_BTSTACK_PATH}/src/l2cap.c - PROPERTIES - COMPILE_OPTIONS "-Wno-cast-qual;-Wno-unused-parameter" - ) - set_source_files_properties( - ${PICO_BTSTACK_PATH}/src/btstack_hid_parser.c - PROPERTIES - COMPILE_OPTIONS "-Wno-maybe-uninitialized" - ) - set_source_files_properties( - ${PICO_BTSTACK_PATH}/src/btstack_tlv_none.c - ${PICO_BTSTACK_PATH}/src/classic/avdtp_util.c - PROPERTIES - COMPILE_OPTIONS "-Wno-unused-parameter" - ) - set_source_files_properties( - ${PICO_BTSTACK_PATH}/platform/embedded/hci_dump_embedded_stdout.c - PROPERTIES - COMPILE_OPTIONS "-Wno-suggest-attribute=format" - ) - set_source_files_properties( - ${PICO_BTSTACK_PATH}/src/hci.c - ${PICO_BTSTACK_PATH}/src/classic/rfcomm.c - PROPERTIES - COMPILE_OPTIONS "-Wno-cast-qual;-Wno-format" - ) - set_source_files_properties( - ${PICO_BTSTACK_PATH}/platform/embedded/hal_flash_bank_memory.c - PROPERTIES - COMPILE_OPTIONS "-Wno-sign-compare;-Wno-format" - ) - set_source_files_properties( - ${PICO_BTSTACK_PATH}/platform/embedded/btstack_tlv_flash_bank.c - PROPERTIES - COMPILE_OPTIONS "-Wno-unused-parameter;-Wno-sign-compare" - ) - set_source_files_properties( - ${PICO_BTSTACK_PATH}/src/ble/gatt-service/hids_client.c - PROPERTIES - COMPILE_OPTIONS "-Wno-cast-qual;-Wno-null-dereference" - ) - set_source_files_properties( - ${PICO_BTSTACK_PATH}/src/classic/hfp.c - PROPERTIES - COMPILE_OPTIONS "-Wno-cast-qual;-Wno-null-dereference;-Wno-unused-parameter" - ) - set_source_files_properties( - ${PICO_BTSTACK_PATH}/src/classic/goep_server.c - PROPERTIES - COMPILE_OPTIONS "-Wno-unused-parameter;-Wno-null-dereference" - ) - set_source_files_properties( - ${PICO_BTSTACK_PATH}/src/ble/gatt-service/battery_service_client.c - ${PICO_BTSTACK_PATH}/src/ble/gatt-service/device_information_service_client.c - PROPERTIES - COMPILE_OPTIONS "-Wno-null-dereference" - ) - set_source_files_properties( - ${PICO_BTSTACK_PATH}/src/classic/hfp_hf.c - PROPERTIES - COMPILE_OPTIONS "-Wno-type-limits;-Wno-stringop-overflow" - ) - set_source_files_properties( - ${PICO_BTSTACK_PATH}/src/btstack_crypto.c - PROPERTIES - COMPILE_OPTIONS "-Wno-cast-qual;-Wno-sign-compare" - ) - endfunction() -endif() diff --git a/pico-sdk/src/rp2_common/pico_btstack/LICENSE.RP b/pico-sdk/src/rp2_common/pico_btstack/LICENSE.RP deleted file mode 100644 index 79e0080..0000000 --- a/pico-sdk/src/rp2_common/pico_btstack/LICENSE.RP +++ /dev/null @@ -1,30 +0,0 @@ -“BlueKitchen” shall refer to BlueKitchen GmbH. -“Raspberry Pi” shall refer to Raspberry Pi Ltd. -“Product” shall refer to Raspberry Pi hardware products Raspberry Pi Pico W or Raspberry Pi Pico WH. -“Customer” means any purchaser of a Product. -“Customer Products” means products manufactured or distributed by Customers which use or are derived from Products. - -Raspberry Pi grants to the Customer a non-exclusive, non-transferable, non-sublicensable, irrevocable, perpetual -and worldwide licence to use, copy, store, develop, modify, and transmit BTstack in order to use BTstack with or -integrate BTstack into Products or Customer Products, and distribute BTstack as part of these Products or -Customer Products or their related documentation or SDKs. - -All use of BTstack by the Customer is limited to Products or Customer Products, and the Customer represents and -warrants that all such use shall be in compliance with the terms of this licence and all applicable laws and -regulations, including but not limited to, copyright and other intellectual property laws and privacy regulations. - -BlueKitchen retains all rights, title and interest in, to and associated with BTstack and associated websites. -Customer shall not take any action inconsistent with BlueKitchen’s ownership of BTstack, any associated services, -websites and related content. - -There are no implied licences under the terms set forth in this licence, and any rights not expressly granted -hereunder are reserved by BlueKitchen. - -BTSTACK IS PROVIDED BY RASPBERRY PI "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED TO THE FULLEST EXTENT -PERMISSIBLE UNDER APPLICABLE LAW. IN NO EVENT SHALL RASPBERRY PI OR BLUEKITCHEN BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -OUT OF THE USE OF BTSTACK, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - diff --git a/pico-sdk/src/rp2_common/pico_btstack/btstack_flash_bank.c b/pico-sdk/src/rp2_common/pico_btstack/btstack_flash_bank.c deleted file mode 100644 index 9ed9c02..0000000 --- a/pico-sdk/src/rp2_common/pico_btstack/btstack_flash_bank.c +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include "pico/btstack_flash_bank.h" -#include "pico/flash.h" -#include "hardware/sync.h" -#include - -// Check sizes -static_assert(PICO_FLASH_BANK_TOTAL_SIZE % (FLASH_SECTOR_SIZE * 2) == 0, "PICO_FLASH_BANK_TOTAL_SIZE invalid"); -static_assert(PICO_FLASH_BANK_TOTAL_SIZE <= PICO_FLASH_SIZE_BYTES, "PICO_FLASH_BANK_TOTAL_SIZE too big"); - -// Size of one bank -#define PICO_FLASH_BANK_SIZE (PICO_FLASH_BANK_TOTAL_SIZE / 2) - -#if 0 -#define DEBUG_PRINT(format,args...) printf(format, ## args) -#else -#define DEBUG_PRINT(...) -#endif - -static uint32_t pico_flash_bank_get_size(void * context) { - (void)(context); - return PICO_FLASH_BANK_SIZE; -} - -static uint32_t pico_flash_bank_get_alignment(void * context) { - (void)(context); - return 1; -} - -typedef struct { - bool op_is_erase; - uintptr_t p0; - uintptr_t p1; -} mutation_operation_t; - -static void pico_flash_bank_perform_flash_mutation_operation(void *param) { - const mutation_operation_t *mop = (const mutation_operation_t *)param; - if (mop->op_is_erase) { - flash_range_erase(mop->p0, PICO_FLASH_BANK_SIZE); - } else { - flash_range_program(mop->p0, (const uint8_t *)mop->p1, FLASH_PAGE_SIZE); - } -} - -#ifndef pico_flash_bank_get_storage_offset_func -static inline uint32_t pico_flash_bank_get_fixed_storage_offset(void) { - static_assert(PICO_FLASH_BANK_STORAGE_OFFSET + PICO_FLASH_BANK_TOTAL_SIZE <= PICO_FLASH_SIZE_BYTES, "PICO_FLASH_BANK_TOTAL_SIZE too big"); -#ifndef NDEBUG - // Check we're not overlapping the binary in flash - extern char __flash_binary_end; - assert(((uintptr_t)&__flash_binary_end - XIP_BASE <= PICO_FLASH_BANK_STORAGE_OFFSET)); -#endif - return PICO_FLASH_BANK_STORAGE_OFFSET; -} -#define pico_flash_bank_get_storage_offset_func pico_flash_bank_get_fixed_storage_offset -#else -extern uint32_t pico_flash_bank_get_storage_offset_func(void); -#endif - -static void pico_flash_bank_erase(void * context, int bank) { - (void)(context); - DEBUG_PRINT("erase: bank %d\n", bank); - mutation_operation_t mop = { - .op_is_erase = true, - .p0 = pico_flash_bank_get_storage_offset_func() + (PICO_FLASH_BANK_SIZE * bank), - }; - // todo choice of timeout and check return code... currently we have no way to return an error - // to the caller anyway. flash_safe_execute asserts by default on problem other than timeout, - // so that's fine for now, and UINT32_MAX is a timeout of 49 days which seems long enough - flash_safe_execute(pico_flash_bank_perform_flash_mutation_operation, &mop, UINT32_MAX); -} - -static void pico_flash_bank_read(void *context, int bank, uint32_t offset, uint8_t *buffer, uint32_t size) { - (void)(context); - DEBUG_PRINT("read: bank %d offset %u size %u\n", bank, offset, size); - - assert(bank <= 1); - if (bank > 1) return; - - assert(offset < PICO_FLASH_BANK_SIZE); - if (offset >= PICO_FLASH_BANK_SIZE) return; - - assert((offset + size) <= PICO_FLASH_BANK_SIZE); - if ((offset + size) > PICO_FLASH_BANK_SIZE) return; - - // Flash is xip - memcpy(buffer, (void *)(XIP_BASE + pico_flash_bank_get_storage_offset_func() + (PICO_FLASH_BANK_SIZE * bank) + offset), size); -} - -static void pico_flash_bank_write(void * context, int bank, uint32_t offset, const uint8_t *data, uint32_t size) { - (void)(context); - DEBUG_PRINT("write: bank %d offset %u size %u\n", bank, offset, size); - - assert(bank <= 1); - if (bank > 1) return; - - assert(offset < PICO_FLASH_BANK_SIZE); - if (offset >= PICO_FLASH_BANK_SIZE) return; - - assert((offset + size) <= PICO_FLASH_BANK_SIZE); - if ((offset + size) > PICO_FLASH_BANK_SIZE) return; - - if (size == 0) return; - - // calc bank start position - const uint32_t bank_start_pos = pico_flash_bank_get_storage_offset_func() + (PICO_FLASH_BANK_SIZE * bank); - - // Calculate first and last page in the bank - const uint32_t first_page = offset / FLASH_PAGE_SIZE; - const uint32_t last_page = (offset + size + FLASH_PAGE_SIZE - 1) / FLASH_PAGE_SIZE; - - // Now we only care about the offset in the first page - offset %= FLASH_PAGE_SIZE; - - // Amount of data we've copied - uint32_t data_pos = 0; - uint32_t size_left = size; - - // Write all the pages required - for(uint32_t page = first_page; page < last_page; page++) { - uint8_t page_data[FLASH_PAGE_SIZE]; - - assert(data_pos < size && size_left <= size); - - // Copy data we're not going to overwrite in the first page - if (page == first_page && offset > 0) { - memcpy(page_data, - (void *)(XIP_BASE + bank_start_pos + (page * FLASH_PAGE_SIZE)), - offset); - } - - // Copy the data we're not going to overwrite in the last page - if (page == last_page - 1 && (offset + size_left) < FLASH_PAGE_SIZE) { - memcpy(page_data + offset + size_left, - (void *)(XIP_BASE + bank_start_pos + (page * FLASH_PAGE_SIZE) + offset + size_left), - FLASH_PAGE_SIZE - offset - size_left); - } - - // Now copy the new data into the page - const uint32_t size_to_copy = MIN(size_left, FLASH_PAGE_SIZE - offset); - memcpy(page_data + offset, data + data_pos, size_to_copy); - - data_pos += size_to_copy; - size_left -= size_to_copy; - - // zero offset for the following pages - offset = 0; - - // Now program the entire page - mutation_operation_t mop = { - .op_is_erase = false, - .p0 = bank_start_pos + (page * FLASH_PAGE_SIZE), - .p1 = (uintptr_t)page_data - }; - // todo choice of timeout and check return code... currently we have no way to return an error - // to the caller anyway. flash_safe_execute asserts by default on problem other than timeout, - // so that's fine for now, and UINT32_MAX is a timeout of 49 days which seems long enough - flash_safe_execute(pico_flash_bank_perform_flash_mutation_operation, &mop, UINT32_MAX); - } -} - -static const hal_flash_bank_t pico_flash_bank_instance_obj = { - /* uint32_t (*get_size)(..) */ &pico_flash_bank_get_size, - /* uint32_t (*get_alignment)(..); */ &pico_flash_bank_get_alignment, - /* void (*erase)(..); */ &pico_flash_bank_erase, - /* void (*read)(..); */ &pico_flash_bank_read, - /* void (*write)(..); */ &pico_flash_bank_write, -}; - -const hal_flash_bank_t *pico_flash_bank_instance(void) { - return &pico_flash_bank_instance_obj; -} diff --git a/pico-sdk/src/rp2_common/pico_btstack/btstack_run_loop_async_context.c b/pico-sdk/src/rp2_common/pico_btstack/btstack_run_loop_async_context.c deleted file mode 100644 index 9847be9..0000000 --- a/pico-sdk/src/rp2_common/pico_btstack/btstack_run_loop_async_context.c +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include "pico/btstack_run_loop_async_context.h" -#include "hardware/sync.h" - -static async_context_t *btstack_async_context; -static async_at_time_worker_t btstack_timeout_worker; -static async_when_pending_worker_t btstack_processing_worker; -static void btstack_timeout_reached(async_context_t *context, async_at_time_worker_t *worker); -static void btstack_work_pending(async_context_t *context, async_when_pending_worker_t *worker); -static volatile bool run_loop_exit; - -static void btstack_run_loop_async_context_init(void) { - btstack_run_loop_base_init(); - btstack_timeout_worker.do_work = btstack_timeout_reached; - btstack_processing_worker.do_work = btstack_work_pending; - async_context_add_when_pending_worker(btstack_async_context, &btstack_processing_worker); -} - -static void btstack_run_loop_async_context_add_data_source(btstack_data_source_t * data_source) { - async_context_acquire_lock_blocking(btstack_async_context); - btstack_run_loop_base_add_data_source(data_source); - async_context_release_lock(btstack_async_context); -} - -static bool btstack_run_loop_async_context_remove_data_source(btstack_data_source_t * data_source) { - async_context_acquire_lock_blocking(btstack_async_context); - bool rc = btstack_run_loop_base_remove_data_source(data_source); - async_context_release_lock(btstack_async_context); - return rc; -} - -static void btstack_run_loop_async_context_enable_data_source_callbacks(btstack_data_source_t * data_source, uint16_t callbacks) { - async_context_acquire_lock_blocking(btstack_async_context); - btstack_run_loop_base_enable_data_source_callbacks(data_source, callbacks); - async_context_release_lock(btstack_async_context); -} - -static void btstack_run_loop_async_context_disable_data_source_callbacks(btstack_data_source_t * data_source, uint16_t callbacks) { - async_context_acquire_lock_blocking(btstack_async_context); - btstack_run_loop_base_disable_data_source_callbacks(data_source, callbacks); - async_context_release_lock(btstack_async_context); -} - -static void btstack_run_loop_async_context_set_timer(btstack_timer_source_t *ts, uint32_t timeout_in_ms){ - async_context_acquire_lock_blocking(btstack_async_context); - ts->timeout = to_ms_since_boot(get_absolute_time()) + timeout_in_ms + 1; - async_context_set_work_pending(btstack_async_context, &btstack_processing_worker); - async_context_release_lock(btstack_async_context); -} - -static void btstack_run_loop_async_context_add_timer(btstack_timer_source_t *timer) { - async_context_acquire_lock_blocking(btstack_async_context); - btstack_run_loop_base_add_timer(timer); - async_context_set_work_pending(btstack_async_context, &btstack_processing_worker); - async_context_release_lock(btstack_async_context); -} - -static bool btstack_run_loop_async_context_remove_timer(btstack_timer_source_t *timer) { - async_context_acquire_lock_blocking(btstack_async_context); - bool rc = btstack_run_loop_base_remove_timer(timer); - async_context_release_lock(btstack_async_context); - return rc; -} - -static void btstack_run_loop_async_context_dump_timer(void){ - async_context_acquire_lock_blocking(btstack_async_context); - btstack_run_loop_base_dump_timer(); - async_context_release_lock(btstack_async_context); -} - -static uint32_t btstack_run_loop_async_context_get_time_ms(void) -{ - return to_ms_since_boot(get_absolute_time()); -} - -static void btstack_run_loop_async_context_execute(void) -{ - run_loop_exit = false; - while (!run_loop_exit) { - async_context_poll(btstack_async_context); - async_context_wait_for_work_until(btstack_async_context, at_the_end_of_time); - } -} - -static void btstack_run_loop_async_context_trigger_exit(void) -{ - run_loop_exit = true; -} - -static void btstack_run_loop_async_context_execute_on_main_thread(btstack_context_callback_registration_t *callback_registration) -{ - async_context_acquire_lock_blocking(btstack_async_context); - btstack_run_loop_base_add_callback(callback_registration); - async_context_set_work_pending(btstack_async_context, &btstack_processing_worker); - async_context_release_lock(btstack_async_context); -} - -static void btstack_run_loop_async_context_poll_data_sources_from_irq(void) -{ - async_context_set_work_pending(btstack_async_context, &btstack_processing_worker); -} - -static const btstack_run_loop_t btstack_run_loop_async_context = { - &btstack_run_loop_async_context_init, - &btstack_run_loop_async_context_add_data_source, - &btstack_run_loop_async_context_remove_data_source, - &btstack_run_loop_async_context_enable_data_source_callbacks, - &btstack_run_loop_async_context_disable_data_source_callbacks, - &btstack_run_loop_async_context_set_timer, - &btstack_run_loop_async_context_add_timer, - &btstack_run_loop_async_context_remove_timer, - &btstack_run_loop_async_context_execute, - &btstack_run_loop_async_context_dump_timer, - &btstack_run_loop_async_context_get_time_ms, - &btstack_run_loop_async_context_poll_data_sources_from_irq, - &btstack_run_loop_async_context_execute_on_main_thread, - &btstack_run_loop_async_context_trigger_exit, -}; - -const btstack_run_loop_t *btstack_run_loop_async_context_get_instance(async_context_t *async_context) -{ - assert(!btstack_async_context || btstack_async_context == async_context); - btstack_async_context = async_context; - return &btstack_run_loop_async_context; -} - -static void btstack_timeout_reached(__unused async_context_t *context, __unused async_at_time_worker_t *worker) { - // simply wakeup worker - async_context_set_work_pending(btstack_async_context, &btstack_processing_worker); -} - -static void btstack_work_pending(__unused async_context_t *context, __unused async_when_pending_worker_t *worker) { - // poll data sources - btstack_run_loop_base_poll_data_sources(); - - // execute callbacks - btstack_run_loop_base_execute_callbacks(); - - uint32_t now = to_ms_since_boot(get_absolute_time()); - - // process timers - btstack_run_loop_base_process_timers(now); - now = to_ms_since_boot(get_absolute_time()); - int ms = btstack_run_loop_base_get_time_until_timeout(now); - if (ms == -1) { - async_context_remove_at_time_worker(btstack_async_context, &btstack_timeout_worker); - } else { - async_context_add_at_time_worker_in_ms(btstack_async_context, &btstack_timeout_worker, ms); - } -} diff --git a/pico-sdk/src/rp2_common/pico_btstack/btstack_stdin_pico.c b/pico-sdk/src/rp2_common/pico_btstack/btstack_stdin_pico.c deleted file mode 100644 index 1afa4cc..0000000 --- a/pico-sdk/src/rp2_common/pico_btstack/btstack_stdin_pico.c +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include "btstack_config.h" - -#ifdef HAVE_BTSTACK_STDIN - -#include "btstack_stdin.h" -#include "btstack_run_loop.h" -#include "pico/stdio.h" - -static btstack_data_source_t stdin_data_source; -static void (*stdin_handler)(char c); - -// Data source callback, return any character received -static void btstack_stdin_process(__unused struct btstack_data_source *ds, __unused btstack_data_source_callback_type_t callback_type){ - if (stdin_handler) { - while(true) { - int c = getchar_timeout_us(0); - if (c == PICO_ERROR_TIMEOUT) return; - (*stdin_handler)(c); - } - } -} - -void on_chars_available_callback(__unused void *param) { - btstack_run_loop_poll_data_sources_from_irq(); -} - -// Test code calls this if HAVE_BTSTACK_STDIN is defined and it wants key presses -void btstack_stdin_setup(void (*handler)(char c)) { - if (stdin_handler) { - return; - } - - // set handler - stdin_handler = handler; - - // set up polling data_source - btstack_run_loop_set_data_source_handler(&stdin_data_source, &btstack_stdin_process); - btstack_run_loop_enable_data_source_callbacks(&stdin_data_source, DATA_SOURCE_CALLBACK_POLL); - btstack_run_loop_add_data_source(&stdin_data_source); - - stdio_set_chars_available_callback(on_chars_available_callback, NULL); -} - -// Deinit everything -void btstack_stdin_reset(void){ - if (!stdin_handler) { - return; - } - stdio_set_chars_available_callback(NULL, NULL); - stdin_handler = NULL; - btstack_run_loop_remove_data_source(&stdin_data_source); -} - -#endif \ No newline at end of file diff --git a/pico-sdk/src/rp2_common/pico_btstack/doc.h b/pico-sdk/src/rp2_common/pico_btstack/doc.h deleted file mode 100644 index 0adf219..0000000 --- a/pico-sdk/src/rp2_common/pico_btstack/doc.h +++ /dev/null @@ -1,27 +0,0 @@ -/** - * \defgroup pico_btstack pico_btstack - * \brief Integration/wrapper libraries for BTstack - * the documentation for which is here. - * - * A supplemental license for BTstack (in addition to the stock BTstack licensing terms) is provided here. - * - * The \c \b pico_btstack_ble library adds the support needed for Bluetooth Low Energy (BLE). The \c \b pico_btstack_classic library adds the support needed for Bluetooth Classic. - * You can link to either library individually, or to both libraries thus enabling dual-mode support provided by BTstack. - * - * To use BTstack you need to provide a \c btstack_config.h file in your source tree and add its location to your include path. - * The BTstack configuration macros \c ENABLE_CLASSIC and \c ENABLE_BLE are defined for you when you link the \c pico_btstack_classic and \c pico_btstack_ble libraries respectively, so you should not define them yourself. - * - * For more details, see How to configure BTstack and the relevant pico-examples. - * - * The follow libraries are provided for you to link. - * * \c \b pico_btstack_ble - Adds Bluetooth Low Energy (LE) support. - * * \c \b pico_btstack_classic - Adds Bluetooth Classic support. - * * \c \b pico_btstack_sbc_encoder - Adds Bluetooth Sub Band Coding (SBC) encoder support. - * * \c \b pico_btstack_sbc_decoder - Adds Bluetooth Sub Band Coding (SBC) decoder support. - * * \c \b pico_btstack_bnep_lwip - Adds Bluetooth Network Encapsulation Protocol (BNEP) support using LwIP. - * * \c \b pico_btstack_bnep_lwip_sys_freertos - Adds Bluetooth Network Encapsulation Protocol (BNEP) support using LwIP with FreeRTOS for NO_SYS=0. - * - * \note The CMake function pico_btstack_make_gatt_header can be used to run the BTstack compile_gatt tool to make a GATT header file from a BTstack GATT file. - * - * \sa pico_btstack_cyw43 in pico_cyw43_driver, which adds the cyw43 driver support needed for BTstack including BTstack run loop support. - */ diff --git a/pico-sdk/src/rp2_common/pico_btstack/include/pico/btstack_flash_bank.h b/pico-sdk/src/rp2_common/pico_btstack/include/pico/btstack_flash_bank.h deleted file mode 100644 index d275221..0000000 --- a/pico-sdk/src/rp2_common/pico_btstack/include/pico/btstack_flash_bank.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#ifndef _PICO_BTSTACK_FLASH_BANK_H -#define _PICO_BTSTACK_FLASH_BANK_H - -#include "pico.h" -#include "hal_flash_bank.h" - -#ifdef __cplusplus -extern "C" { -#endif - -// PICO_CONFIG: PICO_FLASH_BANK_TOTAL_SIZE, Total size of the Bluetooth flash storage. Must be an even multiple of FLASH_SECTOR_SIZE, type=int, default=FLASH_SECTOR_SIZE * 2, group=pico_btstack -#ifndef PICO_FLASH_BANK_TOTAL_SIZE -#define PICO_FLASH_BANK_TOTAL_SIZE (FLASH_SECTOR_SIZE * 2u) -#endif - -// PICO_CONFIG: PICO_FLASH_BANK_STORAGE_OFFSET, Offset in flash of the Bluetooth flash storage, type=int, default=PICO_FLASH_SIZE_BYTES - PICO_FLASH_BANK_TOTAL_SIZE, group=pico_btstack -#ifndef PICO_FLASH_BANK_STORAGE_OFFSET -#define PICO_FLASH_BANK_STORAGE_OFFSET (PICO_FLASH_SIZE_BYTES - PICO_FLASH_BANK_TOTAL_SIZE) -#endif - -/** - * \brief Return the singleton BTstack HAL flash instance, used for non-volatile storage - * \ingroup pico_btstack - * - * \note By default two sectors at the end of flash are used (see \c PICO_FLASH_BANK_STORAGE_OFFSET and \c PICO_FLASH_BANK_TOTAL_SIZE) - */ -const hal_flash_bank_t *pico_flash_bank_instance(void); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/pico-sdk/src/rp2_common/pico_btstack/include/pico/btstack_run_loop_async_context.h b/pico-sdk/src/rp2_common/pico_btstack/include/pico/btstack_run_loop_async_context.h deleted file mode 100644 index d1b09b7..0000000 --- a/pico-sdk/src/rp2_common/pico_btstack/include/pico/btstack_run_loop_async_context.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#ifndef _PICO_BTSTACK_RUN_LOOP_ASYNC_CONTEXT_H -#define _PICO_BTSTACK_RUN_LOOP_ASYNC_CONTEXT_H - -#include "btstack_run_loop.h" -#include "pico/async_context.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * \brief Initialize and return the singleton BTstack run loop instance that integrates with the async_context API - * \ingroup pico_btstack - * - * \param context the async_context instance that provides the abstraction for handling asynchronous work. - * \return the BTstack run loop instance - */ -const btstack_run_loop_t *btstack_run_loop_async_context_get_instance(async_context_t *context); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/pico-sdk/src/rp2_common/pico_cyw43_arch/CMakeLists.txt b/pico-sdk/src/rp2_common/pico_cyw43_arch/CMakeLists.txt deleted file mode 100644 index 9ba7f59..0000000 --- a/pico-sdk/src/rp2_common/pico_cyw43_arch/CMakeLists.txt +++ /dev/null @@ -1,89 +0,0 @@ -if (PICO_CYW43_SUPPORTED) # set by BOARD=pico-w - if (TARGET cyw43_driver_picow) - pico_add_library(pico_cyw43_arch) - target_sources(pico_cyw43_arch INTERFACE - ${CMAKE_CURRENT_LIST_DIR}/cyw43_arch.c - ${CMAKE_CURRENT_LIST_DIR}/cyw43_arch_poll.c - ${CMAKE_CURRENT_LIST_DIR}/cyw43_arch_threadsafe_background.c - ${CMAKE_CURRENT_LIST_DIR}/cyw43_arch_freertos.c - ) - - target_include_directories(pico_cyw43_arch_headers INTERFACE - ${CMAKE_CURRENT_LIST_DIR}/include) - - pico_mirrored_target_link_libraries(pico_cyw43_arch INTERFACE - pico_unique_id - cyw43_driver_picow # driver for pico w - pico_cyw43_driver # integration with async_context - ) - - if (NOT TARGET pico_lwip) - message(WARNING "lwIP is not available; Full Pico W wireless support will be unavailable") - else() - message("Pico W Wi-Fi build support available.") - pico_add_library(pico_cyw43_arch_poll NOFLAG) - target_compile_definitions(pico_cyw43_arch_poll_headers INTERFACE - PICO_CYW43_ARCH_POLL=1 - ) - pico_mirrored_target_link_libraries(pico_cyw43_arch_poll INTERFACE - pico_cyw43_arch - pico_async_context_poll) - - pico_add_library(pico_cyw43_arch_lwip_poll NOFLAG) - pico_mirrored_target_link_libraries(pico_cyw43_arch_lwip_poll INTERFACE - pico_lwip_nosys - pico_cyw43_arch_poll) - target_compile_definitions(pico_cyw43_arch_lwip_poll_headers INTERFACE - CYW43_LWIP=1 - ) - - pico_add_library(pico_cyw43_arch_threadsafe_background NOFLAG) - pico_mirrored_target_link_libraries(pico_cyw43_arch_threadsafe_background INTERFACE - pico_cyw43_arch - pico_async_context_threadsafe_background) - target_compile_definitions(pico_cyw43_arch_threadsafe_background_headers INTERFACE - PICO_CYW43_ARCH_THREADSAFE_BACKGROUND=1 - ) - - pico_add_library(pico_cyw43_arch_lwip_threadsafe_background NOFLAG) - pico_mirrored_target_link_libraries(pico_cyw43_arch_lwip_threadsafe_background INTERFACE - pico_lwip_nosys - pico_cyw43_arch_threadsafe_background) - target_compile_definitions(pico_cyw43_arch_lwip_threadsafe_background_headers INTERFACE - CYW43_LWIP=1 - ) - - pico_add_library(pico_cyw43_arch_sys_freertos NOFLAG) - pico_mirrored_target_link_libraries(pico_cyw43_arch_sys_freertos INTERFACE - pico_cyw43_arch - pico_async_context_freertos) - target_compile_definitions(pico_cyw43_arch_sys_freertos_headers INTERFACE - PICO_CYW43_ARCH_FREERTOS=1 - ) - - pico_add_library(pico_cyw43_arch_lwip_sys_freertos NOFLAG) - pico_mirrored_target_link_libraries(pico_cyw43_arch_lwip_sys_freertos INTERFACE - pico_lwip_freertos - pico_cyw43_arch_sys_freertos) - target_compile_definitions(pico_cyw43_arch_lwip_sys_freertos_headers INTERFACE - CYW43_LWIP=1 - LWIP_PROVIDE_ERRNO=1 - # now the default - #PICO_LWIP_CUSTOM_LOCK_TCPIP_CORE=1 # we want to override the lwip locking mechanism to use our mutex - ) - endif() - - pico_add_library(pico_cyw43_arch_none NOFLAG) - pico_mirrored_target_link_libraries(pico_cyw43_arch_none INTERFACE - pico_cyw43_arch - pico_async_context_threadsafe_background) - target_compile_definitions(pico_cyw43_arch_none_headers INTERFACE - CYW43_LWIP=0 - PICO_CYW43_ARCH_THREADSAFE_BACKGROUND=1 # none still uses threadsafe_background to make gpio use easy - ) - endif() -endif() - -if (PICO_CYW43_DRIVER_PATH AND EXISTS "${PICO_CYW43_DRIVER_PATH}") - pico_add_doxygen(${PICO_CYW43_DRIVER_PATH}/src) -endif() \ No newline at end of file diff --git a/pico-sdk/src/rp2_common/pico_cyw43_arch/cyw43_arch.c b/pico-sdk/src/rp2_common/pico_cyw43_arch/cyw43_arch.c deleted file mode 100644 index bdfab8c..0000000 --- a/pico-sdk/src/rp2_common/pico_cyw43_arch/cyw43_arch.c +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include -#include -#include - -#include "pico/unique_id.h" -#include "cyw43.h" -#include "pico/cyw43_arch.h" -#include "cyw43_ll.h" -#include "cyw43_stats.h" - -#if PICO_CYW43_ARCH_DEBUG_ENABLED -#define CYW43_ARCH_DEBUG(...) printf(__VA_ARGS__) -#else -#define CYW43_ARCH_DEBUG(...) ((void)0) -#endif - -static uint32_t country_code = PICO_CYW43_ARCH_DEFAULT_COUNTRY_CODE; - -static async_context_t *async_context; - -void cyw43_arch_set_async_context(async_context_t *context) { - async_context = context; -} - -void cyw43_arch_enable_sta_mode(void) { - assert(cyw43_is_initialized(&cyw43_state)); - cyw43_wifi_set_up(&cyw43_state, CYW43_ITF_STA, true, cyw43_arch_get_country_code()); -} - -void cyw43_arch_disable_sta_mode(void) { - assert(cyw43_is_initialized(&cyw43_state)); - if (cyw43_state.itf_state & (1 << CYW43_ITF_STA)) { - cyw43_cb_tcpip_deinit(&cyw43_state, CYW43_ITF_STA); - cyw43_state.itf_state &= ~(1 << CYW43_ITF_STA); - } - if (cyw43_state.wifi_join_state) { - cyw43_wifi_leave(&cyw43_state, CYW43_ITF_STA); - } -} - -void cyw43_arch_enable_ap_mode(const char *ssid, const char *password, uint32_t auth) { - assert(cyw43_is_initialized(&cyw43_state)); - cyw43_wifi_ap_set_ssid(&cyw43_state, strlen(ssid), (const uint8_t *) ssid); - if (password) { - cyw43_wifi_ap_set_password(&cyw43_state, strlen(password), (const uint8_t *) password); - cyw43_wifi_ap_set_auth(&cyw43_state, auth); - } else { - cyw43_wifi_ap_set_auth(&cyw43_state, CYW43_AUTH_OPEN); - } - cyw43_wifi_set_up(&cyw43_state, CYW43_ITF_AP, true, cyw43_arch_get_country_code()); -} - -void cyw43_arch_disable_ap_mode(void) { - assert(cyw43_is_initialized(&cyw43_state)); - cyw43_wifi_set_up(&cyw43_state, CYW43_ITF_AP, false, cyw43_arch_get_country_code()); - cyw43_state.itf_state &= ~(1 << CYW43_ITF_AP); -} - -#if PICO_CYW43_ARCH_DEBUG_ENABLED -// Return a string for the wireless state -static const char* cyw43_tcpip_link_status_name(int status) -{ - switch (status) { - case CYW43_LINK_DOWN: - return "link down"; - case CYW43_LINK_JOIN: - return "joining"; - case CYW43_LINK_NOIP: - return "no ip"; - case CYW43_LINK_UP: - return "link up"; - case CYW43_LINK_FAIL: - return "link fail"; - case CYW43_LINK_NONET: - return "network fail"; - case CYW43_LINK_BADAUTH: - return "bad auth"; - } - return "unknown"; -} -#endif - - -int cyw43_arch_wifi_connect_bssid_async(const char *ssid, const uint8_t *bssid, const char *pw, uint32_t auth) { - if (!pw) auth = CYW43_AUTH_OPEN; - // Connect to wireless - return cyw43_wifi_join(&cyw43_state, strlen(ssid), (const uint8_t *)ssid, pw ? strlen(pw) : 0, (const uint8_t *)pw, auth, bssid, CYW43_CHANNEL_NONE); -} - -int cyw43_arch_wifi_connect_async(const char *ssid, const char *pw, uint32_t auth) { - return cyw43_arch_wifi_connect_bssid_async(ssid, NULL, pw, auth); -} - -static int cyw43_arch_wifi_connect_bssid_until(const char *ssid, const uint8_t *bssid, const char *pw, uint32_t auth, absolute_time_t until) { - int err = cyw43_arch_wifi_connect_bssid_async(ssid, bssid, pw, auth); - if (err) return err; - - int status = CYW43_LINK_UP + 1; - while(status >= 0 && status != CYW43_LINK_UP) { - int new_status = cyw43_tcpip_link_status(&cyw43_state, CYW43_ITF_STA); - // If there was no network, keep trying - if (new_status == CYW43_LINK_NONET) { - new_status = CYW43_LINK_JOIN; - err = cyw43_arch_wifi_connect_bssid_async(ssid, bssid, pw, auth); - if (err) return err; - } - if (new_status != status) { - status = new_status; - CYW43_ARCH_DEBUG("connect status: %s\n", cyw43_tcpip_link_status_name(status)); - } - if (time_reached(until)) { - return PICO_ERROR_TIMEOUT; - } - // Do polling - cyw43_arch_poll(); - cyw43_arch_wait_for_work_until(until); - } - // Turn status into a pico_error_codes, CYW43_LINK_NONET shouldn't happen as we fail with PICO_ERROR_TIMEOUT instead - assert(status == CYW43_LINK_UP || status == CYW43_LINK_BADAUTH || status == CYW43_LINK_FAIL); - if (status == CYW43_LINK_UP) { - return PICO_OK; // success - } else if (status == CYW43_LINK_BADAUTH) { - return PICO_ERROR_BADAUTH; - } else { - return PICO_ERROR_CONNECT_FAILED; - } -} - -// Connect to wireless, return with success when an IP address has been assigned -static int cyw43_arch_wifi_connect_until(const char *ssid, const char *pw, uint32_t auth, absolute_time_t until) { - return cyw43_arch_wifi_connect_bssid_until(ssid, NULL, pw, auth, until); -} - -int cyw43_arch_wifi_connect_blocking(const char *ssid, const char *pw, uint32_t auth) { - return cyw43_arch_wifi_connect_until(ssid, pw, auth, at_the_end_of_time); -} - -int cyw43_arch_wifi_connect_bssid_blocking(const char *ssid, const uint8_t *bssid, const char *pw, uint32_t auth) { - return cyw43_arch_wifi_connect_bssid_until(ssid, bssid, pw, auth, at_the_end_of_time); -} - -int cyw43_arch_wifi_connect_timeout_ms(const char *ssid, const char *pw, uint32_t auth, uint32_t timeout_ms) { - return cyw43_arch_wifi_connect_until(ssid, pw, auth, make_timeout_time_ms(timeout_ms)); -} - -int cyw43_arch_wifi_connect_bssid_timeout_ms(const char *ssid, const uint8_t *bssid, const char *pw, uint32_t auth, uint32_t timeout_ms) { - return cyw43_arch_wifi_connect_bssid_until(ssid, bssid, pw, auth, make_timeout_time_ms(timeout_ms)); -} - -uint32_t cyw43_arch_get_country_code(void) { - return country_code; -} - -int cyw43_arch_init_with_country(uint32_t country) { - country_code = country; - return cyw43_arch_init(); -} - -void cyw43_arch_gpio_put(uint wl_gpio, bool value) { - invalid_params_if(CYW43_ARCH, wl_gpio >= CYW43_WL_GPIO_COUNT); - cyw43_gpio_set(&cyw43_state, (int)wl_gpio, value); -} - -bool cyw43_arch_gpio_get(uint wl_gpio) { - invalid_params_if(CYW43_ARCH, wl_gpio >= CYW43_WL_GPIO_COUNT); - bool value = false; - cyw43_gpio_get(&cyw43_state, (int)wl_gpio, &value); - return value; -} - -async_context_t *cyw43_arch_async_context(void) { - return async_context; -} - -void cyw43_arch_poll(void) -{ - async_context_poll(async_context); -} - -void cyw43_arch_wait_for_work_until(absolute_time_t until) { - async_context_wait_for_work_until(async_context, until); -} diff --git a/pico-sdk/src/rp2_common/pico_cyw43_arch/cyw43_arch_freertos.c b/pico-sdk/src/rp2_common/pico_cyw43_arch/cyw43_arch_freertos.c deleted file mode 100644 index 93f73ad..0000000 --- a/pico-sdk/src/rp2_common/pico_cyw43_arch/cyw43_arch_freertos.c +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#if PICO_CYW43_ARCH_FREERTOS - -#include "pico/cyw43_arch.h" -#include "pico/cyw43_driver.h" -#include "pico/async_context_freertos.h" - -#if CYW43_LWIP -#include "pico/lwip_freertos.h" -#include -#endif - -#if CYW43_ENABLE_BLUETOOTH -#include "pico/btstack_cyw43.h" -#endif - -#if NO_SYS -#error example_cyw43_arch_freetos_sys requires NO_SYS=0 -#endif - -static async_context_freertos_t cyw43_async_context_freertos; - -async_context_t *cyw43_arch_init_default_async_context(void) { - async_context_freertos_config_t config = async_context_freertos_default_config(); -#ifdef CYW43_TASK_PRIORITY - config.task_priority = CYW43_TASK_PRIORITY; -#endif -#ifdef CYW43_TASK_STACK_SIZE - config.task_stack_size = CYW43_TASK_STACK_SIZE; -#endif - if (async_context_freertos_init(&cyw43_async_context_freertos, &config)) - return &cyw43_async_context_freertos.core; - return NULL; -} - -int cyw43_arch_init(void) { - async_context_t *context = cyw43_arch_async_context(); - if (!context) { - context = cyw43_arch_init_default_async_context(); - if (!context) return PICO_ERROR_GENERIC; - cyw43_arch_set_async_context(context); - } - bool ok = cyw43_driver_init(context); -#if CYW43_LWIP - ok &= lwip_freertos_init(context); -#endif -#if CYW43_ENABLE_BLUETOOTH - ok &= btstack_cyw43_init(context); -#endif - if (!ok) { - cyw43_arch_deinit(); - return PICO_ERROR_GENERIC; - } else { - return 0; - } -} - -void cyw43_arch_deinit(void) { - async_context_t *context = cyw43_arch_async_context(); -#if CYW43_ENABLE_BLUETOOTH - btstack_cyw43_deinit(context); -#endif - // there is a bit of a circular dependency here between lwIP and cyw43_driver. We - // shut down cyw43_driver first as it has IRQs calling back into lwIP. Also lwIP itself - // does not actually get shut down. - // todo add a "pause" method to async_context if we need to provide some atomicity (we - // don't want to take the lock as these methods may invoke execute_sync() - cyw43_driver_deinit(context); -#if CYW43_LWIP - lwip_freertos_deinit(context); -#endif - // if it is our context, then we de-init it. - if (context == &cyw43_async_context_freertos.core) { - async_context_deinit(context); - cyw43_arch_set_async_context(NULL); - } -} - -#endif \ No newline at end of file diff --git a/pico-sdk/src/rp2_common/pico_cyw43_arch/cyw43_arch_poll.c b/pico-sdk/src/rp2_common/pico_cyw43_arch/cyw43_arch_poll.c deleted file mode 100644 index e885985..0000000 --- a/pico-sdk/src/rp2_common/pico_cyw43_arch/cyw43_arch_poll.c +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#if PICO_CYW43_ARCH_POLL - -#include "pico/cyw43_arch.h" -#include "pico/cyw43_driver.h" - -#include "pico/async_context_poll.h" -#if CYW43_LWIP -#include "pico/lwip_nosys.h" -#endif - -#if CYW43_ENABLE_BLUETOOTH -#include "pico/btstack_cyw43.h" -#endif - -#if CYW43_LWIP && !NO_SYS -#error PICO_CYW43_ARCH_POLL requires lwIP NO_SYS=1 -#endif - -static async_context_poll_t cyw43_async_context_poll; - -async_context_t *cyw43_arch_init_default_async_context(void) { - if (async_context_poll_init_with_defaults(&cyw43_async_context_poll)) - return &cyw43_async_context_poll.core; - return NULL; -} - -int cyw43_arch_init(void) { - async_context_t *context = cyw43_arch_async_context(); - if (!context) { - context = cyw43_arch_init_default_async_context(); - if (!context) return PICO_ERROR_GENERIC; - cyw43_arch_set_async_context(context); - } - bool ok = cyw43_driver_init(context); -#if CYW43_LWIP - ok &= lwip_nosys_init(context); -#endif -#if CYW43_ENABLE_BLUETOOTH - ok &= btstack_cyw43_init(context); -#endif - if (!ok) { - cyw43_arch_deinit(); - return PICO_ERROR_GENERIC; - } else { - return 0; - } -} - -void cyw43_arch_deinit(void) { - async_context_t *context = cyw43_arch_async_context(); -#if CYW43_ENABLE_BLUETOOTH - btstack_cyw43_deinit(context); -#endif - // there is a bit of a circular dependency here between lwIP and cyw43_driver. We - // shut down cyw43_driver first as it has IRQs calling back into lwIP. Also lwIP itself - // does not actually get shut down. - // todo add a "pause" method to async_context if we need to provide some atomicity (we - // don't want to take the lock as these methods may invoke execute_sync() - cyw43_driver_deinit(context); -#if CYW43_LWIP - lwip_nosys_deinit(context); -#endif - // if it is our context, then we de-init it. - if (context == &cyw43_async_context_poll.core) { - async_context_deinit(context); - cyw43_arch_set_async_context(NULL); - } -} - -#endif diff --git a/pico-sdk/src/rp2_common/pico_cyw43_arch/cyw43_arch_threadsafe_background.c b/pico-sdk/src/rp2_common/pico_cyw43_arch/cyw43_arch_threadsafe_background.c deleted file mode 100644 index 397da8a..0000000 --- a/pico-sdk/src/rp2_common/pico_cyw43_arch/cyw43_arch_threadsafe_background.c +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#if PICO_CYW43_ARCH_THREADSAFE_BACKGROUND - -#include "pico/cyw43_arch.h" -#include "pico/cyw43_driver.h" -#include "pico/async_context_threadsafe_background.h" - -#if CYW43_LWIP -#include "pico/lwip_nosys.h" -#endif - -#if CYW43_ENABLE_BLUETOOTH -#include "pico/btstack_cyw43.h" -#endif - -#if CYW43_LWIP && !NO_SYS -#error PICO_CYW43_ARCH_THREADSAFE_BACKGROUND requires lwIP NO_SYS=1 -#endif -#if CYW43_LWIP && MEM_LIBC_MALLOC -// would attempt to use malloc from IRQ context -#error MEM_LIBC_MALLOC is incompatible with PICO_CYW43_ARCH_THREADSAFE_BACKGROUND -#endif - -static async_context_threadsafe_background_t cyw43_async_context_threadsafe_background; - -async_context_t *cyw43_arch_init_default_async_context(void) { - async_context_threadsafe_background_config_t config = async_context_threadsafe_background_default_config(); - if (async_context_threadsafe_background_init(&cyw43_async_context_threadsafe_background, &config)) - return &cyw43_async_context_threadsafe_background.core; - return NULL; -} - -int cyw43_arch_init(void) { - async_context_t *context = cyw43_arch_async_context(); - if (!context) { - context = cyw43_arch_init_default_async_context(); - if (!context) return PICO_ERROR_GENERIC; - cyw43_arch_set_async_context(context); - } - bool ok = cyw43_driver_init(context); -#if CYW43_LWIP - ok &= lwip_nosys_init(context); -#endif -#if CYW43_ENABLE_BLUETOOTH - ok &= btstack_cyw43_init(context); -#endif - if (!ok) { - cyw43_arch_deinit(); - return PICO_ERROR_GENERIC; - } else { - return 0; - } -} - -void cyw43_arch_deinit(void) { - async_context_t *context = cyw43_arch_async_context(); -#if CYW43_ENABLE_BLUETOOTH - btstack_cyw43_deinit(context); -#endif - // there is a bit of a circular dependency here between lwIP and cyw43_driver. We - // shut down cyw43_driver first as it has IRQs calling back into lwIP. Also lwIP itself - // does not actually get shut down. - // todo add a "pause" method to async_context if we need to provide some atomicity (we - // don't want to take the lock as these methods may invoke execute_sync() - cyw43_driver_deinit(context); -#if CYW43_LWIP - lwip_nosys_deinit(context); -#endif - // if it is our context, then we de-init it. - if (context == &cyw43_async_context_threadsafe_background.core) { - async_context_deinit(context); - cyw43_arch_set_async_context(NULL); - } -} - -#endif diff --git a/pico-sdk/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch.h b/pico-sdk/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch.h deleted file mode 100644 index bae2112..0000000 --- a/pico-sdk/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch.h +++ /dev/null @@ -1,504 +0,0 @@ -/* - * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#ifndef _PICO_CYW43_ARCH_H -#define _PICO_CYW43_ARCH_H - -#include "pico.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#include "cyw43.h" -#include "cyw43_country.h" -#include "pico/async_context.h" - -#ifdef PICO_CYW43_ARCH_HEADER -#include __XSTRING(PICO_CYW43_ARCH_HEADER) -#else -#if PICO_CYW43_ARCH_POLL -#include "pico/cyw43_arch/arch_poll.h" -#elif PICO_CYW43_ARCH_THREADSAFE_BACKGROUND -#include "pico/cyw43_arch/arch_threadsafe_background.h" -#elif PICO_CYW43_ARCH_FREERTOS -#include "pico/cyw43_arch/arch_freertos.h" -#else -#error must specify support pico_cyw43_arch architecture type or set PICO_CYW43_ARCH_HEADER -#endif -#endif - -/** - * \defgroup cyw43_driver cyw43_driver - * \ingroup pico_cyw43_arch - * \brief Driver used for Pico W wireless -*/ - -/** - * \defgroup cyw43_ll cyw43_ll - * \ingroup cyw43_driver - * \brief Low Level CYW43 driver interface -*/ - -/** \file pico/cyw43_arch.h - * \defgroup pico_cyw43_arch pico_cyw43_arch - * - * Architecture for integrating the CYW43 driver (for the wireless on Pico W) and lwIP (for TCP/IP stack) into the SDK. It is also necessary for accessing the on-board LED on Pico W - * - * Both the low level \c cyw43_driver and the lwIP stack require periodic servicing, and have limitations - * on whether they can be called from multiple cores/threads. - * - * \c pico_cyw43_arch attempts to abstract these complications into several behavioral groups: - * - * * \em 'poll' - This not multi-core/IRQ safe, and requires the user to call \ref cyw43_arch_poll periodically from their main loop - * * \em 'thread_safe_background' - This is multi-core/thread/task safe, and maintenance of the driver and TCP/IP stack is handled automatically in the background - * * \em 'freertos' - This is multi-core/thread/task safe, and uses a separate FreeRTOS task to handle lwIP and and driver work. - * - * As of right now, lwIP is the only supported TCP/IP stack, however the use of \c pico_cyw43_arch is intended to be independent of - * the particular TCP/IP stack used (and possibly Bluetooth stack used) in the future. For this reason, the integration of lwIP - * is handled in the base (\c pico_cyw43_arch) library based on the #define \ref CYW43_LWIP used by the \c cyw43_driver. - * - * \note As of version 1.5.0 of the Raspberry Pi Pico SDK, the \c pico_cyw43_arch library no longer directly implements - * the distinct behavioral abstractions. This is now handled by the more general \ref pico_async_context library. The - * user facing behavior of pico_cyw43_arch has not changed as a result of this implementation detail, however pico_cyw43_arch - * is now just a thin wrapper which creates an appropriate async_context and makes a simple call to add lwIP or cyw43_driver support - * as appropriate. You are free to perform this context creation and adding of lwIP, cyw43_driver or indeed any other additional - * future protocol/driver support to your async_context, however for now pico_cyw43_arch does still provide a few cyw43_ specific (i.e. Pico W) - * APIs for connection management, locking and GPIO interaction. - * - * \note The connection management APIs at least may be moved - * to a more generic library in a future release. The locking methods are now backed by their \ref pico_async_context equivalents, and - * those methods may be used interchangeably (see \ref cyw43_arch_lwip_begin, \ref cyw43_arch_lwip_end and \ref cyw43_arch_lwip_check for more details). - * - * \note For examples of creating of your own async_context and addition of \c cyw43_driver and \c lwIP support, please - * refer to the specific source files \c cyw43_arch_poll.c, \c cyw43_arch_threadsafe_background.c and \c cyw43_arch_freertos.c. - * - * Whilst you can use the \c pico_cyw43_arch library directly and specify \ref CYW43_LWIP (and other defines) yourself, several - * other libraries are made available to the build which aggregate the defines and other dependencies for you: - * - * * \b pico_cyw43_arch_lwip_poll - For using the RAW lwIP API (in `NO_SYS=1` mode) without any background processing or multi-core/thread safety. - * - * The user must call \ref cyw43_arch_poll periodically from their main loop. - * - * This wrapper library: - * - Sets \c CYW43_LWIP=1 to enable lwIP support in \c pico_cyw43_arch and \c cyw43_driver. - * - Sets \c PICO_CYW43_ARCH_POLL=1 to select the polling behavior. - * - Adds the \c pico_lwip as a dependency to pull in lwIP. - * - * * \b pico_cyw43_arch_lwip_threadsafe_background - For using the RAW lwIP API (in `NO_SYS=1` mode) with multi-core/thread safety, and automatic servicing of the \c cyw43_driver and - * lwIP in background. - * - * Calls into the \c cyw43_driver high level API (cyw43.h) may be made from either core or from lwIP callbacks, however calls into lwIP (which - * is not thread-safe) other than those made from lwIP callbacks, must be bracketed with \ref cyw43_arch_lwip_begin and \ref cyw43_arch_lwip_end. It is fine to bracket - * calls made from within lwIP callbacks too; you just don't have to. - * - * \note lwIP callbacks happen in a (low priority) IRQ context (similar to an alarm callback), so care should be taken when interacting - * with other code. - * - * This wrapper library: - * - Sets \c CYW43_LWIP=1 to enable lwIP support in \c pico_cyw43_arch and \c cyw43_driver - * - Sets \c PICO_CYW43_ARCH_THREADSAFE_BACKGROUND=1 to select the thread-safe/non-polling behavior. - * - Adds the pico_lwip as a dependency to pull in lwIP. - * - * - * This library \em can also be used under the RP2040 port of FreeRTOS with lwIP in `NO_SYS=1` mode (allowing you to call \c cyw43_driver APIs - * from any task, and to call lwIP from lwIP callbacks, or from any task if you bracket the calls with \ref cyw43_arch_lwip_begin and \ref cyw43_arch_lwip_end. Again, you should be - * careful about what you do in lwIP callbacks, as you cannot call most FreeRTOS APIs from within an IRQ context. Unless you have good reason, you should probably - * use the full FreeRTOS integration (with `NO_SYS=0`) provided by \c pico_cyw43_arch_lwip_sys_freertos. - * - * * \b pico_cyw43_arch_lwip_sys_freertos - For using the full lwIP API including blocking sockets in OS (`NO_SYS=0`) mode, along with with multi-core/task/thread safety, and automatic servicing of the \c cyw43_driver and - * the lwIP stack. - * - * This wrapper library: - * - Sets \c CYW43_LWIP=1 to enable lwIP support in \c pico_cyw43_arch and \c cyw43_driver. - * - Sets \c PICO_CYW43_ARCH_FREERTOS=1 to select the NO_SYS=0 lwip/FreeRTOS integration - * - Sets \c LWIP_PROVIDE_ERRNO=1 to provide error numbers needed for compilation without an OS - * - Adds the \c pico_lwip as a dependency to pull in lwIP. - * - Adds the lwIP/FreeRTOS code from lwip-contrib (in the contrib directory of lwIP) - * - * Calls into the \c cyw43_driver high level API (cyw43.h) may be made from any task or from lwIP callbacks, but not from IRQs. Calls into the lwIP RAW API (which is not thread safe) - * must be bracketed with \ref cyw43_arch_lwip_begin and \ref cyw43_arch_lwip_end. It is fine to bracket calls made from within lwIP callbacks too; you just don't have to. - * - * \note this wrapper library requires you to link FreeRTOS functionality with your application yourself. - * - * * \b pico_cyw43_arch_none - If you do not need the TCP/IP stack but wish to use the on-board LED. - * - * This wrapper library: - * - Sets \c CYW43_LWIP=0 to disable lwIP support in \c pico_cyw43_arch and \c cyw43_driver - */ - -// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_CYW43_ARCH, Enable/disable assertions in the pico_cyw43_arch module, type=bool, default=0, group=pico_cyw43_arch -#ifndef PARAM_ASSERTIONS_ENABLED_CYW43_ARCH -#define PARAM_ASSERTIONS_ENABLED_CYW43_ARCH 0 -#endif - -// PICO_CONFIG: PICO_CYW43_ARCH_DEBUG_ENABLED, Enable/disable some debugging output in the pico_cyw43_arch module, type=bool, default=1 in debug builds, group=pico_cyw43_arch -#ifndef PICO_CYW43_ARCH_DEBUG_ENABLED -#ifndef NDEBUG -#define PICO_CYW43_ARCH_DEBUG_ENABLED 1 -#else -#define PICO_CYW43_ARCH_DEBUG_ENABLED 0 -#endif -#endif - -// PICO_CONFIG: PICO_CYW43_ARCH_DEFAULT_COUNTRY_CODE, Default country code for the cyw43 wireless driver, default=CYW43_COUNTRY_WORLDWIDE, group=pico_cyw43_arch -#ifndef PICO_CYW43_ARCH_DEFAULT_COUNTRY_CODE -#define PICO_CYW43_ARCH_DEFAULT_COUNTRY_CODE CYW43_COUNTRY_WORLDWIDE -#endif - -/*! - * \brief Initialize the CYW43 architecture - * \ingroup pico_cyw43_arch - * - * This method initializes the `cyw43_driver` code and initializes the lwIP stack (if it - * was enabled at build time). This method must be called prior to using any other \c pico_cyw43_arch, - * \c cyw43_driver or lwIP functions. - * - * \note this method initializes wireless with a country code of \c PICO_CYW43_ARCH_DEFAULT_COUNTRY_CODE - * which defaults to \c CYW43_COUNTRY_WORLDWIDE. Worldwide settings may not give the best performance; consider - * setting PICO_CYW43_ARCH_DEFAULT_COUNTRY_CODE to a different value or calling \ref cyw43_arch_init_with_country - * - * By default this method initializes the cyw43_arch code's own async_context by calling - * \ref cyw43_arch_init_default_async_context, however the user can specify use of their own async_context - * by calling \ref cyw43_arch_set_async_context() before calling this method - * - * \return 0 if the initialization is successful, an error code otherwise \see pico_error_codes - */ -int cyw43_arch_init(void); - -/*! - * \brief Initialize the CYW43 architecture for use in a specific country - * \ingroup pico_cyw43_arch - * - * This method initializes the `cyw43_driver` code and initializes the lwIP stack (if it - * was enabled at build time). This method must be called prior to using any other \c pico_cyw43_arch, - * \c cyw43_driver or lwIP functions. - * - * By default this method initializes the cyw43_arch code's own async_context by calling - * \ref cyw43_arch_init_default_async_context, however the user can specify use of their own async_context - * by calling \ref cyw43_arch_set_async_context() before calling this method - * - * \param country the country code to use (see \ref CYW43_COUNTRY_) - * \return 0 if the initialization is successful, an error code otherwise \see pico_error_codes - */ -int cyw43_arch_init_with_country(uint32_t country); - -/*! - * \brief De-initialize the CYW43 architecture - * \ingroup pico_cyw43_arch - * - * This method de-initializes the `cyw43_driver` code and de-initializes the lwIP stack (if it - * was enabled at build time). Note this method should always be called from the same core (or RTOS - * task, depending on the environment) as \ref cyw43_arch_init. - * - * Additionally if the cyw43_arch is using its own async_context instance, then that instance is de-initialized. - */ -void cyw43_arch_deinit(void); - -/*! - * \brief Return the current async_context currently in use by the cyw43_arch code - * \ingroup pico_cyw43_arch - * - * \return the async_context. - */ -async_context_t *cyw43_arch_async_context(void); - -/*! - * \brief Set the async_context to be used by the cyw43_arch_init - * \ingroup pico_cyw43_arch - * - * \note This method must be called before calling cyw43_arch_init or cyw43_arch_init_with_country - * if you wish to use a custom async_context instance. - * - * \param context the async_context to be used - */ -void cyw43_arch_set_async_context(async_context_t *context); - -/*! - * \brief Initialize the default async_context for the current cyw43_arch type - * \ingroup pico_cyw43_arch - * - * This method initializes and returns a pointer to the static async_context associated - * with cyw43_arch. This method is called by \ref cyw43_arch_init automatically - * if a different async_context has not been set by \ref cyw43_arch_set_async_context - * - * \return the context or NULL if initialization failed. - */ -async_context_t *cyw43_arch_init_default_async_context(void); - -/*! - * \brief Perform any processing required by the \c cyw43_driver or the TCP/IP stack - * \ingroup pico_cyw43_arch - * - * This method must be called periodically from the main loop when using a - * \em polling style \c pico_cyw43_arch (e.g. \c pico_cyw43_arch_lwip_poll ). It - * may be called in other styles, but it is unnecessary to do so. - */ -void cyw43_arch_poll(void); - -/*! - * \brief Sleep until there is cyw43_driver work to be done - * \ingroup pico_cyw43_arch - * - * This method may be called by code that is waiting for an event to - * come from the cyw43_driver, and has no work to do, but would like - * to sleep without blocking any background work associated with the cyw43_driver. - * - * \param until the time to wait until if there is no work to do. - */ -void cyw43_arch_wait_for_work_until(absolute_time_t until); - -/*! - * \fn cyw43_arch_lwip_begin - * \brief Acquire any locks required to call into lwIP - * \ingroup pico_cyw43_arch - * - * The lwIP API is not thread safe. You should surround calls into the lwIP API - * with calls to this method and \ref cyw43_arch_lwip_end. Note these calls are not - * necessary (but harmless) when you are calling back into the lwIP API from an lwIP callback. - * If you are using single-core polling only (pico_cyw43_arch_poll) then these calls are no-ops - * anyway it is good practice to call them anyway where they are necessary. - * - * \note as of SDK release 1.5.0, this is now equivalent to calling \ref async_context_acquire_lock_blocking - * on the async_context associated with cyw43_arch and lwIP. - * - * \sa cyw43_arch_lwip_end - * \sa cyw43_arch_lwip_protect - * \sa async_context_acquire_lock_blocking - * \sa cyw43_arch_async_context - */ -static inline void cyw43_arch_lwip_begin(void) { - cyw43_thread_enter(); -} - -/*! - * \fn void cyw43_arch_lwip_end(void) - * \brief Release any locks required for calling into lwIP - * \ingroup pico_cyw43_arch - * - * The lwIP API is not thread safe. You should surround calls into the lwIP API - * with calls to \ref cyw43_arch_lwip_begin and this method. Note these calls are not - * necessary (but harmless) when you are calling back into the lwIP API from an lwIP callback. - * If you are using single-core polling only (pico_cyw43_arch_poll) then these calls are no-ops - * anyway it is good practice to call them anyway where they are necessary. - * - * \note as of SDK release 1.5.0, this is now equivalent to calling \ref async_context_release_lock - * on the async_context associated with cyw43_arch and lwIP. - * - * \sa cyw43_arch_lwip_begin - * \sa cyw43_arch_lwip_protect - * \sa async_context_release_lock - * \sa cyw43_arch_async_context - */ -static inline void cyw43_arch_lwip_end(void) { - cyw43_thread_exit(); -} - -/*! - * \fn int cyw43_arch_lwip_protect(int (*func)(void *param), void *param) - * \brief sad Release any locks required for calling into lwIP - * \ingroup pico_cyw43_arch - * - * The lwIP API is not thread safe. You can use this method to wrap a function - * with any locking required to call into the lwIP API. If you are using - * single-core polling only (pico_cyw43_arch_poll) then there are no - * locks to required, but it is still good practice to use this function. - * - * \param func the function ta call with any required locks held - * \param param parameter to pass to \c func - * \return the return value from \c func - * \sa cyw43_arch_lwip_begin - * \sa cyw43_arch_lwip_end - */ -static inline int cyw43_arch_lwip_protect(int (*func)(void *param), void *param) { - cyw43_arch_lwip_begin(); - int rc = func(param); - cyw43_arch_lwip_end(); - return rc; -} - -/*! - * \fn void cyw43_arch_lwip_check(void) - * \brief Checks the caller has any locks required for calling into lwIP - * \ingroup pico_cyw43_arch - * - * The lwIP API is not thread safe. You should surround calls into the lwIP API - * with calls to \ref cyw43_arch_lwip_begin and this method. Note these calls are not - * necessary (but harmless) when you are calling back into the lwIP API from an lwIP callback. - * - * This method will assert in debug mode, if the above conditions are not met (i.e. it is not safe to - * call into the lwIP API) - * - * \note as of SDK release 1.5.0, this is now equivalent to calling \ref async_context_lock_check - * on the async_context associated with cyw43_arch and lwIP. - * - * \sa cyw43_arch_lwip_begin - * \sa cyw43_arch_lwip_protect - * \sa async_context_lock_check - * \sa cyw43_arch_async_context - */ - -/*! - * \brief Return the country code used to initialize cyw43_arch - * \ingroup pico_cyw43_arch - * - * \return the country code (see \ref CYW43_COUNTRY_) - */ -uint32_t cyw43_arch_get_country_code(void); - -/*! - * \brief Enables Wi-Fi STA (Station) mode. - * \ingroup pico_cyw43_arch - * - * This enables the Wi-Fi in \em Station mode such that connections can be made to other Wi-Fi Access Points - */ -void cyw43_arch_enable_sta_mode(void); - -/*! - * \brief Disables Wi-Fi STA (Station) mode. - * \ingroup pico_cyw43_arch - * - * This disables the Wi-Fi in \em Station mode, disconnecting any active connection. - * You should subsequently check the status by calling \ref cyw43_wifi_link_status. - */ -void cyw43_arch_disable_sta_mode(void); - -/*! - * \brief Enables Wi-Fi AP (Access point) mode. - * \ingroup pico_cyw43_arch - * - * This enables the Wi-Fi in \em Access \em Point mode such that connections can be made to the device by other Wi-Fi clients - * \param ssid the name for the access point - * \param password the password to use or NULL for no password. - * \param auth the authorization type to use when the password is enabled. Values are \ref CYW43_AUTH_WPA_TKIP_PSK, - * \ref CYW43_AUTH_WPA2_AES_PSK, or \ref CYW43_AUTH_WPA2_MIXED_PSK (see \ref CYW43_AUTH_) - */ -void cyw43_arch_enable_ap_mode(const char *ssid, const char *password, uint32_t auth); - -/*! - * \brief Disables Wi-Fi AP (Access point) mode. - * \ingroup pico_cyw43_arch - * - * This Disbles the Wi-Fi in \em Access \em Point mode. - */ -void cyw43_arch_disable_ap_mode(void); - -/*! - * \brief Attempt to connect to a wireless access point, blocking until the network is joined or a failure is detected. - * \ingroup pico_cyw43_arch - * - * \param ssid the network name to connect to - * \param pw the network password or NULL if there is no password required - * \param auth the authorization type to use when the password is enabled. Values are \ref CYW43_AUTH_WPA_TKIP_PSK, - * \ref CYW43_AUTH_WPA2_AES_PSK, or \ref CYW43_AUTH_WPA2_MIXED_PSK (see \ref CYW43_AUTH_) - * - * \return 0 if the initialization is successful, an error code otherwise \see pico_error_codes - */ -int cyw43_arch_wifi_connect_blocking(const char *ssid, const char *pw, uint32_t auth); - -/*! - * \brief Attempt to connect to a wireless access point specified by SSID and BSSID, blocking until the network is joined or a failure is detected. - * \ingroup pico_cyw43_arch - * - * \param ssid the network name to connect to - * \param bssid the network BSSID to connect to or NULL if ignored - * \param pw the network password or NULL if there is no password required - * \param auth the authorization type to use when the password is enabled. Values are \ref CYW43_AUTH_WPA_TKIP_PSK, - * \ref CYW43_AUTH_WPA2_AES_PSK, or \ref CYW43_AUTH_WPA2_MIXED_PSK (see \ref CYW43_AUTH_) - * - * \return 0 if the initialization is successful, an error code otherwise \see pico_error_codes - */ -int cyw43_arch_wifi_connect_bssid_blocking(const char *ssid, const uint8_t *bssid, const char *pw, uint32_t auth); - -/*! - * \brief Attempt to connect to a wireless access point, blocking until the network is joined, a failure is detected or a timeout occurs - * \ingroup pico_cyw43_arch - * - * \param ssid the network name to connect to - * \param pw the network password or NULL if there is no password required - * \param auth the authorization type to use when the password is enabled. Values are \ref CYW43_AUTH_WPA_TKIP_PSK, - * \ref CYW43_AUTH_WPA2_AES_PSK, or \ref CYW43_AUTH_WPA2_MIXED_PSK (see \ref CYW43_AUTH_) - * \param timeout how long to wait in milliseconds for a connection to succeed before giving up - * - * \return 0 if the initialization is successful, an error code otherwise \see pico_error_codes - */ -int cyw43_arch_wifi_connect_timeout_ms(const char *ssid, const char *pw, uint32_t auth, uint32_t timeout); - -/*! - * \brief Attempt to connect to a wireless access point specified by SSID and BSSID, blocking until the network is joined, a failure is detected or a timeout occurs - * \ingroup pico_cyw43_arch - * - * \param ssid the network name to connect to - * \param bssid the network BSSID to connect to or NULL if ignored - * \param pw the network password or NULL if there is no password required - * \param auth the authorization type to use when the password is enabled. Values are \ref CYW43_AUTH_WPA_TKIP_PSK, - * \ref CYW43_AUTH_WPA2_AES_PSK, or \ref CYW43_AUTH_WPA2_MIXED_PSK (see \ref CYW43_AUTH_) - * \param timeout how long to wait in milliseconds for a connection to succeed before giving up - * - * \return 0 if the initialization is successful, an error code otherwise \see pico_error_codes - */ -int cyw43_arch_wifi_connect_bssid_timeout_ms(const char *ssid, const uint8_t *bssid, const char *pw, uint32_t auth, uint32_t timeout); - -/*! - * \brief Start attempting to connect to a wireless access point - * \ingroup pico_cyw43_arch - * - * This method tells the CYW43 driver to start connecting to an access point. You should subsequently check the - * status by calling \ref cyw43_wifi_link_status. - * - * \param ssid the network name to connect to - * \param pw the network password or NULL if there is no password required - * \param auth the authorization type to use when the password is enabled. Values are \ref CYW43_AUTH_WPA_TKIP_PSK, - * \ref CYW43_AUTH_WPA2_AES_PSK, or \ref CYW43_AUTH_WPA2_MIXED_PSK (see \ref CYW43_AUTH_) - * - * \return 0 if the scan was started successfully, an error code otherwise \see pico_error_codes - */ -int cyw43_arch_wifi_connect_async(const char *ssid, const char *pw, uint32_t auth); - -/*! - * \brief Start attempting to connect to a wireless access point specified by SSID and BSSID - * \ingroup pico_cyw43_arch - * - * This method tells the CYW43 driver to start connecting to an access point. You should subsequently check the - * status by calling \ref cyw43_wifi_link_status. - * - * \param ssid the network name to connect to - * \param bssid the network BSSID to connect to or NULL if ignored - * \param pw the network password or NULL if there is no password required - * \param auth the authorization type to use when the password is enabled. Values are \ref CYW43_AUTH_WPA_TKIP_PSK, - * \ref CYW43_AUTH_WPA2_AES_PSK, or \ref CYW43_AUTH_WPA2_MIXED_PSK (see \ref CYW43_AUTH_) - * - * \return 0 if the scan was started successfully, an error code otherwise \see pico_error_codes - */ -int cyw43_arch_wifi_connect_bssid_async(const char *ssid, const uint8_t *bssid, const char *pw, uint32_t auth); - -/*! - * \brief Set a GPIO pin on the wireless chip to a given value - * \ingroup pico_cyw43_arch - * \note this method does not check for errors setting the GPIO. You can use the lower level \ref cyw43_gpio_set instead if you wish - * to check for errors. - * - * \param wl_gpio the GPIO number on the wireless chip - * \param value true to set the GPIO, false to clear it. - */ -void cyw43_arch_gpio_put(uint wl_gpio, bool value); - -/*! - * \brief Read the value of a GPIO pin on the wireless chip - * \ingroup pico_cyw43_arch - * \note this method does not check for errors setting the GPIO. You can use the lower level \ref cyw43_gpio_get instead if you wish - * to check for errors. - * - * \param wl_gpio the GPIO number on the wireless chip - * \return true if the GPIO is high, false otherwise - */ -bool cyw43_arch_gpio_get(uint wl_gpio); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/pico-sdk/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_freertos.h b/pico-sdk/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_freertos.h deleted file mode 100644 index 1ba23ef..0000000 --- a/pico-sdk/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_freertos.h +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#ifndef _PICO_CYW43_ARCH_ARCH_FREERTOS_H -#define _PICO_CYW43_ARCH_ARCH_FREERTOS_H - -// PICO_CONFIG: CYW43_TASK_STACK_SIZE, Stack size for the CYW43 FreeRTOS task in 4-byte words, type=int, default=1024, group=pico_cyw43_arch -#ifndef CYW43_TASK_STACK_SIZE -#define CYW43_TASK_STACK_SIZE 1024 -#endif - -// PICO_CONFIG: CYW43_TASK_PRIORITY, Priority for the CYW43 FreeRTOS task, type=int, default=tskIDLE_PRIORITY + 4, group=pico_cyw43_arch -#ifndef CYW43_TASK_PRIORITY -#define CYW43_TASK_PRIORITY (tskIDLE_PRIORITY + 4) -#endif - -#endif diff --git a/pico-sdk/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_poll.h b/pico-sdk/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_poll.h deleted file mode 100644 index a221917..0000000 --- a/pico-sdk/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_poll.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#ifndef _PICO_CYW43_ARCH_ARCH_POLL_H -#define _PICO_CYW43_ARCH_ARCH_POLL_H - -// now obsolete; kept for backwards compatibility - -#endif diff --git a/pico-sdk/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_threadsafe_background.h b/pico-sdk/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_threadsafe_background.h deleted file mode 100644 index 005c15a..0000000 --- a/pico-sdk/src/rp2_common/pico_cyw43_arch/include/pico/cyw43_arch/arch_threadsafe_background.h +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#ifndef _PICO_CYW43_ARCH_ARCH_THREADSAFE_BACKGROUND_H -#define _PICO_CYW43_ARCH_ARCH_THREADSAFE_BACKGROUND_H - -// now obsolete; kept for backwards compatibility - -#endif \ No newline at end of file diff --git a/pico-sdk/src/rp2_common/pico_cyw43_driver/CMakeLists.txt b/pico-sdk/src/rp2_common/pico_cyw43_driver/CMakeLists.txt deleted file mode 100644 index 4cdf95b..0000000 --- a/pico-sdk/src/rp2_common/pico_cyw43_driver/CMakeLists.txt +++ /dev/null @@ -1,93 +0,0 @@ -if (DEFINED ENV{PICO_CYW43_DRIVER_PATH} AND (NOT PICO_CYW43_DRIVER_PATH)) - set(PICO_CYW43_DRIVER_PATH $ENV{PICO_CYW43_DRIVER_PATH}) - message("Using PICO_CYW43_DRIVER_PATH from environment ('${PICO_CYW43_DRIVER_PATH}')") -endif() - -set(CYW43_DRIVER_TEST_FILE "src/cyw43.h") - -if (NOT PICO_CYW43_DRIVER_PATH) - set(PICO_CYW43_DRIVER_PATH ${PICO_SDK_PATH}/lib/cyw43-driver) - if (PICO_CYW43_SUPPORTED AND NOT EXISTS ${PICO_CYW43_DRIVER_PATH}/${CYW43_DRIVER_TEST_FILE}) - message(WARNING "cyw43-driver submodule has not been initialized; Pico W wireless support will be unavailable -hint: try 'git submodule update --init' from your SDK directory (${PICO_SDK_PATH}).") - endif() -elseif (NOT EXISTS ${PICO_CYW43_DRIVER_PATH}/${CYW43_DRIVER_TEST_FILE}) - message(WARNING "PICO_CYW43_DRIVER_PATH specified but content not present.") -endif() - -if (EXISTS ${PICO_CYW43_DRIVER_PATH}/${CYW43_DRIVER_TEST_FILE}) - message("cyw43-driver available at ${PICO_CYW43_DRIVER_PATH}") - - add_subdirectory(cybt_shared_bus) - - pico_register_common_scope_var(PICO_CYW43_DRIVER_PATH) - - # base driver without our bus - pico_add_library(cyw43_driver NOFLAG) - target_sources(cyw43_driver INTERFACE - ${PICO_CYW43_DRIVER_PATH}/src/cyw43_ll.c - ${PICO_CYW43_DRIVER_PATH}/src/cyw43_stats.c - ${PICO_CYW43_DRIVER_PATH}/src/cyw43_lwip.c - ${PICO_CYW43_DRIVER_PATH}/src/cyw43_ctrl.c - ) - target_include_directories(cyw43_driver_headers INTERFACE - ${PICO_CYW43_DRIVER_PATH}/src - ${PICO_CYW43_DRIVER_PATH}/firmware - ) - - # pico_cyw43_driver adds async_context integration to cyw43_driver - pico_add_library(pico_cyw43_driver NOFLAG) - target_sources(pico_cyw43_driver INTERFACE - cyw43_driver.c) - target_include_directories(pico_cyw43_driver_headers INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) - pico_mirrored_target_link_libraries(pico_cyw43_driver INTERFACE cyw43_driver) - - # cyw43_driver_picow is cyw43_driver plus Pico W specific bus implementation - pico_add_library(cyw43_driver_picow NOFLAG) - target_sources(cyw43_driver_picow INTERFACE - ${CMAKE_CURRENT_LIST_DIR}/cyw43_bus_pio_spi.c - ) - pico_generate_pio_header(cyw43_driver_picow ${CMAKE_CURRENT_LIST_DIR}/cyw43_bus_pio_spi.pio) - pico_mirrored_target_link_libraries(cyw43_driver_picow INTERFACE - cyw43_driver - cybt_shared_bus - hardware_pio - hardware_dma - hardware_exception - ) - - # Note: This is used by MP, so check for issues when making changes - # e.g. Don't add new depenedences - pico_add_library(pico_btstack_hci_transport_cyw43 NOFLAG) - target_sources(pico_btstack_hci_transport_cyw43 INTERFACE - ${CMAKE_CURRENT_LIST_DIR}/btstack_hci_transport_cyw43.c - ${CMAKE_CURRENT_LIST_DIR}/btstack_chipset_cyw43.c - ) - target_include_directories(pico_btstack_hci_transport_cyw43_headers INTERFACE - ${CMAKE_CURRENT_LIST_DIR}/include - ) - target_compile_definitions(pico_btstack_hci_transport_cyw43_headers INTERFACE - CYW43_ENABLE_BLUETOOTH=1 - ) - - if (TARGET pico_btstack_base) - message("Pico W Bluetooth build support available.") - - pico_add_library(pico_btstack_cyw43) - target_sources(pico_btstack_cyw43 INTERFACE - ${CMAKE_CURRENT_LIST_DIR}/btstack_cyw43.c - ) - target_include_directories(pico_btstack_cyw43_headers INTERFACE - ${CMAKE_CURRENT_LIST_DIR}/include - ) - pico_mirrored_target_link_libraries(pico_btstack_cyw43 INTERFACE - pico_btstack_base - pico_btstack_flash_bank - pico_btstack_run_loop_async_context - pico_cyw43_arch - pico_btstack_hci_transport_cyw43 - ) - endif() - - pico_promote_common_scope_vars() -endif() diff --git a/pico-sdk/src/rp2_common/pico_cyw43_driver/btstack_chipset_cyw43.c b/pico-sdk/src/rp2_common/pico_cyw43_driver/btstack_chipset_cyw43.c deleted file mode 100644 index 2c481ed..0000000 --- a/pico-sdk/src/rp2_common/pico_cyw43_driver/btstack_chipset_cyw43.c +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include "pico/btstack_chipset_cyw43.h" - -static void chipset_set_bd_addr_command(bd_addr_t addr, uint8_t *hci_cmd_buffer) { - hci_cmd_buffer[0] = 0x01; - hci_cmd_buffer[1] = 0xfc; - hci_cmd_buffer[2] = 0x06; - reverse_bd_addr(addr, &hci_cmd_buffer[3]); -} - -static const btstack_chipset_t btstack_chipset_cyw43 = { - .name = "CYW43", - .init = NULL, - .next_command = NULL, - .set_baudrate_command = NULL, - .set_bd_addr_command = chipset_set_bd_addr_command, -}; - -const btstack_chipset_t * btstack_chipset_cyw43_instance(void) { - return &btstack_chipset_cyw43; -} diff --git a/pico-sdk/src/rp2_common/pico_cyw43_driver/btstack_cyw43.c b/pico-sdk/src/rp2_common/pico_cyw43_driver/btstack_cyw43.c deleted file mode 100644 index 75061f1..0000000 --- a/pico-sdk/src/rp2_common/pico_cyw43_driver/btstack_cyw43.c +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include "ble/le_device_db_tlv.h" -#include "classic/btstack_link_key_db_tlv.h" -#include "btstack_tlv.h" -#include "btstack_tlv_flash_bank.h" -#include "btstack_memory.h" -#include "hci.h" - -#if WANT_HCI_DUMP -#include "hci_dump.h" -#ifdef ENABLE_SEGGER_RTT -#include "hci_dump_segger_rtt_stdout.h" -#else -#include "hci_dump_embedded_stdout.h" -#endif -#endif - -#include "pico/btstack_hci_transport_cyw43.h" -#include "pico/btstack_run_loop_async_context.h" -#include "pico/btstack_flash_bank.h" -#include "pico/btstack_cyw43.h" - -static void setup_tlv(void) { - static btstack_tlv_flash_bank_t btstack_tlv_flash_bank_context; - const hal_flash_bank_t *hal_flash_bank_impl = pico_flash_bank_instance(); - - const btstack_tlv_t *btstack_tlv_impl = btstack_tlv_flash_bank_init_instance( - &btstack_tlv_flash_bank_context, - hal_flash_bank_impl, - NULL); - - // setup global TLV - btstack_tlv_set_instance(btstack_tlv_impl, &btstack_tlv_flash_bank_context); -#ifdef ENABLE_CLASSIC - const btstack_link_key_db_t *btstack_link_key_db = btstack_link_key_db_tlv_get_instance(btstack_tlv_impl, &btstack_tlv_flash_bank_context); - hci_set_link_key_db(btstack_link_key_db); -#endif -#ifdef ENABLE_BLE - // configure LE Device DB for TLV - le_device_db_tlv_configure(btstack_tlv_impl, &btstack_tlv_flash_bank_context); -#endif -} - -bool btstack_cyw43_init(async_context_t *context) { - // Initialise bluetooth - btstack_memory_init(); - btstack_run_loop_init(btstack_run_loop_async_context_get_instance(context)); - -#if WANT_HCI_DUMP -#ifdef ENABLE_SEGGER_RTT - hci_dump_init(hci_dump_segger_rtt_stdout_get_instance()); -#else - hci_dump_init(hci_dump_embedded_stdout_get_instance()); -#endif -#endif - - hci_init(hci_transport_cyw43_instance(), NULL); - - // setup TLV storage - setup_tlv(); - return true; -} - -void btstack_cyw43_deinit(__unused async_context_t *context) { - hci_power_control(HCI_POWER_OFF); - hci_close(); - btstack_run_loop_deinit(); - btstack_memory_deinit(); -} \ No newline at end of file diff --git a/pico-sdk/src/rp2_common/pico_cyw43_driver/btstack_hci_transport_cyw43.c b/pico-sdk/src/rp2_common/pico_cyw43_driver/btstack_hci_transport_cyw43.c deleted file mode 100644 index c0210b6..0000000 --- a/pico-sdk/src/rp2_common/pico_cyw43_driver/btstack_hci_transport_cyw43.c +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include "pico.h" -#include "cyw43.h" -#include "hci_transport.h" -#include "hci.h" -#include "pico/btstack_hci_transport_cyw43.h" -#include "pico/btstack_chipset_cyw43.h" - -// assert outgoing pre-buffer for cyw43 header is available -#if !defined(HCI_OUTGOING_PRE_BUFFER_SIZE) || (HCI_OUTGOING_PRE_BUFFER_SIZE < 4) -#error HCI_OUTGOING_PRE_BUFFER_SIZE not defined or smaller than 4. Please update btstack_config.h -#endif - -// assert outgoing packet fragments are word aligned -#if !defined(HCI_ACL_CHUNK_SIZE_ALIGNMENT) || ((HCI_ACL_CHUNK_SIZE_ALIGNMENT & 3) != 0) -#error HCI_ACL_CHUNK_SIZE_ALIGNMENT not defined or not a multiply of 4. Please update btstack_config.h -#endif - -#define BT_DEBUG_ENABLED 0 -#if BT_DEBUG_ENABLED -#define BT_DEBUG(...) CYW43_PRINTF(__VA_ARGS__) -#else -#define BT_DEBUG(...) (void)0 -#endif - -// Callback when we have data -static void (*hci_transport_cyw43_packet_handler)(uint8_t packet_type, uint8_t *packet, uint16_t size) = NULL; - -// Incoming packet buffer - cyw43 packet header (incl packet type) + incoming pre buffer + max(acl header + acl payload, event header + event data) -__attribute__((aligned(4))) -static uint8_t hci_packet_with_pre_buffer[4 + HCI_INCOMING_PRE_BUFFER_SIZE + HCI_INCOMING_PACKET_BUFFER_SIZE ]; - -static btstack_data_source_t transport_data_source; -static bool hci_transport_ready; - -// Forward declaration -static void hci_transport_cyw43_process(void); - -static void hci_transport_data_source_process(btstack_data_source_t *ds, btstack_data_source_callback_type_t callback_type) { - assert(callback_type == DATA_SOURCE_CALLBACK_POLL); - assert(ds == &transport_data_source); - (void)callback_type; - (void)ds; - hci_transport_cyw43_process(); -} - -static void hci_transport_cyw43_init(const void *transport_config) { - UNUSED(transport_config); -} - -static int hci_transport_cyw43_open(void) { - int err = cyw43_bluetooth_hci_init(); - if (err != 0) { - CYW43_PRINTF("Failed to open cyw43 hci controller: %d\n", err); - return err; - } - - // OTP should be set in which case BT gets an address of wifi mac + 1 - // If OTP is not set for some reason BT gets set to 43:43:A2:12:1F:AC. - // So for safety, set the bluetooth device address here. - bd_addr_t addr; - cyw43_hal_get_mac(0, (uint8_t*)&addr); - addr[BD_ADDR_LEN - 1]++; - hci_set_chipset(btstack_chipset_cyw43_instance()); - hci_set_bd_addr(addr); - - btstack_run_loop_set_data_source_handler(&transport_data_source, &hci_transport_data_source_process); - btstack_run_loop_enable_data_source_callbacks(&transport_data_source, DATA_SOURCE_CALLBACK_POLL); - btstack_run_loop_add_data_source(&transport_data_source); - hci_transport_ready = true; - - return 0; -} - -static int hci_transport_cyw43_close(void) { - btstack_run_loop_disable_data_source_callbacks(&transport_data_source, DATA_SOURCE_CALLBACK_POLL); - btstack_run_loop_remove_data_source(&transport_data_source); - hci_transport_ready = false; - - return 0; -} - -static void hci_transport_cyw43_register_packet_handler(void (*handler)(uint8_t packet_type, uint8_t *packet, uint16_t size)) { - hci_transport_cyw43_packet_handler = handler; -} - -static int hci_transport_cyw43_can_send_now(uint8_t packet_type) { - UNUSED(packet_type); - return true; -} - -static int hci_transport_cyw43_send_packet(uint8_t packet_type, uint8_t *packet, int size) { - // store packet type before actual data and increase size - // This relies on HCI_OUTGOING_PRE_BUFFER_SIZE being set - uint8_t *buffer = &packet[-4]; - uint32_t buffer_size = size + 4; - buffer[3] = packet_type; - - CYW43_THREAD_ENTER - int err = cyw43_bluetooth_hci_write(buffer, buffer_size); - - if (err != 0) { - CYW43_PRINTF("Failed to send cyw43 hci packet: %d\n", err); - assert(false); - } else { - BT_DEBUG("bt sent %lu\n", buffer_size); - static uint8_t packet_sent_event[] = { HCI_EVENT_TRANSPORT_PACKET_SENT, 0}; - hci_transport_cyw43_packet_handler(HCI_EVENT_PACKET, &packet_sent_event[0], sizeof(packet_sent_event)); - } - CYW43_THREAD_EXIT - return err; -} - -// configure and return hci transport singleton -static const hci_transport_t hci_transport_cyw43 = { - /* const char * name; */ "CYW43", - /* void (*init) (const void *transport_config); */ &hci_transport_cyw43_init, - /* int (*open)(void); */ &hci_transport_cyw43_open, - /* int (*close)(void); */ &hci_transport_cyw43_close, - /* void (*register_packet_handler)(void (*handler)(...); */ &hci_transport_cyw43_register_packet_handler, - /* int (*can_send_packet_now)(uint8_t packet_type); */ &hci_transport_cyw43_can_send_now, - /* int (*send_packet)(...); */ &hci_transport_cyw43_send_packet, - /* int (*set_baudrate)(uint32_t baudrate); */ NULL, - /* void (*reset_link)(void); */ NULL, - /* void (*set_sco_config)(uint16_t voice_setting, int num_connections); */ NULL, -}; - -const hci_transport_t *hci_transport_cyw43_instance(void) { - return &hci_transport_cyw43; -} - -// Called to perform bt work from a data source -static void hci_transport_cyw43_process(void) { - CYW43_THREAD_LOCK_CHECK - uint32_t len = 0; - bool has_work; - do { - int err = cyw43_bluetooth_hci_read(hci_packet_with_pre_buffer, sizeof(hci_packet_with_pre_buffer), &len); - BT_DEBUG("bt in len=%lu err=%d\n", len, err); - if (err == 0 && len > 0) { - hci_transport_cyw43_packet_handler(hci_packet_with_pre_buffer[3], hci_packet_with_pre_buffer + 4, len - 4); - has_work = true; - } else { - has_work = false; - } - } while (has_work); -} - -// This is called from cyw43_poll_func. -void cyw43_bluetooth_hci_process(void) { - if (hci_transport_ready) { - btstack_run_loop_poll_data_sources_from_irq(); - } -} diff --git a/pico-sdk/src/rp2_common/pico_cyw43_driver/cybt_shared_bus/CMakeLists.txt b/pico-sdk/src/rp2_common/pico_cyw43_driver/cybt_shared_bus/CMakeLists.txt deleted file mode 100644 index fad69cd..0000000 --- a/pico-sdk/src/rp2_common/pico_cyw43_driver/cybt_shared_bus/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -# cyw43 shared bus read and write -pico_add_library(cybt_shared_bus NOFLAG) - -target_sources(cybt_shared_bus INTERFACE - ${CMAKE_CURRENT_LIST_DIR}/cybt_shared_bus.c - ${CMAKE_CURRENT_LIST_DIR}/cybt_shared_bus_driver.c -) -target_include_directories(cybt_shared_bus_headers INTERFACE - ${CMAKE_CURRENT_LIST_DIR} -) - -# The BT firmware is supplied as a source file containing a static array with ascii hex data -# Set this to true to use this for testing -set(CYW43_USE_HEX_BTFW 0) -if (CYW43_USE_HEX_BTFW) - message("Warning: CYW43_USE_HEX_BTFW is true") - target_sources(cybt_shared_bus INTERFACE - ${PICO_CYW43_DRIVER_PATH}/firmware/cybt_firmware_43439.c - ) - target_compile_definitions(cybt_shared_bus INTERFACE - CYW43_USE_HEX_BTFW=1 - ) -endif() \ No newline at end of file diff --git a/pico-sdk/src/rp2_common/pico_cyw43_driver/cybt_shared_bus/cybt_shared_bus.c b/pico-sdk/src/rp2_common/pico_cyw43_driver/cybt_shared_bus/cybt_shared_bus.c deleted file mode 100644 index b464b3f..0000000 --- a/pico-sdk/src/rp2_common/pico_cyw43_driver/cybt_shared_bus/cybt_shared_bus.c +++ /dev/null @@ -1,431 +0,0 @@ -/* - * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include -#include -#include -#include - -#include "cyw43_btbus.h" -#include "cyw43_ll.h" -#include "cyw43_config.h" -#include "cybt_shared_bus_driver.h" - -#include "cyw43_btfw_43439.h" - -#if CYW43_USE_HEX_BTFW -extern const char brcm_patch_version[]; -extern const uint8_t brcm_patchram_buf[]; -extern const int brcm_patch_ram_length; -#endif - -#define BTSDIO_FW_READY_POLLING_INTERVAL_MS (1) -#define BTSDIO_BT_AWAKE_POLLING_INTERVAL_MS (1) - -#define BTSDIO_FW_READY_POLLING_RETRY_COUNT (300) -#define BTSDIO_FW_AWAKE_POLLING_RETRY_COUNT (300) - -#define BTSDIO_FWBUF_OPER_DELAY_US (250) -#define BTFW_WAIT_TIME_MS (150) - -#define CYBT_DEBUG 0 -#define CYBT_VDEBUG 0 - -#if CYBT_DEBUG -#define cybt_debug(format,args...) printf("%d.%d: " format, (int)cyw43_hal_ticks_ms() / 1000, (int)cyw43_hal_ticks_ms() % 1000, ## args) -#else -#define cybt_debug(format, ...) ((void)0) -#endif -#define cybt_printf(format, args...) printf("%d.%d: " format, (int)cyw43_hal_ticks_ms() / 1000, (int)cyw43_hal_ticks_ms() % 1000, ## args) - -#define ROUNDUP(x, a) ((((x) + ((a) - 1)) / (a)) * (a)) -#define ROUNDDN(x, a) ((x) & ~((a) - 1)) -#define ISALIGNED(a, x) (((uint32_t)(a) & ((x) - 1)) == 0) - -#define CIRC_BUF_CNT(in, out) (((in) - (out)) & ((BTSDIO_FWBUF_SIZE)-1)) -#define CIRC_BUF_SPACE(in, out) CIRC_BUF_CNT((out), ((in) + 4)) - -typedef enum { - HCI_PACKET_TYPE_IGNORE = 0x00, - HCI_PACKET_TYPE_COMMAND = 0x01, - HCI_PACKET_TYPE_ACL = 0x02, - HCI_PACKET_TYPE_SCO = 0x03, - HCI_PACKET_TYPE_EVENT = 0x04, - HCI_PACKET_TYPE_DIAG = 0x07, - HCI_PACKET_TYPE_LOOPBACK = 0xFF -} hci_packet_type_t; - -static cybt_result_t cybt_fw_download_prepare(uint8_t **p_write_buf, uint8_t **p_hex_buf) { - *p_write_buf = NULL; - *p_hex_buf = NULL; - - *p_write_buf = cyw43_malloc(BTFW_DOWNLOAD_BLK_SIZE + BTFW_SD_ALIGN); - if (NULL == *p_write_buf) { - return CYBT_ERR_OUT_OF_MEMORY; - } - - *p_hex_buf = cyw43_malloc(BTFW_MAX_STR_LEN); - if (NULL == *p_hex_buf) { - cyw43_free(*p_write_buf); - return CYBT_ERR_OUT_OF_MEMORY; - } - - return CYBT_SUCCESS; -} - -static cybt_result_t cybt_fw_download_finish(uint8_t *p_write_buf, uint8_t *p_hex_buf) { - if (p_write_buf) { - cyw43_free(p_write_buf); - } - - if (p_hex_buf) { - cyw43_free(p_hex_buf); - } - - return CYBT_SUCCESS; -} - -static cybt_result_t cybt_wait_bt_ready(uint32_t max_polling_times) { - cyw43_delay_ms(BTFW_WAIT_TIME_MS); - do { - if (cybt_ready()) { - return CYBT_SUCCESS; - } - cyw43_delay_ms(BTSDIO_FW_READY_POLLING_INTERVAL_MS); - } while (max_polling_times--); - return CYBT_ERR_TIMEOUT; -} - -static cybt_result_t cybt_wait_bt_awake(uint32_t max_polling_times) { - do { - if (cybt_awake()) { - return CYBT_SUCCESS; - } - cyw43_delay_ms(BTSDIO_BT_AWAKE_POLLING_INTERVAL_MS); - } while (max_polling_times--); - return CYBT_ERR_TIMEOUT; -} - -int cyw43_btbus_init(cyw43_ll_t *self) { - cybt_result_t ret; - - uint8_t *p_write_buf = NULL; - uint8_t *p_hex_buf = NULL; - - cybt_sharedbus_driver_init(self); - - ret = cybt_fw_download_prepare(&p_write_buf, &p_hex_buf); - if (CYBT_SUCCESS != ret) { - cybt_printf("Could not allocate memory\n"); - return ret; - } - - cybt_debug("cybt_fw_download\n"); - const uint8_t *fw_data_buf; - uint32_t fw_data_len; -#if CYW43_USE_HEX_BTFW - cybt_printf("CYW43_USE_HEX_BTFW is true\n"); -#ifndef NDEBUG - cybt_printf("BT FW download, version = %s\n", brcm_patch_version); -#endif - fw_data_len = brcm_patch_ram_length; - fw_data_buf = brcm_patchram_buf; -#else - fw_data_len = cyw43_btfw_43439_len; - fw_data_buf = cyw43_btfw_43439; -#endif - ret = cybt_fw_download(fw_data_buf, - fw_data_len, - p_write_buf, - p_hex_buf - ); - - cybt_debug("cybt_fw_download_finish\n"); - cybt_fw_download_finish(p_write_buf, p_hex_buf); - - if (CYBT_SUCCESS != ret) { - cybt_printf("hci_open(): FW download failed (0x%x)\n", ret); - return CYBT_ERR_HCI_INIT_FAILED; - } - - cybt_debug("// cybt_wait_bt_ready\n"); - ret = cybt_wait_bt_ready(BTSDIO_FW_READY_POLLING_RETRY_COUNT); - assert(ret == CYBT_SUCCESS); - if (CYBT_SUCCESS == ret) { - cybt_debug("hci_open(): FW download successfully\n"); - } else { - cybt_printf("hci_open(): Failed to download FW\n"); - return CYBT_ERR_HCI_INIT_FAILED; - } - - ret = cybt_init_buffer(); - assert(ret == 0); - if (ret != 0) { - return ret; - } - ret = cybt_wait_bt_awake(BTSDIO_FW_AWAKE_POLLING_RETRY_COUNT); - assert(ret == 0); - if (ret != 0) { - return ret; - } - - cybt_set_host_ready(); - cybt_toggle_bt_intr(); - - return CYBT_SUCCESS; -} - -#if CYBT_VDEBUG -static void dump_bytes(const uint8_t *bptr, uint32_t len) { - unsigned int i = 0; - - for (i = 0; i < len; i++) { - if ((i & 0x07) == 0) { - printf("\n "); - } - printf("0x%02x", bptr[i]); - if (i != (len-1)) { - printf(", "); - } else { - } - } - printf("\n"); -} -#endif - -static cybt_result_t cybt_hci_write_buf(const uint8_t *p_data, uint32_t length) { - cybt_result_t ret_result = CYBT_SUCCESS; - cybt_fw_membuf_index_t fw_membuf_info = {0}; - - assert(ISALIGNED(p_data, 4)); - if (!ISALIGNED(p_data, 4)) { - cybt_printf("cybt_hci_write_hdr: buffer not aligned\n"); - return CYBT_ERR_BADARG; - } - - // total length including header - length = ROUNDUP(length, 4); - cybt_get_bt_buf_index(&fw_membuf_info); - uint32_t buf_space = CIRC_BUF_SPACE(fw_membuf_info.host2bt_in_val, fw_membuf_info.host2bt_out_val); - assert(length <= buf_space); // queue full? - if (length > buf_space) { - return CYBT_ERR_QUEUE_FULL; - } - - if (fw_membuf_info.host2bt_in_val + length <= BTSDIO_FWBUF_SIZE) { - // Don't need to wrap circular buf - cybt_debug("cybt_hci_write_hdr: 1-round write, len = %" PRId32 "\n", length); - cybt_mem_write_idx(H2B_BUF_ADDR_IDX, fw_membuf_info.host2bt_in_val, p_data, length); - fw_membuf_info.host2bt_in_val += length; - } else { - // Need to wrap circular buf - uint32_t first_write_len = BTSDIO_FWBUF_SIZE - fw_membuf_info.host2bt_in_val; - if (first_write_len >= 4) { - cybt_mem_write_idx(H2B_BUF_ADDR_IDX, fw_membuf_info.host2bt_in_val, p_data, first_write_len); - fw_membuf_info.host2bt_in_val += first_write_len; - } else { - first_write_len = 0; - } - uint32_t second_write_len = length - first_write_len; - cybt_debug("cybt_hci_write_hdr: 2-round write, 1st_len = %" PRId32 ", 2nd_len = %" PRId32 "\n", first_write_len, - second_write_len); - if (second_write_len > 0) { - cybt_mem_write_idx(H2B_BUF_ADDR_IDX, 0, p_data + first_write_len, second_write_len); - fw_membuf_info.host2bt_in_val += second_write_len; - } - } - - // Update circular buf pointer - const uint32_t new_h2b_in_val = fw_membuf_info.host2bt_in_val & (BTSDIO_FWBUF_SIZE - 1); - cybt_reg_write_idx(H2B_BUF_IN_ADDR_IDX, new_h2b_in_val); - - cybt_toggle_bt_intr(); - return ret_result; -} - -static cybt_result_t cybt_hci_read(uint8_t *p_data, uint32_t *p_length) { - cybt_result_t ret_result = CYBT_SUCCESS; - uint32_t fw_b2h_buf_count; - uint32_t new_b2h_out_val; - cybt_fw_membuf_index_t fw_membuf_info = {0}; - static uint32_t available = 0; - - assert(ISALIGNED(p_data, 4)); - if (!ISALIGNED(p_data, 4)) { - assert(false); - cybt_printf("cybt_hci_read: buffer not aligned\n"); - return CYBT_ERR_BADARG; - } - - uint32_t read_len = ROUNDUP(*p_length, 4); - - cybt_get_bt_buf_index(&fw_membuf_info); - fw_b2h_buf_count = CIRC_BUF_CNT(fw_membuf_info.bt2host_in_val, - fw_membuf_info.bt2host_out_val); - cybt_debug("cybt_hci_read: bt2host_in_val=%lu bt2host_out_val=%lu fw_b2h_buf_count=%ld\n", - fw_membuf_info.bt2host_in_val, fw_membuf_info.bt2host_out_val, fw_b2h_buf_count); - if (fw_b2h_buf_count < available) { - cybt_printf("error: cybt_hci_read buffer overflow fw_b2h_buf_count=%ld available=%lu\n", fw_b2h_buf_count, - available); - cybt_printf("error: cybt_hci_read bt2host_in_val=%lu bt2host_out_val=%lu\n", fw_membuf_info.bt2host_in_val, - fw_membuf_info.bt2host_out_val); - panic("cyw43 buffer overflow"); - } - - // No space in buffer - if (fw_b2h_buf_count == 0) { - *p_length = 0; - } else { - if (read_len > fw_b2h_buf_count) { - read_len = fw_b2h_buf_count; - } - - if (fw_membuf_info.bt2host_out_val + read_len <= BTSDIO_FWBUF_SIZE) { - // Don't need to wrap the circular buf - cybt_debug("cybt_hci_read: 1-round read, len = %" PRId32 "\n", read_len); - cybt_mem_read_idx(B2H_BUF_ADDR_IDX, fw_membuf_info.bt2host_out_val, p_data, read_len); - fw_membuf_info.bt2host_out_val += read_len; - } else { - // Need to wrap the circular buf - uint32_t first_read_len = BTSDIO_FWBUF_SIZE - fw_membuf_info.bt2host_out_val; - if (first_read_len >= 4) { - cybt_mem_read_idx(B2H_BUF_ADDR_IDX, fw_membuf_info.bt2host_out_val, p_data, first_read_len); - fw_membuf_info.bt2host_out_val += first_read_len; - } else { - first_read_len = 0; - } - uint32_t second_read_len = read_len - first_read_len; - cybt_debug("cybt_hci_read: 2-round read, 1st_len = %" PRId32 ", 2nd_len = %" PRId32 "\n", first_read_len, - second_read_len); - if (second_read_len > 0) { - cybt_mem_read_idx(B2H_BUF_ADDR_IDX, 0, p_data + first_read_len, second_read_len); - fw_membuf_info.bt2host_out_val += second_read_len; - } - } - available = fw_b2h_buf_count - read_len; // remember amount available to check for buffer overflow - - // Update pointer - new_b2h_out_val = fw_membuf_info.bt2host_out_val & (BTSDIO_FWBUF_SIZE - 1); - cybt_debug("cybt_hci_read new b2h_out = %" PRId32 "\n", new_b2h_out_val); - cybt_reg_write_idx(B2H_BUF_OUT_ADDR_IDX, new_b2h_out_val); - - // in case the real length is less than the requested one - *p_length = read_len; - } - cybt_toggle_bt_intr(); - return ret_result; -} - -static void cybt_bus_request(void) { - CYW43_THREAD_ENTER - // todo: Handle failure - cybt_result_t err = cybt_set_bt_awake(true); - assert(err == 0); - err = cybt_wait_bt_awake(BTSDIO_FW_AWAKE_POLLING_RETRY_COUNT); - assert(err == 0); - (void) err; -} - -static void cybt_bus_release(void) { - // mutex if using wifi - CYW43_THREAD_EXIT -} - -// Send the buffer which includes space for a 4 byte header at the start -// The last byte of the header should already be set to the packet type -int cyw43_btbus_write(uint8_t *buf, uint32_t size) { - uint16_t cmd_len = 0; - - // The size of the buffer should include a 4 byte header at the start - cmd_len = size - 4; //in BTSDIO, cmd_len does not include header length - - // Create payload starting with required headers - // Format: Cmd Len B0, Cmd Len B1, Cmd Len B2, HCI pckt type, Data - buf[0] = (uint8_t) (cmd_len & 0xFF); - buf[1] = (uint8_t) ((cmd_len & 0xFF00) >> 8); - buf[2] = 0; - - cybt_bus_request(); - - cybt_debug("cyw43_btbus_write: %d\n", cmd_len); -#if CYBT_VDEBUG - dump_bytes(buf, size); // dump header and data -#endif - - cybt_hci_write_buf(buf, size); - cybt_bus_release(); - - return 0; -} - -static bool cybt_hci_read_packet(uint8_t *buf, uint32_t max_buf_size, uint32_t *size) { - uint32_t total_read_len = 0; - uint32_t read_len = 0; - cybt_result_t bt_result; - - // Read the header into the first 4 bytes of the buffer - read_len = 4; //3 bytes BTSDIO packet length + 1 bytes PTI - bt_result = cybt_hci_read(buf, &read_len); - - if (bt_result != CYBT_SUCCESS) { - *size = 0; - cybt_printf("cybt_hci_read_packet: error %d", bt_result); - return true; - } - - if (read_len == 0) { - // No data is read from SPI - *size = 0; - cybt_debug("cybt_hci_read_packet: no data\n"); - return true; - } - - uint32_t hci_read_len = ((buf[2] << 16) & 0xFFFF00) | ((buf[1] << 8) & 0xFF00) | (buf[0] & 0xFF); - if (hci_read_len > max_buf_size - 4) { - *size = 0; - cybt_printf("cybt_hci_read_packet: too much data len %" PRId32"\n", hci_read_len); - assert(false); - return false; - } - total_read_len = hci_read_len; - - // Read the packet data after the header - cybt_debug("cybt_hci_read_packet: packet type 0x%" PRIx8 " len %" PRId32 "\n", buf[3], hci_read_len); - bt_result = cybt_hci_read(buf + 4, &total_read_len); - if (bt_result != CYBT_SUCCESS) { - *size = 0; - cybt_printf("cybt_hci_read_packet: read failed\n"); - assert(false); - return false; - } - - // Might read more because of alignment - if (total_read_len >= hci_read_len) { - assert(total_read_len == ROUNDUP(hci_read_len, 4)); // check if we're losing data? - *size = hci_read_len + 4; - } else { - assert(total_read_len > 0); - *size = total_read_len + 4; - cybt_printf("cybt_hci_read_packet: failed to read all data %lu < %lu\n", total_read_len, hci_read_len); - //assert(false); - return true; - } - - cybt_debug("cybt_hci_read_packet: %ld\n", *size); -#if CYBT_VDEBUG - dump_bytes(buf, *size); -#endif - - return true; -} - -// Reads the hci packet prepended with 4 byte header. The last header byte is the packet type -int cyw43_btbus_read(uint8_t *buf, uint32_t max_buf_size, uint32_t *size) { - cybt_bus_request(); - bool result = cybt_hci_read_packet(buf, max_buf_size, size); - cybt_bus_release(); - return result ? 0 : -1; -} diff --git a/pico-sdk/src/rp2_common/pico_cyw43_driver/cybt_shared_bus/cybt_shared_bus_driver.c b/pico-sdk/src/rp2_common/pico_cyw43_driver/cybt_shared_bus/cybt_shared_bus_driver.c deleted file mode 100644 index 04f7147..0000000 --- a/pico-sdk/src/rp2_common/pico_cyw43_driver/cybt_shared_bus/cybt_shared_bus_driver.c +++ /dev/null @@ -1,721 +0,0 @@ -/* - * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include -#include -#include -#include - -#include "cyw43_ll.h" -#include "cybt_shared_bus_driver.h" - -// Bluetooth register corruption occurs if both wifi and bluetooth are fully utilised. -#define CYBT_CORRUPTION_TEST 1 -#if CYBT_CORRUPTION_TEST -static cybt_fw_membuf_index_t last_buf_index; -static uint32_t last_host_ctrl_reg; -static uint32_t last_bt_ctrl_reg; - -#include - -#endif - -#ifndef NDEBUG -#define cybt_printf(format, args ...) printf(format,##args) -#else -#define cybt_printf(...) -#endif - -#ifndef CYBT_DEBUG -#define CYBT_DEBUG 0 -#endif - -#if CYBT_DEBUG -#include -#define cybt_debug(format, args ...) printf(format,##args) -#else -#define cybt_debug(format, ...) ((void)0) -#endif - - -/****************************************************************************** - * Constants - ******************************************************************************/ -#define BTFW_MEM_OFFSET (0x19000000) - -/* BIT0 => WLAN Power UP and BIT1=> WLAN Wake */ -#define BT2WLAN_PWRUP_WAKE (0x03) -#define BT2WLAN_PWRUP_ADDR (0x640894)/* This address is specific to 43012B0 */ - -#define BTSDIO_OFFSET_HOST2BT_IN (0x00002000) -#define BTSDIO_OFFSET_HOST2BT_OUT (0x00002004) -#define BTSDIO_OFFSET_BT2HOST_IN (0x00002008) -#define BTSDIO_OFFSET_BT2HOST_OUT (0x0000200C) - -#define H2B_BUF_ADDR (buf_info.host2bt_buf_addr) -#define H2B_BUF_IN_ADDR (buf_info.host2bt_in_addr) -#define H2B_BUF_OUT_ADDR (buf_info.host2bt_out_addr) -#define B2H_BUF_ADDR (buf_info.bt2host_buf_addr) -#define B2H_BUF_IN_ADDR (buf_info.bt2host_in_addr) -#define B2H_BUF_OUT_ADDR (buf_info.bt2host_out_addr) - -static uint32_t wlan_ram_base_addr; -volatile uint32_t host_ctrl_cache_reg = 0; -#define WLAN_RAM_BASE_ADDR (wlan_ram_base_addr) - -// In wifi host driver these are all constants -#define BT_CTRL_REG_ADDR ((uint32_t)0x18000c7c) -#define HOST_CTRL_REG_ADDR ((uint32_t)0x18000d6c) -#define WLAN_RAM_BASE_REG_ADDR ((uint32_t)0x18000d68) - -typedef struct { - uint32_t host2bt_buf_addr; - uint32_t host2bt_in_addr; - uint32_t host2bt_out_addr; - uint32_t bt2host_buf_addr; - uint32_t bt2host_in_addr; - uint32_t bt2host_out_addr; -} cybt_fw_membuf_info_t; - -cybt_fw_membuf_info_t buf_info; - -#define BTFW_ADDR_MODE_UNKNOWN (0) -#define BTFW_ADDR_MODE_EXTENDED (1) -#define BTFW_ADDR_MODE_SEGMENT (2) -#define BTFW_ADDR_MODE_LINEAR32 (3) - -#define BTFW_HEX_LINE_TYPE_DATA (0) -#define BTFW_HEX_LINE_TYPE_END_OF_DATA (1) -#define BTFW_HEX_LINE_TYPE_EXTENDED_SEGMENT_ADDRESS (2) -#define BTFW_HEX_LINE_TYPE_EXTENDED_ADDRESS (4) -#define BTFW_HEX_LINE_TYPE_ABSOLUTE_32BIT_ADDRESS (5) - -#define BTSDIO_REG_DATA_VALID_BITMASK (1 << 1) -#define BTSDIO_REG_WAKE_BT_BITMASK (1 << 17) -#define BTSDIO_REG_SW_RDY_BITMASK (1 << 24) - -#define BTSDIO_REG_BT_AWAKE_BITMASK (1 << 8) -#define BTSDIO_REG_FW_RDY_BITMASK (1 << 24) - -#define BTSDIO_OFFSET_HOST_WRITE_BUF (0) -#define BTSDIO_OFFSET_HOST_READ_BUF BTSDIO_FWBUF_SIZE - -#define BTSDIO_FWBUF_OPER_DELAY_US (250) - -#define ROUNDUP(x, a) ((((x) + ((a) - 1)) / (a)) * (a)) -#define ROUNDDN(x, a) ((x) & ~((a) - 1)) -#define ISALIGNED(a, x) (((uint32_t)(a) & ((x) - 1)) == 0) - -typedef struct cybt_fw_cb { - const uint8_t *p_fw_mem_start; - uint32_t fw_len; - const uint8_t *p_next_line_start; -} cybt_fw_cb_t; - -typedef struct hex_file_data { - int addr_mode; - uint16_t hi_addr; - uint32_t dest_addr; - uint8_t *p_ds; -} hex_file_data_t; - -static cyw43_ll_t *cyw43_ll = NULL; - -static cybt_result_t cybt_reg_write(uint32_t reg_addr, uint32_t value); -static cybt_result_t cybt_reg_read(uint32_t reg_addr, uint32_t *p_value); -static cybt_result_t cybt_mem_write(uint32_t mem_addr, const uint8_t *p_data, uint32_t data_len); -static cybt_result_t cybt_mem_read(uint32_t mem_addr, uint8_t *p_data, uint32_t data_len); - -#if CYW43_USE_HEX_BTFW -const char *strnchr(const char *str, uint32_t len, int character) { - const char *end = str + len; - char c = (char)character; - do { - if (*str == c) { - return str; - } - } while (++str <= end); - return NULL; -} - -static uint32_t cybt_fw_hex_read_line(cybt_fw_cb_t *p_btfw_cb, - const char **p_line_start, - int len - ) { - uint32_t str_len = 0; - const char *p_str_end = NULL; - - if (NULL == p_btfw_cb || NULL == p_line_start) { - return str_len; - } - - *p_line_start = (const char *)p_btfw_cb->p_next_line_start; - p_str_end = strnchr(*p_line_start, len, '\n'); - if (p_str_end == NULL) { - return str_len; - } - - str_len = (uint32_t)(p_str_end - *p_line_start); - - /* Advance file pointer past the string length */ - p_btfw_cb->p_next_line_start += str_len + 1; - - return str_len; -} - -static inline uint8_t nibble_for_char(char c) { - if ((c >= '0') && (c <= '9')) return c - '0'; - if ((c >= 'A') && (c <= 'F')) return c - 'A' + 10; - return -1; -} - -static inline uint8_t read_hex_byte(const char *str) { - return nibble_for_char(*str) << 4 | nibble_for_char(*(str + 1)); -} - -static uint32_t read_hex(const char *str, int nchars) { - uint32_t result = 0; - assert(nchars > 0 && nchars <= 8 && nchars % 2 == 0); - for(int pos = 0; pos < nchars; pos += 2) { - result <<= 8; - result |= read_hex_byte(str + pos); - } - return result; -} - -static uint32_t cybt_fw_get_data(cybt_fw_cb_t *p_btfw_cb, - hex_file_data_t *hfd - ) { - uint32_t line_len; - uint16_t num_bytes, addr, data_pos, type, idx, octet; - uint32_t abs_base_addr32 = 0; - uint32_t data_len = 0; - const char *p_line_start = NULL; - - if (NULL == p_btfw_cb || NULL == hfd->p_ds) { - return data_len; - } - - while (data_len == 0) { - line_len = cybt_fw_hex_read_line(p_btfw_cb, &p_line_start, BTFW_MAX_STR_LEN); - if (line_len == 0) { - break; - } else if (line_len > 9) { - - num_bytes = (uint16_t)read_hex(p_line_start + 1, 2); - assert(num_bytes * 2 + 8 + 2 + 1 == line_len); - - int addr32 = read_hex(p_line_start + 3, 4); - assert(addr32 <= 0xffff); - addr = (uint16_t)addr32; - type = (uint16_t)read_hex(p_line_start + 7, 2); - assert(type <= 0xff); - - data_pos = 9; - - for (idx = 0; idx < num_bytes; idx++) - { - octet = (uint16_t)read_hex(p_line_start + data_pos, 2); - hfd->p_ds[idx] = (uint8_t)(octet & 0x00FF); - data_pos += 2; - } - - if (type == BTFW_HEX_LINE_TYPE_EXTENDED_ADDRESS) { - hfd->hi_addr = (hfd->p_ds[0] << 8) | hfd->p_ds[1]; - hfd->addr_mode = BTFW_ADDR_MODE_EXTENDED; - } else if (type == BTFW_HEX_LINE_TYPE_EXTENDED_SEGMENT_ADDRESS) { - hfd->hi_addr = (hfd->p_ds[0] << 8) | hfd->p_ds[1]; - hfd->addr_mode = BTFW_ADDR_MODE_SEGMENT; - } else if (type == BTFW_HEX_LINE_TYPE_ABSOLUTE_32BIT_ADDRESS) { - abs_base_addr32 = (hfd->p_ds[0] << 24) | (hfd->p_ds[1] << 16) | - (hfd->p_ds[2] << 8) | hfd->p_ds[3]; - hfd->addr_mode = BTFW_ADDR_MODE_LINEAR32; - } else if (type == BTFW_HEX_LINE_TYPE_DATA) { - hfd->dest_addr = addr; - - if (hfd->addr_mode == BTFW_ADDR_MODE_EXTENDED) { - hfd->dest_addr += (hfd->hi_addr << 16); - } else if (hfd->addr_mode == BTFW_ADDR_MODE_SEGMENT) { - hfd->dest_addr += (hfd->hi_addr << 4); - } else if (hfd->addr_mode == BTFW_ADDR_MODE_LINEAR32) { - hfd->dest_addr += abs_base_addr32; - } - - data_len = num_bytes; - } - } - } - - return data_len; -} -#else - -static uint32_t cybt_fw_get_data(cybt_fw_cb_t *p_btfw_cb, hex_file_data_t *hfd) { - uint32_t abs_base_addr32 = 0; - while (true) { - // 4 byte header - uint8_t num_bytes = *(p_btfw_cb->p_next_line_start)++; - uint16_t addr = *(p_btfw_cb->p_next_line_start)++ << 8; - addr |= *(p_btfw_cb->p_next_line_start)++; - uint8_t type = *(p_btfw_cb->p_next_line_start)++; - - // No data? - if (num_bytes == 0) break; - - // Copy the data - memcpy(hfd->p_ds, p_btfw_cb->p_next_line_start, num_bytes); - p_btfw_cb->p_next_line_start += num_bytes; - - // Adjust address based on type - if (type == BTFW_HEX_LINE_TYPE_EXTENDED_ADDRESS) { - hfd->hi_addr = (hfd->p_ds[0] << 8) | hfd->p_ds[1]; - hfd->addr_mode = BTFW_ADDR_MODE_EXTENDED; - } else if (type == BTFW_HEX_LINE_TYPE_EXTENDED_SEGMENT_ADDRESS) { - hfd->hi_addr = (hfd->p_ds[0] << 8) | hfd->p_ds[1]; - hfd->addr_mode = BTFW_ADDR_MODE_SEGMENT; - } else if (type == BTFW_HEX_LINE_TYPE_ABSOLUTE_32BIT_ADDRESS) { - abs_base_addr32 = (hfd->p_ds[0] << 24) | (hfd->p_ds[1] << 16) | - (hfd->p_ds[2] << 8) | hfd->p_ds[3]; - hfd->addr_mode = BTFW_ADDR_MODE_LINEAR32; - } else if (type == BTFW_HEX_LINE_TYPE_DATA) { - hfd->dest_addr = addr; - if (hfd->addr_mode == BTFW_ADDR_MODE_EXTENDED) { - hfd->dest_addr += (hfd->hi_addr << 16); - } else if (hfd->addr_mode == BTFW_ADDR_MODE_SEGMENT) { - hfd->dest_addr += (hfd->hi_addr << 4); - } else if (hfd->addr_mode == BTFW_ADDR_MODE_LINEAR32) { - hfd->dest_addr += abs_base_addr32; - } - return num_bytes; - } - } - return 0; -} - -#endif - -cybt_result_t cybt_fw_download(const uint8_t *p_bt_firmware, - uint32_t bt_firmware_len, - uint8_t *p_write_buf, - uint8_t *p_hex_buf) { - cybt_fw_cb_t btfw_cb; - hex_file_data_t hfd = {BTFW_ADDR_MODE_EXTENDED, 0, 0, NULL}; - uint8_t *p_mem_ptr; - uint32_t data_len; - - if (cyw43_ll == NULL) { - return CYBT_ERR_BADARG; - } - - if (NULL == p_bt_firmware || 0 == bt_firmware_len || NULL == p_write_buf || NULL == p_hex_buf) { - return CYBT_ERR_BADARG; - } - - // BT firmware starts with length of version string including a null terminator -#if !CYW43_USE_HEX_BTFW - uint8_t version_len = *p_bt_firmware; - assert(*(p_bt_firmware + version_len) == 0); -#ifndef NDEBUG - cybt_printf("BT FW download, version = %s\n", p_bt_firmware + 1); -#endif - p_bt_firmware += version_len + 1; // skip over version - p_bt_firmware += 1; // skip over record count -#endif - - p_mem_ptr = p_write_buf; - if ((uint32_t) (uintptr_t) p_mem_ptr % BTFW_SD_ALIGN) { - p_mem_ptr += (BTFW_SD_ALIGN - ((uint32_t) (uintptr_t) p_mem_ptr % BTFW_SD_ALIGN)); - } - - hfd.p_ds = p_hex_buf; - - btfw_cb.p_fw_mem_start = p_bt_firmware; - btfw_cb.fw_len = bt_firmware_len; - btfw_cb.p_next_line_start = p_bt_firmware; - - cybt_reg_write(BTFW_MEM_OFFSET + BT2WLAN_PWRUP_ADDR, BT2WLAN_PWRUP_WAKE); - - while ((data_len = cybt_fw_get_data(&btfw_cb, &hfd)) > 0) { - uint32_t fwmem_start_addr, fwmem_start_data, fwmem_end_addr, fwmem_end_data; - uint32_t write_data_len, idx, pad; - - fwmem_start_addr = BTFW_MEM_OFFSET + hfd.dest_addr; - write_data_len = 0; - - /** - * Make sure the start address is 4 byte aligned to avoid alignment issues - * with SD host controllers - */ - if (!ISALIGNED(fwmem_start_addr, 4)) { - pad = fwmem_start_addr % 4; - fwmem_start_addr = ROUNDDN(fwmem_start_addr, 4); - - cybt_mem_read(fwmem_start_addr, (uint8_t *) &fwmem_start_data, sizeof(uint32_t)); - - for (idx = 0; idx < pad; idx++, write_data_len++) { - p_mem_ptr[write_data_len] = (uint8_t) ((uint8_t *) &fwmem_start_data)[idx]; - } - } - memcpy(&(p_mem_ptr[write_data_len]), hfd.p_ds, data_len); - write_data_len += data_len; - - /** - * Make sure the length is multiple of 4bytes to avoid alignment issues - * with SD host controllers - */ - fwmem_end_addr = fwmem_start_addr + write_data_len; - if (!ISALIGNED(fwmem_end_addr, 4)) { - cybt_mem_read(ROUNDDN(fwmem_end_addr, 4), (uint8_t *) &fwmem_end_data, sizeof(uint32_t)); - for (idx = (fwmem_end_addr % 4); idx < 4; idx++, write_data_len++) { - p_mem_ptr[write_data_len] = (uint8_t) ((uint8_t *) &fwmem_end_data)[idx]; - } - } - - /* - * write ram - */ - if (((fwmem_start_addr & 0xFFF) + write_data_len) <= 0x1000) { - cybt_mem_write(fwmem_start_addr, p_mem_ptr, write_data_len); - } else { - uint32_t first_write_len = 0x1000 - (fwmem_start_addr & 0xFFF); - cybt_mem_write(fwmem_start_addr, p_mem_ptr, first_write_len); - cybt_mem_write(fwmem_start_addr + first_write_len, - p_mem_ptr + first_write_len, - write_data_len - first_write_len); - } - } - - return CYBT_SUCCESS; -} - -cybt_result_t cybt_set_host_ready(void) { - uint32_t reg_val; - - cybt_reg_read(HOST_CTRL_REG_ADDR, ®_val); - reg_val |= BTSDIO_REG_SW_RDY_BITMASK; - cybt_reg_write(HOST_CTRL_REG_ADDR, reg_val); -#if CYBT_CORRUPTION_TEST - last_host_ctrl_reg = reg_val; -#endif - return CYBT_SUCCESS; -} - -cybt_result_t cybt_toggle_bt_intr(void) { - uint32_t reg_val, new_val; - - cybt_reg_read(HOST_CTRL_REG_ADDR, ®_val); -#if CYBT_CORRUPTION_TEST - if ((reg_val & ~(BTSDIO_REG_SW_RDY_BITMASK | BTSDIO_REG_WAKE_BT_BITMASK | BTSDIO_REG_DATA_VALID_BITMASK)) != 0) { - cybt_printf("cybt_toggle_bt_intr read HOST_CTRL_REG_ADDR as 0x%08lx\n", reg_val); - cybt_debug_dump(); - panic("cyw43 btsdio register corruption"); - } - assert((reg_val & ~(BTSDIO_REG_SW_RDY_BITMASK | BTSDIO_REG_WAKE_BT_BITMASK | BTSDIO_REG_DATA_VALID_BITMASK)) == 0); -#endif - new_val = reg_val ^ BTSDIO_REG_DATA_VALID_BITMASK; - cybt_reg_write(HOST_CTRL_REG_ADDR, new_val); -#if CYBT_CORRUPTION_TEST - last_host_ctrl_reg = new_val; -#endif - return CYBT_SUCCESS; -} - -cybt_result_t cybt_set_bt_intr(int value) { - uint32_t reg_val, new_val; - - cybt_reg_read(HOST_CTRL_REG_ADDR, ®_val); - if (value) { - new_val = reg_val | BTSDIO_REG_DATA_VALID_BITMASK; - } else { - new_val = reg_val & ~BTSDIO_REG_DATA_VALID_BITMASK; - } - cybt_reg_write(HOST_CTRL_REG_ADDR, new_val); -#if CYBT_CORRUPTION_TEST - last_host_ctrl_reg = new_val; -#endif - return CYBT_SUCCESS; -} - -int cybt_ready(void) { - uint32_t reg_val; - cybt_reg_read(BT_CTRL_REG_ADDR, ®_val); -#if CYBT_CORRUPTION_TEST - if (reg_val & BTSDIO_REG_FW_RDY_BITMASK) { - last_bt_ctrl_reg = reg_val; - } -#endif - return (reg_val & BTSDIO_REG_FW_RDY_BITMASK) ? 1 : 0; -} - -int cybt_awake(void) { - uint32_t reg_val; - cybt_reg_read(BT_CTRL_REG_ADDR, ®_val); -#if CYBT_CORRUPTION_TEST - if (reg_val & BTSDIO_REG_BT_AWAKE_BITMASK) { - last_bt_ctrl_reg = reg_val; - } -#endif - return (reg_val & BTSDIO_REG_BT_AWAKE_BITMASK) ? 1 : 0; -} - -cybt_result_t cybt_set_bt_awake(int value) { - uint32_t reg_val_before; - cybt_reg_read(HOST_CTRL_REG_ADDR, ®_val_before); - - uint32_t reg_val_after = reg_val_before; - if (value) - reg_val_after |= BTSDIO_REG_WAKE_BT_BITMASK; - else - reg_val_after &= ~BTSDIO_REG_WAKE_BT_BITMASK; - - if (reg_val_before != reg_val_after) { - cybt_reg_write(HOST_CTRL_REG_ADDR, reg_val_after); -#if CYBT_CORRUPTION_TEST - last_host_ctrl_reg = reg_val_after; -#endif - } - return 0; -} - -void cybt_debug_dump(void) { -#if CYBT_CORRUPTION_TEST - uint32_t reg_val = 0; - cybt_fw_membuf_index_t buf_index; - - cybt_printf("WLAN_RAM_BASE_ADDR: 0x%08lx\n", WLAN_RAM_BASE_ADDR); - cybt_printf("H2B_BUF_ADDR: 0x%08lx\n", H2B_BUF_ADDR); - cybt_printf("B2H_BUF_ADDR: 0x%08lx\n", B2H_BUF_ADDR); - - cybt_reg_read(H2B_BUF_IN_ADDR, &buf_index.host2bt_in_val); - cybt_printf("H2B_BUF_IN_ADDR: 0x%08lx = 0x%08lx (last 0x%08lx)\n", H2B_BUF_IN_ADDR, buf_index.host2bt_in_val, - last_buf_index.host2bt_in_val); - - cybt_reg_read(H2B_BUF_OUT_ADDR, &buf_index.host2bt_out_val); - cybt_printf("H2B_BUF_OUT_ADDR: 0x%08lx = 0x%08lx (last 0x%08lx)\n", H2B_BUF_OUT_ADDR, buf_index.host2bt_out_val, - last_buf_index.host2bt_out_val); - - cybt_reg_read(B2H_BUF_IN_ADDR, &buf_index.bt2host_in_val); - cybt_printf("B2H_BUF_IN_ADDR: 0x%08lx = 0x%08lx (last 0x%08lx)\n", B2H_BUF_IN_ADDR, buf_index.bt2host_in_val, - last_buf_index.bt2host_in_val); - - cybt_reg_read(B2H_BUF_OUT_ADDR, &buf_index.bt2host_out_val); - cybt_printf("B2H_BUF_OUT_ADDR: 0x%08lx = 0x%08lx (last 0x%08lx)\n", B2H_BUF_OUT_ADDR, buf_index.bt2host_out_val, - last_buf_index.bt2host_out_val); - - cybt_reg_read(HOST_CTRL_REG_ADDR, ®_val); - cybt_printf("HOST_CTRL_REG_ADDR: 0x%08lx = 0x%08lx (last 0x%08lx)\n", HOST_CTRL_REG_ADDR, reg_val, - last_host_ctrl_reg); - - cybt_reg_read(BT_CTRL_REG_ADDR, ®_val); - cybt_printf("BT_CTRL_REG_ADDR: 0x%08lx = 0x%08lx (last 0x%08lx)\n", BT_CTRL_REG_ADDR, reg_val, last_bt_ctrl_reg); -#endif -} - -cybt_result_t cybt_get_bt_buf_index(cybt_fw_membuf_index_t *p_buf_index) { - uint32_t buf[4]; - - cybt_mem_read(H2B_BUF_IN_ADDR, (uint8_t *) buf, sizeof(buf)); - - p_buf_index->host2bt_in_val = buf[0]; - p_buf_index->host2bt_out_val = buf[1]; - p_buf_index->bt2host_in_val = buf[2]; - p_buf_index->bt2host_out_val = buf[3]; - - cybt_debug("cybt_get_bt_buf_index: h2b_in = 0x%08lx, h2b_out = 0x%08lx, b2h_in = 0x%08lx, b2h_out = 0x%08lx\n", - p_buf_index->host2bt_in_val, - p_buf_index->host2bt_out_val, - p_buf_index->bt2host_in_val, - p_buf_index->bt2host_out_val); - -#if CYBT_CORRUPTION_TEST - if (p_buf_index->host2bt_in_val >= BTSDIO_FWBUF_SIZE || p_buf_index->host2bt_out_val >= BTSDIO_FWBUF_SIZE || - p_buf_index->bt2host_in_val >= BTSDIO_FWBUF_SIZE || p_buf_index->bt2host_out_val >= BTSDIO_FWBUF_SIZE) { - cybt_printf("cybt_get_bt_buf_index invalid buffer value\n"); - cybt_debug_dump(); - } else { - memcpy((uint8_t *) &last_buf_index, (uint8_t *) p_buf_index, sizeof(cybt_fw_membuf_index_t)); - } -#endif - - assert(p_buf_index->host2bt_in_val < BTSDIO_FWBUF_SIZE); - assert(p_buf_index->host2bt_out_val < BTSDIO_FWBUF_SIZE); - assert(p_buf_index->bt2host_in_val < BTSDIO_FWBUF_SIZE); - assert(p_buf_index->bt2host_out_val < BTSDIO_FWBUF_SIZE); - - return CYBT_SUCCESS; -} - -static cybt_result_t cybt_reg_write(uint32_t reg_addr, uint32_t value) { - cybt_debug("cybt_reg_write 0x%08lx 0x%08lx\n", reg_addr, value); - cyw43_ll_write_backplane_reg(cyw43_ll, reg_addr, value); - if (reg_addr == HOST_CTRL_REG_ADDR) { - host_ctrl_cache_reg = value; - } - return CYBT_SUCCESS; -} - -static cybt_result_t cybt_reg_read(uint32_t reg_addr, uint32_t *p_value) { - if (reg_addr == HOST_CTRL_REG_ADDR) { - *p_value = host_ctrl_cache_reg; - return CYBT_SUCCESS; - } - *p_value = cyw43_ll_read_backplane_reg(cyw43_ll, reg_addr); - cybt_debug("cybt_reg_read 0x%08lx == 0x%08lx\n", reg_addr, *p_value); - return CYBT_SUCCESS; -} - -#if CYBT_DEBUG -static void dump_bytes(const uint8_t *bptr, uint32_t len) { - unsigned int i = 0; - - for (i = 0; i < len; i++) { - if ((i & 0x07) == 0) { - cybt_debug("\n "); - } - cybt_debug("0x%02x", bptr[i]); - if (i != (len - 1)) { - cybt_debug(", "); - } - } - cybt_debug("\n"); -} -#define DUMP_BYTES dump_bytes -#else -#define DUMP_BYTES(...) -#endif - -static cybt_result_t cybt_mem_write(uint32_t mem_addr, const uint8_t *p_data, uint32_t data_len) { - cybt_debug("cybt_mem_write addr 0x%08lx len %ld\n", mem_addr, data_len); - do { - uint32_t transfer_size = (data_len > CYW43_BUS_MAX_BLOCK_SIZE) ? CYW43_BUS_MAX_BLOCK_SIZE : data_len; - if ((mem_addr & 0xFFF) + transfer_size > 0x1000) { - transfer_size = 0x1000 - (mem_addr & 0xFFF); - } - cyw43_ll_write_backplane_mem(cyw43_ll, mem_addr, transfer_size, p_data); - cybt_debug(" write_mem addr 0x%08lx len %ld\n", mem_addr, transfer_size); - DUMP_BYTES(p_data, transfer_size); - data_len -= transfer_size; - p_data += transfer_size; - mem_addr += transfer_size; - } while (data_len > 0); - return CYBT_SUCCESS; -} - -static cybt_result_t cybt_mem_read(uint32_t mem_addr, uint8_t *p_data, uint32_t data_len) { - assert(data_len >= 4); - cybt_debug("cybt_mem_read addr 0x%08lx len %ld\n", mem_addr, data_len); - do { - uint32_t transfer_size = (data_len > CYW43_BUS_MAX_BLOCK_SIZE) ? CYW43_BUS_MAX_BLOCK_SIZE : data_len; - if ((mem_addr & 0xFFF) + transfer_size > 0x1000) { - transfer_size = 0x1000 - (mem_addr & 0xFFF); - } - cyw43_ll_read_backplane_mem(cyw43_ll, mem_addr, transfer_size, p_data); - cybt_debug(" read_mem addr 0x%08lx len %ld\n", transfer_size, mem_addr); - DUMP_BYTES(p_data, transfer_size); - data_len -= transfer_size; - p_data += transfer_size; - mem_addr += transfer_size; - } while (data_len > 0); - return CYBT_SUCCESS; -} - -static uint32_t cybt_get_addr(cybt_addr_idx_t addr_idx) { - uint32_t addr = 0; - - switch (addr_idx) { - case H2B_BUF_ADDR_IDX: - addr = H2B_BUF_ADDR; - break; - case H2B_BUF_IN_ADDR_IDX: - addr = H2B_BUF_IN_ADDR; - break; - case H2B_BUF_OUT_ADDR_IDX: - addr = H2B_BUF_OUT_ADDR; - break; - case B2H_BUF_ADDR_IDX: - addr = B2H_BUF_ADDR; - break; - case B2H_BUF_IN_ADDR_IDX: - addr = B2H_BUF_IN_ADDR; - break; - case B2H_BUF_OUT_ADDR_IDX: - addr = B2H_BUF_OUT_ADDR; - break; - default: - assert(0); - break; - } - return addr; -} - -cybt_result_t cybt_reg_write_idx(cybt_addr_idx_t reg_idx, uint32_t value) { - assert(reg_idx == H2B_BUF_IN_ADDR_IDX || reg_idx == B2H_BUF_OUT_ADDR_IDX); - assert(value < BTSDIO_FWBUF_SIZE); // writing out of bounds register value? - if ((reg_idx != H2B_BUF_IN_ADDR_IDX && reg_idx != B2H_BUF_OUT_ADDR_IDX) || value >= BTSDIO_FWBUF_SIZE) { - assert(0); - return CYBT_ERR_BADARG; - } - uint32_t reg_addr = cybt_get_addr(reg_idx); - return cybt_reg_write(reg_addr, value); -} - -cybt_result_t cybt_mem_write_idx(cybt_addr_idx_t mem_idx, uint32_t offset, const uint8_t *p_data, uint32_t data_len) { - assert(mem_idx == H2B_BUF_ADDR_IDX); // caller should only be writing to here? - assert(offset + data_len <= BTSDIO_FWBUF_SIZE); // writing out of bounds? - if (mem_idx != H2B_BUF_ADDR_IDX || (offset + data_len) > BTSDIO_FWBUF_SIZE) { - assert(0); - return CYBT_ERR_BADARG; - } - if (!ISALIGNED(p_data, 4)) { - return CYBT_ERR_BADARG; - } - uint32_t mem_addr = cybt_get_addr(mem_idx) + offset; - return cybt_mem_write(mem_addr, p_data, data_len); -} - -cybt_result_t cybt_mem_read_idx(cybt_addr_idx_t mem_idx, uint32_t offset, uint8_t *p_data, uint32_t data_len) { - assert(mem_idx == B2H_BUF_ADDR_IDX); // caller should only be reading from here? - assert(offset + data_len <= BTSDIO_FWBUF_SIZE); // reading out of bounds? - if (mem_idx != B2H_BUF_ADDR_IDX || (offset + data_len) > BTSDIO_FWBUF_SIZE) { - assert(0); - return CYBT_ERR_BADARG; - } - uint32_t mem_addr = cybt_get_addr(mem_idx) + offset; - return cybt_mem_read(mem_addr, p_data, data_len); -} - -cybt_result_t cybt_init_buffer(void) { - int result; - result = cybt_reg_read(WLAN_RAM_BASE_REG_ADDR, &WLAN_RAM_BASE_ADDR); - if (CYBT_SUCCESS != result) { - return result; - } - - cybt_debug("hci_open(): btfw ram base = 0x%" PRIx32 "\n", WLAN_RAM_BASE_ADDR); - - // Fill in reg info - // Data buffers - H2B_BUF_ADDR = WLAN_RAM_BASE_ADDR + BTSDIO_OFFSET_HOST_WRITE_BUF; - B2H_BUF_ADDR = WLAN_RAM_BASE_ADDR + BTSDIO_OFFSET_HOST_READ_BUF; - - // circular buffer indexes - H2B_BUF_IN_ADDR = WLAN_RAM_BASE_ADDR + BTSDIO_OFFSET_HOST2BT_IN; - H2B_BUF_OUT_ADDR = WLAN_RAM_BASE_ADDR + BTSDIO_OFFSET_HOST2BT_OUT; - B2H_BUF_IN_ADDR = WLAN_RAM_BASE_ADDR + BTSDIO_OFFSET_BT2HOST_IN; - B2H_BUF_OUT_ADDR = WLAN_RAM_BASE_ADDR + BTSDIO_OFFSET_BT2HOST_OUT; - - uint32_t reg_val = 0; - cybt_reg_write(H2B_BUF_IN_ADDR, reg_val); - cybt_reg_write(H2B_BUF_OUT_ADDR, reg_val); - cybt_reg_write(B2H_BUF_IN_ADDR, reg_val); - cybt_reg_write(B2H_BUF_OUT_ADDR, reg_val); - - return CYBT_SUCCESS; -} - -void cybt_sharedbus_driver_init(cyw43_ll_t *driver) { - cyw43_ll = driver; -} diff --git a/pico-sdk/src/rp2_common/pico_cyw43_driver/cybt_shared_bus/cybt_shared_bus_driver.h b/pico-sdk/src/rp2_common/pico_cyw43_driver/cybt_shared_bus/cybt_shared_bus_driver.h deleted file mode 100644 index c5e6e99..0000000 --- a/pico-sdk/src/rp2_common/pico_cyw43_driver/cybt_shared_bus/cybt_shared_bus_driver.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#ifndef CYBT_SHARE_BUS_DRIVER_H -#define CYBT_SHARE_BUS_DRIVER_H - -#define BTSDIO_FWBUF_SIZE (0x1000) -#define BTFW_MAX_STR_LEN (600) -#define BTFW_SD_ALIGN (32) -#define BTFW_DOWNLOAD_BLK_SIZE (((BTFW_MAX_STR_LEN) / 2) + 8) - -typedef enum { - CYBT_SUCCESS = 0, - CYBT_ERR_BADARG = 0xB1, - CYBT_ERR_OUT_OF_MEMORY, - CYBT_ERR_TIMEOUT, - CYBT_ERR_HCI_INIT_FAILED, - CYBT_ERR_HCI_UNSUPPORTED_IF, - CYBT_ERR_HCI_UNSUPPORTED_BAUDRATE, - CYBT_ERR_HCI_NOT_INITIALIZE, - CYBT_ERR_HCI_WRITE_FAILED, - CYBT_ERR_HCI_READ_FAILED, - CYBT_ERR_HCI_GET_TX_MUTEX_FAILED, - CYBT_ERR_HCI_GET_RX_MUTEX_FAILED, - CYBT_ERR_HCI_SET_BAUDRATE_FAILED, - CYBT_ERR_HCI_SET_FLOW_CTRL_FAILED, - CYBT_ERR_INIT_MEMPOOL_FAILED, - CYBT_ERR_INIT_QUEUE_FAILED, - CYBT_ERR_CREATE_TASK_FAILED, - CYBT_ERR_SEND_QUEUE_FAILED, - CYBT_ERR_MEMPOOL_NOT_INITIALIZE, - CYBT_ERR_QUEUE_ALMOST_FULL, - CYBT_ERR_QUEUE_FULL, - CYBT_ERR_GPIO_POWER_INIT_FAILED, - CYBT_ERR_GPIO_DEV_WAKE_INIT_FAILED, - CYBT_ERR_GPIO_HOST_WAKE_INIT_FAILED, - CYBT_ERR_GENERIC -} cybt_result_t; - -typedef enum { - H2B_BUF_ADDR_IDX = 0x10, - H2B_BUF_IN_ADDR_IDX, - H2B_BUF_OUT_ADDR_IDX, - B2H_BUF_ADDR_IDX, - B2H_BUF_IN_ADDR_IDX, - B2H_BUF_OUT_ADDR_IDX, -} cybt_addr_idx_t; - -typedef struct { - uint32_t host2bt_in_val; - uint32_t host2bt_out_val; - uint32_t bt2host_in_val; - uint32_t bt2host_out_val; -} cybt_fw_membuf_index_t; - -struct _cyw43_ll_t; -void cybt_sharedbus_driver_init(struct _cyw43_ll_t *driver); - -cybt_result_t cybt_init_buffer(void); - -cybt_result_t cybt_reg_write_idx(cybt_addr_idx_t reg_idx, uint32_t value); -cybt_result_t cybt_mem_write_idx(cybt_addr_idx_t mem_idx, uint32_t offset, const uint8_t *p_data, uint32_t data_len); -cybt_result_t cybt_mem_read_idx(cybt_addr_idx_t mem_idx, uint32_t offset, uint8_t *p_data, uint32_t data_len); - -cybt_result_t cybt_fw_download(const uint8_t *p_bt_firmware, uint32_t bt_firmware_len, uint8_t *p_write_buf, uint8_t *p_hex_buf); - -int cybt_ready(void); -int cybt_awake(void); - -cybt_result_t cybt_set_bt_awake(int value); -cybt_result_t cybt_set_host_ready(void); -cybt_result_t cybt_set_bt_intr(int value); -cybt_result_t cybt_toggle_bt_intr(void); -cybt_result_t cybt_get_bt_buf_index(cybt_fw_membuf_index_t *p_buf_index); - -void cybt_debug_dump(void); - -#endif diff --git a/pico-sdk/src/rp2_common/pico_cyw43_driver/cyw43_bus_pio_spi.c b/pico-sdk/src/rp2_common/pico_cyw43_driver/cyw43_bus_pio_spi.c deleted file mode 100644 index 5afe85e..0000000 --- a/pico-sdk/src/rp2_common/pico_cyw43_driver/cyw43_bus_pio_spi.c +++ /dev/null @@ -1,548 +0,0 @@ -/* - * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include -#include -#include - -#include "hardware/gpio.h" -#include "hardware/pio.h" -#include "hardware/clocks.h" -#include "hardware/sync.h" -#include "hardware/dma.h" -#include "cyw43_bus_pio_spi.pio.h" -#include "cyw43.h" -#include "cyw43_internal.h" -#include "cyw43_spi.h" -#include "cyw43_debug_pins.h" - -#if CYW43_SPI_PIO -#define WL_REG_ON 23 -#define DATA_OUT_PIN 24u -#define DATA_IN_PIN 24u -#define IRQ_PIN 24u -// #define MONITOR_PIN 3u -#define CLOCK_PIN 29u -#define CS_PIN 25u -#define IRQ_SAMPLE_DELAY_NS 100 - -#define SPI_PROGRAM_NAME spi_gap01_sample0 -#define SPI_PROGRAM_FUNC __CONCAT(SPI_PROGRAM_NAME, _program) -#define SPI_PROGRAM_GET_DEFAULT_CONFIG_FUNC __CONCAT(SPI_PROGRAM_NAME, _program_get_default_config) -#define SPI_OFFSET_END __CONCAT(SPI_PROGRAM_NAME, _offset_end) -#define SPI_OFFSET_LP1_END __CONCAT(SPI_PROGRAM_NAME, _offset_lp1_end) - -#define CLOCK_DIV 2 -#define CLOCK_DIV_MINOR 0 -#define PADS_DRIVE_STRENGTH PADS_BANK0_GPIO0_DRIVE_VALUE_12MA - -#if !CYW43_USE_SPI -#error CYW43_USE_SPI should be true -#endif - -#ifndef NDEBUG -//#define ENABLE_SPI_DUMPING 1 -#endif - -// Set to 1 to enable -#if ENABLE_SPI_DUMPING //NDEBUG -#if 0 -#define DUMP_SPI_TRANSACTIONS(A) A -#else -static bool enable_spi_packet_dumping; // set to true to dump -#define DUMP_SPI_TRANSACTIONS(A) if (enable_spi_packet_dumping) {A} -#endif - -static uint32_t counter = 0; -#else -#define DUMP_SPI_TRANSACTIONS(A) -#endif - -//#define SWAP32(A) ((((A) & 0xff000000U) >> 8) | (((A) & 0xff0000U) << 8) | (((A) & 0xff00U) >> 8) | (((A) & 0xffU) << 8)) -__force_inline static uint32_t __swap16x2(uint32_t a) { - pico_default_asm ("rev16 %0, %0" : "+l" (a) : : ); - return a; -} -#define SWAP32(a) __swap16x2(a) - -#ifndef CYW43_SPI_PIO_PREFERRED_PIO -#define CYW43_SPI_PIO_PREFERRED_PIO 1 -#endif -static_assert(CYW43_SPI_PIO_PREFERRED_PIO >=0 && CYW43_SPI_PIO_PREFERRED_PIO < NUM_PIOS, ""); - -typedef struct { - pio_hw_t *pio; - uint8_t pio_func_sel; - int8_t pio_offset; - int8_t pio_sm; - int8_t dma_out; - int8_t dma_in; -} bus_data_t; - -static bus_data_t bus_data_instance; - -int cyw43_spi_init(cyw43_int_t *self) { - // Only does something if CYW43_LOGIC_DEBUG=1 - logic_debug_init(); - - static_assert(NUM_PIOS == 2, ""); - - pio_hw_t *pios[2] = {pio0, pio1}; - uint pio_index = CYW43_SPI_PIO_PREFERRED_PIO; - // Check we can add the program - if (!pio_can_add_program(pios[pio_index], &SPI_PROGRAM_FUNC)) { - pio_index ^= 1; - if (!pio_can_add_program(pios[pio_index], &SPI_PROGRAM_FUNC)) { - return CYW43_FAIL_FAST_CHECK(-CYW43_EIO); - } - } - assert(!self->bus_data); - self->bus_data = &bus_data_instance; - bus_data_t *bus_data = (bus_data_t *)self->bus_data; - bus_data->pio = pios[pio_index]; - bus_data->dma_in = -1; - bus_data->dma_out = -1; - - static_assert(GPIO_FUNC_PIO1 == GPIO_FUNC_PIO0 + 1, ""); - bus_data->pio_func_sel = GPIO_FUNC_PIO0 + pio_index; - bus_data->pio_sm = (int8_t)pio_claim_unused_sm(bus_data->pio, false); - if (bus_data->pio_sm < 0) { - cyw43_spi_deinit(self); - return CYW43_FAIL_FAST_CHECK(-CYW43_EIO); - } - - bus_data->pio_offset = pio_add_program(bus_data->pio, &SPI_PROGRAM_FUNC); - pio_sm_config config = SPI_PROGRAM_GET_DEFAULT_CONFIG_FUNC(bus_data->pio_offset); - - sm_config_set_clkdiv_int_frac(&config, CLOCK_DIV, CLOCK_DIV_MINOR); - hw_write_masked(&padsbank0_hw->io[CLOCK_PIN], - (uint)PADS_DRIVE_STRENGTH << PADS_BANK0_GPIO0_DRIVE_LSB, - PADS_BANK0_GPIO0_DRIVE_BITS - ); - hw_write_masked(&padsbank0_hw->io[CLOCK_PIN], - (uint)1 << PADS_BANK0_GPIO0_SLEWFAST_LSB, - PADS_BANK0_GPIO0_SLEWFAST_BITS - ); - - sm_config_set_out_pins(&config, DATA_OUT_PIN, 1); - sm_config_set_in_pins(&config, DATA_IN_PIN); - sm_config_set_set_pins(&config, DATA_OUT_PIN, 1); - sm_config_set_sideset(&config, 1, false, false); - sm_config_set_sideset_pins(&config, CLOCK_PIN); - sm_config_set_in_shift(&config, false, true, 32); - sm_config_set_out_shift(&config, false, true, 32); - hw_set_bits(&bus_data->pio->input_sync_bypass, 1u << DATA_IN_PIN); - pio_sm_set_config(bus_data->pio, bus_data->pio_sm, &config); - pio_sm_set_consecutive_pindirs(bus_data->pio, bus_data->pio_sm, CLOCK_PIN, 1, true); - gpio_set_function(DATA_OUT_PIN, bus_data->pio_func_sel); - - // Set data pin to pull down and schmitt - gpio_set_pulls(DATA_IN_PIN, false, true); - gpio_set_input_hysteresis_enabled(DATA_IN_PIN, true); - - pio_sm_exec(bus_data->pio, bus_data->pio_sm, pio_encode_set(pio_pins, 1)); - - bus_data->dma_out = (int8_t) dma_claim_unused_channel(false); - bus_data->dma_in = (int8_t) dma_claim_unused_channel(false); - if (bus_data->dma_out < 0 || bus_data->dma_in < 0) { - cyw43_spi_deinit(self); - return CYW43_FAIL_FAST_CHECK(-CYW43_EIO); - } - return 0; -} - -void cyw43_spi_deinit(cyw43_int_t *self) { - if (self->bus_data) { - bus_data_t *bus_data = (bus_data_t *)self->bus_data; - if (bus_data->pio_sm >= 0) { - if (bus_data->pio_offset != -1) - pio_remove_program(bus_data->pio, &SPI_PROGRAM_FUNC, bus_data->pio_offset); - pio_sm_unclaim(bus_data->pio, bus_data->pio_sm); - } - if (bus_data->dma_out >= 0) { - dma_channel_cleanup(bus_data->dma_out); - dma_channel_unclaim(bus_data->dma_out); - bus_data->dma_out = -1; - } - if (bus_data->dma_in >= 0) { - dma_channel_cleanup(bus_data->dma_in); - dma_channel_unclaim(bus_data->dma_in); - bus_data->dma_in = -1; - } - self->bus_data = NULL; - } -} - -static void cs_set(bool value) { - gpio_put(CS_PIN, value); -} - -static __noinline void ns_delay(uint32_t ns) { - // cycles = ns * clk_sys_hz / 1,000,000,000 - uint32_t cycles = ns * (clock_get_hz(clk_sys) >> 16u) / (1000000000u >> 16u); - busy_wait_at_least_cycles(cycles); -} - -static void start_spi_comms(cyw43_int_t *self) { - bus_data_t *bus_data = (bus_data_t *)self->bus_data; - gpio_set_function(DATA_OUT_PIN, bus_data->pio_func_sel); - gpio_set_function(CLOCK_PIN, bus_data->pio_func_sel); - gpio_pull_down(CLOCK_PIN); - // Pull CS low - cs_set(false); -} - -// we need to atomically de-assert CS and enable IRQ -static void stop_spi_comms(void) { - // from this point a positive edge will cause an IRQ to be pending - cs_set(true); - - // we need to wait a bit in case the irq line is incorrectly high - ns_delay(IRQ_SAMPLE_DELAY_NS); -} - -#if ENABLE_SPI_DUMPING -static void dump_bytes(const uint8_t *bptr, uint32_t len) { - unsigned int i = 0; - - for (i = 0; i < len;) { - if ((i & 0x0f) == 0) { - printf("\n"); - } else if ((i & 0x07) == 0) { - printf(" "); - } - printf("%02x ", bptr[i++]); - } - printf("\n"); -} -#endif - -int cyw43_spi_transfer(cyw43_int_t *self, const uint8_t *tx, size_t tx_length, uint8_t *rx, - size_t rx_length) { - - if ((tx == NULL) && (rx == NULL)) { - return CYW43_FAIL_FAST_CHECK(-CYW43_EINVAL); - } - - bus_data_t *bus_data = (bus_data_t *)self->bus_data; - start_spi_comms(self); - if (rx != NULL) { - if (tx == NULL) { - tx = rx; - assert(tx_length && tx_length < rx_length); - } - DUMP_SPI_TRANSACTIONS( - printf("[%lu] bus TX/RX %u bytes rx %u:", counter++, tx_length, rx_length); - dump_bytes(tx, tx_length); - ) - assert(!(tx_length & 3)); - assert(!(((uintptr_t)tx) & 3)); - assert(!(((uintptr_t)rx) & 3)); - assert(!(rx_length & 3)); - - pio_sm_set_enabled(bus_data->pio, bus_data->pio_sm, false); - pio_sm_set_wrap(bus_data->pio, bus_data->pio_sm, bus_data->pio_offset, bus_data->pio_offset + SPI_OFFSET_END - 1); - pio_sm_clear_fifos(bus_data->pio, bus_data->pio_sm); - pio_sm_set_pindirs_with_mask(bus_data->pio, bus_data->pio_sm, 1u << DATA_OUT_PIN, 1u << DATA_OUT_PIN); - pio_sm_restart(bus_data->pio, bus_data->pio_sm); - pio_sm_clkdiv_restart(bus_data->pio, bus_data->pio_sm); - pio_sm_put(bus_data->pio, bus_data->pio_sm, tx_length * 8 - 1); - pio_sm_exec(bus_data->pio, bus_data->pio_sm, pio_encode_out(pio_x, 32)); - pio_sm_put(bus_data->pio, bus_data->pio_sm, (rx_length - tx_length) * 8 - 1); - pio_sm_exec(bus_data->pio, bus_data->pio_sm, pio_encode_out(pio_y, 32)); - pio_sm_exec(bus_data->pio, bus_data->pio_sm, pio_encode_jmp(bus_data->pio_offset)); - dma_channel_abort(bus_data->dma_out); - dma_channel_abort(bus_data->dma_in); - - dma_channel_config out_config = dma_channel_get_default_config(bus_data->dma_out); - channel_config_set_bswap(&out_config, true); - channel_config_set_dreq(&out_config, pio_get_dreq(bus_data->pio, bus_data->pio_sm, true)); - - dma_channel_configure(bus_data->dma_out, &out_config, &bus_data->pio->txf[bus_data->pio_sm], tx, tx_length / 4, true); - - dma_channel_config in_config = dma_channel_get_default_config(bus_data->dma_in); - channel_config_set_bswap(&in_config, true); - channel_config_set_dreq(&in_config, pio_get_dreq(bus_data->pio, bus_data->pio_sm, false)); - channel_config_set_write_increment(&in_config, true); - channel_config_set_read_increment(&in_config, false); - dma_channel_configure(bus_data->dma_in, &in_config, rx + tx_length, &bus_data->pio->rxf[bus_data->pio_sm], rx_length / 4 - tx_length / 4, true); - - pio_sm_set_enabled(bus_data->pio, bus_data->pio_sm, true); - __compiler_memory_barrier(); - - dma_channel_wait_for_finish_blocking(bus_data->dma_out); - dma_channel_wait_for_finish_blocking(bus_data->dma_in); - - __compiler_memory_barrier(); - memset(rx, 0, tx_length); // make sure we don't have garbage in what would have been returned data if using real SPI - } else if (tx != NULL) { - DUMP_SPI_TRANSACTIONS( - printf("[%lu] bus TX only %u bytes:", counter++, tx_length); - dump_bytes(tx, tx_length); - ) - assert(!(((uintptr_t)tx) & 3)); - assert(!(tx_length & 3)); - pio_sm_set_enabled(bus_data->pio, bus_data->pio_sm, false); - pio_sm_set_wrap(bus_data->pio, bus_data->pio_sm, bus_data->pio_offset, bus_data->pio_offset + SPI_OFFSET_LP1_END - 1); - pio_sm_clear_fifos(bus_data->pio, bus_data->pio_sm); - pio_sm_set_pindirs_with_mask(bus_data->pio, bus_data->pio_sm, 1u << DATA_OUT_PIN, 1u << DATA_OUT_PIN); - pio_sm_restart(bus_data->pio, bus_data->pio_sm); - pio_sm_clkdiv_restart(bus_data->pio, bus_data->pio_sm); - pio_sm_put(bus_data->pio, bus_data->pio_sm, tx_length * 8 - 1); - pio_sm_exec(bus_data->pio, bus_data->pio_sm, pio_encode_out(pio_x, 32)); - pio_sm_put(bus_data->pio, bus_data->pio_sm, 0); - pio_sm_exec(bus_data->pio, bus_data->pio_sm, pio_encode_out(pio_y, 32)); - pio_sm_exec(bus_data->pio, bus_data->pio_sm, pio_encode_jmp(bus_data->pio_offset)); - dma_channel_abort(bus_data->dma_out); - - dma_channel_config out_config = dma_channel_get_default_config(bus_data->dma_out); - channel_config_set_bswap(&out_config, true); - channel_config_set_dreq(&out_config, pio_get_dreq(bus_data->pio, bus_data->pio_sm, true)); - - dma_channel_configure(bus_data->dma_out, &out_config, &bus_data->pio->txf[bus_data->pio_sm], tx, tx_length / 4, true); - - uint32_t fdebug_tx_stall = 1u << (PIO_FDEBUG_TXSTALL_LSB + bus_data->pio_sm); - bus_data->pio->fdebug = fdebug_tx_stall; - pio_sm_set_enabled(bus_data->pio, bus_data->pio_sm, true); - while (!(bus_data->pio->fdebug & fdebug_tx_stall)) { - tight_loop_contents(); // todo timeout - } - __compiler_memory_barrier(); - pio_sm_set_enabled(bus_data->pio, bus_data->pio_sm, false); - pio_sm_set_consecutive_pindirs(bus_data->pio, bus_data->pio_sm, DATA_IN_PIN, 1, false); - } else if (rx != NULL) { /* currently do one at a time */ - DUMP_SPI_TRANSACTIONS( - printf("[%lu] bus TX %u bytes:", counter++, rx_length); - dump_bytes(rx, rx_length); - ) - panic_unsupported(); - } - pio_sm_exec(bus_data->pio, bus_data->pio_sm, pio_encode_mov(pio_pins, pio_null)); // for next time we turn output on - - stop_spi_comms(); - DUMP_SPI_TRANSACTIONS( - printf("RXed:"); - dump_bytes(rx, rx_length); - printf("\n"); - ) - - return 0; -} - -// Initialise our gpios -void cyw43_spi_gpio_setup(void) { - // Setup WL_REG_ON (23) - gpio_init(WL_REG_ON); - gpio_set_dir(WL_REG_ON, GPIO_OUT); - gpio_pull_up(WL_REG_ON); - - // Setup DO, DI and IRQ (24) - gpio_init(DATA_OUT_PIN); - gpio_set_dir(DATA_OUT_PIN, GPIO_OUT); - gpio_put(DATA_OUT_PIN, false); - - // Setup CS (25) - gpio_init(CS_PIN); - gpio_set_dir(CS_PIN, GPIO_OUT); - gpio_put(CS_PIN, true); -} - -// Reset wifi chip -void cyw43_spi_reset(void) { - gpio_put(WL_REG_ON, false); // off - sleep_ms(20); - gpio_put(WL_REG_ON, true); // on - sleep_ms(250); - - // Setup IRQ (24) - also used for DO, DI - gpio_init(IRQ_PIN); - gpio_set_dir(IRQ_PIN, GPIO_IN); -} - -static inline uint32_t make_cmd(bool write, bool inc, uint32_t fn, uint32_t addr, uint32_t sz) { - return write << 31 | inc << 30 | fn << 28 | (addr & 0x1ffff) << 11 | sz; -} - -#if CYW43_VERBOSE_DEBUG -static const char *func_name(int fn) { - switch (fn) - { - case BUS_FUNCTION: - return "BUS_FUNCTION"; - case BACKPLANE_FUNCTION: - return "BACKPLANE_FUNCTION"; - case WLAN_FUNCTION: - return "WLAN_FUNCTION"; - default: - return "UNKNOWN"; - } -} -#endif - -uint32_t read_reg_u32_swap(cyw43_int_t *self, uint32_t fn, uint32_t reg) { - uint32_t buf[2] = {0}; - assert(fn != BACKPLANE_FUNCTION); - buf[0] = SWAP32(make_cmd(false, true, fn, reg, 4)); - int ret = cyw43_spi_transfer(self, NULL, 4, (uint8_t *)buf, 8); - if (ret != 0) { - return ret; - } - return SWAP32(buf[1]); -} - -static inline uint32_t _cyw43_read_reg(cyw43_int_t *self, uint32_t fn, uint32_t reg, uint size) { - // Padding plus max read size of 32 bits + another 4? - static_assert(CYW43_BACKPLANE_READ_PAD_LEN_BYTES % 4 == 0, ""); - int index = (CYW43_BACKPLANE_READ_PAD_LEN_BYTES / 4) + 1 + 1; - uint32_t buf32[index]; - uint8_t *buf = (uint8_t *)buf32; - const uint32_t padding = (fn == BACKPLANE_FUNCTION) ? CYW43_BACKPLANE_READ_PAD_LEN_BYTES : 0; // Add response delay - buf32[0] = make_cmd(false, true, fn, reg, size); - - if (fn == BACKPLANE_FUNCTION) { - logic_debug_set(pin_BACKPLANE_READ, 1); - } - int ret = cyw43_spi_transfer(self, NULL, 4, buf, 8 + padding); - if (fn == BACKPLANE_FUNCTION) { - logic_debug_set(pin_BACKPLANE_READ, 0); - } - - if (ret != 0) { - return ret; - } - uint32_t result = buf32[padding > 0 ? index - 1 : 1]; - CYW43_VDEBUG("cyw43_read_reg_u%d %s 0x%lx=0x%lx\n", size * 8, func_name(fn), reg, result); - return result; -} - -uint32_t cyw43_read_reg_u32(cyw43_int_t *self, uint32_t fn, uint32_t reg) { - return _cyw43_read_reg(self, fn, reg, 4); -} - -int cyw43_read_reg_u16(cyw43_int_t *self, uint32_t fn, uint32_t reg) { - return _cyw43_read_reg(self, fn, reg, 2); -} - -int cyw43_read_reg_u8(cyw43_int_t *self, uint32_t fn, uint32_t reg) { - return _cyw43_read_reg(self, fn, reg, 1); -} - -// This is only used to switch the word order on boot -int write_reg_u32_swap(cyw43_int_t *self, uint32_t fn, uint32_t reg, uint32_t val) { - uint32_t buf[2]; - // Boots up in little endian so command needs swapping too - buf[0] = SWAP32(make_cmd(true, true, fn, reg, 4)); - buf[1] = SWAP32(val); - int ret = cyw43_spi_transfer(self, (uint8_t *)buf, 8, NULL, 0); - CYW43_VDEBUG("write_reg_u32_swap %s 0x%lx=0x%lx\n", func_name(fn), reg, val); - return ret; -} - -static inline int _cyw43_write_reg(cyw43_int_t *self, uint32_t fn, uint32_t reg, uint32_t val, uint size) { - uint32_t buf[2]; - buf[0] = make_cmd(true, true, fn, reg, size); - buf[1] = val; - if (fn == BACKPLANE_FUNCTION) { - // In case of f1 overflow - self->last_size = 8; - self->last_header[0] = buf[0]; - self->last_header[1] = buf[1]; - self->last_backplane_window = self->cur_backplane_window; - } - - if (fn == BACKPLANE_FUNCTION) { - logic_debug_set(pin_BACKPLANE_WRITE, 1); - } - - int ret = cyw43_spi_transfer(self, (uint8_t *)buf, 8, NULL, 0); - - if (fn == BACKPLANE_FUNCTION) { - logic_debug_set(pin_BACKPLANE_WRITE, 0); - } - - CYW43_VDEBUG("cyw43_write_reg_u%d %s 0x%lx=0x%lx\n", size * 8, func_name(fn), reg, val); - return ret; -} - -int cyw43_write_reg_u32(cyw43_int_t *self, uint32_t fn, uint32_t reg, uint32_t val) { - return _cyw43_write_reg(self, fn, reg, val, 4); -} - -int cyw43_write_reg_u16(cyw43_int_t *self, uint32_t fn, uint32_t reg, uint16_t val) { - return _cyw43_write_reg(self, fn, reg, val, 2); -} - -int cyw43_write_reg_u8(cyw43_int_t *self, uint32_t fn, uint32_t reg, uint32_t val) { - return _cyw43_write_reg(self, fn, reg, val, 1); -} - -#if CYW43_BUS_MAX_BLOCK_SIZE > 0x7f8 -#error Block size is wrong for SPI -#endif - -int cyw43_read_bytes(cyw43_int_t *self, uint32_t fn, uint32_t addr, size_t len, uint8_t *buf) { - assert(fn != BACKPLANE_FUNCTION || (len <= CYW43_BUS_MAX_BLOCK_SIZE)); - const uint32_t padding = (fn == BACKPLANE_FUNCTION) ? CYW43_BACKPLANE_READ_PAD_LEN_BYTES : 0; // Add response delay - size_t aligned_len = (len + 3) & ~3; - assert(aligned_len > 0 && aligned_len <= 0x7f8); - assert(buf == self->spid_buf || buf < self->spid_buf || buf >= (self->spid_buf + sizeof(self->spid_buf))); - self->spi_header[padding > 0 ? 0 : (CYW43_BACKPLANE_READ_PAD_LEN_BYTES / 4)] = make_cmd(false, true, fn, addr, len); - if (fn == WLAN_FUNCTION) { - logic_debug_set(pin_WIFI_RX, 1); - } - int ret = cyw43_spi_transfer(self, NULL, 4, (uint8_t *)&self->spi_header[padding > 0 ? 0 : (CYW43_BACKPLANE_READ_PAD_LEN_BYTES / 4)], aligned_len + 4 + padding); - if (fn == WLAN_FUNCTION) { - logic_debug_set(pin_WIFI_RX, 0); - } - if (ret != 0) { - printf("cyw43_read_bytes error %d", ret); - return ret; - } - if (buf != self->spid_buf) { // avoid a copy in the usual case just to add the header - memcpy(buf, self->spid_buf, len); - } - return 0; -} - -// See whd_bus_spi_transfer_bytes -// Note, uses spid_buf if src isn't using it already -// Apart from firmware download this appears to only be used for wlan functions? -int cyw43_write_bytes(cyw43_int_t *self, uint32_t fn, uint32_t addr, size_t len, const uint8_t *src) { - assert(fn != BACKPLANE_FUNCTION || (len <= CYW43_BUS_MAX_BLOCK_SIZE)); - const size_t aligned_len = (len + 3) & ~3u; - assert(aligned_len > 0 && aligned_len <= 0x7f8); - if (fn == WLAN_FUNCTION) { - // Wait for FIFO to be ready to accept data - int f2_ready_attempts = 1000; - while (f2_ready_attempts-- > 0) { - uint32_t bus_status = cyw43_read_reg_u32(self, BUS_FUNCTION, SPI_STATUS_REGISTER); - if (bus_status & STATUS_F2_RX_READY) { - logic_debug_set(pin_F2_RX_READY_WAIT, 0); - break; - } else { - logic_debug_set(pin_F2_RX_READY_WAIT, 1); - } - } - if (f2_ready_attempts <= 0) { - printf("F2 not ready\n"); - return CYW43_FAIL_FAST_CHECK(-CYW43_EIO); - } - } - if (src == self->spid_buf) { // avoid a copy in the usual case just to add the header - self->spi_header[(CYW43_BACKPLANE_READ_PAD_LEN_BYTES / 4)] = make_cmd(true, true, fn, addr, len); - logic_debug_set(pin_WIFI_TX, 1); - int res = cyw43_spi_transfer(self, (uint8_t *)&self->spi_header[(CYW43_BACKPLANE_READ_PAD_LEN_BYTES / 4)], aligned_len + 4, NULL, 0); - logic_debug_set(pin_WIFI_TX, 0); - return res; - } else { - // todo: would be nice to get rid of this. Only used for firmware download? - assert(src < self->spid_buf || src >= (self->spid_buf + sizeof(self->spid_buf))); - self->spi_header[(CYW43_BACKPLANE_READ_PAD_LEN_BYTES / 4)] = make_cmd(true, true, fn, addr, len); - memcpy(self->spid_buf, src, len); - return cyw43_spi_transfer(self, (uint8_t *)&self->spi_header[(CYW43_BACKPLANE_READ_PAD_LEN_BYTES / 4)], aligned_len + 4, NULL, 0); - } -} -#endif diff --git a/pico-sdk/src/rp2_common/pico_cyw43_driver/cyw43_bus_pio_spi.pio b/pico-sdk/src/rp2_common/pico_cyw43_driver/cyw43_bus_pio_spi.pio deleted file mode 100644 index ea0d195..0000000 --- a/pico-sdk/src/rp2_common/pico_cyw43_driver/cyw43_bus_pio_spi.pio +++ /dev/null @@ -1,61 +0,0 @@ -; -; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. -; -; SPDX-License-Identifier: BSD-3-Clause -; - -.program spi_gap0_sample1 -.side_set 1 - -; always transmit multiple of 32 bytes -lp: out pins, 1 side 0 - jmp x-- lp side 1 -public lp1_end: - set pindirs, 0 side 0 -lp2: - in pins, 1 side 1 - jmp y-- lp2 side 0 -public end: - -.program spi_gap01_sample0 -.side_set 1 - -; always transmit multiple of 32 bytes -lp: out pins, 1 side 0 - jmp x-- lp side 1 -public lp1_end: - set pindirs, 0 side 0 - nop side 1 -lp2: - in pins, 1 side 0 - jmp y-- lp2 side 1 -public end: - -.program spi_gap010_sample1 -.side_set 1 - -; always transmit multiple of 32 bytes -lp: out pins, 1 side 0 - jmp x-- lp side 1 -public lp1_end: - set pindirs, 0 side 0 - nop side 1 - nop side 0 -lp2: - in pins, 1 side 1 - jmp y-- lp2 side 0 -public end: - -.program spi_gap0_sample1_regular -.side_set 1 - -; always transmit multiple of 32 bytes -lp: out pins, 1 side 0 - jmp x-- lp side 1 -public lp1_end: - set pindirs, 0 side 0 -lp2: - in pins, 1 side 1 - jmp y-- lp2 side 0 -public end: - diff --git a/pico-sdk/src/rp2_common/pico_cyw43_driver/cyw43_driver.c b/pico-sdk/src/rp2_common/pico_cyw43_driver/cyw43_driver.c deleted file mode 100644 index 6a3d01c..0000000 --- a/pico-sdk/src/rp2_common/pico_cyw43_driver/cyw43_driver.c +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include "hardware/gpio.h" -#include "hardware/irq.h" -#include "pico/unique_id.h" -#include "cyw43.h" -#include "pico/cyw43_driver.h" - -#ifndef CYW43_GPIO_IRQ_HANDLER_PRIORITY -#define CYW43_GPIO_IRQ_HANDLER_PRIORITY 0x40 -#endif - -#ifndef CYW43_SLEEP_CHECK_MS -#define CYW43_SLEEP_CHECK_MS 50 -#endif - -static async_context_t *cyw43_async_context; - -static void cyw43_sleep_timeout_reached(async_context_t *context, async_at_time_worker_t *worker); -static void cyw43_do_poll(async_context_t *context, async_when_pending_worker_t *worker); - -static async_at_time_worker_t sleep_timeout_worker = { - .do_work = cyw43_sleep_timeout_reached -}; - -static async_when_pending_worker_t cyw43_poll_worker = { - .do_work = cyw43_do_poll -}; - -static void cyw43_set_irq_enabled(bool enabled) { - gpio_set_irq_enabled(CYW43_PIN_WL_HOST_WAKE, GPIO_IRQ_LEVEL_HIGH, enabled); -} - -// GPIO interrupt handler to tell us there's cyw43 has work to do -static void cyw43_gpio_irq_handler(void) -{ - uint32_t events = gpio_get_irq_event_mask(CYW43_PIN_WL_HOST_WAKE); - if (events & GPIO_IRQ_LEVEL_HIGH) { - // As we use a high level interrupt, it will go off forever until it's serviced - // So disable the interrupt until this is done. It's re-enabled again by CYW43_POST_POLL_HOOK - // which is called at the end of cyw43_poll_func - cyw43_set_irq_enabled(false); - async_context_set_work_pending(cyw43_async_context, &cyw43_poll_worker); - } -} - -uint32_t cyw43_irq_init(__unused void *param) { -#ifndef NDEBUG - assert(get_core_num() == async_context_core_num(cyw43_async_context)); -#endif - gpio_add_raw_irq_handler_with_order_priority(CYW43_PIN_WL_HOST_WAKE, cyw43_gpio_irq_handler, CYW43_GPIO_IRQ_HANDLER_PRIORITY); - cyw43_set_irq_enabled(true); - irq_set_enabled(IO_IRQ_BANK0, true); - return 0; -} - -uint32_t cyw43_irq_deinit(__unused void *param) { -#ifndef NDEBUG - assert(get_core_num() == async_context_core_num(cyw43_async_context)); -#endif - gpio_remove_raw_irq_handler(CYW43_PIN_WL_HOST_WAKE, cyw43_gpio_irq_handler); - cyw43_set_irq_enabled(false); - return 0; -} - -void cyw43_post_poll_hook(void) { -#ifndef NDEBUG - assert(get_core_num() == async_context_core_num(cyw43_async_context)); -#endif - cyw43_set_irq_enabled(true); -} - -void cyw43_schedule_internal_poll_dispatch(__unused void (*func)(void)) { - assert(func == cyw43_poll); - async_context_set_work_pending(cyw43_async_context, &cyw43_poll_worker); -} - -static void cyw43_do_poll(async_context_t *context, __unused async_when_pending_worker_t *worker) { -#ifndef NDEBUG - assert(get_core_num() == async_context_core_num(cyw43_async_context)); -#endif - if (cyw43_poll) { - if (cyw43_sleep > 0) { - cyw43_sleep--; - } - cyw43_poll(); - if (cyw43_sleep) { - async_context_add_at_time_worker_in_ms(context, &sleep_timeout_worker, CYW43_SLEEP_CHECK_MS); - } else { - async_context_remove_at_time_worker(context, &sleep_timeout_worker); - } - } -} - -static void cyw43_sleep_timeout_reached(async_context_t *context, __unused async_at_time_worker_t *worker) { - assert(context == cyw43_async_context); - assert(worker == &sleep_timeout_worker); - async_context_set_work_pending(context, &cyw43_poll_worker); -} - -bool cyw43_driver_init(async_context_t *context) { - cyw43_init(&cyw43_state); - cyw43_async_context = context; - // we need the IRQ to be on the same core as the context, because we need to be able to enable/disable the IRQ - // from there later - async_context_execute_sync(context, cyw43_irq_init, NULL); - async_context_add_when_pending_worker(context, &cyw43_poll_worker); - return true; -} - -void cyw43_driver_deinit(async_context_t *context) { - assert(context == cyw43_async_context); - async_context_remove_at_time_worker(context, &sleep_timeout_worker); - async_context_remove_when_pending_worker(context, &cyw43_poll_worker); - // the IRQ IS on the same core as the context, so must be de-initialized there - async_context_execute_sync(context, cyw43_irq_deinit, NULL); - cyw43_deinit(&cyw43_state); - cyw43_async_context = NULL; -} - -// todo maybe add an #ifdef in cyw43_driver -uint32_t storage_read_blocks(__unused uint8_t *dest, __unused uint32_t block_num, __unused uint32_t num_blocks) { - // shouldn't be used - panic_unsupported(); -} - -// Generate a mac address if one is not set in otp -void __attribute__((weak)) cyw43_hal_generate_laa_mac(__unused int idx, uint8_t buf[6]) { - CYW43_DEBUG("Warning. No mac in otp. Generating mac from board id\n"); - pico_unique_board_id_t board_id; - pico_get_unique_board_id(&board_id); - memcpy(buf, &board_id.id[2], 6); - buf[0] &= (uint8_t)~0x1; // unicast - buf[0] |= 0x2; // locally administered -} - -// Return mac address -void cyw43_hal_get_mac(__unused int idx, uint8_t buf[6]) { - // The mac should come from cyw43 otp. - // This is loaded into the state after the driver is initialised - // cyw43_hal_generate_laa_mac is called by the driver to generate a mac if otp is not set - memcpy(buf, cyw43_state.mac, 6); -} - -// Prevent background processing in pensv and access by the other core -// These methods are called in pensv context and on either core -// They can be called recursively -void cyw43_thread_enter(void) { - async_context_acquire_lock_blocking(cyw43_async_context); -} - -void cyw43_thread_exit(void) { - async_context_release_lock(cyw43_async_context); -} - -#ifndef NDEBUG -void cyw43_thread_lock_check(void) { - async_context_lock_check(cyw43_async_context); -} -#endif - -void cyw43_await_background_or_timeout_us(uint32_t timeout_us) { - async_context_wait_for_work_until(cyw43_async_context, make_timeout_time_us(timeout_us)); -} - -void cyw43_delay_ms(uint32_t ms) { - async_context_wait_until(cyw43_async_context, make_timeout_time_ms(ms)); -} - -void cyw43_delay_us(uint32_t us) { - async_context_wait_until(cyw43_async_context, make_timeout_time_us(us)); -} - -#if !CYW43_LWIP -static void no_lwip_fail() { - panic("cyw43 has no ethernet interface"); -} -void __attribute__((weak)) cyw43_cb_tcpip_init(cyw43_t *self, int itf) { -} -void __attribute__((weak)) cyw43_cb_tcpip_deinit(cyw43_t *self, int itf) { -} -void __attribute__((weak)) cyw43_cb_tcpip_set_link_up(cyw43_t *self, int itf) { - no_lwip_fail(); -} -void __attribute__((weak)) cyw43_cb_tcpip_set_link_down(cyw43_t *self, int itf) { - no_lwip_fail(); -} -void __attribute__((weak)) cyw43_cb_process_ethernet(void *cb_data, int itf, size_t len, const uint8_t *buf) { - no_lwip_fail(); -} -#endif diff --git a/pico-sdk/src/rp2_common/pico_cyw43_driver/include/cyw43_configport.h b/pico-sdk/src/rp2_common/pico_cyw43_driver/include/cyw43_configport.h deleted file mode 100644 index f012d24..0000000 --- a/pico-sdk/src/rp2_common/pico_cyw43_driver/include/cyw43_configport.h +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -// This header is included by cyw43_driver to setup its environment - -#ifndef _CYW43_CONFIGPORT_H -#define _CYW43_CONFIGPORT_H - -#include "pico.h" -#include "hardware/gpio.h" -#include "pico/time.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef CYW43_HOST_NAME -#define CYW43_HOST_NAME "PicoW" -#endif - -#ifndef CYW43_GPIO -#define CYW43_GPIO 1 -#endif - -#ifndef CYW43_LOGIC_DEBUG -#define CYW43_LOGIC_DEBUG 0 -#endif - -#ifndef CYW43_USE_OTP_MAC -#define CYW43_USE_OTP_MAC 1 -#endif - -#ifndef CYW43_NO_NETUTILS -#define CYW43_NO_NETUTILS 1 -#endif - -#ifndef CYW43_IOCTL_TIMEOUT_US -#define CYW43_IOCTL_TIMEOUT_US 1000000 -#endif - -#ifndef CYW43_USE_STATS -#define CYW43_USE_STATS 0 -#endif - -// todo should this be user settable? -#ifndef CYW43_HAL_MAC_WLAN0 -#define CYW43_HAL_MAC_WLAN0 0 -#endif - -#ifndef STATIC -#define STATIC static -#endif - -#ifndef CYW43_USE_SPI -#define CYW43_USE_SPI 1 -#endif - -#ifndef CYW43_SPI_PIO -#define CYW43_SPI_PIO 1 -#endif - -#ifndef CYW43_CHIPSET_FIRMWARE_INCLUDE_FILE -#if CYW43_ENABLE_BLUETOOTH -#define CYW43_CHIPSET_FIRMWARE_INCLUDE_FILE "wb43439A0_7_95_49_00_combined.h" -#else -#define CYW43_CHIPSET_FIRMWARE_INCLUDE_FILE "w43439A0_7_95_49_00_combined.h" -#endif -#endif - -#ifndef CYW43_WIFI_NVRAM_INCLUDE_FILE -#define CYW43_WIFI_NVRAM_INCLUDE_FILE "wifi_nvram_43439.h" -#endif - -// Note, these are negated, because cyw43_driver negates them before returning! -#define CYW43_EPERM (-PICO_ERROR_NOT_PERMITTED) // Operation not permitted -#define CYW43_EIO (-PICO_ERROR_IO) // I/O error -#define CYW43_EINVAL (-PICO_ERROR_INVALID_ARG) // Invalid argument -#define CYW43_ETIMEDOUT (-PICO_ERROR_TIMEOUT) // Connection timed out - -#define CYW43_NUM_GPIOS CYW43_WL_GPIO_COUNT - -#define cyw43_hal_pin_obj_t uint - -// get the number of elements in a fixed-size array -#define CYW43_ARRAY_SIZE(a) count_of(a) - -static inline uint32_t cyw43_hal_ticks_us(void) { - return time_us_32(); -} - -static inline uint32_t cyw43_hal_ticks_ms(void) { - return to_ms_since_boot(get_absolute_time()); -} - -static inline int cyw43_hal_pin_read(cyw43_hal_pin_obj_t pin) { - return gpio_get(pin); -} - -static inline void cyw43_hal_pin_low(cyw43_hal_pin_obj_t pin) { - gpio_clr_mask(1 << pin); -} - -static inline void cyw43_hal_pin_high(cyw43_hal_pin_obj_t pin) { - gpio_set_mask(1 << pin); -} - -#define CYW43_HAL_PIN_MODE_INPUT (GPIO_IN) -#define CYW43_HAL_PIN_MODE_OUTPUT (GPIO_OUT) - -#define CYW43_HAL_PIN_PULL_NONE (0) -#define CYW43_HAL_PIN_PULL_UP (1) -#define CYW43_HAL_PIN_PULL_DOWN (2) - -static inline void cyw43_hal_pin_config(cyw43_hal_pin_obj_t pin, uint32_t mode, uint32_t pull, __unused uint32_t alt) { - assert((mode == CYW43_HAL_PIN_MODE_INPUT || mode == CYW43_HAL_PIN_MODE_OUTPUT) && alt == 0); - gpio_set_dir(pin, mode); - gpio_set_pulls(pin, pull == CYW43_HAL_PIN_PULL_UP, pull == CYW43_HAL_PIN_PULL_DOWN); -} - -void cyw43_hal_get_mac(int idx, uint8_t buf[6]); - -void cyw43_hal_generate_laa_mac(int idx, uint8_t buf[6]); - - -void cyw43_thread_enter(void); - -void cyw43_thread_exit(void); - -#define CYW43_THREAD_ENTER cyw43_thread_enter(); -#define CYW43_THREAD_EXIT cyw43_thread_exit(); -#ifndef NDEBUG - -void cyw43_thread_lock_check(void); - -#define cyw43_arch_lwip_check() cyw43_thread_lock_check() -#define CYW43_THREAD_LOCK_CHECK cyw43_arch_lwip_check(); -#else -#define cyw43_arch_lwip_check() ((void)0) -#define CYW43_THREAD_LOCK_CHECK -#endif - -void cyw43_await_background_or_timeout_us(uint32_t timeout_us); -// todo not 100% sure about the timeouts here; MP uses __WFI which will always wakeup periodically -#define CYW43_SDPCM_SEND_COMMON_WAIT cyw43_await_background_or_timeout_us(1000); -#define CYW43_DO_IOCTL_WAIT cyw43_await_background_or_timeout_us(1000); - -void cyw43_delay_ms(uint32_t ms); - -void cyw43_delay_us(uint32_t us); - -void cyw43_schedule_internal_poll_dispatch(void (*func)(void)); - -void cyw43_post_poll_hook(void); - -#define CYW43_POST_POLL_HOOK cyw43_post_poll_hook(); - -// Allow malloc and free to be changed -#ifndef cyw43_malloc -#define cyw43_malloc malloc -#endif -#ifndef cyw43_free -#define cyw43_free free -#endif - -#ifdef __cplusplus -} -#endif - - -#endif \ No newline at end of file diff --git a/pico-sdk/src/rp2_common/pico_cyw43_driver/include/pico/btstack_chipset_cyw43.h b/pico-sdk/src/rp2_common/pico_cyw43_driver/include/pico/btstack_chipset_cyw43.h deleted file mode 100644 index 037b68d..0000000 --- a/pico-sdk/src/rp2_common/pico_cyw43_driver/include/pico/btstack_chipset_cyw43.h +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#ifndef _PICO_BTSTACK_CHIPSET_CYW43_H -#define _PICO_BTSTACK_CHIPSET_CYW43_H - -#include "btstack_chipset.h" - -/** - * \brief Return the singleton BTstack chipset CY43 API instance - * \ingroup pico_btstack - */ -const btstack_chipset_t * btstack_chipset_cyw43_instance(void); - -#endif diff --git a/pico-sdk/src/rp2_common/pico_cyw43_driver/include/pico/btstack_cyw43.h b/pico-sdk/src/rp2_common/pico_cyw43_driver/include/pico/btstack_cyw43.h deleted file mode 100644 index 366ea21..0000000 --- a/pico-sdk/src/rp2_common/pico_cyw43_driver/include/pico/btstack_cyw43.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#ifndef _PICO_BTSTACK_CYW43_H -#define _PICO_BTSTACK_CYW43_H - -#include "pico/async_context.h" -#ifdef __cplusplus -extern "C" { -#endif - -/** \file pico/btstack_cyw43.h - * \defgroup pico_btstack_cyw43 pico_btstack_cyw43 - * \ingroup pico_cyw43_driver - * - * \brief Low-level Bluetooth HCI support. - * - * This library provides utility functions to initialise and de-initialise BTstack for CYW43, -*/ - -/* - * \brief Perform initialisation of BTstack/CYW43 integration - * \ingroup pico_btstack_cyw43 - * - * \param context the async_context instance that provides the abstraction for handling asynchronous work. - * \return true on success or false an error - */ -bool btstack_cyw43_init(async_context_t *context); - -/* - * \brief De-initialise BTstack/CYW43 integration - * \ingroup pico_btstack_cyw43 - * - * \param context the async_context the btstack_cyw43 support was added to via \ref btstack_cyw43_init - */ -void btstack_cyw43_deinit(async_context_t *context); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/pico-sdk/src/rp2_common/pico_cyw43_driver/include/pico/btstack_hci_transport_cyw43.h b/pico-sdk/src/rp2_common/pico_cyw43_driver/include/pico/btstack_hci_transport_cyw43.h deleted file mode 100644 index e025858..0000000 --- a/pico-sdk/src/rp2_common/pico_cyw43_driver/include/pico/btstack_hci_transport_cyw43.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#ifndef _PICO_BTSTACK_HCI_TRANSPORT_CYW43_H -#define _PICO_BTSTACK_HCI_TRANSPORT_CYW43_H - -#ifdef __cplusplus -extern "C" { -#endif - -/** \file pico/btstack_hci_transport_cyw43.h -* \ingroup pico_cyw43_driver -* \brief Adds low level Bluetooth HCI support -*/ - -/** - * \brief Get the Bluetooth HCI transport instance for cyw43 - * \ingroup pico_cyw43_driver - * - * \return An instantiation of the hci_transport_t interface for the cyw43 chipset - */ -const hci_transport_t *hci_transport_cyw43_instance(void); - -#ifdef __cplusplus -} -#endif - -#endif // HCI_TRANSPORT_CYW43_H diff --git a/pico-sdk/src/rp2_common/pico_cyw43_driver/include/pico/cyw43_driver.h b/pico-sdk/src/rp2_common/pico_cyw43_driver/include/pico/cyw43_driver.h deleted file mode 100644 index bfef3f7..0000000 --- a/pico-sdk/src/rp2_common/pico_cyw43_driver/include/pico/cyw43_driver.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#ifndef _PICO_CYW43_DRIVER_H -#define _PICO_CYW43_DRIVER_H - -/** \file pico/cyw43_driver.h - * \defgroup pico_cyw43_driver pico_cyw43_driver - * - * A wrapper around the lower level cyw43_driver, that integrates it with \ref pico_async_context - * for handling background work. - */ - -#include "pico.h" -#include "pico/async_context.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/*! \brief Initializes the lower level cyw43_driver and integrates it with the provided async_context - * \ingroup pico_cyw43_driver - * - * If the initialization succeeds, \ref lwip_nosys_deinit() can be called to shutdown lwIP support - * - * \param context the async_context instance that provides the abstraction for handling asynchronous work. - * \return true if the initialization succeeded -*/ -bool cyw43_driver_init(async_context_t *context); - -/*! \brief De-initialize the lowever level cyw43_driver and unhooks it from the async_context - * \ingroup pico_cyw43_driver - * - * \param context the async_context the cyw43_driver support was added to via \ref cyw43_driver_init -*/ -void cyw43_driver_deinit(async_context_t *context); - -#ifdef __cplusplus -} -#endif -#endif \ No newline at end of file diff --git a/pico-sdk/src/rp2_common/pico_lwip/CMakeLists.txt b/pico-sdk/src/rp2_common/pico_lwip/CMakeLists.txt deleted file mode 100644 index 59d33c2..0000000 --- a/pico-sdk/src/rp2_common/pico_lwip/CMakeLists.txt +++ /dev/null @@ -1,306 +0,0 @@ -if (DEFINED ENV{PICO_LWIP_PATH} AND (NOT PICO_LWIP_PATH)) - set(PICO_LWIP_PATH $ENV{PICO_LWIP_PATH}) - message("Using PICO_LWIP_PATH from environment ('${PICO_LWIP_PATH}')") -endif () - -set(LWIP_TEST_PATH "src/Filelists.cmake") -if (NOT PICO_LWIP_PATH) - set(PICO_LWIP_PATH ${PROJECT_SOURCE_DIR}/lib/lwip) - if (PICO_CYW43_SUPPORTED AND NOT EXISTS ${PICO_LWIP_PATH}/${LWIP_TEST_PATH}) - message(WARNING "LWIP submodule has not been initialized; Pico W wireless support will be unavailable -#hint: try 'git submodule update --init' from your SDK directory (${PICO_SDK_PATH}).") - endif() -elseif (NOT EXISTS ${PICO_LWIP_PATH}/${LWIP_TEST_PATH}) - message(WARNING "PICO_LWIP_PATH specified but content not present.") -endif() - -if (EXISTS ${PICO_LWIP_PATH}/${LWIP_TEST_PATH}) - message("lwIP available at ${PICO_LWIP_PATH}") - - # argh... wanted to use this, but they dump stuff into the source tree, which breaks parallel builds - #set(LWIP_DIR ${PICO_LWIP_PATH}) - #include(${PICO_LWIP_PATH}/src/Filelists.cmake) - - pico_register_common_scope_var(PICO_LWIP_PATH) - - # The minimum set of files needed for lwIP. - pico_add_library(pico_lwip_core NOFLAG) - target_sources(pico_lwip_core INTERFACE - ${PICO_LWIP_PATH}/src/core/init.c - ${PICO_LWIP_PATH}/src/core/def.c - ${PICO_LWIP_PATH}/src/core/dns.c - ${PICO_LWIP_PATH}/src/core/inet_chksum.c - ${PICO_LWIP_PATH}/src/core/ip.c - ${PICO_LWIP_PATH}/src/core/mem.c - ${PICO_LWIP_PATH}/src/core/memp.c - ${PICO_LWIP_PATH}/src/core/netif.c - ${PICO_LWIP_PATH}/src/core/pbuf.c - ${PICO_LWIP_PATH}/src/core/raw.c - ${PICO_LWIP_PATH}/src/core/stats.c - ${PICO_LWIP_PATH}/src/core/sys.c - ${PICO_LWIP_PATH}/src/core/altcp.c - ${PICO_LWIP_PATH}/src/core/altcp_alloc.c - ${PICO_LWIP_PATH}/src/core/altcp_tcp.c - ${PICO_LWIP_PATH}/src/core/tcp.c - ${PICO_LWIP_PATH}/src/core/tcp_in.c - ${PICO_LWIP_PATH}/src/core/tcp_out.c - ${PICO_LWIP_PATH}/src/core/timeouts.c - ${PICO_LWIP_PATH}/src/core/udp.c - ) - target_include_directories(pico_lwip_core_headers INTERFACE - ${PICO_LWIP_PATH}/src/include) - - pico_add_library(pico_lwip_core4 NOFLAG) - target_sources(pico_lwip_core4 INTERFACE - ${PICO_LWIP_PATH}/src/core/ipv4/autoip.c - ${PICO_LWIP_PATH}/src/core/ipv4/dhcp.c - ${PICO_LWIP_PATH}/src/core/ipv4/etharp.c - ${PICO_LWIP_PATH}/src/core/ipv4/icmp.c - ${PICO_LWIP_PATH}/src/core/ipv4/igmp.c - ${PICO_LWIP_PATH}/src/core/ipv4/ip4_frag.c - ${PICO_LWIP_PATH}/src/core/ipv4/ip4.c - ${PICO_LWIP_PATH}/src/core/ipv4/ip4_addr.c - ) - - # Doesn't exists in version earlier than 2.1.3 - if (EXISTS ${PICO_LWIP_PATH}/src/core/ipv4/acd.c) - target_sources(pico_lwip_core4 INTERFACE - ${PICO_LWIP_PATH}/src/core/ipv4/acd.c - ) - endif() - - pico_add_library(pico_lwip_core6 NOFLAG) - target_sources(pico_lwip_core6 INTERFACE - ${PICO_LWIP_PATH}/src/core/ipv6/dhcp6.c - ${PICO_LWIP_PATH}/src/core/ipv6/ethip6.c - ${PICO_LWIP_PATH}/src/core/ipv6/icmp6.c - ${PICO_LWIP_PATH}/src/core/ipv6/inet6.c - ${PICO_LWIP_PATH}/src/core/ipv6/ip6.c - ${PICO_LWIP_PATH}/src/core/ipv6/ip6_addr.c - ${PICO_LWIP_PATH}/src/core/ipv6/ip6_frag.c - ${PICO_LWIP_PATH}/src/core/ipv6/mld6.c - ${PICO_LWIP_PATH}/src/core/ipv6/nd6.c - ) - - # APIFILES: The files which implement the sequential and socket APIs. - pico_add_library(pico_lwip_api NOFLAG) - target_sources(pico_lwip_api INTERFACE - ${PICO_LWIP_PATH}/src/api/api_lib.c - ${PICO_LWIP_PATH}/src/api/api_msg.c - ${PICO_LWIP_PATH}/src/api/err.c - ${PICO_LWIP_PATH}/src/api/if_api.c - ${PICO_LWIP_PATH}/src/api/netbuf.c - ${PICO_LWIP_PATH}/src/api/netdb.c - ${PICO_LWIP_PATH}/src/api/netifapi.c - ${PICO_LWIP_PATH}/src/api/sockets.c - ${PICO_LWIP_PATH}/src/api/tcpip.c - ) - - # Files implementing various generic network interface functions - pico_add_library(pico_lwip_netif NOFLAG) - target_sources(pico_lwip_netif INTERFACE - ${PICO_LWIP_PATH}/src/netif/ethernet.c - ${PICO_LWIP_PATH}/src/netif/bridgeif.c - ${PICO_LWIP_PATH}/src/netif/bridgeif_fdb.c - ${PICO_LWIP_PATH}/src/netif/slipif.c - ) - - # 6LoWPAN - pico_add_library(pico_lwip_sixlowpan NOFLAG) - target_sources(pico_lwip_sixlowpan INTERFACE - ${PICO_LWIP_PATH}/src/netif/lowpan6_common.c - ${PICO_LWIP_PATH}/src/netif/lowpan6.c - ${PICO_LWIP_PATH}/src/netif/lowpan6_ble.c - ${PICO_LWIP_PATH}/src/netif/zepif.c - ) - - # PPP - pico_add_library(pico_lwip_ppp NOFLAG) - target_sources(pico_lwip_ppp INTERFACE - ${PICO_LWIP_PATH}/src/netif/ppp/auth.c - ${PICO_LWIP_PATH}/src/netif/ppp/ccp.c - ${PICO_LWIP_PATH}/src/netif/ppp/chap-md5.c - ${PICO_LWIP_PATH}/src/netif/ppp/chap_ms.c - ${PICO_LWIP_PATH}/src/netif/ppp/chap-new.c - ${PICO_LWIP_PATH}/src/netif/ppp/demand.c - ${PICO_LWIP_PATH}/src/netif/ppp/eap.c - ${PICO_LWIP_PATH}/src/netif/ppp/ecp.c - ${PICO_LWIP_PATH}/src/netif/ppp/eui64.c - ${PICO_LWIP_PATH}/src/netif/ppp/fsm.c - ${PICO_LWIP_PATH}/src/netif/ppp/ipcp.c - ${PICO_LWIP_PATH}/src/netif/ppp/ipv6cp.c - ${PICO_LWIP_PATH}/src/netif/ppp/lcp.c - ${PICO_LWIP_PATH}/src/netif/ppp/magic.c - ${PICO_LWIP_PATH}/src/netif/ppp/mppe.c - ${PICO_LWIP_PATH}/src/netif/ppp/multilink.c - ${PICO_LWIP_PATH}/src/netif/ppp/ppp.c - ${PICO_LWIP_PATH}/src/netif/ppp/pppapi.c - ${PICO_LWIP_PATH}/src/netif/ppp/pppcrypt.c - ${PICO_LWIP_PATH}/src/netif/ppp/pppoe.c - ${PICO_LWIP_PATH}/src/netif/ppp/pppol2tp.c - ${PICO_LWIP_PATH}/src/netif/ppp/pppos.c - ${PICO_LWIP_PATH}/src/netif/ppp/upap.c - ${PICO_LWIP_PATH}/src/netif/ppp/utils.c - ${PICO_LWIP_PATH}/src/netif/ppp/vj.c - ${PICO_LWIP_PATH}/src/netif/ppp/polarssl/arc4.c - ${PICO_LWIP_PATH}/src/netif/ppp/polarssl/des.c - ${PICO_LWIP_PATH}/src/netif/ppp/polarssl/md4.c - ${PICO_LWIP_PATH}/src/netif/ppp/polarssl/md5.c - ${PICO_LWIP_PATH}/src/netif/ppp/polarssl/sha1.c - ) - - # SNMPv3 agent - pico_add_library(pico_lwip_snmp NOFLAG) - target_sources(pico_lwip_snmp INTERFACE - ${PICO_LWIP_PATH}/src/apps/snmp/snmp_asn1.c - ${PICO_LWIP_PATH}/src/apps/snmp/snmp_core.c - ${PICO_LWIP_PATH}/src/apps/snmp/snmp_mib2.c - ${PICO_LWIP_PATH}/src/apps/snmp/snmp_mib2_icmp.c - ${PICO_LWIP_PATH}/src/apps/snmp/snmp_mib2_interfaces.c - ${PICO_LWIP_PATH}/src/apps/snmp/snmp_mib2_ip.c - ${PICO_LWIP_PATH}/src/apps/snmp/snmp_mib2_snmp.c - ${PICO_LWIP_PATH}/src/apps/snmp/snmp_mib2_system.c - ${PICO_LWIP_PATH}/src/apps/snmp/snmp_mib2_tcp.c - ${PICO_LWIP_PATH}/src/apps/snmp/snmp_mib2_udp.c - ${PICO_LWIP_PATH}/src/apps/snmp/snmp_snmpv2_framework.c - ${PICO_LWIP_PATH}/src/apps/snmp/snmp_snmpv2_usm.c - ${PICO_LWIP_PATH}/src/apps/snmp/snmp_msg.c - ${PICO_LWIP_PATH}/src/apps/snmp/snmpv3.c - ${PICO_LWIP_PATH}/src/apps/snmp/snmp_netconn.c - ${PICO_LWIP_PATH}/src/apps/snmp/snmp_pbuf_stream.c - ${PICO_LWIP_PATH}/src/apps/snmp/snmp_raw.c - ${PICO_LWIP_PATH}/src/apps/snmp/snmp_scalar.c - ${PICO_LWIP_PATH}/src/apps/snmp/snmp_table.c - ${PICO_LWIP_PATH}/src/apps/snmp/snmp_threadsync.c - ${PICO_LWIP_PATH}/src/apps/snmp/snmp_traps.c - ) - - # HTTP server + client - pico_add_library(pico_lwip_http NOFLAG) - target_sources(pico_lwip_http INTERFACE - ${PICO_LWIP_PATH}/src/apps/http/altcp_proxyconnect.c - ${PICO_LWIP_PATH}/src/apps/http/fs.c - ${PICO_LWIP_PATH}/src/apps/http/http_client.c - ${PICO_LWIP_PATH}/src/apps/http/httpd.c - ) - - # MAKEFSDATA HTTP server host utility - pico_add_library(pico_lwip_makefsdata NOFLAG) - target_sources(pico_lwip_makefsdata INTERFACE - ${PICO_LWIP_PATH}/src/apps/http/makefsdata/makefsdata.c - ) - - # iperf - pico_add_library(pico_lwip_iperf NOFLAG) - target_sources(pico_lwip_iperf INTERFACE - ${PICO_LWIP_PATH}/src/apps/lwiperf/lwiperf.c - ) - - # SMTP client - pico_add_library(pico_lwip_smtp NOFLAG) - target_sources(pico_lwip_smtp INTERFACE - ${PICO_LWIP_PATH}/src/apps/smtp/smtp.c - ) - - # SNTP client - pico_add_library(pico_lwip_sntp NOFLAG) - target_sources(pico_lwip_sntp INTERFACE - ${PICO_LWIP_PATH}/src/apps/sntp/sntp.c - ) - - # MDNS responder - pico_add_library(pico_lwip_mdns NOFLAG) - target_sources(pico_lwip_mdns INTERFACE - ${PICO_LWIP_PATH}/src/apps/mdns/mdns.c - ) - - # Old versions of lwip had everything in mdns.c - if (EXISTS ${PICO_LWIP_PATH}/src/apps/mdns/mdns_out.c) - target_sources(pico_lwip_mdns INTERFACE - ${PICO_LWIP_PATH}/src/apps/mdns/mdns_out.c - ${PICO_LWIP_PATH}/src/apps/mdns/mdns_domain.c - ) - endif() - - # NetBIOS name server - pico_add_library(pico_lwip_netbios NOFLAG) - target_sources(pico_lwip_netbios INTERFACE - ${PICO_LWIP_PATH}/src/apps/netbiosns/netbiosns.c - ) - - # TFTP server files - pico_add_library(pico_lwip_tftp NOFLAG) - target_sources(pico_lwip_tftp INTERFACE - ${PICO_LWIP_PATH}/src/apps/tftp/tftp.c - ) - - # Mbed TLS files - pico_add_library(pico_lwip_mbedtls NOFLAG) - target_sources(pico_lwip_mbedtls INTERFACE - ${PICO_LWIP_PATH}/src/apps/altcp_tls/altcp_tls_mbedtls.c - ${PICO_LWIP_PATH}/src/apps/altcp_tls/altcp_tls_mbedtls_mem.c - ${PICO_LWIP_PATH}/src/apps/snmp/snmpv3_mbedtls.c - ) - - # MQTT client files - pico_add_library(pico_lwip_mqtt NOFLAG) - target_sources(pico_lwip_mqtt INTERFACE - ${PICO_LWIP_PATH}/src/apps/mqtt/mqtt.c - ) - - # All LWIP files without apps - pico_add_library(pico_lwip NOFLAG) - pico_mirrored_target_link_libraries(pico_lwip INTERFACE - pico_lwip_core - pico_lwip_core4 - pico_lwip_core6 - pico_lwip_api - pico_lwip_netif - pico_lwip_sixlowpan - pico_lwip_ppp - ) - - # our arch/cc.h - pico_add_library(pico_lwip_arch NOFLAG) - target_include_directories(pico_lwip_arch_headers INTERFACE - ${CMAKE_CURRENT_LIST_DIR}/include) - pico_mirrored_target_link_libraries(pico_lwip_arch INTERFACE pico_rand) - - # our nosys impl - pico_add_library(pico_lwip_nosys NOFLAG) - target_sources(pico_lwip_nosys INTERFACE - ${CMAKE_CURRENT_LIST_DIR}/lwip_nosys.c - ) - pico_mirrored_target_link_libraries(pico_lwip_nosys INTERFACE - pico_async_context_base - pico_lwip_arch - pico_lwip) - - if (NOT PICO_LWIP_CONTRIB_PATH) - set(PICO_LWIP_CONTRIB_PATH ${PICO_LWIP_PATH}/contrib) - endif() - pico_register_common_scope_var(PICO_LWIP_CONTRIB_PATH) - - # Make lwip_contrib_freertos library, with the FreeRTOS/lwIP code from lwip-contrib - pico_add_library(pico_lwip_contrib_freertos NOFLAG) - target_sources(pico_lwip_contrib_freertos INTERFACE - ${PICO_LWIP_CONTRIB_PATH}/ports/freertos/sys_arch.c - ) - target_include_directories(pico_lwip_contrib_freertos_headers INTERFACE - ${PICO_LWIP_CONTRIB_PATH}/ports/freertos/include - ) - pico_mirrored_target_link_libraries(pico_lwip_contrib_freertos INTERFACE - pico_lwip_arch) - - pico_add_library(pico_lwip_freertos NOFLAG) - target_sources(pico_lwip_freertos INTERFACE - ${CMAKE_CURRENT_LIST_DIR}/lwip_freertos.c - ) - pico_mirrored_target_link_libraries(pico_lwip_freertos INTERFACE - pico_async_context_base - pico_lwip - pico_lwip_contrib_freertos - pico_rand) - - pico_promote_common_scope_vars() -endif() diff --git a/pico-sdk/src/rp2_common/pico_lwip/doc.h b/pico-sdk/src/rp2_common/pico_lwip/doc.h deleted file mode 100644 index 0825fab..0000000 --- a/pico-sdk/src/rp2_common/pico_lwip/doc.h +++ /dev/null @@ -1,46 +0,0 @@ -/** - * \defgroup pico_lwip pico_lwip - * \brief Integration/wrapper libraries for lwIP - * the documentation for which is here. - * - * The main \c \b pico_lwip library itself aggregates the lwIP RAW API: \c \b pico_lwip_core, \c \b pico_lwip_core4, \c \b pico_lwip_core6, \c \b pico_lwip_api, \c \b pico_lwip_netif, \c \b pico_lwip_sixlowpan and \c \b pico_lwip_ppp. - * - * If you wish to run in NO_SYS=1 mode, then you can link \c \b pico_lwip along with \ref pico_lwip_nosys. - * - * If you wish to run in NO_SYS=0 mode, then you can link \c \b pico_lwip with (for instance) \ref pico_lwip_freertos, - * and also link in pico_lwip_api for the additional blocking/thread-safe APIs. - * - * Additionally you must link in \ref pico_lwip_arch unless you provide your own compiler bindings for lwIP. - * - * Additional individual pieces of lwIP functionality are available à la cart, by linking any of the libraries below. - * - * The following libraries are provided that contain exactly the equivalent lwIP functionality groups: - * - * * \c \b pico_lwip_core - - * * \c \b pico_lwip_core4 - - * * \c \b pico_lwip_core6 - - * * \c \b pico_lwip_netif - - * * \c \b pico_lwip_sixlowpan - - * * \c \b pico_lwip_ppp - - * * \c \b pico_lwip_api - - * - * The following libraries are provided that contain exactly the equivalent lwIP application support: - * - * * \c \b pico_lwip_snmp - - * * \c \b pico_lwip_http - - * * \c \b pico_lwip_makefsdata - - * * \c \b pico_lwip_iperf - - * * \c \b pico_lwip_smtp - - * * \c \b pico_lwip_sntp - - * * \c \b pico_lwip_mdns - - * * \c \b pico_lwip_netbios - - * * \c \b pico_lwip_tftp - - * * \c \b pico_lwip_mbedtls - - * * \c \b pico_lwip_mqtt - - * - */ - -/** \defgroup pico_lwip_arch pico_lwip_arch - * \ingroup pico_lwip - * \brief lwIP compiler adapters. This is not included by default in \c \b pico_lwip in case you wish to implement your own. - */ diff --git a/pico-sdk/src/rp2_common/pico_lwip/include/arch/cc.h b/pico-sdk/src/rp2_common/pico_lwip/include/arch/cc.h deleted file mode 100644 index 7ac155e..0000000 --- a/pico-sdk/src/rp2_common/pico_lwip/include/arch/cc.h +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2001-2003 Swedish Institute of Computer Science. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT - * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT - * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING - * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY - * OF SUCH DAMAGE. - * - * This file is part of the lwIP TCP/IP stack. - * - * Author: Adam Dunkels - * - */ -#ifndef __CC_H__ -#define __CC_H__ - -#include - -#ifndef PICO_LWIP_CUSTOM_LOCK_TCPIP_CORE -#define PICO_LWIP_CUSTOM_LOCK_TCPIP_CORE 1 -#endif - -#if NO_SYS -// todo really we should just not allow SYS_LIGHTWEIGHT_PROT for nosys mode (it doesn't do anything anyway) -typedef int sys_prot_t; -#elif PICO_LWIP_CUSTOM_LOCK_TCPIP_CORE -void pico_lwip_custom_lock_tcpip_core(void); -void pico_lwip_custom_unlock_tcpip_core(void); -#define LOCK_TCPIP_CORE() pico_lwip_custom_lock_tcpip_core() -#define UNLOCK_TCPIP_CORE() pico_lwip_custom_unlock_tcpip_core() -#endif - -/* define compiler specific symbols */ -#if defined (__ICCARM__) - -#define PACK_STRUCT_BEGIN -#define PACK_STRUCT_STRUCT -#define PACK_STRUCT_END -#define PACK_STRUCT_FIELD(x) x -#define PACK_STRUCT_USE_INCLUDES - -#elif defined (__CC_ARM) - -#define PACK_STRUCT_BEGIN __packed -#define PACK_STRUCT_STRUCT -#define PACK_STRUCT_END -#define PACK_STRUCT_FIELD(x) x - -#elif defined (__GNUC__) - -#define PACK_STRUCT_BEGIN -#define PACK_STRUCT_STRUCT __attribute__ ((__packed__)) -#define PACK_STRUCT_END -#define PACK_STRUCT_FIELD(x) x - -#elif defined (__TASKING__) - -#define PACK_STRUCT_BEGIN -#define PACK_STRUCT_STRUCT -#define PACK_STRUCT_END -#define PACK_STRUCT_FIELD(x) x - -#endif - -#ifndef LWIP_PLATFORM_ASSERT -#include "pico.h" -#define LWIP_PLATFORM_ASSERT(x) panic(x) -#endif - -#ifndef LWIP_RAND -#include "pico/rand.h" -// Use the pico_rand library which goes to reasonable lengths to try to provide good entropy -#define LWIP_RAND() get_rand_32() -#endif -#endif /* __CC_H__ */ diff --git a/pico-sdk/src/rp2_common/pico_lwip/include/pico/lwip_freertos.h b/pico-sdk/src/rp2_common/pico_lwip/include/pico/lwip_freertos.h deleted file mode 100644 index b1b9ab5..0000000 --- a/pico-sdk/src/rp2_common/pico_lwip/include/pico/lwip_freertos.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#ifndef _PICO_LWIP_FREERTOS_H -#define _PICO_LWIP_FREERTOS_H - -#include "pico.h" -#include "pico/async_context.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** \file pico/lwip_freertos.h -* \defgroup pico_lwip_freertos pico_lwip_freertos -* \ingroup pico_lwip -* \brief Glue library for integration lwIP in \c NO_SYS=0 mode with the SDK. Simple \c init and \c deinit -* are all that is required to hook up lwIP (with full blocking API support) via an \ref async_context instance. -*/ - -/*! \brief Initializes lwIP (NO_SYS=0 mode) support support for FreeRTOS using the provided async_context - * \ingroup pico_lwip_freertos - * - * If the initialization succeeds, \ref lwip_freertos_deinit() can be called to shutdown lwIP support - * - * \param context the async_context instance that provides the abstraction for handling asynchronous work. Note in general - * this would be an \ref async_context_freertos instance, though it doesn't have to be. - * - * \return true if the initialization succeeded -*/ -bool lwip_freertos_init(async_context_t *context); - -/*! \brief De-initialize lwIP (NO_SYS=0 mode) support for FreeRTOS - * \ingroup pico_lwip_freertos - * - * Note that since lwIP may only be initialized once, and doesn't itself provide a shutdown mechanism, lwIP - * itself may still consume resources. - * - * It is however safe to call \ref lwip_freertos_init again later. - * - * \param context the async_context the lwip_freertos support was added to via \ref lwip_freertos_init -*/ -void lwip_freertos_deinit(async_context_t *context); - -#ifdef __cplusplus -} -#endif -#endif \ No newline at end of file diff --git a/pico-sdk/src/rp2_common/pico_lwip/include/pico/lwip_nosys.h b/pico-sdk/src/rp2_common/pico_lwip/include/pico/lwip_nosys.h deleted file mode 100644 index cdde9ab..0000000 --- a/pico-sdk/src/rp2_common/pico_lwip/include/pico/lwip_nosys.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#ifndef _PICO_LWIP_NOSYS_H -#define _PICO_LWIP_NOSYS_H - -#include "pico.h" -#include "pico/async_context.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** \file pico/lwip_nosys.h -* \defgroup pico_lwip_nosys pico_lwip_nosys -* \ingroup pico_lwip -* \brief Glue library for integration lwIP in \c NO_SYS=1 mode with the SDK. Simple \c init and \c deinit -* are all that is required to hook up lwIP via an \ref async_context instance. -*/ - -/*! \brief Initializes lwIP (NO_SYS=1 mode) support support using the provided async_context -* \ingroup pico_lwip_nosys -* -* If the initialization succeeds, \ref lwip_nosys_deinit() can be called to shutdown lwIP support -* -* \param context the async_context instance that provides the abstraction for handling asynchronous work. -* \return true if the initialization succeeded -*/ -bool lwip_nosys_init(async_context_t *context); - -/*! \brief De-initialize lwIP (NO_SYS=1 mode) support - * \ingroup pico_lwip_nosys - * - * Note that since lwIP may only be initialized once, and doesn't itself provide a shutdown mechanism, lwIP - * itself may still consume resources - * - * It is however safe to call \ref lwip_nosys_init again later. - * - * \param context the async_context the lwip_nosys support was added to via \ref lwip_nosys_init -*/ -void lwip_nosys_deinit(async_context_t *context); - -#ifdef __cplusplus -} -#endif -#endif \ No newline at end of file diff --git a/pico-sdk/src/rp2_common/pico_lwip/lwip_freertos.c b/pico-sdk/src/rp2_common/pico_lwip/lwip_freertos.c deleted file mode 100644 index 8f178d1..0000000 --- a/pico-sdk/src/rp2_common/pico_lwip/lwip_freertos.c +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -// todo graham #ifdef for LWIP inclusion? - -#include "pico/async_context.h" -#include "pico/time.h" -#include "lwip/tcpip.h" -#include "lwip/timeouts.h" - -#include "FreeRTOS.h" -#include "semphr.h" - -#if NO_SYS -#error lwip_freertos_async_context_bindings requires NO_SYS=0 -#endif - -static async_context_t * volatile lwip_context; -// lwIP tcpip_task cannot be shutdown, so we block it when we are de-initialized. -static SemaphoreHandle_t tcpip_task_blocker; - -static void tcpip_init_done(void *param) { - xSemaphoreGive((SemaphoreHandle_t)param); -} - -bool lwip_freertos_init(async_context_t *context) { - assert(!lwip_context); - lwip_context = context; - static bool done_lwip_init; - if (!done_lwip_init) { - done_lwip_init = true; - SemaphoreHandle_t init_sem = xSemaphoreCreateBinary(); - tcpip_task_blocker = xSemaphoreCreateBinary(); - tcpip_init(tcpip_init_done, init_sem); - xSemaphoreTake(init_sem, portMAX_DELAY); - vSemaphoreDelete(init_sem); - } else { - xSemaphoreGive(tcpip_task_blocker); - } - return true; -} - -static uint32_t clear_lwip_context(__unused void *param) { - lwip_context = NULL; - return 0; -} - -void lwip_freertos_deinit(__unused async_context_t *context) { - // clear the lwip context under lock as lwIP may still be running in tcpip_task - async_context_execute_sync(context, clear_lwip_context, NULL); -} - -void pico_lwip_custom_lock_tcpip_core(void) { - while (!lwip_context) { - xSemaphoreTake(tcpip_task_blocker, portMAX_DELAY); - } - async_context_acquire_lock_blocking(lwip_context); -} - -void pico_lwip_custom_unlock_tcpip_core(void) { - async_context_release_lock(lwip_context); -} diff --git a/pico-sdk/src/rp2_common/pico_lwip/lwip_nosys.c b/pico-sdk/src/rp2_common/pico_lwip/lwip_nosys.c deleted file mode 100644 index 856affa..0000000 --- a/pico-sdk/src/rp2_common/pico_lwip/lwip_nosys.c +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include "pico/async_context.h" - -#include -#include "lwip/timeouts.h" - -static void update_next_timeout(async_context_t *context, async_when_pending_worker_t *worker); -static void lwip_timeout_reached(async_context_t *context, async_at_time_worker_t *worker); - -static async_when_pending_worker_t always_pending_update_timeout_worker = { - .do_work = update_next_timeout -}; - -static async_at_time_worker_t lwip_timeout_worker = { - .do_work = lwip_timeout_reached, -}; - -static void lwip_timeout_reached(__unused async_context_t *context, __unused async_at_time_worker_t *worker) { - assert(worker == &lwip_timeout_worker); - sys_check_timeouts(); -} - -static void update_next_timeout(async_context_t *context, async_when_pending_worker_t *worker) { - assert(worker == &always_pending_update_timeout_worker); - // we want to run on every execution of the helper to re-reflect any changes - // to the underlying lwIP timers which may have happened in the interim - // (note that worker will be called on every outermost exit of the async_context - // lock, and lwIP timers should not be modified whilst not holding the lock. - worker->work_pending = true; - uint32_t sleep_ms = sys_timeouts_sleeptime(); - if (sleep_ms == SYS_TIMEOUTS_SLEEPTIME_INFINITE) { - lwip_timeout_worker.next_time = at_the_end_of_time; - } else { - lwip_timeout_worker.next_time = make_timeout_time_ms(sleep_ms); - } - async_context_add_at_time_worker(context, &lwip_timeout_worker); -} - -bool lwip_nosys_init(async_context_t *context) { - static bool done_lwip_init; - if (!done_lwip_init) { - lwip_init(); - done_lwip_init = true; - } - // we want the worker to be called on every async helper run (starting with the next) - always_pending_update_timeout_worker.work_pending = true; - async_context_add_when_pending_worker(context, &always_pending_update_timeout_worker); - return true; -} - -void lwip_nosys_deinit(async_context_t *context) { - async_context_remove_at_time_worker(context, &lwip_timeout_worker); - async_context_remove_when_pending_worker(context, &always_pending_update_timeout_worker); -} - -#if NO_SYS -/* lwip has provision for using a mutex, when applicable */ -sys_prot_t sys_arch_protect(void) { - return 0; -} - -void sys_arch_unprotect(__unused sys_prot_t pval) { -} - -/* lwip needs a millisecond time source, and the TinyUSB board support code has one available */ -uint32_t sys_now(void) { - return to_ms_since_boot(get_absolute_time()); -} -#endif \ No newline at end of file diff --git a/pico-sdk/src/rp2_common/pico_mbedtls/CMakeLists.txt b/pico-sdk/src/rp2_common/pico_mbedtls/CMakeLists.txt deleted file mode 100644 index 303a03a..0000000 --- a/pico-sdk/src/rp2_common/pico_mbedtls/CMakeLists.txt +++ /dev/null @@ -1,173 +0,0 @@ -if (DEFINED ENV{PICO_MBEDTLS_PATH} AND (NOT PICO_MBEDTLS_PATH)) - set(PICO_MBEDTLS_PATH $ENV{PICO_MBEDTLS_PATH}) - message("Using PICO_MBEDTLS_PATH from environment ('${PICO_MBEDTLS_PATH}')") -endif() - -set(MBEDTLS_TEST_PATH "library/aes.c") -if (NOT PICO_MBEDTLS_PATH) - set(PICO_MBEDTLS_PATH ${PROJECT_SOURCE_DIR}/lib/mbedtls) -elseif (NOT EXISTS ${PICO_MBEDTLS_PATH}/${MBEDTLS_TEST_PATH}) - message(WARNING "PICO_MBEDTLS_PATH specified but content not present.") -endif() - -if (EXISTS ${PICO_MBEDTLS_PATH}/${MBEDTLS_TEST_PATH}) - message("mbedtls available at ${PICO_MBEDTLS_PATH}") - - pico_register_common_scope_var(PICO_MBEDTLS_PATH) - - set(src_crypto - aes.c - aesni.c - arc4.c - aria.c - asn1parse.c - asn1write.c - base64.c - bignum.c - blowfish.c - camellia.c - ccm.c - chacha20.c - chachapoly.c - cipher.c - cipher_wrap.c - constant_time.c - cmac.c - ctr_drbg.c - des.c - dhm.c - ecdh.c - ecdsa.c - ecjpake.c - ecp.c - ecp_curves.c - entropy.c - entropy_poll.c - error.c - gcm.c - havege.c - hkdf.c - hmac_drbg.c - md.c - md2.c - md4.c - md5.c - memory_buffer_alloc.c - mps_reader.c - mps_trace.c - nist_kw.c - oid.c - padlock.c - pem.c - pk.c - pk_wrap.c - pkcs12.c - pkcs5.c - pkparse.c - pkwrite.c - platform.c - platform_util.c - poly1305.c - psa_crypto.c - psa_crypto_aead.c - psa_crypto_cipher.c - psa_crypto_client.c - psa_crypto_driver_wrappers.c - psa_crypto_ecp.c - psa_crypto_hash.c - psa_crypto_mac.c - psa_crypto_rsa.c - psa_crypto_se.c - psa_crypto_slot_management.c - psa_crypto_storage.c - psa_its_file.c - ripemd160.c - rsa.c - rsa_internal.c - sha1.c - sha256.c - sha512.c - threading.c - timing.c - version.c - version_features.c - xtea.c - ) - list(TRANSFORM src_crypto PREPEND ${PICO_MBEDTLS_PATH}/library/) - pico_add_library(pico_mbedtls_crypto NOFLAG) - target_sources(pico_mbedtls_crypto INTERFACE ${src_crypto}) - - set(src_x509 - certs.c - pkcs11.c - x509.c - x509_create.c - x509_crl.c - x509_crt.c - x509_csr.c - x509write_crt.c - x509write_csr.c - ) - list(TRANSFORM src_x509 PREPEND ${PICO_MBEDTLS_PATH}/library/) - pico_add_library(pico_mbedtls_x509 NOFLAG) - target_sources(pico_mbedtls_x509 INTERFACE ${src_x509}) - - set(src_tls - debug.c - net_sockets.c - ssl_cache.c - ssl_ciphersuites.c - ssl_cli.c - ssl_cookie.c - ssl_msg.c - ssl_srv.c - ssl_ticket.c - ssl_tls.c - ssl_tls13_keys.c - ) - list(TRANSFORM src_tls PREPEND ${PICO_MBEDTLS_PATH}/library/) - pico_add_library(pico_mbedtls_tls NOFLAG) - target_sources(pico_mbedtls_tls INTERFACE ${src_tls}) - - pico_add_library(pico_mbedtls NOFLAG) - pico_mirrored_target_link_libraries(pico_mbedtls INTERFACE pico_mbedtls_crypto pico_mbedtls_x509 pico_mbedtls_tls pico_rand) - if (DEFINED PICO_MBEDTLS_CONFIG_FILE) - target_compile_definitions(pico_mbedtls_headers INTERFACE MBEDTLS_CONFIG_FILE="${PICO_MBEDTLS_CONFIG_FILE}") - else() - target_compile_definitions(pico_mbedtls_headers INTERFACE MBEDTLS_CONFIG_FILE="mbedtls_config.h") - endif() - target_sources(pico_mbedtls INTERFACE ${CMAKE_CURRENT_LIST_DIR}/pico_mbedtls.c) - target_include_directories(pico_mbedtls_headers INTERFACE ${PICO_MBEDTLS_PATH}/include/ ${PICO_MBEDTLS_PATH}/library/) - - function(suppress_mbedtls_warnings) - set_source_files_properties( - ${PICO_MBEDTLS_PATH}/library/ecdsa.c - ${PICO_MBEDTLS_PATH}/library/ecp.c - ${PICO_MBEDTLS_PATH}/library/ecp_curves.c - ${PICO_MBEDTLS_PATH}/library/pk_wrap.c - ${PICO_MBEDTLS_PATH}/library/pkparse.c - ${PICO_MBEDTLS_PATH}/library/ssl_cli.c - PROPERTIES - COMPILE_OPTIONS "-Wno-cast-qual" - ) - set_source_files_properties( - ${PICO_MBEDTLS_PATH}/library/psa_crypto_client.c - ${PICO_MBEDTLS_PATH}/library/psa_crypto_driver_wrappers.c - PROPERTIES - COMPILE_OPTIONS "-Wno-redundant-decls" - ) - set_source_files_properties( - ${PICO_MBEDTLS_PATH}/library/x509_crt.c - PROPERTIES - COMPILE_OPTIONS "-Wno-cast-qual;-Wno-null-dereference" - ) - set_source_files_properties( - ${PICO_MBEDTLS_PATH}/library/ssl_srv.c - ${PICO_MBEDTLS_PATH}/library/ssl_tls.c - PROPERTIES - COMPILE_OPTIONS "-Wno-null-dereference" - ) - endfunction() - - pico_promote_common_scope_vars() -endif() diff --git a/pico-sdk/src/rp2_common/pico_mbedtls/pico_mbedtls.c b/pico-sdk/src/rp2_common/pico_mbedtls/pico_mbedtls.c deleted file mode 100644 index 5878956..0000000 --- a/pico-sdk/src/rp2_common/pico_mbedtls/pico_mbedtls.c +++ /dev/null @@ -1,15 +0,0 @@ -#include -#include "pico/platform.h" -#include "pico/rand.h" - -/* Function to feed mbedtls entropy. */ -int mbedtls_hardware_poll(void *data __unused, unsigned char *output, size_t len, size_t *olen) { - *olen = 0; - while(*olen < len) { - uint64_t rand_data = get_rand_64(); - size_t to_copy = MIN(len, sizeof(rand_data)); - memcpy(output + *olen, &rand_data, to_copy); - *olen += to_copy; - } - return 0; -} diff --git a/pico-sdk/tools/pioasm/CMakeLists.txt b/pico-sdk/tools/pioasm/CMakeLists.txt index 322408a..6fe3817 100644 --- a/pico-sdk/tools/pioasm/CMakeLists.txt +++ b/pico-sdk/tools/pioasm/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.4) +cmake_minimum_required(VERSION 3.6...3.27) project(pioasm CXX) set(CMAKE_CXX_STANDARD 11) diff --git a/src/constants.c b/src/constants.c new file mode 100644 index 0000000..ee08596 --- /dev/null +++ b/src/constants.c @@ -0,0 +1,38 @@ +#include "main.h" + +/* CRC32 Lookup Table, Polynomial = 0xEDB88320 */ +const uint32_t crc32_lookup_table[] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; + diff --git a/src/defaults.c b/src/defaults.c index b3e6418..0843b96 100644 --- a/src/defaults.c +++ b/src/defaults.c @@ -44,4 +44,11 @@ const config_t default_config = { .max_time_us = SCREENSAVER_B_MAX_TIME_SEC * 1000000, } }, + .enforce_ports = ENFORCE_PORTS, + .force_kbd_boot_protocol = ENFORCE_KEYBOARD_BOOT_PROTOCOL, + .force_mouse_boot_mode = false, + .enable_acceleration = ENABLE_ACCELERATION, + .hotkey_toggle = HID_KEY_F24, + .kbd_led_as_indicator = KBD_LED_AS_INDICATOR, + .jump_treshold = 0, }; \ No newline at end of file diff --git a/src/handlers.c b/src/handlers.c index 3566688..ea09858 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -31,12 +31,12 @@ void output_toggle_hotkey_handler(device_t *state, hid_keyboard_report_t *report switch_output(state, state->active_output); }; -void get_border_position(device_t *state, border_size_t *border) { +void _get_border_position(device_t *state, border_size_t *border) { /* To avoid having 2 different keys, if we're above half, it's the top coord */ - if (state->mouse_y > (MAX_SCREEN_COORD / 2)) - border->bottom = state->mouse_y; + if (state->pointer_y > (MAX_SCREEN_COORD / 2)) + border->bottom = state->pointer_y; else - border->top = state->mouse_y; + border->top = state->pointer_y; } @@ -44,11 +44,11 @@ void get_border_position(device_t *state, border_size_t *border) { void screen_border_hotkey_handler(device_t *state, hid_keyboard_report_t *report) { border_size_t *border = &state->config.output[state->active_output].border; if (CURRENT_BOARD_IS_ACTIVE_OUTPUT) { - get_border_position(state, border); + _get_border_position(state, border); save_config(state); } - send_packet((uint8_t *)border, SYNC_BORDERS_MSG, sizeof(border_size_t)); + queue_packet((uint8_t *)border, SYNC_BORDERS_MSG, sizeof(border_size_t)); }; /* This key combo puts board A in firmware upgrade mode */ @@ -67,37 +67,6 @@ void switchlock_hotkey_handler(device_t *state, hid_keyboard_report_t *report) { send_value(state->switch_lock, SWITCH_LOCK_MSG); } -/* This key combo configures multiple output parameters */ -void output_config_hotkey_handler(device_t *state, hid_keyboard_report_t *report) { - output_t *current = &state->config.output[state->active_output]; - - /* Pressing 1 or 2 with this hotkey sets the screen count */ - if(key_in_report(HID_KEY_1, report)) - current->screen_count = 1; - else if (key_in_report(HID_KEY_2, report)) - current->screen_count = 2; - - /* Pressing 7, 8 or 9 with this hotkey sets the OS to LINUX, WIN or MAC */ - else if (key_in_report(HID_KEY_7, report)) - current->os = LINUX; - else if (key_in_report(HID_KEY_8, report)) - current->os = WINDOWS; - else if (key_in_report(HID_KEY_9, report)) - current->os = MACOS; - - /* If nothing matches, don't send or save anything but bail out. */ - else - return; - - /* Save config and acknowledge */ - save_config(state); - blink_led(state); - - /* 4 bits are more than enough to transfer this */ - uint8_t value = current->screen_count | (current->os << 4); - send_value(value, OUTPUT_CONFIG_MSG); -} - /* This key combo locks both outputs simultaneously */ void screenlock_hotkey_handler(device_t *state, hid_keyboard_report_t *report) { hid_keyboard_report_t lock_report = {0}, release_keys = {0}; @@ -110,19 +79,19 @@ void screenlock_hotkey_handler(device_t *state, hid_keyboard_report_t *report) { lock_report.keycode[0] = HID_KEY_L; break; case MACOS: - lock_report.modifier = KEYBOARD_MODIFIER_LEFTCTRL | KEYBOARD_MODIFIER_LEFTGUI; + lock_report.modifier = KEYBOARD_MODIFIER_LEFTCTRL | KEYBOARD_MODIFIER_LEFTALT; lock_report.keycode[0] = HID_KEY_Q; break; default: break; } - if (BOARD_ROLE == out) { + if (global_state.active_output == out) { queue_kbd_report(&lock_report, state); release_all_keys(state); } else { - send_packet((uint8_t *)&lock_report, KEYBOARD_REPORT_MSG, KBD_REPORT_LENGTH); - send_packet((uint8_t *)&release_keys, KEYBOARD_REPORT_MSG, KBD_REPORT_LENGTH); + queue_packet((uint8_t *)&lock_report, KEYBOARD_REPORT_MSG, KBD_REPORT_LENGTH); + queue_packet((uint8_t *)&release_keys, KEYBOARD_REPORT_MSG, KBD_REPORT_LENGTH); } } } @@ -134,17 +103,25 @@ void wipe_config_hotkey_handler(device_t *state, hid_keyboard_report_t *report) send_value(ENABLE, WIPE_CONFIG_MSG); } -void screensaver_hotkey_handler(device_t *state, hid_keyboard_report_t *report) { - state->config.output[BOARD_ROLE].screensaver.enabled ^= 1; - send_value(state->config.output[BOARD_ROLE].screensaver.enabled, SCREENSAVER_MSG); -} - /* When pressed, toggles the current mouse zoom mode state */ void mouse_zoom_hotkey_handler(device_t *state, hid_keyboard_report_t *report) { state->mouse_zoom ^= 1; send_value(state->mouse_zoom, MOUSE_ZOOM_MSG); }; + +/* Put the device into a special configuration mode */ +void config_enable_hotkey_handler(device_t *state, hid_keyboard_report_t *report) { + /* If config mode is already active, skip this and reboot to return to normal mode */ + if (!state->config_mode_active) { + watchdog_hw->scratch[5] = MAGIC_WORD_1; + watchdog_hw->scratch[6] = MAGIC_WORD_2; + } + + reboot(); +}; + + /**==================================================== * * ========== UART Message Handling Routines ======== * * ==================================================== */ @@ -160,9 +137,9 @@ void handle_mouse_abs_uart_msg(uart_packet_t *packet, device_t *state) { mouse_report_t *mouse_report = (mouse_report_t *)packet->data; queue_mouse_report(mouse_report, state); - state->mouse_x = mouse_report->x; - state->mouse_y = mouse_report->y; - state->mouse_buttons = mouse_report->buttons; + state->pointer_x = mouse_report->x; + state->pointer_y = mouse_report->y; + state->mouse_buttons = mouse_report->buttons; state->last_activity[BOARD_ROLE] = time_us_64(); } @@ -202,8 +179,8 @@ void handle_sync_borders_msg(uart_packet_t *packet, device_t *state) { border_size_t *border = &state->config.output[state->active_output].border; if (CURRENT_BOARD_IS_ACTIVE_OUTPUT) { - get_border_position(state, border); - send_packet((uint8_t *)border, SYNC_BORDERS_MSG, sizeof(border_size_t)); + _get_border_position(state, border); + queue_packet((uint8_t *)border, SYNC_BORDERS_MSG, sizeof(border_size_t)); } else memcpy(border, packet->data, sizeof(border_size_t)); @@ -221,21 +198,120 @@ void handle_wipe_config_msg(uart_packet_t *packet, device_t *state) { load_config(state); } -void handle_screensaver_msg(uart_packet_t *packet, device_t *state) { - state->config.output[BOARD_ROLE].screensaver.enabled = packet->data[0]; -} - -void handle_output_config_msg(uart_packet_t *packet, device_t *state) { - state->config.output[state->active_output].os = packet->data[0] >> 4; - state->config.output[state->active_output].screen_count = packet->data[0] & 0x0F; - save_config(state); -} - -/* Process consumer control keyboard message. Send immediately, w/o queing */ +/* Process consumer control message, TODO: use queue instead of sending directly */ void handle_consumer_control_msg(uart_packet_t *packet, device_t *state) { tud_hid_n_report(0, REPORT_ID_CONSUMER, &packet->data[0], CONSUMER_CONTROL_LENGTH); } +/* Process request to store config to flash */ +void handle_save_config_msg(uart_packet_t *packet, device_t *state) { + save_config(state); +} + +/* Process request to reboot the board */ +void handle_reboot_msg(uart_packet_t *packet, device_t *state) { + reboot(); +} + +/* Decapsulate and send to the other box */ +void handle_proxy_msg(uart_packet_t *packet, device_t *state) { + queue_packet(&packet->data[1], (enum packet_type_e)packet->data[0], PACKET_DATA_LENGTH - 1); +} + +/* Process api communication messages */ +void handle_api_msgs(uart_packet_t *packet, device_t *state) { + uint8_t value_idx = packet->data[0]; + const field_map_t *map = get_field_map_entry(value_idx); + + /* If we don't have a valid map entry, return immediately */ + if (map == NULL) + return; + + /* Create a pointer to the offset into the structure we need to access */ + uint8_t *ptr = (((uint8_t *)&global_state) + map->offset); + + if (packet->type == SET_VAL_MSG) { + /* Not allowing writes to objects defined as read-only */ + if (map->readonly) + return; + + memcpy(ptr, &packet->data[1], map->len); + } + else if (packet->type == GET_VAL_MSG) { + uart_packet_t response = {.type=GET_VAL_MSG, .data={0}}; + memcpy(response.data, ptr, map->len); + queue_try_add(&state->cfg_queue_out, &response); + } + + /* With each GET/SET message, we reset the configuration mode timeout */ + reset_config_timer(state); +} + + +/* Process request packet and create a response */ +void handle_request_byte_msg(uart_packet_t *packet, device_t *state) { + uint32_t address = packet->data32[0]; + + if (address > STAGING_IMAGE_SIZE) + return; + + /* Add requested data to bytes 4-7 in the packet and return it with a different type */ + uint32_t data = *(uint32_t *)&ADDR_FW_RUNNING[address]; + packet->data32[1] = data; + + queue_packet(packet->data, RESPONSE_BYTE_MSG, PACKET_DATA_LENGTH); +} + +/* Process response message following a request we sent to read a byte */ +/* state->page_offset and state->page_number are kept locally and compared to returned values */ +void handle_response_byte_msg(uart_packet_t *packet, device_t *state) { + uint16_t offset = packet->data[0]; + uint32_t address = packet->data32[0]; + + if (address != state->fw.address) { + state->fw.upgrade_in_progress = false; + state->fw.address = 0; + return; + } + else { + /* Provide visual feedback of the ongoing copy by toggling LED for every sector */ + if((address & 0xfff) == 0x000) + toggle_led(); + } + + /* Update checksum as we receive each byte */ + if (address < STAGING_IMAGE_SIZE - FLASH_SECTOR_SIZE) + for (int i=0; i<4; i++) + state->fw.checksum = crc32_iter(state->fw.checksum, packet->data[4 + i]); + + memcpy(state->page_buffer + offset, &packet->data32[1], sizeof(uint32_t)); + + /* Neeeeeeext byte, please! */ + state->fw.address += sizeof(uint32_t); + state->fw.byte_done = true; +} + +/* Process a request to read a firmware package from flash */ +void handle_heartbeat_msg(uart_packet_t *packet, device_t *state) { + uint16_t other_running_version = packet->data16[0]; + + if (state->fw.upgrade_in_progress) + return; + + /* If the other board isn't running a newer version, we are done */ + if (other_running_version <= state->_running_fw.version) + return; + + /* It is? Ok, kick off the firmware upgrade */ + state->fw = (fw_upgrade_state_t) { + .upgrade_in_progress = true, + .byte_done = true, + .address = 0, + .checksum = 0xffffffff, + }; +} + + /**==================================================== * * ============== Output Switch Routines ============ * * ==================================================== */ diff --git a/src/hid_parser.c b/src/hid_parser.c index 123e3b4..94ae72c 100644 --- a/src/hid_parser.c +++ b/src/hid_parser.c @@ -20,9 +20,10 @@ #include "main.h" -#define IS_BLOCK_END (collection.start == collection.end) +#define IS_BLOCK_END (parser->collection.start == parser->collection.end) enum { SIZE_0_BIT = 0, SIZE_8_BIT = 1, SIZE_16_BIT = 2, SIZE_32_BIT = 3 }; +const uint8_t SIZE_LOOKUP[4] = {0, 1, 2, 4}; /* Size is 0, 1, 2, or 3, describing cases of no data, 8-bit, 16-bit, or 32-bit data. */ @@ -39,132 +40,106 @@ uint32_t get_descriptor_value(uint8_t const *report, int size) { } } -/* We store all globals as unsigned to avoid countless switch/cases. -In case of e.g. min/max, we need to treat some data as signed retroactively. */ -int32_t to_signed(globals_t *data) { - switch (data->hdr.size) { - case SIZE_8_BIT: - return (int8_t)data->val; - case SIZE_16_BIT: - return (int16_t)data->val; - default: - return data->val; - } -} - -/* Given a value struct with size and offset in bits, - find and return a value from the HID report */ - -int32_t get_report_value(uint8_t *report, report_val_t *val) { - /* Calculate the bit offset within the byte */ - uint8_t offset_in_bits = val->offset % 8; - - /* Calculate the remaining bits in the first byte */ - uint8_t remaining_bits = 8 - offset_in_bits; - - /* Calculate the byte offset in the array */ - uint8_t byte_offset = val->offset >> 3; - - /* Create a mask for the specified number of bits */ - uint32_t mask = (1u << val->size) - 1; - - /* Initialize the result value with the bits from the first byte */ - int32_t result = report[byte_offset] >> offset_in_bits; - - /* Move to the next byte and continue fetching bits until the desired length is reached */ - while (val->size > remaining_bits) { - result |= report[++byte_offset] << remaining_bits; - remaining_bits += 8; - } - - /* Apply the mask to retain only the desired number of bits */ - result = result & mask; - - /* Special case if result is negative. - Check if the most significant bit of 'val' is set */ - if (result & ((mask >> 1) + 1)) { - /* If it is set, sign-extend 'val' by filling the higher bits with 1s */ - result |= (0xFFFFFFFFU << val->size); - } - - return result; -} - void update_usage(parser_state_t *parser, int i) { /* If we don't have as many usages as elements, the usage for the previous element applies */ - if (i && i >= parser->usage_count) { - *(parser->p_usage + i) = *(parser->p_usage + parser->usage_count - 1); - } + if (i > 0 && i >= parser->usage_count && i < HID_MAX_USAGES) + *(parser->p_usage + i) = *(parser->p_usage + i - 1); } -void find_and_store_element(parser_state_t *parser, int map_len, int i) { - usage_map_t *map = &parser->map[0]; +void store_element(parser_state_t *parser, report_val_t *val, int i, uint32_t data, uint16_t size) { + *val = (report_val_t){ + .offset = parser->offset_in_bits, + .offset_idx = parser->offset_in_bits >> 3, + .size = size, - for (int j = 0; j < map_len; j++, map++) { - /* Filter based on usage criteria */ - if (map->report_usage == parser->global_usage - && map->usage_page == parser->globals[RI_GLOBAL_USAGE_PAGE].val - && map->usage == *(parser->p_usage + i)) { + .usage_max = parser->locals[RI_LOCAL_USAGE_MAX].val, + .usage_min = parser->locals[RI_LOCAL_USAGE_MIN].val, - /* Buttons are the ones that appear multiple times, aggregate for now */ - if (map->element->size) { - map->element->size++; - continue; - } + .item_type = (data & 0x01) ? CONSTANT : DATA, + .data_type = (data & 0x02) ? VARIABLE : ARRAY, - /* Store the found element's attributes */ - map->element->offset = parser->offset_in_bits; - map->element->size = parser->globals[RI_GLOBAL_REPORT_SIZE].val; - map->element->min = to_signed(&parser->globals[RI_GLOBAL_LOGICAL_MIN]); - map->element->max = to_signed(&parser->globals[RI_GLOBAL_LOGICAL_MAX]); - } - } + .usage = *(parser->p_usage + i), + .usage_page = parser->globals[RI_GLOBAL_USAGE_PAGE].val, + .global_usage = parser->global_usage, + .report_id = parser->report_id + }; } -void handle_global_item(parser_state_t *parser, header_t *header, uint32_t data, mouse_t *mouse) { +void handle_global_item(parser_state_t *parser, item_t *item) { + if (item->hdr.tag == RI_GLOBAL_REPORT_ID) + parser->report_id = item->val; + + parser->globals[item->hdr.tag] = *item; +} + +void handle_local_item(parser_state_t *parser, item_t *item) { /* There are just 16 possible tags, store any one that comes along to an array instead of doing switch and 16 cases */ - parser->globals[header->tag].val = data; - parser->globals[header->tag].hdr = *header; + parser->locals[item->hdr.tag] = *item; - if (header->tag == RI_GLOBAL_REPORT_ID) { - /* Important to track, if report IDs are used reports are preceded/offset by a 1-byte ID value */ - if (parser->global_usage == HID_USAGE_DESKTOP_MOUSE) - mouse->report_id = data; + if (item->hdr.tag == RI_LOCAL_USAGE) { + if(IS_BLOCK_END) + parser->global_usage = item->val; - mouse->uses_report_id = true; + else if (parser->usage_count < HID_MAX_USAGES - 1) + *(parser->p_usage + parser->usage_count++) = item->val; } } -void handle_local_item(parser_state_t *parser, header_t *header, uint32_t data) { - if (header->tag == RI_LOCAL_USAGE) { - /* If we are not within a collection, the usage tag applies to the entire section */ - if (parser->collection.start == parser->collection.end) { - parser->global_usage = data; - } else { - *(parser->p_usage + parser->usage_count++) = data; - } +void handle_main_input(parser_state_t *parser, item_t *item, hid_interface_t *iface) { + uint32_t size = parser->globals[RI_GLOBAL_REPORT_SIZE].val; + uint32_t count = parser->globals[RI_GLOBAL_REPORT_COUNT].val; + report_val_t val = {0}; + + /* Swap count and size for 1-bit variables, it makes sense to process e.g. NKRO with + size = 1 and count = 240 in one go instead of doing 240 iterations + Don't do this if there are usages in the queue, though. + */ + if (size == 1 && parser->usage_count <= 1) { + size = count; + count = 1; } + + for (int i = 0; i < count; i++) { + update_usage(parser, i); + store_element(parser, &val, i, item->val, size); + + /* Use the parsed data to populate internal device structures */ + extract_data(iface, &val); + + /* Iterate times and increase offset by amount, moving by x bits */ + parser->offset_in_bits += size; + } + + /* Advance the usage array pointer by global report count and reset the count variable */ + parser->p_usage += parser->usage_count; + + /* Carry the last usage to the new location */ + *parser->p_usage = *(parser->p_usage - parser->usage_count); } -void handle_main_item(parser_state_t *parser, header_t *header, int map_len) { - /* Update Collection */ - parser->collection.start += (header->tag == RI_MAIN_COLLECTION); - parser->collection.end += (header->tag == RI_MAIN_COLLECTION_END); +void handle_main_item(parser_state_t *parser, item_t *item, hid_interface_t *iface) { + if (IS_BLOCK_END) + parser->offset_in_bits = 0; - if (header->tag == RI_MAIN_INPUT) { - for (int i = 0; i < parser->globals[RI_GLOBAL_REPORT_COUNT].val; i++) { - update_usage(parser, i); - find_and_store_element(parser, map_len, i); + switch (item->hdr.tag) { + case RI_MAIN_COLLECTION: + parser->collection.start++; + break; - /* Iterate times and increase offset by amount, moving by x bits */ - parser->offset_in_bits += parser->globals[RI_GLOBAL_REPORT_SIZE].val; - } + case RI_MAIN_COLLECTION_END: + parser->collection.end++; + break; - /* Advance the usage array pointer by global report count and reset the count variable */ - parser->p_usage += parser->globals[RI_GLOBAL_REPORT_COUNT].val; - parser->usage_count = 0; + case RI_MAIN_INPUT: + handle_main_input(parser, item, iface); + break; } + + parser->usage_count = 0; + + /* Local items do not carry over to the next Main item (HID spec v1.11, section 6.2.2.8) */ + memset(parser->locals, 0, sizeof(parser->locals)); } @@ -172,53 +147,37 @@ void handle_main_item(parser_state_t *parser, header_t *header, int map_len) { * hopefully work well enough to find the basic values we care about to move the mouse around. * Your descriptor for a mouse with 2 wheels and 264 buttons might not parse correctly. **/ -uint8_t parse_report_descriptor(mouse_t *mouse, uint8_t arr_count, uint8_t const *report, uint16_t desc_len) { - usage_map_t usage_map[] = { - {.report_usage = HID_USAGE_DESKTOP_MOUSE, - .usage_page = HID_USAGE_PAGE_BUTTON, - .usage = HID_USAGE_DESKTOP_POINTER, - .element = &mouse->buttons}, +parser_state_t parser_state = {0}; // Avoid placing it on the stack, it's large - {.report_usage = HID_USAGE_DESKTOP_MOUSE, - .usage_page = HID_USAGE_PAGE_DESKTOP, - .usage = HID_USAGE_DESKTOP_X, - .element = &mouse->move_x}, +void parse_report_descriptor(hid_interface_t *iface, + uint8_t const *report, + int desc_len + ) { + item_t item = {0}; - {.report_usage = HID_USAGE_DESKTOP_MOUSE, - .usage_page = HID_USAGE_PAGE_DESKTOP, - .usage = HID_USAGE_DESKTOP_Y, - .element = &mouse->move_y}, - - {.report_usage = HID_USAGE_DESKTOP_MOUSE, - .usage_page = HID_USAGE_PAGE_DESKTOP, - .usage = HID_USAGE_DESKTOP_WHEEL, - .element = &mouse->wheel}, - }; - - parser_state_t parser = {0}; - parser.p_usage = parser.usages; - parser.map = usage_map; + /* Wipe parser_state clean */ + memset(&parser_state, 0, sizeof(parser_state_t)); + parser_state.p_usage = parser_state.usages; while (desc_len > 0) { - header_t header = *(header_t *)report++; - uint32_t data = get_descriptor_value(report, header.size); + item.hdr = *(header_t *)report++; + item.val = get_descriptor_value(report, item.hdr.size); - switch (header.type) { + switch (item.hdr.type) { case RI_TYPE_MAIN: - handle_main_item(&parser, &header, ARRAY_SIZE(usage_map)); + handle_main_item(&parser_state, &item, iface); break; case RI_TYPE_GLOBAL: - handle_global_item(&parser, &header, data, mouse); + handle_global_item(&parser_state, &item); break; case RI_TYPE_LOCAL: - handle_local_item(&parser, &header, data); + handle_local_item(&parser_state, &item); break; } /* Move to the next position and decrement size by header length + data length */ - report += header.size; - desc_len -= header.size + 1; - } - return 0; + report += SIZE_LOOKUP[item.hdr.size]; + desc_len -= (SIZE_LOOKUP[item.hdr.size] + 1); + } } diff --git a/src/hid_report.c b/src/hid_report.c new file mode 100644 index 0000000..0a7842b --- /dev/null +++ b/src/hid_report.c @@ -0,0 +1,300 @@ +/* + * This file is part of DeskHop (https://github.com/hrvach/deskhop). + * Copyright (c) 2024 Hrvoje Cavrak + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "hid_report.h" +#include "main.h" + +/* Given a value struct with size and offset in bits, find and return a value from the HID report */ +int32_t get_report_value(uint8_t *report, report_val_t *val) { + /* Calculate the bit offset within the byte */ + uint16_t offset_in_bits = val->offset % 8; + + /* Calculate the remaining bits in the first byte */ + uint16_t remaining_bits = 8 - offset_in_bits; + + /* Calculate the byte offset in the array */ + uint16_t byte_offset = val->offset >> 3; + + /* Create a mask for the specified number of bits */ + uint32_t mask = (1u << val->size) - 1; + + /* Initialize the result value with the bits from the first byte */ + int32_t result = report[byte_offset] >> offset_in_bits; + + /* Move to the next byte and continue fetching bits until the desired length is reached */ + while (val->size > remaining_bits) { + result |= report[++byte_offset] << remaining_bits; + remaining_bits += 8; + } + + /* Apply the mask to retain only the desired number of bits */ + result = result & mask; + + /* Special case if our result is negative. + Check if the most significant bit of 'val' is set */ + if (result & ((mask >> 1) + 1)) { + /* If it is set, sign-extend 'val' by filling the higher bits with 1s */ + result |= (0xFFFFFFFFU << val->size); + } + + return result; +} + +/* After processing the descriptor, assign the values so we can later use them to interpret reports */ +void handle_consumer_control_values(report_val_t *src, report_val_t *dst, hid_interface_t *iface) { + if (src->offset > MAX_CC_BUTTONS) { + return; + } + + if (src->data_type == VARIABLE) { + iface->keyboard.cc_array[src->offset] = src->usage; + iface->consumer.is_variable = true; + } + + iface->consumer.is_array |= (src->data_type == ARRAY); +} + +/* After processing the descriptor, assign the values so we can later use them to interpret reports */ +void handle_system_control_values(report_val_t *src, report_val_t *dst, hid_interface_t *iface) { + if (src->offset > MAX_SYS_BUTTONS) { + return; + } + + if (src->data_type == VARIABLE) { + iface->keyboard.sys_array[src->offset] = src->usage; + iface->system.is_variable = true; + } + + iface->system.is_array |= (src->data_type == ARRAY); +} + +/* After processing the descriptor, assign the values so we can later use them to interpret reports */ +void handle_keyboard_descriptor_values(report_val_t *src, report_val_t *dst, hid_interface_t *iface) { + const int LEFT_CTRL = 0xE0; + + /* Constants are normally used for padding, so skip'em */ + if (src->item_type == CONSTANT) + return; + + /* Make note if we're using a report ID or not */ + if (src->report_id > 0) + iface->keyboard.uses_report_id = true; + + /* Detect and handle modifier keys. <= if modifier is less + constant padding? */ + if (src->size <= MODIFIER_BIT_LENGTH && src->data_type == VARIABLE) { + /* To make sure this really is the modifier key, we expect e.g. left control to be + within the usage interval */ + if (LEFT_CTRL >= src->usage_min && LEFT_CTRL <= src->usage_max) + iface->keyboard.modifier = *src; + } + + /* If we have an array member, that's most likely a key (0x00 - 0xFF, 1 byte) */ + if (src->offset_idx < MAX_KEYS) { + iface->keyboard.key_array[src->offset_idx] = (src->data_type == ARRAY); + } + + /* Handle NKRO, normally size = 1, count = 240 or so, but they are swapped. */ + if (src->size > 32 && src->data_type == VARIABLE) { + iface->keyboard.is_nkro = true; + iface->keyboard.nkro = *src; + } + + /* We found a keyboard on this interface. */ + iface->keyboard.is_found = true; +} + +void handle_buttons(report_val_t *src, report_val_t *dst, hid_interface_t *iface) { + /* Constant is normally used for padding with mouse buttons, aggregate to simplify things */ + if (src->item_type == CONSTANT) { + iface->mouse.buttons.size += src->size; + return; + } + + iface->mouse.buttons = *src; + + /* We found a mouse on this interface. */ + iface->mouse.is_found = true; +} + +void _store(report_val_t *src, report_val_t *dst, hid_interface_t *iface) { + if (src->item_type != CONSTANT) + *dst = *src; +} + + +void extract_data(hid_interface_t *iface, report_val_t *val) { + const usage_map_t map[] = { + {.usage_page = HID_USAGE_PAGE_BUTTON, + .global_usage = HID_USAGE_DESKTOP_MOUSE, + .usage = HID_USAGE_DESKTOP_POINTER, + .handler = handle_buttons, + .receiver = process_mouse_report, + .dst = &iface->mouse.buttons, + .id = &iface->mouse.report_id}, + + {.usage_page = HID_USAGE_PAGE_DESKTOP, + .global_usage = HID_USAGE_DESKTOP_MOUSE, + .usage = HID_USAGE_DESKTOP_X, + .handler = _store, + .receiver = process_mouse_report, + .dst = &iface->mouse.move_x, + .id = &iface->mouse.report_id}, + + {.usage_page = HID_USAGE_PAGE_DESKTOP, + .global_usage = HID_USAGE_DESKTOP_MOUSE, + .usage = HID_USAGE_DESKTOP_Y, + .handler = _store, + .receiver = process_mouse_report, + .dst = &iface->mouse.move_y, + .id = &iface->mouse.report_id}, + + {.usage_page = HID_USAGE_PAGE_DESKTOP, + .global_usage = HID_USAGE_DESKTOP_MOUSE, + .usage = HID_USAGE_DESKTOP_WHEEL, + .handler = _store, + .receiver = process_mouse_report, + .dst = &iface->mouse.wheel, + .id = &iface->mouse.report_id}, + + {.usage_page = HID_USAGE_PAGE_KEYBOARD, + .global_usage = HID_USAGE_DESKTOP_KEYBOARD, + .handler = handle_keyboard_descriptor_values, + .receiver = process_keyboard_report, + .id = &iface->keyboard.report_id}, + + {.usage_page = HID_USAGE_PAGE_CONSUMER, + .global_usage = HID_USAGE_CONSUMER_CONTROL, + .handler = handle_consumer_control_values, + .receiver = process_consumer_report, + .dst = &iface->consumer.val, + .id = &iface->consumer.report_id}, + + {.usage_page = HID_USAGE_PAGE_DESKTOP, + .global_usage = HID_USAGE_DESKTOP_SYSTEM_CONTROL, + .handler = _store, + .receiver = process_system_report, + .dst = &iface->system.val, + .id = &iface->system.report_id}, + }; + + /* We extracted all we could find in the descriptor to report_values, now go through them and + match them up with the values in the table above, then store those values for later reference */ + + for (const usage_map_t *hay = map; hay != &map[ARRAY_SIZE(map)]; hay++) { + /* ---> If any condition is not defined, we consider it as matched <--- */ + bool global_usages_match = (val->global_usage == hay->global_usage) || (hay->global_usage == 0); + bool usages_match = (val->usage == hay->usage) || (hay->usage == 0); + bool usage_pages_match = (val->usage_page == hay->usage_page) || (hay->usage_page == 0); + + if (global_usages_match && usages_match && usage_pages_match) { + hay->handler(val, hay->dst, iface); + *hay->id = val->report_id; + + if (val->report_id < MAX_REPORTS) + iface->report_handler[val->report_id] = hay->receiver; + } + } +} + +int32_t extract_bit_variable(uint32_t min_val, uint32_t max_val, uint8_t *raw_report, int len, uint8_t *dst) { + int key_count = 0; + + for (int i = min_val, j = 0; i <= max_val && key_count < len; i++, j++) { + int byte_index = j >> 3; + int bit_index = j & 0b111; + + if (raw_report[byte_index] & (1 << bit_index)) { + dst[key_count++] = i; + } + } + + return key_count; +} + +int32_t _extract_kbd_boot(uint8_t *raw_report, int len, hid_keyboard_report_t *report) { + uint8_t *src = raw_report; + + /* In case keyboard still uses report ID in this, just pick the last 8 bytes */ + if (len == KBD_REPORT_LENGTH + 1) + src++; + + memcpy(report, src, KBD_REPORT_LENGTH); + return KBD_REPORT_LENGTH; +} + +int32_t _extract_kbd_other(uint8_t *raw_report, int len, keyboard_t *kb, hid_keyboard_report_t *report) { + uint8_t *src = raw_report; + + if (kb->uses_report_id) + src++; + + report->modifier = src[kb->modifier.offset_idx]; + for (int i=0, j=0; i < MAX_KEYS && j < KEYS_IN_USB_REPORT; i++) { + if(kb->key_array[i]) + report->keycode[j++] = src[i]; + } + + return KBD_REPORT_LENGTH; +} + +int32_t _extract_kbd_nkro(uint8_t *raw_report, int len, keyboard_t *kb, hid_keyboard_report_t *report) { + uint8_t *ptr = raw_report; + + /* Skip report ID */ + if (kb->uses_report_id) + ptr++; + + /* We expect array of bits mapping 1:1 from usage_min to usage_max, otherwise panic */ + if ((kb->nkro.usage_max - kb->nkro.usage_min + 1) != kb->nkro.size) + return -1; + + /* We expect modifier to be 8 bits long, otherwise we'll fallback to boot mode */ + if (kb->modifier.size == MODIFIER_BIT_LENGTH) { + report->modifier = ptr[kb->modifier.offset_idx]; + } else + return -1; + + /* Move the pointer to the nkro offset's byte index */ + ptr = &ptr[kb->nkro.offset_idx]; + + return extract_bit_variable( + kb->nkro.usage_min, kb->nkro.usage_max, ptr, KEYS_IN_USB_REPORT, report->keycode); +} + +int32_t extract_kbd_data( + uint8_t *raw_report, int len, uint8_t itf, hid_interface_t *iface, hid_keyboard_report_t *report) { + int report_id = raw_report[0]; + + /* Clear the report to start fresh */ + memset(report, 0, KBD_REPORT_LENGTH); + + /* NKRO is a special case */ + if (report_id == iface->keyboard.nkro.report_id + && iface->keyboard.is_nkro + && itf == HID_ITF_PROTOCOL_NONE) + return _extract_kbd_nkro(raw_report, len, &iface->keyboard, report); + + if (iface->protocol == HID_PROTOCOL_BOOT) + return _extract_kbd_boot(raw_report, len, report); + + /* If we're getting 8 bytes of report, it's safe to assume standard modifier + reserved + keys */ + if (len == KBD_REPORT_LENGTH || len == KBD_REPORT_LENGTH + 1) + return _extract_kbd_boot(raw_report, len, report); + + /* This is something completely different, look at the report */ + return _extract_kbd_other(raw_report, len, &iface->keyboard, report); +} \ No newline at end of file diff --git a/src/hid_parser.h b/src/include/hid_parser.h similarity index 51% rename from src/hid_parser.h rename to src/include/hid_parser.h index 45324e9..08c89a0 100644 --- a/src/hid_parser.h +++ b/src/include/hid_parser.h @@ -20,8 +20,16 @@ #pragma once #include "main.h" +#include "tusb.h" -#define MAX_REPORTS 32 +#define MAX_REPORTS 24 +#define MAX_DEVICES 3 +#define MAX_INTERFACES 6 +#define HID_MAX_USAGES 128 +#define HID_DEFAULT_NUM_COLLECTIONS 16 +#define MAX_CC_BUTTONS 16 +#define MAX_SYS_BUTTONS 8 +#define MAX_KEYS 32 /* Counts how many collection starts and ends we've seen, when they equalize (and not zero), we are at the end of a block */ @@ -42,7 +50,20 @@ typedef struct TU_ATTR_PACKED { typedef struct { header_t hdr; uint32_t val; -} globals_t; +} item_t; + +typedef enum { + DATA = 0, + CONSTANT, + ARRAY, + VARIABLE, + ABSOLUTE_DATA, + RELATIVE_DATA, + NO_WRAP, + WRAP, + LINEAR, + NONLINEAR, +} data_type_e; // Extended precision mouse movement information typedef struct { @@ -54,11 +75,21 @@ typedef struct { } mouse_values_t; /* Describes where can we find a value in a HID report */ -typedef struct { - uint16_t offset; // In bits - uint8_t size; // In bits - int32_t min; - int32_t max; +typedef struct TU_ATTR_PACKED { + uint16_t offset; // In bits + uint16_t offset_idx; // In bytes + uint16_t size; // In bits + + int32_t usage_min; + int32_t usage_max; + + uint8_t item_type; + uint8_t data_type; + + uint8_t report_id; + uint16_t global_usage; + uint16_t usage_page; + uint16_t usage; } report_val_t; /* Defines information about HID report format for the mouse. */ @@ -69,38 +100,64 @@ typedef struct { report_val_t wheel; uint8_t report_id; - uint8_t protocol; + bool is_found; bool uses_report_id; } mouse_t; +typedef struct hid_interface_t hid_interface_t; +typedef void (*process_report_f)(uint8_t *, int, uint8_t, hid_interface_t *); + /* Defines information about HID report format for the keyboard. */ typedef struct { - uint8_t keyboard_report_id; - uint8_t consumer_report_id; - uint8_t system_report_id; - uint8_t protocol; + report_val_t modifier; + report_val_t nkro; + uint16_t cc_array[MAX_CC_BUTTONS]; + uint16_t sys_array[MAX_SYS_BUTTONS]; + bool key_array[MAX_KEYS]; + + uint8_t report_id; + uint8_t key_array_idx; + + bool uses_report_id; + bool is_found; + bool is_nkro; } keyboard_t; -/* For each element type we're interested in there is an entry -in an array of these, defining its usage and in case matched, where to -store the data. */ typedef struct { - uint8_t report_usage; - uint8_t usage_page; - uint8_t usage; - report_val_t *element; -} usage_map_t; + report_val_t val; + uint8_t report_id; + bool is_variable; + bool is_array; +} report_t; + +struct hid_interface_t { + keyboard_t keyboard; + mouse_t mouse; + report_t consumer; + report_t system; + process_report_f report_handler[MAX_REPORTS]; + uint8_t protocol; +}; typedef struct { - uint8_t usage_count; - uint8_t global_usage; + report_val_t *map; + int map_index; /* Index of the current element we've found */ + int report_id; /* Report ID of the current section we're parsing */ + + uint32_t usage_count; uint32_t offset_in_bits; - uint8_t usages[256]; - uint8_t *p_usage; + uint16_t usages[HID_MAX_USAGES]; + uint16_t *p_usage; + uint16_t global_usage; collection_t collection; - usage_map_t *map; - globals_t globals[16]; /* as tag is 4 bits, there can be 16 different tags in global header type */ + /* as tag is 4 bits, there can be 16 different tags in global header type */ + item_t globals[16]; + + /* as tag is 4 bits, there can be 16 different tags in local header type */ + item_t locals[16]; } parser_state_t; + +/////////////// diff --git a/src/include/hid_report.h b/src/include/hid_report.h new file mode 100644 index 0000000..5e9297d --- /dev/null +++ b/src/include/hid_report.h @@ -0,0 +1,37 @@ +/* + * This file is part of DeskHop (https://github.com/hrvach/deskhop). + * Copyright (c) 2024 Hrvoje Cavrak + * + * Based on the TinyUSB HID parser routine and the amazing USB2N64 + * adapter (https://github.com/pdaxrom/usb2n64-adapter) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include "main.h" + +typedef void (*value_handler_f)(report_val_t *, report_val_t *, hid_interface_t *); + +/* For each element type we're interested in there is an entry +in an array of these, defining its usage and in case matched, where to +store the data. */ +typedef struct { + int global_usage; + int usage_page; + int usage; + uint8_t *id; + report_val_t *dst; + value_handler_f handler; + process_report_f receiver; +} usage_map_t; diff --git a/src/main.h b/src/include/main.h similarity index 51% rename from src/main.h rename to src/include/main.h index dbd9c30..46dbb5f 100644 --- a/src/main.h +++ b/src/include/main.h @@ -20,7 +20,6 @@ #include #include #include -#include #include #include @@ -29,6 +28,10 @@ #include "tusb.h" #include "usb_descriptors.h" #include "user_config.h" +#include "protocol.h" +#include +#include +#include #include #include #include @@ -38,9 +41,6 @@ #include /********* Misc definitions for better readability **********/ -#define PICO_A 0 -#define PICO_B 1 - #define OUTPUT_A 0 #define OUTPUT_B 1 @@ -50,26 +50,32 @@ #define ABSOLUTE 0 #define RELATIVE 1 -#define MAX_REPORT_ITEMS 16 #define MOUSE_BOOT_REPORT_LEN 4 - -#define NUM_SCREENS 2 // Will be more in the future #define MOUSE_ZOOM_SCALING_FACTOR 2 +#define NUM_SCREENS 2 // Will be more in the future +#define CONFIG_MODE_TIMEOUT 300000000 // 5 minutes into the future #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) -#define CURRENT_BOARD_IS_ACTIVE_OUTPUT (global_state.active_output == BOARD_ROLE) +#define CURRENT_BOARD_IS_ACTIVE_OUTPUT (global_state.active_output == global_state.board_role) + +#define _TOP() 0 +#define _MS(x) (x * 1000) +#define _SEC(x) (x * 1000000) +#define _HZ(x) ((uint64_t)((1000000) / (x))) /********* Pinout definitions **********/ #define PIO_USB_DP_PIN 14 // D+ is pin 14, D- is pin 15 #define GPIO_LED_PIN 25 // LED is connected to pin 25 on a PICO -#if BOARD_ROLE == PICO_B -#define SERIAL_TX_PIN 16 -#define SERIAL_RX_PIN 17 -#elif BOARD_ROLE == PICO_A -#define SERIAL_TX_PIN 12 -#define SERIAL_RX_PIN 13 -#endif +#define BOARD_A_RX 13 +#define BOARD_A_TX 12 +#define BOARD_B_RX 17 +#define BOARD_B_TX 16 + +#define SERIAL_TX_PIN (global_state.board_role == OUTPUT_A ? BOARD_A_TX : BOARD_B_TX) +#define SERIAL_RX_PIN (global_state.board_role == OUTPUT_A ? BOARD_A_RX : BOARD_B_RX) + +#define BOARD_ROLE (global_state.board_role) /********* Serial port definitions **********/ #define SERIAL_UART uart0 @@ -79,11 +85,21 @@ #define SERIAL_STOP_BITS 1 #define SERIAL_PARITY UART_PARITY_NONE +/********* DMA definitions **********/ +#define DMA_RX_BUFFER_SIZE 1024 +#define DMA_TX_BUFFER_SIZE 32 + +extern uint8_t uart_rxbuf[DMA_RX_BUFFER_SIZE] __attribute__((aligned(DMA_RX_BUFFER_SIZE))); +extern uint8_t uart_txbuf[DMA_TX_BUFFER_SIZE] __attribute__((aligned(DMA_TX_BUFFER_SIZE))); + /********* Watchdog definitions **********/ -#define WATCHDOG_TIMEOUT 1000 // In milliseconds => needs to be reset every second +#define WATCHDOG_TIMEOUT 500 // In milliseconds => needs to be reset at least every 200ms #define WATCHDOG_PAUSE_ON_DEBUG 1 // When using a debugger, disable watchdog #define CORE1_HANG_TIMEOUT_US WATCHDOG_TIMEOUT * 1000 // Convert to microseconds +#define MAGIC_WORD_1 0xdeadf00f // When these are set, we'll boot to configuration mode +#define MAGIC_WORD_2 0x00c0ffee + /********* Protocol definitions ********* * * - every packet starts with 0xAA 0x55 for easy re-sync @@ -104,12 +120,17 @@ enum packet_type_e { SWITCH_LOCK_MSG = 7, SYNC_BORDERS_MSG = 8, FLASH_LED_MSG = 9, - SCREENSAVER_MSG = 10, - WIPE_CONFIG_MSG = 11, - SWAP_OUTPUTS_MSG = 12, - HEARTBEAT_MSG = 13, - OUTPUT_CONFIG_MSG = 14, - CONSUMER_CONTROL_MSG = 15, + WIPE_CONFIG_MSG = 10, + HEARTBEAT_MSG = 12, + CONSUMER_CONTROL_MSG = 14, + SYSTEM_CONTROL_MSG = 15, + SAVE_CONFIG_MSG = 18, + REBOOT_MSG = 19, + GET_VAL_MSG = 20, + SET_VAL_MSG = 21, + PROXY_PACKET_MSG = 23, + REQUEST_BYTE_MSG = 24, + RESPONSE_BYTE_MSG = 25, }; /* @@ -123,9 +144,13 @@ enum packet_type_e { /* Data structure defining packets of information transferred */ typedef struct { uint8_t type; // Enum field describing the type of packet - uint8_t data[8]; // Data goes here (type + payload + checksum) + union { + uint8_t data[8]; // Data goes here (type + payload + checksum) + uint16_t data16[4]; // We can treat it as 4 16-byte chunks + uint32_t data32[2]; // We can treat it as 2 32-byte chunks + }; uint8_t checksum; // Checksum, a simple XOR-based one -} uart_packet_t; +} __attribute__((packed)) uart_packet_t; /********* Packet parameters **********/ @@ -140,27 +165,33 @@ typedef struct { #define PACKET_LENGTH (TYPE_LENGTH + PACKET_DATA_LENGTH + CHECKSUM_LENGTH) #define RAW_PACKET_LENGTH (START_LENGTH + PACKET_LENGTH) +#define UART_QUEUE_LENGTH 256 +#define CFG_QUEUE_LENGTH 128 #define KBD_QUEUE_LENGTH 128 -#define MOUSE_QUEUE_LENGTH 2048 +#define MOUSE_QUEUE_LENGTH 512 +#define KEYARRAY_BIT_OFFSET 16 #define KEYS_IN_USB_REPORT 6 #define KBD_REPORT_LENGTH 8 #define MOUSE_REPORT_LENGTH 7 #define CONSUMER_CONTROL_LENGTH 4 +#define SYSTEM_CONTROL_LENGTH 1 +#define MODIFIER_BIT_LENGTH 8 /********* Screen **********/ #define MIN_SCREEN_COORD 0 #define MAX_SCREEN_COORD 32767 -#define SCREEN_MIDPOINT 16384 +#define SCREEN_MIDPOINT 16384 /********* Configuration storage definitions **********/ -#define CURRENT_CONFIG_VERSION 4 +#define CURRENT_CONFIG_VERSION 7 enum os_type_e { LINUX = 1, MACOS = 2, WINDOWS = 3, + ANDROID = 4, OTHER = 255, }; @@ -170,10 +201,10 @@ enum screen_pos_e { MIDDLE = 3, }; -enum itf_num_e { - ITF_NUM_HID = 0, - ITF_NUM_HID_REL_M = 1, -}; +#define ITF_NUM_HID 0 +#define ITF_NUM_HID_REL_M 1 +#define ITF_NUM_HID_VENDOR 1 +#define ITF_NUM_MSC 2 typedef struct { int top; // When jumping from a smaller to a bigger screen, go to THIS top height @@ -191,14 +222,15 @@ typedef struct { /* Define output parameters */ typedef struct { - int number; // Number of this output (e.g. OUTPUT_A = 0 etc) - int screen_count; // How many monitors per output (e.g. Output A is Windows with 3 monitors) - int screen_index; // Current active screen - int speed_x; // Mouse speed per output, in direction X - int speed_y; // Mouse speed per output, in direction Y + uint32_t number; // Number of this output (e.g. OUTPUT_A = 0 etc) + uint32_t screen_count; // How many monitors per output (e.g. Output A is Windows with 3 monitors) + uint32_t screen_index; // Current active screen + int32_t speed_x; // Mouse speed per output, in direction X + int32_t speed_y; // Mouse speed per output, in direction Y border_size_t border; // Screen border size/offset to keep cursor at same height when switching - enum os_type_e os; // Operating system on this output - enum screen_pos_e pos; // Screen position on this output + uint8_t os; // Operating system on this output + uint8_t pos; // Screen position on this output + uint8_t mouse_park_pos; // Where the mouse goes after switch screensaver_t screensaver; // Screensaver parameters for this output } output_t; @@ -206,17 +238,80 @@ typedef struct { typedef struct { uint32_t magic_header; uint32_t version; + uint8_t force_mouse_boot_mode; - output_t output[NUM_SCREENS]; - uint8_t screensaver_enabled; + uint8_t force_kbd_boot_protocol; + + uint8_t kbd_led_as_indicator; + uint8_t hotkey_toggle; + uint8_t enable_acceleration; + + uint8_t enforce_ports; + uint16_t jump_treshold; + + output_t output[NUM_SCREENS]; + uint32_t _reserved; + // Keep checksum at the end of the struct uint32_t checksum; } config_t; extern const config_t default_config; -extern config_t ADDR_CONFIG[]; -#define ADDR_CONFIG_BASE_ADDR (ADDR_CONFIG) +/********* Flash data section **********/ +typedef struct { + uint8_t cmd; // Byte 0 = command + uint16_t page_number; // Bytes 1-2 = page number + union { + uint8_t offset; // Byte 3 = offset + uint8_t checksum; // In write packets, it's checksum + }; + uint8_t data[4]; // Bytes 4-7 = data +} fw_packet_t; + +extern const config_t ADDR_CONFIG[]; +extern const uint8_t ADDR_FW_METADATA[]; +extern const uint8_t ADDR_FW_RUNNING[]; +extern const uint8_t ADDR_FW_STAGING[]; +extern const uint8_t ADDR_DISK_IMAGE[]; + +/* Ring buffer wraps around after reaching 4095 */ +#define NEXT_RING_IDX(x) ((x + 1) & 0x3FF) + +typedef struct { + uint16_t magic; + uint16_t version; + uint32_t checksum; +} firmware_metadata_t; + +extern firmware_metadata_t _firmware_metadata; + +#define FIRMWARE_METADATA_MAGIC 0xf00d +#define RUNNING_FIRMWARE_SLOT 0 +#define STAGING_FIRMWARE_SLOT 1 + +#define STAGING_PAGES_CNT 1024 +#define STAGING_IMAGE_SIZE STAGING_PAGES_CNT * FLASH_PAGE_SIZE + +extern const uint32_t crc32_lookup_table[]; + + +typedef struct { + uint32_t magicStart0; + uint32_t magicStart1; + uint32_t flags; + uint32_t targetAddr; + uint32_t payloadSize; + uint32_t blockNo; + uint32_t numBlocks; + uint32_t fileSize; + uint8_t data[476]; + uint32_t magicEnd; +} uf2_t; + +#define UF2_MAGIC_START0 0x0A324655 +#define UF2_MAGIC_START1 0x9E5D5157 +#define UF2_MAGIC_END 0x0AB16F30 // -------------------------------------------------------+ @@ -228,12 +323,12 @@ typedef struct { // Maps message type -> message handler function } uart_handler_t; typedef struct { - uint8_t modifier; // Which modifier is pressed - uint8_t keys[6]; // Which keys need to be pressed - uint8_t key_count; // How many keys are pressed - action_handler_t action_handler; // What to execute when the key combination is detected - bool pass_to_os; // True if we are to pass the key to the OS too - bool acknowledge; // True if we are to notify the user about registering keypress + uint8_t modifier; // Which modifier is pressed + uint8_t keys[KEYS_IN_USB_REPORT]; // Which keys need to be pressed + uint8_t key_count; // How many keys are pressed + action_handler_t action_handler; // What to execute when the key combination is detected + bool pass_to_os; // True if we are to pass the key to the OS too + bool acknowledge; // True if we are to notify the user about registering keypress } hotkey_combo_t; typedef struct TU_ATTR_PACKED { @@ -246,43 +341,74 @@ typedef struct TU_ATTR_PACKED { typedef enum { IDLE, READING_PACKET, PROCESSING_PACKET } receiver_state_t; +typedef struct { + uint32_t address; // Address we're sending to the other box + uint32_t checksum; + uint16_t version; + bool byte_done; // Has the byte been successfully transferred + bool upgrade_in_progress; // True if firmware transfer from the other box is in progress +} fw_upgrade_state_t; + typedef struct { uint8_t kbd_dev_addr; // Address of the keyboard device uint8_t kbd_instance; // Keyboard instance (d'uh - isn't this a useless comment) uint8_t keyboard_leds[NUM_SCREENS]; // State of keyboard LEDs (index 0 = A, index 1 = B) uint64_t last_activity[NUM_SCREENS]; // Timestamp of the last input activity (-||-) - receiver_state_t receiver_state; // Storing the state for the simple receiver state machine uint64_t core1_last_loop_pass; // Timestamp of last core1 loop execution uint8_t active_output; // Currently selected output (0 = A, 1 = B) + uint8_t board_role; // Which board are we running on? (0 = A, 1 = B, etc.) - int16_t mouse_x; // Store and update the location of our mouse pointer - int16_t mouse_y; + int16_t pointer_x; // Store and update the location of our mouse pointer + int16_t pointer_y; int16_t mouse_buttons; // Store and update the state of mouse buttons - config_t config; // Device configuration, loaded from flash or defaults used - mouse_t mouse_dev; // Mouse device specifics, e.g. stores locations for keys in report - keyboard_t kbd_dev; // Keyboard device specifics, like report IDs - queue_t kbd_queue; // Queue that stores keyboard reports - queue_t mouse_queue; // Queue that stores mouse reports + config_t config; // Device configuration, loaded from flash or defaults used + queue_t cfg_queue_out; // Queue that stores outgoing vendor config messages + queue_t kbd_queue; // Queue that stores keyboard reports + queue_t mouse_queue; // Queue that stores mouse reports + queue_t uart_tx_queue; // Queue that stores outgoing packets + + hid_interface_t iface[MAX_DEVICES][MAX_INTERFACES]; // Store info about HID interfaces + uart_packet_t in_packet; + + /* DMA */ + uint32_t dma_ptr; // Stores info about DMA ring buffer last checked position + uint32_t dma_rx_channel; // DMA RX channel we're using to receive + uint32_t dma_control_channel; // DMA channel that controls the RX transfer channel + uint32_t dma_tx_channel; // DMA TX channel we're using to send + + /* Firmware */ + fw_upgrade_state_t fw; // State of the firmware upgrader + firmware_metadata_t _running_fw; // RAM copy of running fw metadata + bool reboot_requested; // If set, stop updating watchdog + uint64_t config_mode_timer; // Counts how long are we to remain in config mode + + uint8_t page_buffer[FLASH_PAGE_SIZE]; // For firmware-over-serial upgrades /* Connection status flags */ bool tud_connected; // True when TinyUSB device successfully connects bool keyboard_connected; // True when our keyboard is connected locally - bool mouse_connected; // True when a mouse is connected locally /* Feature flags */ bool mouse_zoom; // True when "mouse zoom" is enabled bool switch_lock; // True when device is prevented from switching bool onboard_led_state; // True when LED is ON bool relative_mouse; // True when relative mouse mode is used - + bool config_mode_active; // True when config mode is active + /* Onboard LED blinky (provide feedback when e.g. mouse connected) */ int32_t blinks_left; // How many blink transitions are left int32_t last_led_change; // Timestamp of the last time led state transitioned - } device_t; +typedef struct { + void (*exec)(device_t *state); + uint64_t frequency; + uint64_t next_run; + bool *enabled; +} task_t; + /********* Setup **********/ void initial_setup(device_t *); void serial_init(void); @@ -290,50 +416,80 @@ void core1_main(void); /********* Keyboard **********/ bool check_specific_hotkey(hotkey_combo_t, const hid_keyboard_report_t *); -void process_keyboard_report(uint8_t *, int, device_t *); -void process_consumer_report(uint8_t *, int, device_t *); +void process_keyboard_report(uint8_t *, int, uint8_t, hid_interface_t *); +void process_consumer_report(uint8_t *, int, uint8_t, hid_interface_t *); +void process_system_report(uint8_t *, int, uint8_t, hid_interface_t *); void release_all_keys(device_t *); void queue_kbd_report(hid_keyboard_report_t *, device_t *); -void process_kbd_queue_task(device_t *); void send_key(hid_keyboard_report_t *, device_t *); -bool key_in_report(uint8_t, const hid_keyboard_report_t *); void send_consumer_control(uint8_t *, device_t *); +bool key_in_report(uint8_t, const hid_keyboard_report_t *); +int32_t extract_bit_variable(uint32_t, uint32_t, uint8_t *, int, uint8_t *); +int32_t extract_kbd_data(uint8_t *, int, uint8_t, hid_interface_t *, hid_keyboard_report_t *); /********* Mouse **********/ bool tud_mouse_report(uint8_t mode, uint8_t buttons, int16_t x, int16_t y, int8_t wheel); -void process_mouse_report(uint8_t *, int, device_t *); -uint8_t -parse_report_descriptor(mouse_t *mouse, uint8_t arr_count, uint8_t const *desc_report, uint16_t desc_len); +void process_mouse_report(uint8_t *, int, uint8_t, hid_interface_t *); +void parse_report_descriptor(hid_interface_t *, uint8_t const *, int); +void extract_data(hid_interface_t *, report_val_t *); int32_t get_report_value(uint8_t *report, report_val_t *val); -void process_mouse_queue_task(device_t *); void queue_mouse_report(mouse_report_t *, device_t *); void output_mouse_report(mouse_report_t *, device_t *); /********* UART **********/ -void receive_char(uart_packet_t *, device_t *); -void send_packet(const uint8_t *, enum packet_type_e, int); +void process_packet(uart_packet_t *, device_t *); +void queue_packet(const uint8_t *, enum packet_type_e, int); +void write_raw_packet(uint8_t *, uart_packet_t *); void send_value(const uint8_t, enum packet_type_e); +bool get_packet_from_buffer(device_t *); /********* LEDs **********/ void restore_leds(device_t *); void blink_led(device_t *); -void led_blinking_task(device_t *); +uint8_t toggle_led(void); /********* Checksum **********/ uint8_t calc_checksum(const uint8_t *, int); bool verify_checksum(const uart_packet_t *); +uint32_t crc32(const uint8_t *, size_t); +uint32_t crc32_iter(uint32_t, const uint8_t); -/********* Watchdog **********/ -void kick_watchdog(device_t *); +/********* Firmware **********/ +void write_flash_page(uint32_t, uint8_t *); +uint32_t calculate_firmware_crc32(void); +bool is_bootsel_pressed(void); +void request_byte(device_t *, uint32_t); +uint32_t get_ptr_delta(uint32_t, device_t *); +bool is_start_of_packet(device_t *); +void fetch_packet(device_t *); +void reboot(void); + +/********* Tasks **********/ +void process_uart_tx_task(device_t *); +void process_mouse_queue_task(device_t *); +void process_cfg_queue_task(device_t *); +void process_kbd_queue_task(device_t *); +void usb_device_task(device_t *); +void kick_watchdog_task(device_t *); +void usb_host_task(device_t *); +void packet_receiver_task(device_t *); +void screensaver_task(device_t *); +void firmware_upgrade_task(device_t *); +void heartbeat_output_task(device_t *); +void led_blinking_task(device_t *); + +void task_scheduler(device_t *, task_t *); /********* Configuration **********/ void load_config(device_t *); void save_config(device_t *); void wipe_config(void); +void reset_config_timer(device_t *); -/********* Misc **********/ -void screensaver_task(device_t *); +extern const field_map_t api_field_map[]; +const field_map_t* get_field_map_entry(uint32_t); +bool validate_packet(uart_packet_t *); /********* Handlers **********/ void output_toggle_hotkey_handler(device_t *, hid_keyboard_report_t *); @@ -346,12 +502,11 @@ void switchlock_hotkey_handler(device_t *, hid_keyboard_report_t *); void screenlock_hotkey_handler(device_t *, hid_keyboard_report_t *); void output_config_hotkey_handler(device_t *, hid_keyboard_report_t *); void wipe_config_hotkey_handler(device_t *, hid_keyboard_report_t *); -void screensaver_hotkey_handler(device_t *, hid_keyboard_report_t *); +void config_enable_hotkey_handler(device_t *, hid_keyboard_report_t *); void handle_keyboard_uart_msg(uart_packet_t *, device_t *); void handle_mouse_abs_uart_msg(uart_packet_t *, device_t *); void handle_output_select_msg(uart_packet_t *, device_t *); -void handle_output_config_msg(uart_packet_t *, device_t *); void handle_mouse_zoom_msg(uart_packet_t *, device_t *); void handle_set_report_msg(uart_packet_t *, device_t *); void handle_switch_lock_msg(uart_packet_t *, device_t *); @@ -359,8 +514,16 @@ void handle_sync_borders_msg(uart_packet_t *, device_t *); void handle_flash_led_msg(uart_packet_t *, device_t *); void handle_fw_upgrade_msg(uart_packet_t *, device_t *); void handle_wipe_config_msg(uart_packet_t *, device_t *); -void handle_screensaver_msg(uart_packet_t *, device_t *); void handle_consumer_control_msg(uart_packet_t *, device_t *); +void handle_read_config_msg(uart_packet_t *, device_t *); +void handle_save_config_msg(uart_packet_t *, device_t *); +void handle_reboot_msg(uart_packet_t *, device_t *); +void handle_write_fw_msg(uart_packet_t *, device_t *); +void handle_request_byte_msg(uart_packet_t *, device_t *); +void handle_response_byte_msg(uart_packet_t *, device_t *); +void handle_heartbeat_msg(uart_packet_t *, device_t *); +void handle_proxy_msg(uart_packet_t *, device_t *); +void handle_api_msgs(uart_packet_t *, device_t *); void switch_output(device_t *, uint8_t); diff --git a/src/include/protocol.h b/src/include/protocol.h new file mode 100644 index 0000000..85852a5 --- /dev/null +++ b/src/include/protocol.h @@ -0,0 +1,23 @@ +#pragma once + +#include "main.h" + +typedef enum { + UINT8 = 0, + UINT16 = 1, + UINT32 = 2, + UINT64 = 3, + INT8 = 4, + INT16 = 5, + INT32 = 6, + INT64 = 7, + BOOL = 8 +} type_e; + +typedef struct { + uint32_t idx; + bool readonly; + type_e type; + uint32_t len; + size_t offset; +} field_map_t; diff --git a/src/tusb_config.h b/src/include/tusb_config.h similarity index 97% rename from src/tusb_config.h rename to src/include/tusb_config.h index 5be6c90..1215abd 100644 --- a/src/tusb_config.h +++ b/src/include/tusb_config.h @@ -26,10 +26,6 @@ #ifndef _TUSB_CONFIG_H_ #define _TUSB_CONFIG_H_ -#ifdef __cplusplus -extern "C" { -#endif - //-------------------------------------------------------------------- // COMMON CONFIGURATION //-------------------------------------------------------------------- @@ -105,12 +101,14 @@ extern int dh_debug_printf(const char *__restrict __format, ...); //------------- CLASS -------------// #define CFG_TUD_HID 2 -#define CFG_TUD_MSC 0 #define CFG_TUD_MIDI 0 #define CFG_TUD_VENDOR 0 +#define CFG_TUD_MSC 1 // HID buffer size Should be sufficient to hold ID (if any) + Data #define CFG_TUD_HID_EP_BUFSIZE 32 +#define CFG_TUD_MSC_EP_BUFSIZE 512 + //-------------------------------------------------------------------- // HOST CONFIGURATION @@ -127,8 +125,5 @@ extern int dh_debug_printf(const char *__restrict __format, ...); #define CFG_TUH_HID_EPIN_BUFSIZE 64 #define CFG_TUH_HID_EPOUT_BUFSIZE 64 -#ifdef __cplusplus -} -#endif #endif /* _TUSB_CONFIG_H_ */ diff --git a/src/usb_descriptors.h b/src/include/usb_descriptors.h similarity index 65% rename from src/usb_descriptors.h rename to src/include/usb_descriptors.h index 1571e7f..a295851 100644 --- a/src/usb_descriptors.h +++ b/src/include/usb_descriptors.h @@ -25,20 +25,37 @@ #ifndef USB_DESCRIPTORS_H_ #define USB_DESCRIPTORS_H_ -enum -{ - REPORT_ID_KEYBOARD = 1, - REPORT_ID_MOUSE, - REPORT_ID_COUNT, - REPORT_ID_CONSUMER -}; +// Interface 0 +#define REPORT_ID_KEYBOARD 1 +#define REPORT_ID_MOUSE 2 +#define REPORT_ID_CONSUMER 3 +#define REPORT_ID_SYSTEM 4 -enum -{ - REPORT_ID_RELMOUSE = 1, -}; +// Interface 1 +#define REPORT_ID_RELMOUSE 5 -#define TUD_HID_REPORT_DESC_ABSMOUSE(...) \ +// Interface 2 +#define REPORT_ID_VENDOR 6 + + +#define DEVICE_DESCRIPTOR(vid, pid) \ +{.bLength = sizeof(tusb_desc_device_t),\ + .bDescriptorType = TUSB_DESC_DEVICE,\ + .bcdUSB = 0x0200,\ + .bDeviceClass = 0x00,\ + .bDeviceSubClass = 0x00,\ + .bDeviceProtocol = 0x00,\ + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,\ + .idVendor = vid,\ + .idProduct = pid,\ + .bcdDevice = 0x0100,\ + .iManufacturer = 0x01,\ + .iProduct = 0x02,\ + .iSerialNumber = 0x03,\ + .bNumConfigurations = 0x01}\ + + +#define TUD_HID_REPORT_DESC_ABS_MOUSE(...) \ HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ HID_USAGE ( HID_USAGE_DESKTOP_MOUSE ) ,\ HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\ @@ -94,13 +111,44 @@ HID_COLLECTION_END \ HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\ /* Report ID if any */\ __VA_ARGS__ \ - HID_LOGICAL_MIN ( 0x01 ) ,\ + HID_LOGICAL_MIN ( 0x00 ) ,\ HID_LOGICAL_MAX_N( 0x0FFF, 2 ) ,\ - HID_USAGE_MIN ( 0x01 ) ,\ + HID_USAGE_MIN ( 0x00 ) ,\ HID_USAGE_MAX_N ( 0x0FFF, 2 ) ,\ HID_REPORT_SIZE ( 16 ) ,\ HID_REPORT_COUNT ( 2 ) ,\ HID_INPUT ( HID_DATA | HID_ARRAY | HID_ABSOLUTE ) ,\ HID_COLLECTION_END \ +// System Control Report Descriptor Template +#define TUD_HID_REPORT_DESC_SYSTEM_CTRL(...) \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_SYSTEM_CONTROL ) ,\ + HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\ + /* Report ID if any */\ + __VA_ARGS__ \ + HID_LOGICAL_MIN ( 0x00 ) ,\ + HID_LOGICAL_MAX ( 0xff ) ,\ + HID_REPORT_COUNT( 1 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_INPUT ( HID_DATA | HID_ARRAY | HID_ABSOLUTE ) ,\ + HID_COLLECTION_END \ + +// Vendor Config Descriptor Template +#define TUD_HID_REPORT_DESC_VENDOR_CTRL(...) \ + HID_USAGE_PAGE_N ( HID_USAGE_PAGE_VENDOR, 2 ) ,\ + HID_USAGE ( 0x10 ) ,\ + HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\ + /* Report ID if any */\ + __VA_ARGS__ \ + HID_LOGICAL_MIN ( 0x80 ) ,\ + HID_LOGICAL_MAX ( 0x7f ) ,\ + HID_REPORT_COUNT( 12 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_USAGE ( 0x10 ) ,\ + HID_INPUT ( HID_DATA | HID_ARRAY | HID_ABSOLUTE ) ,\ + HID_USAGE ( 0x10 ) ,\ + HID_OUTPUT ( HID_DATA | HID_ARRAY | HID_ABSOLUTE ) ,\ + HID_COLLECTION_END \ + #endif /* USB_DESCRIPTORS_H_ */ diff --git a/src/user_config.h b/src/include/user_config.h similarity index 91% rename from src/user_config.h rename to src/include/user_config.h index d7ab84a..8b00eb2 100644 --- a/src/user_config.h +++ b/src/include/user_config.h @@ -32,7 +32,7 @@ * * */ -#define HOTKEY_TOGGLE HID_KEY_CAPS_LOCK +#define HOTKEY_TOGGLE HID_KEY_F24 /**================================================== * * ============== Mouse Speed Factor ============== * @@ -66,20 +66,6 @@ /* Mouse acceleration */ #define ENABLE_ACCELERATION 1 - -/**================================================== * - * =========== Mouse General Settings ============= * - * ================================================== * - * - * MOUSE_PARKING_POSITION: [0, 1, 2 ] 0 means park mouse on TOP - * 1 means park mouse on BOTTOM - * 2 means park mouse on PREVIOUS position - * - * */ - -#define MOUSE_PARKING_POSITION 0 - - /**================================================== * * ============== Screensaver Config ============== * * ================================================== * @@ -170,9 +156,10 @@ * * */ -#define OUTPUT_A_OS LINUX +#define OUTPUT_A_OS MACOS #define OUTPUT_B_OS LINUX + /**================================================== * * ================= Enforce Ports ================= * * ================================================== @@ -185,4 +172,21 @@ * * */ -#define ENFORCE_PORTS 0 \ No newline at end of file +#define ENFORCE_PORTS 0 + + +/**================================================== * + * ============= Enforce Boot Protocol ============= * + * ================================================== + * + * If enabled, fixes some device incompatibilities by + * enforcing the boot protocol (which is simpler to parse + * and with less variation) + * + * ENFORCE_KEYBOARD_BOOT_PROTOCOL: [0, 1] - 1 means keyboard will forcefully use + * the boot protocol + * - 0 means no such thing is enforced + * + * */ + +#define ENFORCE_KEYBOARD_BOOT_PROTOCOL 0 diff --git a/src/keyboard.c b/src/keyboard.c index 032abfa..785907f 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -51,13 +51,6 @@ hotkey_combo_t hotkeys[] = { .acknowledge = true, .action_handler = &screenlock_hotkey_handler}, - /* Erase stored config */ - {.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT, - .keys = {HID_KEY_BACKSPACE}, - .key_count = 1, - .pass_to_os = true, - .action_handler = &output_config_hotkey_handler}, - /* Erase stored config */ {.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT, .keys = {HID_KEY_F12, HID_KEY_D}, @@ -65,13 +58,6 @@ hotkey_combo_t hotkeys[] = { .acknowledge = true, .action_handler = &wipe_config_hotkey_handler}, - /* Toggle screensaver function */ - {.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT, - .keys = {HID_KEY_F12, HID_KEY_S}, - .key_count = 2, - .acknowledge = true, - .action_handler = &screensaver_hotkey_handler}, - /* Record switch y coordinate */ {.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT, .keys = {HID_KEY_F12, HID_KEY_Y}, @@ -79,17 +65,24 @@ hotkey_combo_t hotkeys[] = { .acknowledge = true, .action_handler = &screen_border_hotkey_handler}, + /* Switch to configuration mode */ + {.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT | KEYBOARD_MODIFIER_LEFTSHIFT, + .keys = {HID_KEY_C, HID_KEY_O}, + .key_count = 2, + .acknowledge = true, + .action_handler = &config_enable_hotkey_handler}, + /* Hold down left shift + right shift + F12 + A ==> firmware upgrade mode for board A (kbd) */ {.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT | KEYBOARD_MODIFIER_LEFTSHIFT, - .keys = {HID_KEY_F12, HID_KEY_A}, - .key_count = 2, + .keys = {HID_KEY_A}, + .key_count = 1, .acknowledge = true, .action_handler = &fw_upgrade_hotkey_handler_A}, /* Hold down left shift + right shift + F12 + B ==> firmware upgrade mode for board B (mouse) */ {.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT | KEYBOARD_MODIFIER_LEFTSHIFT, - .keys = {HID_KEY_F12, HID_KEY_B}, - .key_count = 2, + .keys = {HID_KEY_B}, + .key_count = 1, .acknowledge = true, .action_handler = &fw_upgrade_hotkey_handler_B}}; @@ -150,13 +143,16 @@ void process_kbd_queue_task(device_t *state) { if (!queue_try_peek(&state->kbd_queue, &report)) return; - bool succeeded = false; - + /* If we are suspended, let's wake the host up */ if (tud_suspended()) - succeeded = tud_remote_wakeup(); - else - /* ... try sending it to the host, if it's successful */ - succeeded = tud_hid_keyboard_report(REPORT_ID_KEYBOARD, report.modifier, report.keycode); + tud_remote_wakeup(); + + /* If it's not ok to send yet, we'll try on the next pass */ + if (!tud_hid_n_ready(ITF_NUM_HID)) + return; + + /* ... try sending it to the host, if it's successful */ + bool succeeded = tud_hid_keyboard_report(REPORT_ID_KEYBOARD, report.modifier, report.keycode); /* ... then we can remove it from the queue. Race conditions shouldn't happen [tm] */ if (succeeded) @@ -182,7 +178,7 @@ void send_key(hid_keyboard_report_t *report, device_t *state) { queue_kbd_report(report, state); state->last_activity[BOARD_ROLE] = time_us_64(); } else { - send_packet((uint8_t *)report, KEYBOARD_REPORT_MSG, KBD_REPORT_LENGTH); + queue_packet((uint8_t *)report, KEYBOARD_REPORT_MSG, KBD_REPORT_LENGTH); } } @@ -192,7 +188,17 @@ void send_consumer_control(uint8_t *raw_report, device_t *state) { tud_hid_n_report(0, REPORT_ID_CONSUMER, raw_report, CONSUMER_CONTROL_LENGTH); state->last_activity[BOARD_ROLE] = time_us_64(); } else { - send_packet((uint8_t *)raw_report, CONSUMER_CONTROL_MSG, CONSUMER_CONTROL_LENGTH); + queue_packet((uint8_t *)raw_report, CONSUMER_CONTROL_MSG, CONSUMER_CONTROL_LENGTH); + } +} + +/* Decide if consumer control reports go local or to the other board */ +void send_system_control(uint8_t *raw_report, device_t *state) { + if (CURRENT_BOARD_IS_ACTIVE_OUTPUT) { + tud_hid_n_report(0, REPORT_ID_SYSTEM, raw_report, SYSTEM_CONTROL_LENGTH); + state->last_activity[BOARD_ROLE] = time_us_64(); + } else { + queue_packet((uint8_t *)raw_report, SYSTEM_CONTROL_MSG, SYSTEM_CONTROL_LENGTH); } } @@ -200,21 +206,18 @@ void send_consumer_control(uint8_t *raw_report, device_t *state) { * Parse and interpret the keys pressed on the keyboard * ==================================================== */ -void process_keyboard_report(uint8_t *raw_report, int length, device_t *state) { - hid_keyboard_report_t *keyboard_report = (hid_keyboard_report_t *)raw_report; - hotkey_combo_t *hotkey = NULL; +void process_keyboard_report(uint8_t *raw_report, int length, uint8_t itf, hid_interface_t *iface) { + hid_keyboard_report_t new_report = {0}; + device_t *state = &global_state; + hotkey_combo_t *hotkey = NULL; if (length < KBD_REPORT_LENGTH) return; - /* If the report is longer by 1 byte, we can assume the first byte is the report ID - and that the keyboard didn't switch to boot mode properly. Use this workaround - until full HID report parsing is implemented */ - if (length == KBD_REPORT_LENGTH + 1) - keyboard_report = (hid_keyboard_report_t *)(raw_report + 1); + extract_kbd_data(raw_report, length, itf, iface, &new_report); /* Check if any hotkey was pressed */ - hotkey = check_all_hotkeys(keyboard_report, state); + hotkey = check_all_hotkeys(&new_report, state); /* ... and take appropriate action */ if (hotkey != NULL) { @@ -223,7 +226,7 @@ void process_keyboard_report(uint8_t *raw_report, int length, device_t *state) { blink_led(state); /* Execute the corresponding handler */ - hotkey->action_handler(state, keyboard_report); + hotkey->action_handler(state, &new_report); /* And pass the key to the output PC if configured to do so. */ if (!hotkey->pass_to_os) @@ -231,22 +234,35 @@ void process_keyboard_report(uint8_t *raw_report, int length, device_t *state) { } /* This method will decide if the key gets queued locally or sent through UART */ - send_key(keyboard_report, state); + send_key(&new_report, state); } -void process_consumer_report(uint8_t *raw_report, int length, device_t *state) { +void process_consumer_report(uint8_t *raw_report, int length, uint8_t itf, hid_interface_t *iface) { uint8_t new_report[CONSUMER_CONTROL_LENGTH] = {0}; + uint16_t *report_ptr = (uint16_t *)new_report; + + /* If consumer control is variable, read the values from cc_array and send as array. */ + if (iface->consumer.is_variable) { + for (int i = 0; i < MAX_CC_BUTTONS && i < 8 * (length - 1); i++) { + int bit_idx = i % 8; + int byte_idx = i >> 3; - /* We expect length not to be zero or bail out */ - if (!length) - return; - - /* Consumer control report ID rewrite and forward */ - if (raw_report[0] && raw_report[0] == global_state.kbd_dev.consumer_report_id) { - for (int i = 0; i < length - 1 || i < CONSUMER_CONTROL_LENGTH; i++) { - new_report[i] = raw_report[i + 1]; + if ((raw_report[byte_idx + 1] >> bit_idx) & 1) { + report_ptr[0] = iface->keyboard.cc_array[i]; + } } - - send_consumer_control(new_report, &global_state); } + else { + for (int i = 0; i < length - 1 && i < CONSUMER_CONTROL_LENGTH; i++) + new_report[i] = raw_report[i + 1]; + } + + send_consumer_control(new_report, &global_state); +} + +void process_system_report(uint8_t *raw_report, int length, uint8_t itf, hid_interface_t *iface) { + uint16_t new_report = raw_report[1]; + uint8_t *report_ptr = (uint8_t *)&new_report; + + send_system_control(report_ptr, &global_state); } \ No newline at end of file diff --git a/src/led.c b/src/led.c index 4f890b4..af04ae3 100644 --- a/src/led.c +++ b/src/led.c @@ -47,6 +47,13 @@ void restore_leds(device_t *state) { } } +uint8_t toggle_led(void) { + uint8_t new_led_state = gpio_get(GPIO_LED_PIN) ^ 1; + gpio_put(GPIO_LED_PIN, new_led_state); + + return new_led_state; +} + void blink_led(device_t *state) { /* Since LEDs might be ON previously, we go OFF, ON, OFF, ON, OFF */ state->blinks_left = 5; @@ -56,7 +63,7 @@ void blink_led(device_t *state) { void led_blinking_task(device_t *state) { const int blink_interval_us = 80000; /* 80 ms off, 80 ms on */ static uint8_t leds; - + /* If there is no more blinking to be done, exit immediately */ if (state->blinks_left == 0) return; @@ -66,8 +73,7 @@ void led_blinking_task(device_t *state) { return; /* Toggle the LED state */ - uint8_t new_led_state = gpio_get(GPIO_LED_PIN) ^ 1; - gpio_put(GPIO_LED_PIN, new_led_state); + uint8_t new_led_state = toggle_led(); /* Also keyboard leds (if it's connected locally) since on-board leds are not visible */ leds = new_led_state * 0x07; /* Numlock, capslock, scrollock */ diff --git a/src/main.c b/src/main.c index 06cc77e..d8ba3c4 100644 --- a/src/main.c +++ b/src/main.c @@ -17,15 +17,29 @@ #include "main.h" -/********* Global Variable **********/ -device_t global_state = {0}; -device_t *device = &global_state; +/********* Global Variables **********/ +device_t global_state = {0}; +device_t *device = &global_state; -/**================================================== * +firmware_metadata_t _firmware_metadata __attribute__((section(".section_metadata"))) = { + .version = 0x0001, +}; + +/* ================================================== * * ============== Main Program Loops ============== * * ================================================== */ int main(void) { + static task_t tasks_core0[] = { + [0] = {.exec = &usb_device_task, .frequency = _TOP()}, // .-> USB device task, needs to run as often as possible + [1] = {.exec = &kick_watchdog_task, .frequency = _HZ(30)}, // | Verify core1 is still running and if so, reset watchdog timer + [2] = {.exec = &process_kbd_queue_task, .frequency = _HZ(2000)}, // | Check if there were any keypresses and send them + [3] = {.exec = &process_mouse_queue_task, .frequency = _HZ(2000)}, // | Check if there were any mouse movements and send them + [4] = {.exec = &process_cfg_queue_task, .frequency = _HZ(1000)}, // | Check if there are any packets to send over vendor link + [5] = {.exec = &process_uart_tx_task, .frequency = _TOP()}, // | Check if there are any packets to send over UART + }; // `----- then go back and repeat forever + const int NUM_TASKS = ARRAY_SIZE(tasks_core0); + // Wait for the board to settle sleep_ms(10); @@ -36,40 +50,28 @@ int main(void) { switch_output(device, OUTPUT_A); while (true) { - // USB device task, needs to run as often as possible - tud_task(); - - // Verify core1 is still running and if so, reset watchdog timer - kick_watchdog(device); - - // Check if there were any keypresses and send them - process_kbd_queue_task(device); - - // Check if there were any mouse movements and send them - process_mouse_queue_task(device); - } + for (int i = 0; i < NUM_TASKS; i++) + task_scheduler(device, &tasks_core0[i]); + } } void core1_main() { - uart_packet_t in_packet = {0}; + static task_t tasks_core1[] = { + [0] = {.exec = &usb_host_task, .frequency = _TOP()}, // .-> USB host task, needs to run as often as possible + [1] = {.exec = &packet_receiver_task, .frequency = _TOP()}, // | Receive data over serial from the other board + [2] = {.exec = &led_blinking_task, .frequency = _HZ(30)}, // | Check if LED needs blinking + [3] = {.exec = &screensaver_task, .frequency = _HZ(120)}, // | Handle "screensaver" movements + [4] = {.exec = &firmware_upgrade_task, .frequency = _HZ(4000)}, // | Send firmware to the other board if needed + [5] = {.exec = &heartbeat_output_task, .frequency = _HZ(1)}, // | Output periodic heartbeats + }; // `----- then go back and repeat forever + const int NUM_TASKS = ARRAY_SIZE(tasks_core1); while (true) { // Update the timestamp, so core0 can figure out if we're dead device->core1_last_loop_pass = time_us_64(); - // USB host task, needs to run as often as possible - if (tuh_inited()) - tuh_task(); - - // Receives data over serial from the other board - receive_char(&in_packet, device); - - // Check if LED needs blinking - led_blinking_task(device); - - // Mouse screensaver task - screensaver_task(device); + for (int i = 0; i < NUM_TASKS; i++) + task_scheduler(device, &tasks_core1[i]); } } - /* ======= End of Main Program Loops ======= */ diff --git a/src/mouse.c b/src/mouse.c index 0a35175..5a4a2d6 100644 --- a/src/mouse.c +++ b/src/mouse.c @@ -48,7 +48,7 @@ int32_t accelerate(int32_t offset) { {70, 4.0}, // ------------------------------------------- }; // 10 20 30 40 50 60 70 - if (!ENABLE_ACCELERATION) + if (!global_state.config.enable_acceleration) return offset; for (int i = 0; i < 7; i++) { @@ -73,8 +73,8 @@ void update_mouse_position(device_t *state, mouse_values_t *values) { int offset_y = accelerate(values->move_y) * (current->speed_y >> reduce_speed); /* Update movement */ - state->mouse_x = move_and_keep_on_screen(state->mouse_x, offset_x); - state->mouse_y = move_and_keep_on_screen(state->mouse_y, offset_y); + state->pointer_x = move_and_keep_on_screen(state->pointer_x, offset_x); + state->pointer_y = move_and_keep_on_screen(state->pointer_y, offset_y); /* Update buttons state */ state->mouse_buttons = values->buttons; @@ -86,8 +86,8 @@ void output_mouse_report(mouse_report_t *report, device_t *state) { queue_mouse_report(report, state); state->last_activity[BOARD_ROLE] = time_us_64(); } else { - send_packet((uint8_t *)report, MOUSE_REPORT_MSG, MOUSE_REPORT_LENGTH); - } + queue_packet((uint8_t *)report, MOUSE_REPORT_MSG, MOUSE_REPORT_LENGTH); + } } /* Calculate and return Y coordinate when moving from screen out_from to screen out_to */ @@ -100,38 +100,41 @@ int16_t scale_y_coordinate(int screen_from, int screen_to, device_t *state) { /* If sizes match, there is nothing to do */ if (size_from == size_to) - return state->mouse_y; + return state->pointer_y; /* Moving from smaller ==> bigger screen y_a = top + (((bottom - top) * y_b) / HEIGHT) */ if (size_from > size_to) { - return to->border.top + ((size_to * state->mouse_y) / MAX_SCREEN_COORD); + return to->border.top + ((size_to * state->pointer_y) / MAX_SCREEN_COORD); } /* Moving from bigger ==> smaller screen y_b = ((y_a - top) * HEIGHT) / (bottom - top) */ - if (state->mouse_y < from->border.top) + if (state->pointer_y < from->border.top) return MIN_SCREEN_COORD; - if (state->mouse_y > from->border.bottom) + if (state->pointer_y > from->border.bottom) return MAX_SCREEN_COORD; - return ((state->mouse_y - from->border.top) * MAX_SCREEN_COORD) / size_from; + return ((state->pointer_y - from->border.top) * MAX_SCREEN_COORD) / size_from; } void switch_screen( device_t *state, output_t *output, int new_x, int output_from, int output_to, int direction) { - unsigned mouse_y = (MOUSE_PARKING_POSITION == 0) ? MIN_SCREEN_COORD : /*TOP*/ - (MOUSE_PARKING_POSITION == 1) ? MAX_SCREEN_COORD : /*BOTTOM*/ - state->mouse_y; /*PREVIOUS*/ - mouse_report_t hidden_pointer = {.y = mouse_y, .x = MAX_SCREEN_COORD}; + uint8_t *mouse_park_pos = &state->config.output[state->active_output].mouse_park_pos; + + int16_t mouse_y = (*mouse_park_pos == 0) ? MIN_SCREEN_COORD : /* Top */ + (*mouse_park_pos == 1) ? MAX_SCREEN_COORD : /* Bottom */ + state->pointer_y; /* Previous */ + + mouse_report_t hidden_pointer = {.y = mouse_y, .x = MAX_SCREEN_COORD}; output_mouse_report(&hidden_pointer, state); switch_output(state, output_to); - state->mouse_x = (direction == LEFT) ? MAX_SCREEN_COORD : MIN_SCREEN_COORD; - state->mouse_y = scale_y_coordinate(output->number, 1 - output->number, state); + state->pointer_x = (direction == LEFT) ? MAX_SCREEN_COORD : MIN_SCREEN_COORD; + state->pointer_y = scale_y_coordinate(output->number, 1 - output->number, state); } void switch_desktop(device_t *state, output_t *output, int new_index, int direction) { @@ -142,9 +145,9 @@ void switch_desktop(device_t *state, output_t *output, int new_index, int direct switch (output->os) { case MACOS: - /* Once isn't reliable enough, but repeating it does the trick */ - for (int move_cnt=0; move_cnt<5; move_cnt++) - output_mouse_report(&move_relative_one, state); + /* Once doesn't seem reliable enough, do it twice */ + output_mouse_report(&move_relative_one, state); + output_mouse_report(&move_relative_one, state); break; case WINDOWS: @@ -153,13 +156,14 @@ void switch_desktop(device_t *state, output_t *output, int new_index, int direct break; case LINUX: + case ANDROID: case OTHER: /* Linux should treat all desktops as a single virtual screen, so you should leave screen_count at 1 and it should just work */ break; } - state->mouse_x = (direction == RIGHT) ? MIN_SCREEN_COORD : MAX_SCREEN_COORD; + state->pointer_x = (direction == RIGHT) ? MIN_SCREEN_COORD : MAX_SCREEN_COORD; output->screen_index = new_index; } @@ -172,11 +176,11 @@ void switch_desktop(device_t *state, output_t *output, int new_index, int direct )___( )___( | )___( )___( )___( */ void check_screen_switch(const mouse_values_t *values, device_t *state) { - int new_x = state->mouse_x + values->move_x; + int new_x = state->pointer_x + values->move_x; output_t *output = &state->config.output[state->active_output]; - bool jump_left = new_x < MIN_SCREEN_COORD - JUMP_THRESHOLD; - bool jump_right = new_x > MAX_SCREEN_COORD + JUMP_THRESHOLD; + bool jump_left = new_x < MIN_SCREEN_COORD - state->config.jump_treshold; + bool jump_right = new_x > MAX_SCREEN_COORD + state->config.jump_treshold; int direction = jump_left ? LEFT : RIGHT; @@ -203,9 +207,9 @@ void check_screen_switch(const mouse_values_t *values, device_t *state) { switch_desktop(state, output, output->screen_index + 1, direction); } -void extract_report_values(uint8_t *raw_report, device_t *state, mouse_values_t *values) { +void extract_report_values(uint8_t *raw_report, device_t *state, mouse_values_t *values, hid_interface_t *iface) { /* Interpret values depending on the current protocol used. */ - if (state->mouse_dev.protocol == HID_PROTOCOL_BOOT) { + if (iface->protocol == HID_PROTOCOL_BOOT) { hid_mouse_report_t *mouse_report = (hid_mouse_report_t *)raw_report; values->move_x = mouse_report->x; @@ -216,20 +220,20 @@ void extract_report_values(uint8_t *raw_report, device_t *state, mouse_values_t } /* If HID Report ID is used, the report is prefixed by the report ID so we have to move by 1 byte */ - if (state->mouse_dev.uses_report_id) + if (iface->mouse.report_id) raw_report++; - values->move_x = get_report_value(raw_report, &state->mouse_dev.move_x); - values->move_y = get_report_value(raw_report, &state->mouse_dev.move_y); - values->wheel = get_report_value(raw_report, &state->mouse_dev.wheel); - values->buttons = get_report_value(raw_report, &state->mouse_dev.buttons); + values->move_x = get_report_value(raw_report, &iface->mouse.move_x); + values->move_y = get_report_value(raw_report, &iface->mouse.move_y); + values->wheel = get_report_value(raw_report, &iface->mouse.wheel); + values->buttons = get_report_value(raw_report, &iface->mouse.buttons); } mouse_report_t create_mouse_report(device_t *state, mouse_values_t *values) { mouse_report_t mouse_report = { .buttons = values->buttons, - .x = state->mouse_x, - .y = state->mouse_y, + .x = state->pointer_x, + .y = state->pointer_y, .wheel = values->wheel, .mode = ABSOLUTE, }; @@ -242,14 +246,16 @@ mouse_report_t create_mouse_report(device_t *state, mouse_values_t *values) { mouse_report.buttons = values->buttons; mouse_report.wheel = values->wheel; } + return mouse_report; } -void process_mouse_report(uint8_t *raw_report, int len, device_t *state) { +void process_mouse_report(uint8_t *raw_report, int len, uint8_t itf, hid_interface_t *iface) { mouse_values_t values = {0}; + device_t *state = &global_state; /* Interpret the mouse HID report, extract and save values we need. */ - extract_report_values(raw_report, state, &values); + extract_report_values(raw_report, state, &values, iface); /* Calculate and update mouse pointer movement. */ update_mouse_position(state, &values); @@ -261,7 +267,7 @@ void process_mouse_report(uint8_t *raw_report, int len, device_t *state) { output_mouse_report(&report, state); /* We use the mouse to switch outputs, the logic is in check_screen_switch() */ - check_screen_switch(&values, state); + check_screen_switch(&values, state); } /* ==================================================== * @@ -283,6 +289,10 @@ void process_mouse_queue_task(device_t *state) { if (tud_suspended()) tud_remote_wakeup(); + /* If it's not ready, we'll try on the next pass */ + if (!tud_hid_n_ready(ITF_NUM_HID)) + return; + /* ... try sending it to the host, if it's successful */ bool succeeded = tud_mouse_report(report.mode, report.buttons, report.x, report.y, report.wheel); diff --git a/src/protocol.c b/src/protocol.c new file mode 100644 index 0000000..5d730eb --- /dev/null +++ b/src/protocol.c @@ -0,0 +1,69 @@ +#include "main.h" + +const field_map_t api_field_map[] = { +/* Index, Rdonly, Type, Len, Offset in struct */ + { 0, true, UINT8, 1, offsetof(device_t, active_output) }, + { 1, true, INT16, 2, offsetof(device_t, pointer_x) }, + { 2, true, INT16, 2, offsetof(device_t, pointer_y) }, + { 3, true, INT16, 2, offsetof(device_t, mouse_buttons) }, + + /* Output A */ + { 10, false, UINT32, 4, offsetof(device_t, config.output[0].number) }, + { 11, false, UINT32, 4, offsetof(device_t, config.output[0].screen_count) }, + { 12, false, INT32, 4, offsetof(device_t, config.output[0].speed_x) }, + { 13, false, INT32, 4, offsetof(device_t, config.output[0].speed_y) }, + { 14, false, INT32, 4, offsetof(device_t, config.output[0].border.top) }, + { 15, false, INT32, 4, offsetof(device_t, config.output[0].border.bottom) }, + { 16, false, UINT8, 1, offsetof(device_t, config.output[0].os) }, + { 17, false, UINT8, 1, offsetof(device_t, config.output[0].pos) }, + { 18, false, UINT8, 1, offsetof(device_t, config.output[0].mouse_park_pos) }, + { 19, false, UINT8, 1, offsetof(device_t, config.output[0].screensaver.enabled) }, + { 20, false, UINT8, 1, offsetof(device_t, config.output[0].screensaver.only_if_inactive) }, + + /* Until we increase the payload size from 8 bytes, clamp to avoid exceeding the field size */ + { 21, false, UINT64, 7, offsetof(device_t, config.output[0].screensaver.idle_time_us) }, + { 22, false, UINT64, 7, offsetof(device_t, config.output[0].screensaver.max_time_us) }, + + /* Output B */ + { 40, false, UINT32, 4, offsetof(device_t, config.output[1].number) }, + { 41, false, UINT32, 4, offsetof(device_t, config.output[1].screen_count) }, + { 42, false, INT32, 4, offsetof(device_t, config.output[1].speed_x) }, + { 43, false, INT32, 4, offsetof(device_t, config.output[1].speed_y) }, + { 44, false, INT32, 4, offsetof(device_t, config.output[1].border.top) }, + { 45, false, INT32, 4, offsetof(device_t, config.output[1].border.bottom) }, + { 46, false, UINT8, 1, offsetof(device_t, config.output[1].os) }, + { 47, false, UINT8, 1, offsetof(device_t, config.output[1].pos) }, + { 48, false, UINT8, 1, offsetof(device_t, config.output[1].mouse_park_pos) }, + { 49, false, UINT8, 1, offsetof(device_t, config.output[1].screensaver.enabled) }, + { 50, false, UINT8, 1, offsetof(device_t, config.output[1].screensaver.only_if_inactive) }, + { 51, false, UINT64, 7, offsetof(device_t, config.output[1].screensaver.idle_time_us) }, + { 52, false, UINT64, 7, offsetof(device_t, config.output[1].screensaver.max_time_us) }, + + /* Common config */ + { 70, false, UINT32, 4, offsetof(device_t, config.version) }, + { 71, false, UINT8, 1, offsetof(device_t, config.force_mouse_boot_mode) }, + { 72, false, UINT8, 1, offsetof(device_t, config.force_kbd_boot_protocol) }, + { 73, false, UINT8, 1, offsetof(device_t, config.kbd_led_as_indicator) }, + { 74, false, UINT8, 1, offsetof(device_t, config.hotkey_toggle) }, + { 75, false, UINT8, 1, offsetof(device_t, config.enable_acceleration) }, + { 76, false, UINT8, 1, offsetof(device_t, config.enforce_ports) }, + { 77, false, UINT16, 2, offsetof(device_t, config.jump_treshold) }, + + /* Firmware */ + { 78, true, UINT16, 2, offsetof(device_t, _running_fw.version) }, + { 79, true, UINT32, 4, offsetof(device_t, _running_fw.checksum) }, + + { 80, true, UINT8, 1, offsetof(device_t, keyboard_connected) }, + { 81, true, UINT8, 1, offsetof(device_t, switch_lock) }, + { 82, true, UINT8, 1, offsetof(device_t, relative_mouse) }, +}; + +const field_map_t* get_field_map_entry(uint32_t index) { + for (unsigned int i = 0; i < ARRAY_SIZE(api_field_map); i++) { + if (api_field_map[i].idx == index) { + return &api_field_map[i]; + } + } + + return NULL; +} \ No newline at end of file diff --git a/src/ramdisk.c b/src/ramdisk.c new file mode 100644 index 0000000..d31d2e1 --- /dev/null +++ b/src/ramdisk.c @@ -0,0 +1,119 @@ +/* + * This file is part of DeskHop (https://github.com/hrvach/deskhop). + * Copyright (c) 2024 Hrvoje Cavrak + * + * Based on the TinyUSB example by Ha Thach. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "main.h" + +#define NUMBER_OF_BLOCKS 4096 +#define ACTUAL_NUMBER_OF_BLOCKS 128 +#define BLOCK_SIZE 512 + +void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) { + strcpy((char *)vendor_id, "DeskHop"); + strcpy((char *)product_id, "Config Mode"); + strcpy((char *)product_rev, "1.0"); +} + +bool tud_msc_test_unit_ready_cb(uint8_t lun) { + return true; +} + +void tud_msc_capacity_cb(uint8_t lun, uint32_t *block_count, uint16_t *block_size) { + *block_count = NUMBER_OF_BLOCKS; + *block_size = BLOCK_SIZE; +} + +bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) { + return true; +} + +/* Return the requested data, or -1 if out-of-bounds */ +int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void *buffer, uint32_t bufsize) { + const uint8_t *addr = &ADDR_DISK_IMAGE[lba * BLOCK_SIZE + offset]; + + if (lba >= NUMBER_OF_BLOCKS) + return -1; + + /* We lie about the image size - actually it's 64 kB, not 512 kB, so if we're out of bounds, return zeros */ + else if (lba >= ACTUAL_NUMBER_OF_BLOCKS) + memset(buffer, 0x00, bufsize); + + else + memcpy(buffer, addr, bufsize); + + return (int32_t)bufsize; +} + +/* We're writable, so return true */ +bool tud_msc_is_writable_cb(uint8_t lun) { + return true; +} + +/* Simple firmware write routine, we get 512-byte uf2 blocks with 256 byte payload */ +int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize) { + uf2_t *uf2 = (uf2_t *)&buffer[0]; + bool is_final_block = uf2->blockNo == (STAGING_IMAGE_SIZE / FLASH_PAGE_SIZE) - 1; + uint32_t flash_addr = (uint32_t)ADDR_FW_RUNNING + uf2->blockNo * FLASH_PAGE_SIZE - XIP_BASE; + + if (lba >= NUMBER_OF_BLOCKS) + return -1; + + /* If we're not detecting UF2 magic constants, we have nothing to do... */ + if (uf2->magicStart0 != UF2_MAGIC_START0 || uf2->magicStart1 != UF2_MAGIC_START1 || uf2->magicEnd != UF2_MAGIC_END) + return (int32_t)bufsize; + + if (uf2->blockNo == 0) { + global_state.fw.checksum = 0xffffffff; + + /* Make sure nobody else touches the flash during this operation, otherwise we get empty pages */ + global_state.fw.upgrade_in_progress = true; + } + + /* Update checksum continuously as blocks are being received */ + const uint32_t last_block_with_checksum = (STAGING_IMAGE_SIZE - FLASH_SECTOR_SIZE) / FLASH_PAGE_SIZE; + for (int i=0; iblockNo < last_block_with_checksum; i++) + global_state.fw.checksum = crc32_iter(global_state.fw.checksum, buffer[32 + i]); + + write_flash_page(flash_addr, &buffer[32]); + + if (is_final_block) { + global_state.fw.checksum = ~global_state.fw.checksum; + + /* If checksums don't match, overwrite first sector and rely on ROM bootloader for recovery */ + if (global_state.fw.checksum != calculate_firmware_crc32()) { + flash_range_erase((uint32_t)ADDR_FW_RUNNING - XIP_BASE, FLASH_SECTOR_SIZE); + reset_usb_boot(1 << PICO_DEFAULT_LED_PIN, 0); + } + else { + global_state.reboot_requested = true; + } + } + + /* Provide some visual indication that fw is being uploaded */ + toggle_led(); + watchdog_update(); + + return (int32_t)bufsize; +} + +/* This is a super-dumb, rudimentary disk, any other scsi command is simply rejected */ +int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void *buffer, uint16_t bufsize) { + tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); + return -1; +} + \ No newline at end of file diff --git a/src/setup.c b/src/setup.c index 333a44a..b02d366 100644 --- a/src/setup.c +++ b/src/setup.c @@ -50,25 +50,165 @@ void serial_init() { * PIO USB configuration, D+ pin 14, D- pin 15 * ================================================== */ -void pio_usb_host_config(void) { +void pio_usb_host_config(device_t *state) { /* tuh_configure() must be called before tuh_init() */ static pio_usb_configuration_t config = PIO_USB_DEFAULT_CONFIG; config.pin_dp = PIO_USB_DP_PIN_DEFAULT; - /* Make HID protocol the default for port B as a fix for devices enumerating - themselves as both keyboards and mice, but having just a single common mode */ - if(BOARD_ROLE == OUTPUT_B) - tuh_hid_set_default_protocol(HID_PROTOCOL_REPORT); - + tuh_hid_set_default_protocol(HID_PROTOCOL_REPORT); tuh_configure(BOARD_TUH_RHPORT, TUH_CFGID_RPI_PIO_USB_CONFIGURATION, &config); /* Initialize and configure TinyUSB Host */ tuh_init(1); } +/* ================================================== * + * Board Autoprobe Routine + * ================================================== */ + +/* Probing algorithm logic: + - RX pin is driven by the digital isolator IC + - IF we are board A, it will be connected to pin 13 + and it will drive it either high or low at any given time + - Before uart setup, enable it as an input + - Go through a probing sequence of 8 values and pull either up or down + to that value + - Read out the value on the RX pin + - If the entire sequence of values match, we are definitely floating + so IC is not connected on BOARD_A_RX, and we're BOARD B +*/ +int board_autoprobe(void) { + const bool probing_sequence[] = {true, false, false, true, true, false, true, false}; + const int seq_len = ARRAY_SIZE(probing_sequence); + + /* Set the pin as INPUT and initialize it */ + gpio_init(BOARD_A_RX); + gpio_set_dir(BOARD_A_RX, GPIO_IN); + + for (int i=0; iscratch[5] == MAGIC_WORD_1 && + watchdog_hw->scratch[6] == MAGIC_WORD_2); + + /* Remove, so next reboot it's no longer active */ + if (is_active) + watchdog_hw->scratch[5] = 0; + + reset_config_timer(state); + + return is_active; +} + + +/* ================================================== * + * Configure DMA for reliable UART transfers + * ================================================== */ +const uint8_t* uart_buffer_pointers[1] = {uart_rxbuf}; +uint8_t uart_rxbuf[DMA_RX_BUFFER_SIZE] __attribute__((aligned(DMA_RX_BUFFER_SIZE))) ; +uint8_t uart_txbuf[DMA_TX_BUFFER_SIZE] __attribute__((aligned(DMA_TX_BUFFER_SIZE))) ; + +static void configure_tx_dma(device_t *state) { + state->dma_tx_channel = dma_claim_unused_channel(true); + + dma_channel_config tx_config = dma_channel_get_default_config(state->dma_tx_channel); + channel_config_set_transfer_data_size(&tx_config, DMA_SIZE_8); + + /* Writing uart (always write the same address, but source addr changes as we read) */ + channel_config_set_read_increment(&tx_config, true); + channel_config_set_write_increment(&tx_config, false); + + // channel_config_set_ring(&tx_config, false, 4); + channel_config_set_dreq(&tx_config, DREQ_UART0_TX); + + /* Configure, but don't start immediately. We'll do this each time the outgoing + packet is ready and we copy it to the buffer */ + dma_channel_configure( + state->dma_tx_channel, + &tx_config, + &uart0_hw->dr, + uart_txbuf, + 0, + false + ); +} + +static void configure_rx_dma(device_t *state) { + /* Find an empty channel, store it for later reference */ + state->dma_rx_channel = dma_claim_unused_channel(true); + state->dma_control_channel = dma_claim_unused_channel(true); + + dma_channel_config config = dma_channel_get_default_config(state->dma_rx_channel); + dma_channel_config control_config = dma_channel_get_default_config(state->dma_control_channel); + + channel_config_set_transfer_data_size(&config, DMA_SIZE_8); + channel_config_set_transfer_data_size(&control_config, DMA_SIZE_32); + + // The read address is the address of the UART data register which is constant + channel_config_set_read_increment(&config, false); + channel_config_set_read_increment(&control_config, false); + + // Read into a ringbuffer with 1024 (2^10) elements + channel_config_set_write_increment(&config, true); + channel_config_set_write_increment(&control_config, false); + + channel_config_set_ring(&config, true, 10); + + // The UART signals when data is avaliable + channel_config_set_dreq(&config, DREQ_UART0_RX); + + channel_config_set_chain_to(&config, state->dma_control_channel); + + dma_channel_configure( + state->dma_rx_channel, + &config, + uart_rxbuf, + &uart0_hw->dr, + DMA_RX_BUFFER_SIZE, + false); + + dma_channel_configure( + state->dma_control_channel, + &control_config, + &dma_hw->ch[state->dma_rx_channel].al2_write_addr_trig, + uart_buffer_pointers, + 1, + false); + + dma_channel_start(state->dma_control_channel); +} + + /* ================================================== * * Perform initial board/usb setup * ================================================== */ +int board; void initial_setup(device_t *state) { /* PIO USB requires a clock multiple of 12 MHz, setting to 120 MHz */ @@ -81,6 +221,12 @@ void initial_setup(device_t *state) { gpio_init(GPIO_LED_PIN); gpio_set_dir(GPIO_LED_PIN, GPIO_OUT); + /* Check if we should boot in configuration mode or not */ + state->config_mode_active = is_config_mode_active(state); + + /* Detect which board we're running on */ + state->board_role = board_autoprobe(); + /* Initialize and configure UART */ serial_init(); @@ -88,6 +234,12 @@ void initial_setup(device_t *state) { queue_init(&state->kbd_queue, sizeof(hid_keyboard_report_t), KBD_QUEUE_LENGTH); queue_init(&state->mouse_queue, sizeof(mouse_report_t), MOUSE_QUEUE_LENGTH); + /* Initialize vendor config protocol queue */ + queue_init(&state->cfg_queue_out, sizeof(uart_packet_t), CFG_QUEUE_LENGTH); + + /* Initialize UART queue */ + queue_init(&state->uart_tx_queue, sizeof(uart_packet_t), UART_QUEUE_LENGTH); + /* Setup RP2040 Core 1 */ multicore_reset_core1(); multicore_launch_core1(core1_main); @@ -96,11 +248,18 @@ void initial_setup(device_t *state) { tud_init(BOARD_TUD_RHPORT); /* Initialize and configure TinyUSB Host */ - pio_usb_host_config(); + pio_usb_host_config(state); + + /* Initialize and configure DMA */ + configure_tx_dma(state); + configure_rx_dma(state); + + /* Load the current firmware info */ + state->_running_fw = _firmware_metadata; /* Update the core1 initial pass timestamp before enabling the watchdog */ state->core1_last_loop_pass = time_us_64(); - + /* Setup the watchdog so we reboot and recover from a crash */ watchdog_enable(WATCHDOG_TIMEOUT, WATCHDOG_PAUSE_ON_DEBUG); } diff --git a/src/tasks.c b/src/tasks.c new file mode 100644 index 0000000..3a9f2eb --- /dev/null +++ b/src/tasks.c @@ -0,0 +1,213 @@ +/* + * This file is part of DeskHop (https://github.com/hrvach/deskhop). + * Copyright (c) 2024 Hrvoje Cavrak + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "main.h" + +void task_scheduler(device_t *state, task_t *task) { + uint64_t current_time = time_us_64(); + + if (current_time < task->next_run) + return; + + task->next_run = current_time + task->frequency; + task->exec(state); +} + +/**================================================== * + * ============== Watchdog Functions ============== * + * ================================================== */ + +void kick_watchdog_task(device_t *state) { + /* Read the timer AFTER duplicating the core1 timestamp, + so it doesn't get updated in the meantime. */ + uint64_t core1_last_loop_pass = state->core1_last_loop_pass; + uint64_t current_time = time_us_64(); + + /* If a reboot is requested, we'll stop updating watchdog */ + if (state->reboot_requested) + return; + + /* If core1 stops updating the timestamp, we'll stop kicking the watchog and reboot */ + if (current_time - core1_last_loop_pass < CORE1_HANG_TIMEOUT_US) + watchdog_update(); +} + +/**================================================== * + * =============== USB Device / Host ============== * + * ================================================== */ + +void usb_device_task(device_t *state) { + tud_task(); +} + +void usb_host_task(device_t *state) { + if (tuh_inited()) + tuh_task(); +} + +/* Have something fun and entertaining when idle. */ +void screensaver_task(device_t *state) { + const int mouse_move_delay = 5000; + screensaver_t *screensaver = &state->config.output[BOARD_ROLE].screensaver; + uint64_t inactivity_period = time_us_64() - state->last_activity[BOARD_ROLE]; + + static mouse_report_t report = {0}; + static int last_pointer_move = 0; + static int dx = 20, dy = 25; + + /* If we're not enabled, nothing to do here. */ + if (!screensaver->enabled) + return; + + /* System is still not idle for long enough to activate or we've been running for too long */ + if (inactivity_period < screensaver->idle_time_us) + return; + + /* We exceeded the maximum permitted screensaver runtime */ + if (screensaver->max_time_us + && inactivity_period > (screensaver->max_time_us + screensaver->idle_time_us)) + return; + + /* If we're the selected output and we can only run on inactive output, nothing to do here. */ + if (screensaver->only_if_inactive && CURRENT_BOARD_IS_ACTIVE_OUTPUT) + return; + + /* We're active! Now check if it's time to move the cursor yet. */ + if ((time_us_32()) - last_pointer_move < mouse_move_delay) + return; + + /* Check if we are bouncing off the walls and reverse direction in that case. */ + if (report.x + dx < MIN_SCREEN_COORD || report.x + dx > MAX_SCREEN_COORD) + dx = -dx; + + if (report.y + dy < MIN_SCREEN_COORD || report.y + dy > MAX_SCREEN_COORD) + dy = -dy; + + report.x += dx; + report.y += dy; + + /* Move mouse pointer */ + queue_mouse_report(&report, state); + + /* Update timer of the last pointer move */ + last_pointer_move = time_us_32(); +} + +/* Periodically emit heartbeat packets */ +void heartbeat_output_task(device_t *state) { + /* If firmware upgrade is in progress, don't touch flash_cs */ + if (state->fw.upgrade_in_progress) + return; + + if (state->config_mode_active) { + /* Leave config mode if timeout expired and user didn't click exit */ + if (time_us_64() > state->config_mode_timer) + reboot(); + + /* Keep notifying the user we're still in config mode */ + blink_led(state); + } + + /* Holding the button invokes bootsel firmware upgrade */ + if (is_bootsel_pressed()) + reset_usb_boot(1 << PICO_DEFAULT_LED_PIN, 0); + + uart_packet_t packet = { + .type = HEARTBEAT_MSG, + .data16 = { + [0] = state->_running_fw.version, + [2] = state->active_output, + }, + }; + + queue_try_add(&global_state.uart_tx_queue, &packet); +} + +/* Process outgoing config report messages. */ +void process_cfg_queue_task(device_t *state) { + uint8_t raw_packet[RAW_PACKET_LENGTH] = {[0] = START1, [1] = START2, [11] = 0}; + uart_packet_t packet; + + if (!queue_try_peek(&state->cfg_queue_out, &packet)) + return; + + if (!tud_hid_n_ready(ITF_NUM_HID_VENDOR)) + return; + + write_raw_packet(raw_packet, &packet); + + /* ... try sending it to the host, if it's successful */ + bool succeeded = tud_hid_n_report(ITF_NUM_HID_VENDOR, REPORT_ID_VENDOR, raw_packet, RAW_PACKET_LENGTH); + + /* ... then we can remove it from the queue. Race conditions shouldn't happen [tm] */ + if (succeeded) + queue_try_remove(&state->cfg_queue_out, &packet); +} + +/* Task that handles copying firmware from the other device to ours */ +void firmware_upgrade_task(device_t *state) { + if (!state->fw.upgrade_in_progress || !state->fw.byte_done) + return; + + if (queue_is_full(&state->uart_tx_queue)) + return; + + /* End condition, when reached the process is completed. */ + if (state->fw.address > STAGING_IMAGE_SIZE) { + state->fw.upgrade_in_progress = 0; + state->fw.checksum = ~state->fw.checksum; + + /* Checksum mismatch, we wipe the stage 2 bootloader and rely on ROM recovery */ + if(calculate_firmware_crc32() != state->fw.checksum) { + flash_range_erase((uint32_t)ADDR_FW_RUNNING - XIP_BASE, FLASH_SECTOR_SIZE); + reset_usb_boot(1 << PICO_DEFAULT_LED_PIN, 0); + } + + else { + state->_running_fw = _firmware_metadata; + global_state.reboot_requested = true; + } + } + + /* If we're on the last element of the current page, page is done - write it. */ + if (TU_U32_BYTE0(state->fw.address) == 0x00) { + + uint32_t page_start_addr = (state->fw.address - 1) & 0xFFFFFF00; + write_flash_page((uint32_t)ADDR_FW_RUNNING + page_start_addr - XIP_BASE, state->page_buffer); + } + + request_byte(state, state->fw.address); +} + +void packet_receiver_task(device_t *state) { + uint32_t current_pointer + = (uint32_t)DMA_RX_BUFFER_SIZE - dma_channel_hw_addr(state->dma_rx_channel)->transfer_count; + uint32_t delta = get_ptr_delta(current_pointer, state); + + /* If we don't have enough characters for a packet, skip loop and return immediately */ + while (delta >= RAW_PACKET_LENGTH) { + if (is_start_of_packet(state)) { + fetch_packet(state); + process_packet(&state->in_packet, state); + return; + } + + /* No packet found, advance to next position and decrement delta */ + state->dma_ptr = NEXT_RING_IDX(state->dma_ptr); + delta--; + } +} \ No newline at end of file diff --git a/src/uart.c b/src/uart.c index 3f7b7ba..c99b953 100644 --- a/src/uart.c +++ b/src/uart.c @@ -21,23 +21,43 @@ * =============== Sending Packets ================ * * ================================================== */ -void send_packet(const uint8_t *data, enum packet_type_e packet_type, int length) { - uint8_t raw_packet[RAW_PACKET_LENGTH] = {[0] = START1, - [1] = START2, - [2] = packet_type, - /* [3-10] is data, defaults to 0 */ - [11] = calc_checksum(data, length)}; - - if (length > 0) - memcpy(&raw_packet[START_LENGTH + TYPE_LENGTH], data, length); - - /* Packets are short, fixed length, high speed and there is no flow control to block this */ - uart_write_blocking(SERIAL_UART, raw_packet, RAW_PACKET_LENGTH); +/* Takes a packet as uart_packet_t struct, adds preamble, checksum and encodes it to a raw array. */ +void write_raw_packet(uint8_t *dst, uart_packet_t *packet) { + uint8_t pkt[RAW_PACKET_LENGTH] = {[0] = START1, + [1] = START2, + [2] = packet->type, + /* [3-10] is data, defaults to 0 */ + [11] = calc_checksum(packet->data, PACKET_DATA_LENGTH)}; + + memcpy(&pkt[START_LENGTH + TYPE_LENGTH], packet->data, PACKET_DATA_LENGTH); + memcpy(dst, &pkt, RAW_PACKET_LENGTH); } +/* Schedule packet for sending to the other box */ +void queue_packet(const uint8_t *data, enum packet_type_e packet_type, int length) { + uart_packet_t packet = {.type = packet_type}; + memcpy(packet.data, data, length); + + queue_try_add(&global_state.uart_tx_queue, &packet); +} + +/* Sends just one byte of a certain packet type to the other box. */ void send_value(const uint8_t value, enum packet_type_e packet_type) { - const uint8_t data = value; - send_packet(&data, packet_type, sizeof(uint8_t)); + queue_packet(&value, packet_type, sizeof(uint8_t)); +} + +/* Process outgoing config report messages. */ +void process_uart_tx_task(device_t *state) { + uart_packet_t packet = {0}; + + if (dma_channel_is_busy(state->dma_tx_channel)) + return; + + if (!queue_try_remove(&state->uart_tx_queue, &packet)) + return; + + write_raw_packet(uart_txbuf, &packet); + dma_channel_transfer_from_buffer_now(state->dma_tx_channel, uart_txbuf, RAW_PACKET_LENGTH); } /**================================================== * @@ -45,19 +65,33 @@ void send_value(const uint8_t value, enum packet_type_e packet_type) { * ================================================== */ const uart_handler_t uart_handler[] = { + /* Core functions */ {.type = KEYBOARD_REPORT_MSG, .handler = handle_keyboard_uart_msg}, {.type = MOUSE_REPORT_MSG, .handler = handle_mouse_abs_uart_msg}, {.type = OUTPUT_SELECT_MSG, .handler = handle_output_select_msg}, - {.type = FIRMWARE_UPGRADE_MSG, .handler = handle_fw_upgrade_msg}, + + /* Box control */ {.type = MOUSE_ZOOM_MSG, .handler = handle_mouse_zoom_msg}, {.type = KBD_SET_REPORT_MSG, .handler = handle_set_report_msg}, {.type = SWITCH_LOCK_MSG, .handler = handle_switch_lock_msg}, {.type = SYNC_BORDERS_MSG, .handler = handle_sync_borders_msg}, {.type = FLASH_LED_MSG, .handler = handle_flash_led_msg}, - {.type = SCREENSAVER_MSG, .handler = handle_screensaver_msg}, + {.type = CONSUMER_CONTROL_MSG, .handler = handle_consumer_control_msg}, + + /* Config */ {.type = WIPE_CONFIG_MSG, .handler = handle_wipe_config_msg}, - {.type = OUTPUT_CONFIG_MSG, .handler = handle_output_config_msg}, - {.type = CONSUMER_CONTROL_MSG, .handler = handle_consumer_control_msg}, + {.type = SAVE_CONFIG_MSG, .handler = handle_save_config_msg}, + {.type = REBOOT_MSG, .handler = handle_reboot_msg}, + {.type = GET_VAL_MSG, .handler = handle_api_msgs}, + {.type = SET_VAL_MSG, .handler = handle_api_msgs}, + + /* Firmware */ + {.type = REQUEST_BYTE_MSG, .handler = handle_request_byte_msg}, + {.type = RESPONSE_BYTE_MSG, .handler = handle_response_byte_msg}, + {.type = FIRMWARE_UPGRADE_MSG, .handler = handle_fw_upgrade_msg}, + + {.type = HEARTBEAT_MSG, .handler = handle_heartbeat_msg}, + {.type = PROXY_PACKET_MSG, .handler = handle_proxy_msg}, }; void process_packet(uart_packet_t *packet, device_t *state) { @@ -71,62 +105,3 @@ void process_packet(uart_packet_t *packet, device_t *state) { } } } - -/**================================================== * - * ============== Receiving Packets =============== * - * ================================================== */ - -/* We are in IDLE state until we detect the packet start (0xAA 0x55) */ -void handle_idle_state(uint8_t *raw_packet, device_t *state) { - if (!uart_is_readable(SERIAL_UART)) { - return; - } - - raw_packet[0] = raw_packet[1]; /* Remember the previous byte received */ - raw_packet[1] = uart_getc(SERIAL_UART); /* Try to match packet start */ - - /* If we found 0xAA 0x55, we're in sync and can move on to read/process the packet */ - if (raw_packet[0] == START1 && raw_packet[1] == START2) { - state->receiver_state = READING_PACKET; - } -} - -/* Read a character off the line until we reach fixed packet length */ -void handle_reading_state(uint8_t *raw_packet, device_t *state, int *count) { - while (uart_is_readable(SERIAL_UART) && *count < PACKET_LENGTH) { - /* Read and store the incoming byte */ - raw_packet[(*count)++] = uart_getc(SERIAL_UART); - } - - /* Check if a complete packet is received */ - if (*count >= PACKET_LENGTH) { - state->receiver_state = PROCESSING_PACKET; - } -} - -/* Process that packet, restart counters and state machine to have it back to IDLE */ -void handle_processing_state(uart_packet_t *packet, device_t *state, int *count) { - process_packet(packet, state); - state->receiver_state = IDLE; - *count = 0; -} - -/* Very simple state machine to receive and process packets over serial */ -void receive_char(uart_packet_t *packet, device_t *state) { - uint8_t *raw_packet = (uint8_t *)packet; - static int count = 0; - - switch (state->receiver_state) { - case IDLE: - handle_idle_state(raw_packet, state); - break; - - case READING_PACKET: - handle_reading_state(raw_packet, state, &count); - break; - - case PROCESSING_PACKET: - handle_processing_state(packet, state, &count); - break; - } -} diff --git a/src/usb.c b/src/usb.c index 1027198..29d6dbf 100644 --- a/src/usb.c +++ b/src/usb.c @@ -28,7 +28,7 @@ uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, - uint16_t request_len) { + uint16_t request_len) { return 0; } @@ -44,13 +44,35 @@ void tud_hid_set_report_cb(uint8_t instance, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize) { + + /* We received a report on the config report ID */ + if (instance == ITF_NUM_HID_VENDOR && report_id == REPORT_ID_VENDOR) { + /* Security - only if config mode is enabled are we allowed to do anything. While the report_id + isn't even advertised when not in config mode, security must always be explicit and never assume */ + if (!global_state.config_mode_active) + return; + + /* We insist on a fixed size packet. No overflows. */ + if (bufsize != RAW_PACKET_LENGTH) + return; + + uart_packet_t *packet = (uart_packet_t *) (buffer + START_LENGTH); + + /* Only a certain packet types are accepted */ + if (!validate_packet(packet)) + return; + + process_packet(packet, &global_state); + } + + /* Only other set report we care about is LED state change, and that's exactly 1 byte long */ if (report_id != REPORT_ID_KEYBOARD || bufsize != 1 || report_type != HID_REPORT_TYPE_OUTPUT) return; uint8_t leds = buffer[0]; /* If we are using caps lock LED to indicate the chosen output, that has priority */ - if (KBD_LED_AS_INDICATOR) { + if (global_state.config.kbd_led_as_indicator) { leds = leds & 0xFD; /* 1111 1101 (Clear Caps Lock bit) */ if (global_state.active_output) @@ -82,65 +104,75 @@ void tud_umount_cb(void) { * ================================================== */ void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) { - uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); + uint8_t itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); + + if (dev_addr >= MAX_DEVICES || instance > MAX_INTERFACES) + return; + + hid_interface_t *iface = &global_state.iface[dev_addr-1][instance]; switch (itf_protocol) { case HID_ITF_PROTOCOL_KEYBOARD: global_state.keyboard_connected = false; - memset(&global_state.kbd_dev, 0, sizeof(global_state.kbd_dev)); break; case HID_ITF_PROTOCOL_MOUSE: - global_state.mouse_connected = false; - - /* Clear this so reconnecting a mouse doesn't try to continue in HID REPORT protocol */ - memset(&global_state.mouse_dev, 0, sizeof(global_state.mouse_dev)); break; } + + /* Also clear the interface structure, otherwise plugging something else later + might be a fun (and confusing) experience */ + memset(iface, 0, sizeof(hid_interface_t)); } -void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_report, uint16_t desc_len) { - uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); - tuh_hid_report_info_t report_info[MAX_REPORT_ITEMS] = {0}; - tuh_hid_report_info_t *info; +void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_report, uint16_t desc_len) { + uint8_t itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); + + if (dev_addr >= MAX_DEVICES || instance > MAX_INTERFACES) + return; + + /* Get interface information */ + hid_interface_t *iface = &global_state.iface[dev_addr-1][instance]; + + iface->protocol = tuh_hid_get_protocol(dev_addr, instance); + + /* Safeguard against memory corruption in case the number of instances exceeds our maximum */ + if (instance >= MAX_INTERFACES) + return; + + /* Parse the report descriptor into our internal structure. */ + parse_report_descriptor(iface, desc_report, desc_len); switch (itf_protocol) { case HID_ITF_PROTOCOL_KEYBOARD: - if (ENFORCE_PORTS && BOARD_ROLE == PICO_B) + if (global_state.config.enforce_ports && BOARD_ROLE == OUTPUT_B) return; - + + if (global_state.config.force_kbd_boot_protocol) + tuh_hid_set_protocol(dev_addr, instance, HID_PROTOCOL_BOOT); + + iface->keyboard.uses_report_id = iface->keyboard.report_id > 0; + /* Keeping this is required for setting leds from device set_report callback */ global_state.kbd_dev_addr = dev_addr; global_state.kbd_instance = instance; - global_state.keyboard_connected = true; + global_state.keyboard_connected = true; break; case HID_ITF_PROTOCOL_MOUSE: - if (ENFORCE_PORTS && BOARD_ROLE == PICO_A) + if (global_state.config.enforce_ports && BOARD_ROLE == OUTPUT_A) return; - /* Switch to using protocol report instead of boot report, it's more complicated but + /* Switch to using report protocol instead of boot, it's more complicated but at least we get all the information we need (looking at you, mouse wheel) */ if (tuh_hid_get_protocol(dev_addr, instance) == HID_PROTOCOL_BOOT) { tuh_hid_set_protocol(dev_addr, instance, HID_PROTOCOL_REPORT); } - global_state.mouse_dev.protocol = tuh_hid_get_protocol(dev_addr, instance); - parse_report_descriptor(&global_state.mouse_dev, MAX_REPORTS, desc_report, desc_len); - - global_state.mouse_connected = true; + iface->mouse.uses_report_id = iface->mouse.report_id > 0; break; - - case HID_ITF_PROTOCOL_NONE: - uint8_t num_parsed - = tuh_hid_parse_report_descriptor(&report_info[0], MAX_REPORT_ITEMS, desc_report, desc_len); - - for (int report_num = 0; report_num < num_parsed; report_num++) { - info = &report_info[report_num]; - - if (info->usage == HID_USAGE_CONSUMER_CONTROL && info->usage_page == HID_USAGE_PAGE_CONSUMER) - global_state.kbd_dev.consumer_report_id = info->report_id; - } + + case HID_ITF_PROTOCOL_NONE: break; } /* Flash local led to indicate a device was connected */ @@ -149,7 +181,7 @@ void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_re /* Also signal the other board to flash LED, to enable easy verification if serial works */ send_value(ENABLE, FLASH_LED_MSG); - /* Kick off the report querying */ + /* Kick off the report querying */ tuh_hid_receive_report(dev_addr, instance); } @@ -157,17 +189,34 @@ void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_re void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len) { uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); + if (dev_addr >= MAX_DEVICES || instance > MAX_INTERFACES) + return; + + hid_interface_t *iface = &global_state.iface[dev_addr-1][instance]; + + /* Safeguard against memory corruption in case the number of instances exceeds our maximum */ + if (instance >= MAX_INTERFACES) + return; + switch (itf_protocol) { case HID_ITF_PROTOCOL_KEYBOARD: - process_keyboard_report((uint8_t *)report, len, &global_state); + process_keyboard_report((uint8_t *)report, len, itf_protocol, iface); break; case HID_ITF_PROTOCOL_MOUSE: - process_mouse_report((uint8_t *)report, len, &global_state); + process_mouse_report((uint8_t *)report, len, itf_protocol, iface); break; + /* This can be NKRO keyboard, consumer control, system, mouse, anything. */ case HID_ITF_PROTOCOL_NONE: - process_consumer_report((uint8_t *)report, len, &global_state); + uint8_t report_id = report[0]; + + if (report_id < MAX_REPORTS) { + process_report_f receiver = iface->report_handler[report_id]; + + if (receiver != NULL) + receiver((uint8_t *)report, len, itf_protocol, iface); + } break; } @@ -175,8 +224,11 @@ void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t cons tuh_hid_receive_report(dev_addr, instance); } -/* Set protocol in a callback. If we were called, command succeeded. We're only - doing this for the mouse for now, so we can only be called about the mouse */ -void tuh_hid_set_protocol_complete_cb(uint8_t dev_addr, uint8_t idx, uint8_t protocol) { - global_state.mouse_dev.protocol = protocol; +/* Set protocol in a callback. This is tied to an interface, not a specific report ID */ +void tuh_hid_set_protocol_complete_cb(uint8_t dev_addr, uint8_t idx, uint8_t protocol) { + if (dev_addr >= MAX_DEVICES || idx > MAX_INTERFACES) + return; + + hid_interface_t *iface = &global_state.iface[dev_addr-1][idx]; + iface->protocol = protocol; } diff --git a/src/usb_descriptors.c b/src/usb_descriptors.c index 7baac05..70c20f2 100644 --- a/src/usb_descriptors.c +++ b/src/usb_descriptors.c @@ -30,29 +30,20 @@ //--------------------------------------------------------------------+ // Device Descriptors //--------------------------------------------------------------------+ -tusb_desc_device_t const desc_device = {.bLength = sizeof(tusb_desc_device_t), - .bDescriptorType = TUSB_DESC_DEVICE, - .bcdUSB = 0x0200, - .bDeviceClass = 0x00, - .bDeviceSubClass = 0x00, - .bDeviceProtocol = 0x00, - .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, // https://github.com/raspberrypi/usb-pid - .idVendor = 0x2E8A, - .idProduct = 0x107C, - .bcdDevice = 0x0100, - - .iManufacturer = 0x01, - .iProduct = 0x02, - .iSerialNumber = 0x03, - - .bNumConfigurations = 0x01}; +tusb_desc_device_t const desc_device = DEVICE_DESCRIPTOR(0x2e8a, 0x107c); + + // https://pid.codes/1209/C000/ +tusb_desc_device_t const desc_device_config = DEVICE_DESCRIPTOR(0x1209, 0xc000); // Invoked when received GET DEVICE DESCRIPTOR // Application return pointer to descriptor uint8_t const *tud_descriptor_device_cb(void) { - return (uint8_t const *)&desc_device; + if (global_state.config_mode_active) + return (uint8_t const *)&desc_device_config; + else + return (uint8_t const *)&desc_device; } //--------------------------------------------------------------------+ @@ -62,36 +53,42 @@ uint8_t const *tud_descriptor_device_cb(void) { // Relative mouse is used to overcome limitations of multiple desktops on MacOS and Windows uint8_t const desc_hid_report[] = {TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(REPORT_ID_KEYBOARD)), - TUD_HID_REPORT_DESC_ABSMOUSE(HID_REPORT_ID(REPORT_ID_MOUSE)), - TUD_HID_REPORT_DESC_CONSUMER_CTRL(HID_REPORT_ID(REPORT_ID_CONSUMER)) + TUD_HID_REPORT_DESC_ABS_MOUSE(HID_REPORT_ID(REPORT_ID_MOUSE)), + TUD_HID_REPORT_DESC_CONSUMER_CTRL(HID_REPORT_ID(REPORT_ID_CONSUMER)), + TUD_HID_REPORT_DESC_SYSTEM_CONTROL(HID_REPORT_ID(REPORT_ID_SYSTEM)) }; uint8_t const desc_hid_report_relmouse[] = {TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(REPORT_ID_RELMOUSE))}; +uint8_t const desc_hid_report_vendor[] = {TUD_HID_REPORT_DESC_VENDOR_CTRL(HID_REPORT_ID(REPORT_ID_VENDOR))}; + + // Invoked when received GET HID REPORT DESCRIPTOR // Application return pointer to descriptor // Descriptor contents must exist long enough for transfer to complete uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance) { - if (instance == ITF_NUM_HID_REL_M) { - return desc_hid_report_relmouse; - } + if (global_state.config_mode_active) + if (instance == ITF_NUM_HID_VENDOR) + return desc_hid_report_vendor; - /* Default */ - return desc_hid_report; + switch(instance) { + case ITF_NUM_HID: + return desc_hid_report; + case ITF_NUM_HID_REL_M: + return desc_hid_report_relmouse; + default: + return desc_hid_report; + } } -bool tud_mouse_report(uint8_t mode, - uint8_t buttons, - int16_t x, - int16_t y, - int8_t wheel) { +bool tud_mouse_report(uint8_t mode, uint8_t buttons, int16_t x, int16_t y, int8_t wheel) { if (mode == ABSOLUTE) { mouse_report_t report = {.buttons = buttons, .x = x, .y = y, .wheel = wheel}; return tud_hid_n_report(ITF_NUM_HID, REPORT_ID_MOUSE, &report, sizeof(report)); - } - else { - hid_mouse_report_t report = {.buttons = buttons, .x = x - 16384, .y = y - 16384, .wheel = wheel, .pan = 0}; + } else { + hid_mouse_report_t report + = {.buttons = buttons, .x = x - SCREEN_MIDPOINT, .y = y - SCREEN_MIDPOINT, .wheel = wheel, .pan = 0}; return tud_hid_n_report(ITF_NUM_HID_REL_M, REPORT_ID_RELMOUSE, &report, sizeof(report)); } } @@ -107,9 +104,11 @@ char const *string_desc_arr[] = { "Hrvoje Cavrak", // 1: Manufacturer "DeskHop Switch", // 2: Product "0", // 3: Serials, should use chip ID - "MouseHelper", // 4: Relative mouse to work around OS issues + "DeskHop Helper", // 4: Mouse Helper Interface + "DeskHop Config", // 5: Vendor Interface + "DeskHop Disk", // 6: Disk Interface #ifdef DH_DEBUG - "Debug Interface", // 5: Debug Interface + "DeskHop Debug", // 7: Debug Interface #endif }; @@ -120,6 +119,8 @@ enum { STRID_PRODUCT, STRID_SERIAL, STRID_MOUSE, + STRID_VENDOR, + STRID_DISK, STRID_DEBUG, }; @@ -166,21 +167,31 @@ uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) { // Configuration Descriptor //--------------------------------------------------------------------+ -#define EPNUM_HID 0x81 -#define EPNUM_HID_REL_M 0x82 +#define EPNUM_HID 0x81 +#define EPNUM_HID_REL_M 0x82 +#define EPNUM_HID_VENDOR 0x83 + +#define EPNUM_MSC_OUT 0x04 +#define EPNUM_MSC_IN 0x84 #ifndef DH_DEBUG -enum { ITF_NUM_TOTAL = 2 }; -#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN + TUD_HID_DESC_LEN) +#define ITF_NUM_TOTAL 2 +#define ITF_NUM_TOTAL_CONFIG 3 +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + 2 * TUD_HID_DESC_LEN) +#define CONFIG_TOTAL_LEN_CFG (TUD_CONFIG_DESC_LEN + 2 * TUD_HID_DESC_LEN + TUD_MSC_DESC_LEN) #else +#define ITF_NUM_CDC 3 +#define ITF_NUM_TOTAL 3 +#define ITF_NUM_TOTAL_CONFIG 4 -enum { ITF_NUM_CDC = 2, ITF_NUM_TOTAL = 3 }; -#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN + TUD_HID_DESC_LEN + TUD_CDC_DESC_LEN) -#define EPNUM_CDC_NOTIF 0x83 -#define EPNUM_CDC_OUT 0x04 -#define EPNUM_CDC_IN 0x84 +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + 2 * TUD_HID_DESC_LEN + TUD_CDC_DESC_LEN) +#define CONFIG_TOTAL_LEN_CFG (TUD_CONFIG_DESC_LEN + 2 * TUD_HID_DESC_LEN + TUD_MSC_DESC_LEN + TUD_CDC_DESC_LEN) + +#define EPNUM_CDC_NOTIF 0x85 +#define EPNUM_CDC_OUT 0x06 +#define EPNUM_CDC_IN 0x86 #endif @@ -205,16 +216,51 @@ uint8_t const desc_configuration[] = { EPNUM_HID_REL_M, CFG_TUD_HID_EP_BUFSIZE, 1), - #ifdef DH_DEBUG // Interface number, string index, EP notification address and size, EP data address (out, in) and size. TUD_CDC_DESCRIPTOR( ITF_NUM_CDC, STRID_DEBUG, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, CFG_TUD_CDC_EP_BUFSIZE), #endif +}; +uint8_t const desc_configuration_config[] = { + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL_CONFIG, 0, CONFIG_TOTAL_LEN_CFG, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 500), + + // Interface number, string index, protocol, report descriptor len, EP In address, size & polling interval + TUD_HID_DESCRIPTOR(ITF_NUM_HID, + STRID_PRODUCT, + HID_ITF_PROTOCOL_NONE, + sizeof(desc_hid_report), + EPNUM_HID, + CFG_TUD_HID_EP_BUFSIZE, + 1), + + TUD_HID_DESCRIPTOR(ITF_NUM_HID_VENDOR, + STRID_VENDOR, + HID_ITF_PROTOCOL_NONE, + sizeof(desc_hid_report_vendor), + EPNUM_HID_VENDOR, + CFG_TUD_HID_EP_BUFSIZE, + 1), + + TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, + STRID_DISK, + EPNUM_MSC_OUT, + EPNUM_MSC_IN, + 64), +#ifdef DH_DEBUG + // Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_CDC_DESCRIPTOR( + ITF_NUM_CDC, STRID_DEBUG, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, CFG_TUD_CDC_EP_BUFSIZE), +#endif }; uint8_t const *tud_descriptor_configuration_cb(uint8_t index) { (void)index; // for multiple configurations - return desc_configuration; + + if (global_state.config_mode_active) + return desc_configuration_config; + else + return desc_configuration; } diff --git a/src/utils.c b/src/utils.c index 45df43c..51e1782 100644 --- a/src/utils.c +++ b/src/utils.c @@ -36,20 +36,23 @@ bool verify_checksum(const uart_packet_t *packet) { return checksum == packet->checksum; } -/**================================================== * - * ============== Watchdog Functions ============== * - * ================================================== */ +uint32_t crc32_iter(uint32_t crc, const uint8_t byte) { + return crc32_lookup_table[(byte ^ crc) & 0xff] ^ (crc >> 8); +} -void kick_watchdog(device_t *state) { - /* Read the timer AFTER duplicating the core1 timestamp, - so it doesn't get updated in the meantime. */ +/* TODO - use DMA sniffer's built-in CRC32 */ +uint32_t calc_crc32(const uint8_t *s, size_t n) { + uint32_t crc = 0xffffffff; - uint64_t core1_last_loop_pass = state->core1_last_loop_pass; - uint64_t current_time = time_us_64(); + for(size_t i=0; i < n; i++) { + crc = crc32_iter(crc, s[i]); + } - /* If core1 stops updating the timestamp, we'll stop kicking the watchog and reboot */ - if (current_time - core1_last_loop_pass < CORE1_HANG_TIMEOUT_US) - watchdog_update(); + return ~crc; +} + +uint32_t calculate_firmware_crc32(void) { + return calc_crc32(ADDR_FW_RUNNING, STAGING_IMAGE_SIZE - FLASH_SECTOR_SIZE); } /* ================================================== * @@ -58,19 +61,31 @@ void kick_watchdog(device_t *state) { void wipe_config(void) { uint32_t ints = save_and_disable_interrupts(); - flash_range_erase(PICO_FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE, FLASH_SECTOR_SIZE); + flash_range_erase((uint32_t)ADDR_CONFIG - XIP_BASE, FLASH_SECTOR_SIZE); restore_interrupts(ints); } +void write_flash_page(uint32_t target_addr, uint8_t *buffer) { + /* Start of sector == first 256-byte page in a 4096 byte block */ + bool is_sector_start = (target_addr & 0xf00) == 0; + + uint32_t ints = save_and_disable_interrupts(); + if (is_sector_start) + flash_range_erase(target_addr, FLASH_SECTOR_SIZE); + + flash_range_program(target_addr, buffer, FLASH_PAGE_SIZE); + restore_interrupts(ints); +} + void load_config(device_t *state) { - const config_t *config = ADDR_CONFIG_BASE_ADDR; + const config_t *config = ADDR_CONFIG; config_t *running_config = &state->config; /* Load the flash config first, including the checksum */ - memcpy(running_config, config, sizeof(config_t)); - + memcpy(running_config, config, sizeof(config_t)); + /* Calculate and update checksum, size without checksum */ - uint8_t checksum = calc_checksum((uint8_t *)running_config, sizeof(config_t) - sizeof(uint32_t)); + uint8_t checksum = calc_crc32((uint8_t *)running_config, sizeof(config_t) - sizeof(uint32_t)); /* We expect a certain byte to start the config header */ bool magic_header_fail = (running_config->magic_header != 0xB00B1E5); @@ -87,75 +102,118 @@ void load_config(device_t *state) { } void save_config(device_t *state) { - uint8_t buf[FLASH_PAGE_SIZE]; uint8_t *raw_config = (uint8_t *)&state->config; /* Calculate and update checksum, size without checksum */ - uint8_t checksum = calc_checksum(raw_config, sizeof(config_t) - sizeof(uint32_t)); + uint8_t checksum = calc_crc32(raw_config, sizeof(config_t) - sizeof(uint32_t)); state->config.checksum = checksum; - /* Copy the config to buffer and wipe the old one */ - memcpy(buf, raw_config, sizeof(config_t)); - wipe_config(); + /* Copy the config to buffer and pad the rest with zeros */ + memcpy(state->page_buffer, raw_config, sizeof(config_t)); + memset(state->page_buffer + sizeof(config_t), 0, FLASH_PAGE_SIZE - sizeof(config_t)); - /* Disable interrupts, then write the flash page and re-enable */ - uint32_t ints = save_and_disable_interrupts(); - flash_range_program(PICO_FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE, buf, FLASH_PAGE_SIZE); - restore_interrupts(ints); + /* Write the new config to flash */ + write_flash_page((uint32_t)ADDR_CONFIG - XIP_BASE, state->page_buffer); } -/* Have something fun and entertaining when idle. */ -void screensaver_task(device_t *state) { - const int mouse_move_delay = 5000; - screensaver_t *screensaver = &state->config.output[BOARD_ROLE].screensaver; - - static mouse_report_t report = {.x = 0, .y = 0}; - static int last_pointer_move = 0; - - uint64_t current_time = time_us_64(); - uint64_t inactivity_period = current_time - state->last_activity[BOARD_ROLE]; - - /* "Randomly" chosen initial values */ - static int dx = 20; - static int dy = 25; - - /* If we're not enabled, nothing to do here. */ - if (!screensaver->enabled) - return; - - /* System is still not idle for long enough to activate or we've been running for too long */ - if (inactivity_period < screensaver->idle_time_us) - return; - - /* We exceeded the maximum permitted screensaver runtime */ - if (screensaver->max_time_us - && inactivity_period > (screensaver->max_time_us + screensaver->idle_time_us)) - return; - - /* If we're not the selected output and that is required, nothing to do here. */ - if (screensaver->only_if_inactive && CURRENT_BOARD_IS_ACTIVE_OUTPUT) - return; - - /* We're active! Now check if it's time to move the cursor yet. */ - if ((time_us_32()) - last_pointer_move < mouse_move_delay) - return; - - /* Check if we are bouncing off the walls and reverse direction in that case. */ - if (report.x + dx < MIN_SCREEN_COORD || report.x + dx > MAX_SCREEN_COORD) - dx = -dx; - - if (report.y + dy < MIN_SCREEN_COORD || report.y + dy > MAX_SCREEN_COORD) - dy = -dy; - - report.x += dx; - report.y += dy; - - /* Move mouse pointer */ - queue_mouse_report(&report, state); - - /* Update timer of the last pointer move */ - last_pointer_move = time_us_32(); +void reset_config_timer(device_t *state) { + /* Once this is reached, we leave the config mode */ + state->config_mode_timer = time_us_64() + CONFIG_MODE_TIMEOUT; } + +void _configure_flash_cs(enum gpio_override gpo, uint pin_index) { + hw_write_masked(&ioqspi_hw->io[pin_index].ctrl, + gpo << IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_LSB, + IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_BITS); +} + +bool is_bootsel_pressed(void) { + const uint CS_PIN_INDEX = 1; + uint32_t flags = save_and_disable_interrupts(); + + /* Set chip select to high impedance */ + _configure_flash_cs(GPIO_OVERRIDE_LOW, CS_PIN_INDEX); + sleep_us(20); + + /* Button pressed pulls pin DOWN, so invert */ + bool button_pressed = !(sio_hw->gpio_hi_in & (1u << CS_PIN_INDEX)); + + /* Restore chip select state */ + _configure_flash_cs(GPIO_OVERRIDE_NORMAL, CS_PIN_INDEX); + restore_interrupts(flags); + + return button_pressed; +} + +void request_byte(device_t *state, uint32_t address) { + uart_packet_t packet = { + .data32[0] = address, + .type = REQUEST_BYTE_MSG, + }; + state->fw.byte_done = false; + + queue_try_add(&global_state.uart_tx_queue, &packet); +} + +void reboot(void) { + *((volatile uint32_t*)(PPB_BASE + 0x0ED0C)) = 0x5FA0004; +} + +bool is_start_of_packet(device_t *state) { + return (uart_rxbuf[state->dma_ptr] == START1 && uart_rxbuf[NEXT_RING_IDX(state->dma_ptr)] == START2); +} + +uint32_t get_ptr_delta(uint32_t current_pointer, device_t *state) { + uint32_t delta; + + if (current_pointer >= state->dma_ptr) + delta = current_pointer - state->dma_ptr; + else + delta = DMA_RX_BUFFER_SIZE - state->dma_ptr + current_pointer; + + /* Clamp to 12 bits since it can never be bigger */ + delta = delta & 0x3FF; + + return delta; +} + +void fetch_packet(device_t *state) { + uint8_t *dst = (uint8_t *)&state->in_packet; + + for (int i = 0; i < RAW_PACKET_LENGTH; i++) { + /* Skip the header preamble */ + if (i >= START_LENGTH) + dst[i - START_LENGTH] = uart_rxbuf[state->dma_ptr]; + + state->dma_ptr = NEXT_RING_IDX(state->dma_ptr); + } +} + +/* Validating any input is mandatory. Only packets of these type are allowed + to be sent to the device over configuration endpoint. */ +bool validate_packet(uart_packet_t *packet) { + const enum packet_type_e ALLOWED_PACKETS[] = { + FLASH_LED_MSG, + GET_VAL_MSG, + SET_VAL_MSG, + WIPE_CONFIG_MSG, + SAVE_CONFIG_MSG, + REBOOT_MSG, + PROXY_PACKET_MSG, + }; + uint8_t packet_type = packet->type; + + /* Proxied packets are encapsulated in the data field, but same rules apply */ + if (packet->type == PROXY_PACKET_MSG) + packet_type = packet->data[0]; + + for (int i = 0; i < ARRAY_SIZE(ALLOWED_PACKETS); i++) { + if (ALLOWED_PACKETS[i] == packet_type) + return true; + } + return false; +} + /* ================================================== * * Debug functions @@ -175,5 +233,10 @@ int dh_debug_printf(const char *format, ...) { va_end(args); return string_len; } +#else + +int dh_debug_printf(const char *format, ...) { + return 0; +} #endif diff --git a/tools/crc32.py b/tools/crc32.py new file mode 100644 index 0000000..2929f9d --- /dev/null +++ b/tools/crc32.py @@ -0,0 +1,20 @@ +import sys +import struct +import binascii + +FLASH_SECTOR_SIZE = 4096 +MAGIC_VALUE = 0xf00d + +elf_filename = sys.argv[1] +output_filename = sys.argv[2] +version = sys.argv[3] + +with open(elf_filename, 'r+b') as f: + data = f.read() + + data = data[:-FLASH_SECTOR_SIZE] + crc32_value = binascii.crc32(data) & 0xFFFFFFFF + +with open(output_filename, 'wb') as f: + f.write(struct.pack('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 U_0!}6?HJB$z|TIL409v@A6?*Q_y7O^ literal 0 HcmV?d00001 diff --git a/webconfig/config-unpacked.htm b/webconfig/config-unpacked.htm new file mode 100644 index 0000000..001fb13 --- /dev/null +++ b/webconfig/config-unpacked.htm @@ -0,0 +1,1910 @@ + + + + + + + + + + + DeskHop Config + + + + +