From 4b539209f10d7ea58eaa769e839fda7e71bccd3c Mon Sep 17 00:00:00 2001 From: TDA2030 <1932489836@qq.com> Date: Fri, 31 Dec 2021 06:56:06 +0800 Subject: [PATCH 1/2] Support esp32s2 for the first time esp32s2: fix local_encoder, TMC4671, Motorpwm, VescCAN esp32s2: update gpio definition for my board esp32s2: add esp-idf patch esp32s2: fix adc function esp32s2: rebase to development branch add compiled bin file esp32s2: update twai api adc: reduce latency add esp32s3 support update tinyusb --- .github/workflows/build-firmware.yml | 45 +- Firmware/FFBoard/Inc/CAN.h | 2 +- Firmware/FFBoard/Inc/CommandInterface.h | 11 + Firmware/FFBoard/Inc/DebugLog.h | 40 + Firmware/FFBoard/Inc/ErrorHandler.h | 9 + .../FFBoard/Inc/FFBoardMainCommandThread.h | 10 +- Firmware/FFBoard/Inc/HidCommandInterface.h | 8 + Firmware/FFBoard/Inc/USBdevice.h | 8 +- Firmware/FFBoard/Inc/cppmain.h | 17 +- Firmware/FFBoard/Inc/critical.hpp | 4 + Firmware/FFBoard/Inc/tickhook.hpp | 2 +- Firmware/FFBoard/Src/CAN.cpp | 5 + Firmware/FFBoard/Src/CDCcomm.cpp | 15 +- Firmware/FFBoard/Src/CmdParser.cpp | 4 +- Firmware/FFBoard/Src/CommandHandler.cpp | 2 +- Firmware/FFBoard/Src/CommandInterface.cpp | 6 +- Firmware/FFBoard/Src/ErrorHandler.cpp | 2 +- .../FFBoard/Src/FFBoardMainCommandThread.cpp | 2 +- Firmware/FFBoard/Src/HidCommandInterface.cpp | 2 +- Firmware/FFBoard/Src/SystemCommands.cpp | 3 +- Firmware/FFBoard/Src/USBdevice.cpp | 2 +- Firmware/FFBoard/Src/cppmain.cpp | 47 +- Firmware/FFBoard/Src/ctickhook.cpp | 4 +- Firmware/FFBoard/Src/eeprom.c | 163 ++ Firmware/FFBoard/Src/global_callbacks.cpp | 42 +- Firmware/FFBoard/Src/voltagesense.cpp | 8 + Firmware/FFBoard/UserExtensions/Inc/ADS111X.h | 8 + .../FFBoard/UserExtensions/Inc/EncoderBissC.h | 8 + .../FFBoard/UserExtensions/Inc/FFBHIDMain.h | 8 + .../FFBoard/UserExtensions/Inc/MtEncoderSPI.h | 8 + .../FFBoard/UserExtensions/Inc/ODriveCAN.h | 6 + Firmware/FFBoard/UserExtensions/Inc/PCF8574.h | 9 + Firmware/FFBoard/UserExtensions/Inc/TMC4671.h | 7 + Firmware/FFBoard/UserExtensions/Inc/VescCAN.h | 7 + .../FFBoard/UserExtensions/Src/ADS111X.cpp | 2 +- .../UserExtensions/Src/EncoderBissC.cpp | 2 +- .../UserExtensions/Src/EncoderLocal.cpp | 36 +- .../FFBoard/UserExtensions/Src/FFBHIDMain.cpp | 3 +- .../FFBoard/UserExtensions/Src/MotorPWM.cpp | 26 +- .../UserExtensions/Src/MtEncoderSPI.cpp | 2 +- .../FFBoard/UserExtensions/Src/PCF8574.cpp | 2 +- .../FFBoard/UserExtensions/Src/TMC4671.cpp | 1 - .../FFBoard/UserExtensions/Src/VescCAN.cpp | 9 + .../UserExtensions/Src/usb_descriptors.cpp | 1 - Firmware/Targets/ESP32SX/.gitignore | 10 + Firmware/Targets/ESP32SX/CMakeLists.txt | 6 + Firmware/Targets/ESP32SX/README.md | 95 + .../ESP32SX/components/tinyusb/CMakeLists.txt | 84 + .../ESP32SX/components/tinyusb/Kconfig | 192 ++ .../tinyusb/additions/include/tinyusb.h | 102 + .../tinyusb/additions/include/tusb_config.h | 101 + .../components/tinyusb/sdkconfig.rename | 22 + .../components/tinyusb/tinyusb/LICENSE | 21 + .../components/tinyusb/tinyusb/README.rst | 146 ++ .../tinyusb/tinyusb/src/class/audio/audio.h | 933 +++++++ .../tinyusb/src/class/audio/audio_device.c | 2294 +++++++++++++++++ .../tinyusb/src/class/audio/audio_device.h | 637 +++++ .../tinyusb/src/class/bth/bth_device.c | 258 ++ .../tinyusb/src/class/bth/bth_device.h | 109 + .../tinyusb/tinyusb/src/class/cdc/cdc.h | 409 +++ .../tinyusb/src/class/cdc/cdc_device.c | 486 ++++ .../tinyusb/src/class/cdc/cdc_device.h | 260 ++ .../tinyusb/tinyusb/src/class/cdc/cdc_host.c | 249 ++ .../tinyusb/tinyusb/src/class/cdc/cdc_host.h | 134 + .../tinyusb/tinyusb/src/class/cdc/cdc_rndis.h | 301 +++ .../tinyusb/src/class/cdc/cdc_rndis_host.c | 279 ++ .../tinyusb/src/class/cdc/cdc_rndis_host.h | 63 + .../tinyusb/tinyusb/src/class/dfu/dfu.h | 119 + .../tinyusb/src/class/dfu/dfu_device.c | 458 ++++ .../tinyusb/src/class/dfu/dfu_device.h | 98 + .../tinyusb/src/class/dfu/dfu_rt_device.c | 128 + .../tinyusb/src/class/dfu/dfu_rt_device.h | 54 + .../tinyusb/tinyusb/src/class/hid/hid.h | 1119 ++++++++ .../tinyusb/src/class/hid/hid_device.c | 417 +++ .../tinyusb/src/class/hid/hid_device.h | 393 +++ .../tinyusb/tinyusb/src/class/hid/hid_host.c | 636 +++++ .../tinyusb/tinyusb/src/class/hid/hid_host.h | 152 ++ .../tinyusb/tinyusb/src/class/midi/midi.h | 212 ++ .../tinyusb/src/class/midi/midi_device.c | 545 ++++ .../tinyusb/src/class/midi/midi_device.h | 173 ++ .../tinyusb/tinyusb/src/class/msc/msc.h | 382 +++ .../tinyusb/src/class/msc/msc_device.c | 950 +++++++ .../tinyusb/src/class/msc/msc_device.h | 162 ++ .../tinyusb/tinyusb/src/class/msc/msc_host.c | 491 ++++ .../tinyusb/tinyusb/src/class/msc/msc_host.h | 119 + .../tinyusb/src/class/net/ecm_rndis_device.c | 445 ++++ .../tinyusb/tinyusb/src/class/net/ncm.h | 69 + .../tinyusb/src/class/net/ncm_device.c | 510 ++++ .../tinyusb/src/class/net/net_device.h | 118 + .../tinyusb/tinyusb/src/class/usbtmc/usbtmc.h | 316 +++ .../tinyusb/src/class/usbtmc/usbtmc_device.c | 858 ++++++ .../tinyusb/src/class/usbtmc/usbtmc_device.h | 116 + .../tinyusb/src/class/vendor/vendor_device.c | 257 ++ .../tinyusb/src/class/vendor/vendor_device.h | 136 + .../tinyusb/src/class/vendor/vendor_host.c | 146 ++ .../tinyusb/src/class/vendor/vendor_host.h | 67 + .../tinyusb/tinyusb/src/class/video/video.h | 480 ++++ .../tinyusb/src/class/video/video_device.c | 1149 +++++++++ .../tinyusb/src/class/video/video_device.h | 97 + .../tinyusb/tinyusb/src/common/tusb_common.h | 406 +++ .../tinyusb/src/common/tusb_compiler.h | 260 ++ .../tinyusb/tinyusb/src/common/tusb_error.h | 75 + .../tinyusb/tinyusb/src/common/tusb_fifo.c | 1007 ++++++++ .../tinyusb/tinyusb/src/common/tusb_fifo.h | 151 ++ .../tinyusb/tinyusb/src/common/tusb_timeout.h | 80 + .../tinyusb/tinyusb/src/common/tusb_types.h | 546 ++++ .../tinyusb/tinyusb/src/common/tusb_verify.h | 181 ++ .../tinyusb/tinyusb/src/device/dcd.h | 193 ++ .../tinyusb/tinyusb/src/device/dcd_attr.h | 225 ++ .../tinyusb/tinyusb/src/device/usbd.c | 1419 ++++++++++ .../tinyusb/tinyusb/src/device/usbd.h | 853 ++++++ .../tinyusb/tinyusb/src/device/usbd_control.c | 233 ++ .../tinyusb/tinyusb/src/device/usbd_pvt.h | 115 + .../components/tinyusb/tinyusb/src/host/hcd.h | 179 ++ .../tinyusb/tinyusb/src/host/hcd_attr.h | 105 + .../components/tinyusb/tinyusb/src/host/hub.c | 388 +++ .../components/tinyusb/tinyusb/src/host/hub.h | 196 ++ .../tinyusb/tinyusb/src/host/usbh.c | 1204 +++++++++ .../tinyusb/tinyusb/src/host/usbh.h | 99 + .../tinyusb/src/host/usbh_classdriver.h | 83 + .../tinyusb/tinyusb/src/host/usbh_control.c | 138 + .../tinyusb/tinyusb/src/osal/osal.h | 113 + .../tinyusb/tinyusb/src/osal/osal_freertos.h | 174 ++ .../tinyusb/tinyusb/src/osal/osal_mynewt.h | 174 ++ .../tinyusb/tinyusb/src/osal/osal_none.h | 204 ++ .../tinyusb/tinyusb/src/osal/osal_pico.h | 187 ++ .../tinyusb/tinyusb/src/osal/osal_rtthread.h | 130 + .../tinyusb/tinyusb/src/osal/osal_rtx4.h | 170 ++ .../portable/espressif/esp32sx/dcd_esp32sx.c | 854 ++++++ .../src/portable/template/dcd_template.c | 136 + .../components/tinyusb/tinyusb/src/tusb.c | 245 ++ .../components/tinyusb/tinyusb/src/tusb.h | 140 + .../tinyusb/tinyusb/src/tusb_option.h | 386 +++ .../Targets/ESP32SX/firmware/generate_bin.sh | 48 + .../firmware/openffb-esp32s2-hw_v1.0.bin | Bin 0 -> 609840 bytes .../firmware/openffb-esp32s2-hw_v1.1.bin | Bin 0 -> 609840 bytes .../firmware/openffb-esp32s3-hw_v1.1.bin | Bin 0 -> 626176 bytes Firmware/Targets/ESP32SX/main/CMakeLists.txt | 9 + Firmware/Targets/ESP32SX/main/app_main.c | 83 + Firmware/Targets/ESP32SX/main/cmsis_os.h | 1 + .../ESP32SX/main/cpp_target_config.cpp | 59 + Firmware/Targets/ESP32SX/main/glue.c | 667 +++++ Firmware/Targets/ESP32SX/main/glue.h | 117 + .../ESP32SX/main/glue_stm32f4xx_hal_spi.h | 304 +++ Firmware/Targets/ESP32SX/main/main.h | 1 + .../main/openffboard_esp32s2_v1.0_pins.h | 75 + .../main/openffboard_esp32s2_v1.1_pins.h | 75 + .../main/openffboard_esp32s3_v1.1_pins.h | 75 + Firmware/Targets/ESP32SX/main/stm32f4xx_hal.h | 441 ++++ .../Targets/ESP32SX/main/target_constants.h | 118 + Firmware/Targets/ESP32SX/partitions.csv | 5 + Firmware/Targets/ESP32SX/sdkconfig.defaults | 17 + 152 files changed, 32270 insertions(+), 45 deletions(-) create mode 100644 Firmware/FFBoard/Inc/DebugLog.h create mode 100644 Firmware/Targets/ESP32SX/.gitignore create mode 100644 Firmware/Targets/ESP32SX/CMakeLists.txt create mode 100644 Firmware/Targets/ESP32SX/README.md create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/CMakeLists.txt create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/Kconfig create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/additions/include/tinyusb.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/additions/include/tusb_config.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/sdkconfig.rename create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/LICENSE create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/README.rst create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/audio/audio.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/audio/audio_device.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/audio/audio_device.h create mode 100755 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/bth/bth_device.c create mode 100755 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/bth/bth_device.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_device.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_device.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_host.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_host.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_rndis.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_rndis_host.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_rndis_host.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/dfu/dfu.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/dfu/dfu_device.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/dfu/dfu_device.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/dfu/dfu_rt_device.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/dfu/dfu_rt_device.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/hid/hid.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/hid/hid_device.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/hid/hid_device.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/hid/hid_host.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/hid/hid_host.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/midi/midi.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/midi/midi_device.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/midi/midi_device.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/msc/msc.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/msc/msc_device.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/msc/msc_device.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/msc/msc_host.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/msc/msc_host.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/net/ecm_rndis_device.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/net/ncm.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/net/ncm_device.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/net/net_device.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/usbtmc/usbtmc.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/usbtmc/usbtmc_device.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/usbtmc/usbtmc_device.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/vendor/vendor_device.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/vendor/vendor_device.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/vendor/vendor_host.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/vendor/vendor_host.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/video/video.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/video/video_device.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/video/video_device.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_common.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_compiler.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_error.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_fifo.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_fifo.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_timeout.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_types.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_verify.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/device/dcd.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/device/dcd_attr.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/device/usbd.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/device/usbd.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/device/usbd_control.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/device/usbd_pvt.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/hcd.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/hcd_attr.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/hub.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/hub.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/usbh.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/usbh.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/usbh_classdriver.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/usbh_control.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal_freertos.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal_mynewt.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal_none.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal_pico.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal_rtthread.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal_rtx4.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/portable/espressif/esp32sx/dcd_esp32sx.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/portable/template/dcd_template.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/tusb.c create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/tusb.h create mode 100644 Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/tusb_option.h create mode 100755 Firmware/Targets/ESP32SX/firmware/generate_bin.sh create mode 100644 Firmware/Targets/ESP32SX/firmware/openffb-esp32s2-hw_v1.0.bin create mode 100644 Firmware/Targets/ESP32SX/firmware/openffb-esp32s2-hw_v1.1.bin create mode 100644 Firmware/Targets/ESP32SX/firmware/openffb-esp32s3-hw_v1.1.bin create mode 100644 Firmware/Targets/ESP32SX/main/CMakeLists.txt create mode 100644 Firmware/Targets/ESP32SX/main/app_main.c create mode 100644 Firmware/Targets/ESP32SX/main/cmsis_os.h create mode 100644 Firmware/Targets/ESP32SX/main/cpp_target_config.cpp create mode 100644 Firmware/Targets/ESP32SX/main/glue.c create mode 100644 Firmware/Targets/ESP32SX/main/glue.h create mode 100644 Firmware/Targets/ESP32SX/main/glue_stm32f4xx_hal_spi.h create mode 100644 Firmware/Targets/ESP32SX/main/main.h create mode 100644 Firmware/Targets/ESP32SX/main/openffboard_esp32s2_v1.0_pins.h create mode 100644 Firmware/Targets/ESP32SX/main/openffboard_esp32s2_v1.1_pins.h create mode 100644 Firmware/Targets/ESP32SX/main/openffboard_esp32s3_v1.1_pins.h create mode 100644 Firmware/Targets/ESP32SX/main/stm32f4xx_hal.h create mode 100644 Firmware/Targets/ESP32SX/main/target_constants.h create mode 100644 Firmware/Targets/ESP32SX/partitions.csv create mode 100644 Firmware/Targets/ESP32SX/sdkconfig.defaults diff --git a/.github/workflows/build-firmware.yml b/.github/workflows/build-firmware.yml index 599c0ad3b..6b6dba701 100644 --- a/.github/workflows/build-firmware.yml +++ b/.github/workflows/build-firmware.yml @@ -6,6 +6,7 @@ on: branches: - master - development + - feature/* pull_request: branches: - master @@ -40,6 +41,47 @@ jobs: name: OpenFFBoard-Firmware-${{ matrix.target }} path: ./Firmware/Output + Build-ESP32SX: + strategy: + fail-fast: false + matrix: + target: ['esp32s2', 'esp32s3'] # Targets to build + runs-on: ubuntu-latest + env: + PROJECT_PATH: Firmware/Targets/ESP32SX + container: espressif/idf:release-v4.4 + steps: + - name: Checkout repo + uses: actions/checkout@v2 + + - name: esp-idf build + run: | + pwd + ls -al + git config --global --add safe.directory $GITHUB_WORKSPACE + git log -n1 + cd /opt/esp/idf + git checkout c29343eb94d + git submodule update --init --recursive + tools/idf_tools.py --non-interactive install cmake + ./install.sh + source ./export.sh + cd $GITHUB_WORKSPACE/$PROJECT_PATH + idf.py set-target ${{ matrix.target }} + idf.py build + esptool.py --chip ${{ matrix.target }} merge_bin -o build/OpenFFBoard-Firmware-${{ matrix.target }}.bin --flash_mode dio --flash_size 4MB 0x1000 build/bootloader/bootloader.bin 0x10000 build/openffboard.bin 0x8000 build/partition_table/partition-table.bin + shell: bash + + - name: Upload a Build Artifact + uses: actions/upload-artifact@v3.0.0 + with: + # Artifact name + name: OpenFFBoard-Firmware-${{ matrix.target }} + # A file, directory or wildcard pattern that describes what to upload + path: | + Firmware/Targets/ESP32SX/build/OpenFFBoard-Firmware-*.bin + retention-days: 90 + # Release: # needs: [Build_firmware] # Requires build first # name: Release if tagged @@ -60,4 +102,5 @@ jobs: # if: startsWith(github.ref, 'refs/tags/') # with: # body_path: ${{ github.workspace }}/CHANGELOG.txt - # body: "Release notes coming soon" \ No newline at end of file + # body: "Release notes coming soon" + diff --git a/Firmware/FFBoard/Inc/CAN.h b/Firmware/FFBoard/Inc/CAN.h index 5715410b6..896a81881 100644 --- a/Firmware/FFBoard/Inc/CAN.h +++ b/Firmware/FFBoard/Inc/CAN.h @@ -24,7 +24,7 @@ typedef struct{ typedef struct{ uint8_t data[8] = {0}; - CAN_RxHeaderTypeDef header = {0,0,0,0,0,0}; + CAN_RxHeaderTypeDef header = {0,0,0,0,0,0,0}; } CAN_rx_msg; diff --git a/Firmware/FFBoard/Inc/CommandInterface.h b/Firmware/FFBoard/Inc/CommandInterface.h index d99ec7b92..a5bd5d0b5 100644 --- a/Firmware/FFBoard/Inc/CommandInterface.h +++ b/Firmware/FFBoard/Inc/CommandInterface.h @@ -14,6 +14,17 @@ #include "thread.hpp" #include "CommandHandler.h" +#ifdef HW_ESP32SX +#define CDC_COMMANDINTERFACE_THREAD_MEM 4096 +#define CDC_COMMANDINTERFACE_THREAD_PRIO (37*25/56) +#define UART_COMMANDINTERFACE_THREAD_MEM CDC_COMMANDINTERFACE_THREAD_MEM +#define UART_COMMANDINTERFACE_THREAD_PRIO CDC_COMMANDINTERFACE_THREAD_PRIO +#else +#define CDC_COMMANDINTERFACE_THREAD_MEM 512 +#define CDC_COMMANDINTERFACE_THREAD_PRIO 36 +#define UART_COMMANDINTERFACE_THREAD_MEM 150 +#define UART_COMMANDINTERFACE_THREAD_PRIO CDC_COMMANDINTERFACE_THREAD_PRIO +#endif class FFBoardMainCommandThread; diff --git a/Firmware/FFBoard/Inc/DebugLog.h b/Firmware/FFBoard/Inc/DebugLog.h new file mode 100644 index 000000000..da74a9cb0 --- /dev/null +++ b/Firmware/FFBoard/Inc/DebugLog.h @@ -0,0 +1,40 @@ + +#ifndef DEBUGLOG_H_ +#define DEBUGLOG_H_ + +#include "target_constants.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// LOG DEBUG +#ifdef HW_ESP32SX +#define FFB_LOGI(format, ...) do { \ + ESP_LOGI(__FUNCTION__, "%s:%d -- " format, __FILE__, __LINE__ __VA_OPT__(,) __VA_ARGS__); \ + } while(0) +#define FFB_LOGW(format, ...) do { \ + ESP_LOGW(__FUNCTION__, "%s:%d -- " format, __FILE__, __LINE__ __VA_OPT__(,) __VA_ARGS__); \ + } while(0) +#define FFB_LOGE(format, ...) do { \ + ESP_LOGE(__FUNCTION__, "%s:%d -- " format, __FILE__, __LINE__ __VA_OPT__(,) __VA_ARGS__); \ + } while(0) +#define FFB_LOGD(format, ...) do { \ + ESP_LOGD(__FUNCTION__, "%s:%d -- " format, __FILE__, __LINE__ __VA_OPT__(,) __VA_ARGS__); \ + } while(0) + +#else + +#define FFB_LOGI(format, ...) +#define FFB_LOGW(format, ...) +#define FFB_LOGE(format, ...) +#define FFB_LOGD(format, ...) + +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* DEBUGLOG_H_ */ diff --git a/Firmware/FFBoard/Inc/ErrorHandler.h b/Firmware/FFBoard/Inc/ErrorHandler.h index d62f1812a..65af1c9c8 100644 --- a/Firmware/FFBoard/Inc/ErrorHandler.h +++ b/Firmware/FFBoard/Inc/ErrorHandler.h @@ -11,6 +11,15 @@ #include #include "thread.hpp" #include "CommandHandler.h" +#include "target_constants.h" + +#ifdef HW_ESP32SX +#define ERROR_PRINTER_MEM 4096 +#define ERROR_PRINTER_PRIO 19*25/56 +#else +#define ERROR_PRINTER_MEM 512 +#define ERROR_PRINTER_PRIO 19 +#endif /* * Error code definitions diff --git a/Firmware/FFBoard/Inc/FFBoardMainCommandThread.h b/Firmware/FFBoard/Inc/FFBoardMainCommandThread.h index c92b35a08..a8489392c 100644 --- a/Firmware/FFBoard/Inc/FFBoardMainCommandThread.h +++ b/Firmware/FFBoard/Inc/FFBoardMainCommandThread.h @@ -11,9 +11,7 @@ #include #include "cppmain.h" -#include "main.h" #include -#include "cdc_device.h" #include "ChoosableClass.h" #include "CommandHandler.h" #include @@ -24,6 +22,14 @@ #include "CommandInterface.h" +#ifdef HW_ESP32SX +#define FFBOARDMAINCOMMANDTHREAD_MEM 4096 +#define FFBOARDMAINCOMMANDTHREAD_PRIO 32*25/56 +#else +#define FFBOARDMAINCOMMANDTHREAD_MEM 700 +#define FFBOARDMAINCOMMANDTHREAD_PRIO 32 +#endif + class FFBoardMain; //class CommandInterface; diff --git a/Firmware/FFBoard/Inc/HidCommandInterface.h b/Firmware/FFBoard/Inc/HidCommandInterface.h index 36eab6f17..2868eace6 100644 --- a/Firmware/FFBoard/Inc/HidCommandInterface.h +++ b/Firmware/FFBoard/Inc/HidCommandInterface.h @@ -13,6 +13,14 @@ #include "ffb_defs.h" #include "CommandHandler.h" +#ifdef HW_ESP32SX +#define HID_COMMANDINTERFACE_MEM 2048 +#define HID_COMMANDINTERFACE_PRIO 18*25/56 +#else +#define HID_COMMANDINTERFACE_MEM 128 +#define HID_COMMANDINTERFACE_PRIO 18 +#endif + enum class HidCmdType : uint8_t {write = 0, request = 1, info = 2, writeAddr = 3, requestAddr = 4,ACK = 10, notFound = 13, notification = 14, err = 15}; diff --git a/Firmware/FFBoard/Inc/USBdevice.h b/Firmware/FFBoard/Inc/USBdevice.h index 9dc879e62..b98656d91 100644 --- a/Firmware/FFBoard/Inc/USBdevice.h +++ b/Firmware/FFBoard/Inc/USBdevice.h @@ -15,7 +15,13 @@ #define USB_STRING_DESC_BUF_SIZE 32 - +#ifdef HW_ESP32SX +#define USBDEVICE_MEM 4096 +#define USBDEVICE_PRIO 40*25/56 +#else +#define USBDEVICE_MEM 256 +#define USBDEVICE_PRIO 40 +#endif /** * This class defines a usb device and implements callbacks for getting the basic diff --git a/Firmware/FFBoard/Inc/cppmain.h b/Firmware/FFBoard/Inc/cppmain.h index c30f99714..8befe1b65 100644 --- a/Firmware/FFBoard/Inc/cppmain.h +++ b/Firmware/FFBoard/Inc/cppmain.h @@ -22,18 +22,23 @@ extern "C" { #include "eeprom_addresses.h" #include "main.h" -#include "cmsis_compiler.h" - +#include "DebugLog.h" void cppmain(); +#ifndef HW_ESP32SX +#include "cmsis_compiler.h" void usb_init(); void tudThread(void *argument); - +#endif #ifdef __cplusplus } static inline bool inIsr(){ +#ifdef HW_ESP32SX + return xPortInIsrContext(); +#else return (__get_PRIMASK() != 0U) || (__get_IPSR() != 0U); +#endif } template @@ -53,12 +58,14 @@ T clip(T v, C l, C h) { return { v > h ? h : v < l ? l : v }; } - +#ifdef HW_ESP32SX +#define micros() esp_timer_get_time() // Returns microsecond scaled time +#else uint32_t micros(); // Returns microsecond scaled time unsigned long getRunTimeCounterValue(void); // RTOS void refreshWatchdog(); // Refreshes the watchdog - +#endif #endif diff --git a/Firmware/FFBoard/Inc/critical.hpp b/Firmware/FFBoard/Inc/critical.hpp index 80dbac826..71d86db2a 100644 --- a/Firmware/FFBoard/Inc/critical.hpp +++ b/Firmware/FFBoard/Inc/critical.hpp @@ -65,7 +65,9 @@ class CriticalSection { */ static inline void Enter() { + #ifndef HW_ESP32SX taskENTER_CRITICAL(); + #endif } /** @@ -73,7 +75,9 @@ class CriticalSection { */ static inline void Exit() { + #ifndef HW_ESP32SX taskEXIT_CRITICAL(); + #endif } /** diff --git a/Firmware/FFBoard/Inc/tickhook.hpp b/Firmware/FFBoard/Inc/tickhook.hpp index 76ec36600..486fea401 100644 --- a/Firmware/FFBoard/Inc/tickhook.hpp +++ b/Firmware/FFBoard/Inc/tickhook.hpp @@ -44,7 +44,7 @@ #include "task.h" #include -#if ( configUSE_TICK_HOOK == 1 ) +#if ( configUSE_TICK_HOOK == 1) && !defined(HW_ESP32SX) /** * FreeRTOS expects this function to exist and requires it to be diff --git a/Firmware/FFBoard/Src/CAN.cpp b/Firmware/FFBoard/Src/CAN.cpp index f60e8ebbc..cc8d3de35 100644 --- a/Firmware/FFBoard/Src/CAN.cpp +++ b/Firmware/FFBoard/Src/CAN.cpp @@ -205,7 +205,12 @@ void CANPort::setSpeedPreset(uint8_t preset){ takeSemaphore(); HAL_CAN_Stop(this->hcan); HAL_CAN_AbortTxRequest(hcan, txMailbox); +#ifdef HW_ESP32SX + const uint32_t rate[6]={50000, 100000, 125000, 250000, 500000, 1000000}; //bit/s + glue_can_set_speed(this->hcan, rate[preset]); +#else this->hcan->Instance->BTR = canSpeedBTR_preset[preset]; +#endif HAL_CAN_ResetError(hcan); HAL_CAN_Start(this->hcan); diff --git a/Firmware/FFBoard/Src/CDCcomm.cpp b/Firmware/FFBoard/Src/CDCcomm.cpp index 5bea71355..dd5a85845 100644 --- a/Firmware/FFBoard/Src/CDCcomm.cpp +++ b/Firmware/FFBoard/Src/CDCcomm.cpp @@ -29,7 +29,12 @@ CDCcomm::~CDCcomm() { * Global callback if cdc transfer is finished. Used to retry a failed transfer */ void CDCcomm::cdcFinished(uint8_t itf){ - cdcSems[itf].Give(); + bool ret = cdcSems[itf].Give(); + if (true != ret){ + // FFB_LOGE("cdcSem give error\n"); + return; + } + if(CDCcomm::usb_busy_retry && CDCcomm::remainingStrs[itf].length() > 0){ cdcSend(&CDCcomm::remainingStrs[itf], itf); // Retry with remaining string } @@ -67,8 +72,12 @@ uint16_t CDCcomm::cdcSend(std::string* reply,uint8_t itf){ cdcSems[itf].Take(); uint32_t bufferRemaining = tud_cdc_n_write_available(itf); uint32_t cdc_sent = tud_cdc_n_write(itf,reply->c_str(), std::min(reply->length(),bufferRemaining)); - if(!usb_busy_retry) // We did not retransmit so flush now. otherwise TUD will flush if we were in the callback before - tud_cdc_n_write_flush(itf); + if(!usb_busy_retry){ // We did not retransmit so flush now. otherwise TUD will flush if we were in the callback before + int res = tud_cdc_n_write_flush(itf); + if (!res) { + FFB_LOGW("flush failed (res: %d, len=%d)", res, reply->length()); + } + } // If we can't write the whole reply copy remainder to send later if(cdc_sent < reply->length()){ diff --git a/Firmware/FFBoard/Src/CmdParser.cpp b/Firmware/FFBoard/Src/CmdParser.cpp index 73640b9fd..f3c777b2e 100644 --- a/Firmware/FFBoard/Src/CmdParser.cpp +++ b/Firmware/FFBoard/Src/CmdParser.cpp @@ -142,8 +142,8 @@ bool CmdParser::parse(std::vector& commands){ cmd.type = CMDtype::err; }else{ - uint32_t peq = word.find('=', 0); // set - uint32_t pqm = word.find('?', 0); // read with var + int32_t peq = word.find('=', 0); // set + int32_t pqm = word.find('?', 0); // read with var // \n if(pqm == std::string::npos && peq == std::string::npos){ diff --git a/Firmware/FFBoard/Src/CommandHandler.cpp b/Firmware/FFBoard/Src/CommandHandler.cpp index ae7c3fcbc..5dc07e02d 100644 --- a/Firmware/FFBoard/Src/CommandHandler.cpp +++ b/Firmware/FFBoard/Src/CommandHandler.cpp @@ -132,7 +132,7 @@ std::string CommandHandler::getCsvHelpstring(){ for(CmdHandlerCommanddef& cmd : registeredCommands){ if(cmd.helpstring != nullptr && cmd.cmd != nullptr){ char cmdhex[11]; - std::snprintf(cmdhex,11,"0x%lX",cmd.cmdId); + std::snprintf(cmdhex,11,"0x%X",cmd.cmdId); helpstring.append(cmd.cmd); helpstring += ','; helpstring += std::string(cmdhex); diff --git a/Firmware/FFBoard/Src/CommandInterface.cpp b/Firmware/FFBoard/Src/CommandInterface.cpp index 9aa004c91..e2f00bb1c 100644 --- a/Firmware/FFBoard/Src/CommandInterface.cpp +++ b/Firmware/FFBoard/Src/CommandInterface.cpp @@ -240,7 +240,7 @@ void StringCommandInterface::generateReplyFromCmd(std::string& replyPart,const P */ -CDC_CommandInterface::CDC_CommandInterface() : StringCommandInterface(1024), Thread("CDCCMD", 512, 37) { +CDC_CommandInterface::CDC_CommandInterface() : StringCommandInterface(1024), Thread("CDCCMD", CDC_COMMANDINTERFACE_THREAD_MEM, CDC_COMMANDINTERFACE_THREAD_PRIO) { parser.setClearBufferTimeout(parserTimeout); this->Start(); } @@ -273,11 +273,13 @@ void CDC_CommandInterface::sendReplies(const std::vector& results if(HAL_GetTick() - lastSendTime > parserTimeout){ bufferLength = 0; + FFB_LOGE("parserTimeout"); resultsBuffer.clear(); // Empty buffer because we were not able to reply in time to prevent the full buffer from blocking future commands //CDCcomm::clearRemainingBuffer(0); } if( (!enableBroadcastFromOtherInterfaces && originalInterface != this) ){ + FFB_LOGW("not originalInterface"); return; } for(const CommandResult& r : results){ @@ -324,7 +326,7 @@ bool CDC_CommandInterface::readyToSend(){ */ extern UARTPort external_uart; // defined in cpp_target_config.cpp -UART_CommandInterface::UART_CommandInterface(uint32_t baud) : UARTDevice(external_uart),Thread("UARTCMD", 150, 36),StringCommandInterface(512), baud(baud){ // +UART_CommandInterface::UART_CommandInterface(uint32_t baud) : UARTDevice(external_uart),Thread("UARTCMD", UART_COMMANDINTERFACE_THREAD_MEM, UART_COMMANDINTERFACE_THREAD_PRIO),StringCommandInterface(512), baud(baud){ // uartconfig = uartport->getConfig(); if(baud != 0){ uartconfig.BaudRate = this->baud; diff --git a/Firmware/FFBoard/Src/ErrorHandler.cpp b/Firmware/FFBoard/Src/ErrorHandler.cpp index ffb15dc02..f775bcee9 100644 --- a/Firmware/FFBoard/Src/ErrorHandler.cpp +++ b/Firmware/FFBoard/Src/ErrorHandler.cpp @@ -131,7 +131,7 @@ void ErrorHandler::errorCallback(const Error &error, bool cleared){ // return info; //} -ErrorPrinter::ErrorPrinter() : Thread("errprint",256,19){ // Higher than default task but low. +ErrorPrinter::ErrorPrinter() : Thread("errprint",ERROR_PRINTER_MEM,ERROR_PRINTER_PRIO){ // Higher than default task but low. this->Start(); } diff --git a/Firmware/FFBoard/Src/FFBoardMainCommandThread.cpp b/Firmware/FFBoard/Src/FFBoardMainCommandThread.cpp index 13512c4ac..f7993ba97 100644 --- a/Firmware/FFBoard/Src/FFBoardMainCommandThread.cpp +++ b/Firmware/FFBoard/Src/FFBoardMainCommandThread.cpp @@ -30,7 +30,7 @@ Error FFBoardMainCommandThread::cmdExecError = Error(ErrorCode::cmdExecutionErro FFBoardMainCommandThread* commandThread; // Note: allocate enough memory for the command thread to store replies -FFBoardMainCommandThread::FFBoardMainCommandThread(FFBoardMain* mainclass) : Thread("CMD_MAIN",700, 32) { +FFBoardMainCommandThread::FFBoardMainCommandThread(FFBoardMain* mainclass) : Thread("CMD_MAIN",FFBOARDMAINCOMMANDTHREAD_MEM, FFBOARDMAINCOMMANDTHREAD_PRIO) { //main = mainclass; commandThread = this; this->Start(); diff --git a/Firmware/FFBoard/Src/HidCommandInterface.cpp b/Firmware/FFBoard/Src/HidCommandInterface.cpp index c6bcc7a96..8fb08f6a9 100644 --- a/Firmware/FFBoard/Src/HidCommandInterface.cpp +++ b/Firmware/FFBoard/Src/HidCommandInterface.cpp @@ -19,7 +19,7 @@ HID_CommandInterface* HID_CommandInterface::globalInterface = nullptr; -HID_CommandInterface::HID_CommandInterface() : cpp_freertos::Thread("HIDCMD",128,18){ +HID_CommandInterface::HID_CommandInterface() : cpp_freertos::Thread("HIDCMD",HID_COMMANDINTERFACE_MEM,HID_COMMANDINTERFACE_PRIO){ globalInterface = this; this->Start(); } diff --git a/Firmware/FFBoard/Src/SystemCommands.cpp b/Firmware/FFBoard/Src/SystemCommands.cpp index 331f3132a..24d312c6b 100644 --- a/Firmware/FFBoard/Src/SystemCommands.cpp +++ b/Firmware/FFBoard/Src/SystemCommands.cpp @@ -11,8 +11,7 @@ #include "voltagesense.h" #include #include "constants.h" -#include "task.h" -#include "FreeRTOSConfig.h" + extern ClassChooser mainchooser; extern FFBoardMain* mainclass; //extern static const uint8_t SW_VERSION_INT[3]; diff --git a/Firmware/FFBoard/Src/USBdevice.cpp b/Firmware/FFBoard/Src/USBdevice.cpp index 5c2cdc40b..1233bffed 100644 --- a/Firmware/FFBoard/Src/USBdevice.cpp +++ b/Firmware/FFBoard/Src/USBdevice.cpp @@ -11,7 +11,7 @@ uint16_t _desc_str[USB_STRING_DESC_BUF_SIZE]; // String buffer USBdevice::USBdevice(const tusb_desc_device_t* deviceDesc,const uint8_t (*confDesc),const usb_string_desc_t* strings, uint8_t appendSerial) : -Thread("USB", 256, 40), desc_device(deviceDesc), desc_conf(confDesc), string_desc(strings),appendSerial(appendSerial) { +Thread("USB", USBDEVICE_MEM, USBDEVICE_PRIO), desc_device(deviceDesc), desc_conf(confDesc), string_desc(strings),appendSerial(appendSerial) { } diff --git a/Firmware/FFBoard/Src/cppmain.cpp b/Firmware/FFBoard/Src/cppmain.cpp index 0fcf6d8ba..bc7efa58b 100644 --- a/Firmware/FFBoard/Src/cppmain.cpp +++ b/Firmware/FFBoard/Src/cppmain.cpp @@ -5,12 +5,12 @@ #include "global_callbacks.h" #include "cpp_target_config.h" #include "cmsis_os.h" -#include "stm32f4xx_hal_flash.h" +#ifndef HW_ESP32SX #include "tusb.h" - uint32_t clkmhz = HAL_RCC_GetHCLKFreq() / 100000; extern TIM_HandleTypeDef TIM_MICROS; +#endif #ifdef HAL_IWDG_MODULE_ENABLED extern IWDG_HandleTypeDef hiwdg; // Watchdog @@ -21,7 +21,11 @@ bool mainclassChosen = false; uint16_t main_id = 0; +#ifdef HW_ESP32SX +FFBoardMain* mainclass; +#else FFBoardMain* mainclass __attribute__((section (".ccmram"))); +#endif ClassChooser mainchooser(class_registry); @@ -30,6 +34,7 @@ StackType_t usb_device_stack[USBD_STACK_SIZE]; StaticTask_t usb_device_taskdef; +#ifndef HW_ESP32SX void cppmain() { #ifdef FW_DEVID if(HAL_GetDEVID() != FW_DEVID){ @@ -140,3 +145,41 @@ unsigned long getRunTimeCounterValue(void){ return micros(); } +#else // For HW_ESP32SX +static const char *TAG = "cppmain"; +void cppmain() +{ + if ( EE_Init() != HAL_OK) { + Error_Handler(); + } + + startADC(); // enable ADC DMA + + // If switch pressed at boot select failsafe implementation + if (HAL_GPIO_ReadPin(BUTTON_A_GPIO_Port, BUTTON_A_Pin) == 0) { + ESP_LOGI(TAG, "button pressed, boot select failsafe implementation"); + main_id = 0; + } else { + if (!Flash_ReadWriteDefault(ADR_CURRENT_CONFIG, &main_id, 0)) { + Error_Handler(); + } + } + + ESP_LOGI(TAG, "main_id=%d", main_id); + mainclass = mainchooser.Create(main_id); + if (mainclass == nullptr) { // invalid id + mainclass = mainchooser.Create(0); // Baseclass + } + mainclassChosen = true; + + mainclass->usbInit(); // Let mainclass initialize usb + + while (running) { + mainclass->update(); + updateLeds(); + //external_spi.process(); + vTaskDelay(1); + } +} +#endif + diff --git a/Firmware/FFBoard/Src/ctickhook.cpp b/Firmware/FFBoard/Src/ctickhook.cpp index 3702370b3..56d33182b 100644 --- a/Firmware/FFBoard/Src/ctickhook.cpp +++ b/Firmware/FFBoard/Src/ctickhook.cpp @@ -36,10 +36,10 @@ * ***************************************************************************/ - +#include "target_constants.h" #include "tickhook.hpp" -#if ( configUSE_TICK_HOOK == 1 ) +#if ( configUSE_TICK_HOOK == 1) && !defined(HW_ESP32SX) using namespace std; using namespace cpp_freertos; diff --git a/Firmware/FFBoard/Src/eeprom.c b/Firmware/FFBoard/Src/eeprom.c index 3d3305a94..c83208f36 100644 --- a/Firmware/FFBoard/Src/eeprom.c +++ b/Firmware/FFBoard/Src/eeprom.c @@ -49,6 +49,168 @@ /* Includes ------------------------------------------------------------------*/ #include "eeprom.h" #include "eeprom_addresses.h" + +#ifdef HW_ESP32SX + +#include +#include "esp_system.h" +#include "esp_log.h" +#include "nvs_flash.h" + +static const char *TAG = "eeprom"; + +#define _VAL_NAMESPACE "DefParam" + +#define PARAM_CHECK(a, str, label) \ + if (!(a)) { \ + ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, str); \ + goto label; \ + } + +static esp_err_t param_save(const char *space_name, const char *key, void *param, uint16_t len) +{ + esp_err_t ret = ESP_ERR_INVALID_ARG; + PARAM_CHECK(NULL != space_name, "Pointer of space_name is invalid", OPEN_FAIL); + PARAM_CHECK(NULL != key, "Pointer of key is invalid", OPEN_FAIL); + PARAM_CHECK(NULL != param, "Pointer of param is invalid", OPEN_FAIL); + nvs_handle_t my_handle; + ret = nvs_open(space_name, NVS_READWRITE, &my_handle); + PARAM_CHECK(ESP_OK == ret, "nvs open failed", OPEN_FAIL); + ret = nvs_set_blob(my_handle, key, param, len); + PARAM_CHECK(ESP_OK == ret, "nvs set blob failed", SAVE_FINISH); + ret = nvs_commit(my_handle); + +SAVE_FINISH: + nvs_close(my_handle); + +OPEN_FAIL: + return ret; +} + +static esp_err_t param_load(const char *space_name, const char *key, void *dest) +{ + esp_err_t ret = ESP_ERR_INVALID_ARG; + PARAM_CHECK(NULL != space_name, "Pointer of space_name is invalid", OPEN_FAIL); + PARAM_CHECK(NULL != key, "Pointer of key is invalid", OPEN_FAIL); + PARAM_CHECK(NULL != dest, "Pointer of dest is invalid", OPEN_FAIL); + nvs_handle_t my_handle; + size_t required_size = 0; + ret = nvs_open(space_name, NVS_READWRITE, &my_handle); + PARAM_CHECK(ESP_OK == ret, "nvs open failed", OPEN_FAIL); + ret = nvs_get_blob(my_handle, key, NULL, &required_size); + PARAM_CHECK(ESP_OK == ret, "nvs get blob failed", LOAD_FINISH); + if (required_size == 0) { + ESP_LOGW(TAG, "the target you want to load has never been saved"); + ret = ESP_FAIL; + goto LOAD_FINISH; + } + ret = nvs_get_blob(my_handle, key, dest, &required_size); + +LOAD_FINISH: + nvs_close(my_handle); + +OPEN_FAIL: + return ret; +} + +__attribute__((__unused__)) +static esp_err_t param_erase(const char *space_name, const char *key) +{ + esp_err_t ret = ESP_ERR_INVALID_ARG; + PARAM_CHECK(NULL != space_name, "Pointer of space_name is invalid", OPEN_FAIL); + PARAM_CHECK(NULL != key, "Pointer of key is invalid", OPEN_FAIL); + nvs_handle_t my_handle; + ret = nvs_open(space_name, NVS_READWRITE, &my_handle); + PARAM_CHECK(ESP_OK == ret, "nvs open failed", OPEN_FAIL); + ret = nvs_erase_key(my_handle, key); + PARAM_CHECK(ESP_OK == ret, "nvs erase failed", ERASE_FINISH); + ret = nvs_commit(my_handle); + +ERASE_FINISH: + nvs_close(my_handle); + +OPEN_FAIL: + return ret; +} + +/* Virtual address defined by the user: 0xFFFF value is prohibited */ +//extern uint16_t VirtAddVarTab[NB_OF_VAR]; + +static void inline make_key(uint16_t addr, char *key) +{ + sprintf(key, "0x%x", addr); +} +/** + * @brief Restore the pages to a known good state in case of page's status + * corruption after a power loss. + * @param None. + * @retval - Flash error code: on write Flash error + * - FLASH_COMPLETE: on success + */ +uint16_t EE_Init(void) +{ + return HAL_OK; +} + + +/** + * @brief Returns the last stored variable data, if found, which correspond to + * the passed virtual address + * @param VirtAddress: Variable virtual address + * @param Data: Global variable contains the read variable value + * @retval Success or error status: + * - 0: if variable was found + * - 1: if the variable was not found + * - NO_VALID_PAGE: if no valid page was found. + */ +uint16_t EE_ReadVariable(uint16_t VirtAddress, uint16_t *Data) +{ + char key[16] = {0}; + make_key(VirtAddress, key); + esp_err_t ret = param_load(_VAL_NAMESPACE, key, Data); + if (ESP_OK == ret) { + ESP_LOGI(TAG, "read nvs %s=%d", key, *Data); + } else { + ESP_LOGE(TAG, "read nvs %s error", key); + } + /* Return ReadStatus value: (0: variable exist, 1: variable doesn't exist) */ + return ret == ESP_OK ? 0 : 1; +} + +/** + * @brief Writes/upadtes variable data in EEPROM. + * @param VirtAddress: Variable virtual address + * @param Data: 16 bit data to be written + * @retval Success or error status: + * - FLASH_COMPLETE: on success + * - PAGE_FULL: if valid page is full + * - NO_VALID_PAGE: if no valid page was found + * - Flash error code: on write Flash error + */ +uint16_t EE_WriteVariable(uint16_t VirtAddress, uint16_t Data) +{ + char key[16] = {0}; + make_key(VirtAddress, key); + ESP_LOGI(TAG, "write nvs %s=%d", key, Data); + esp_err_t ret = param_save(_VAL_NAMESPACE, key, &Data, 2); + + return ret == ESP_OK ? 0 : 1; +} + +/** + * @brief Erases PAGE and PAGE1 and writes VALID_PAGE header to PAGE + * @param None + * @retval Status of the last operation (Flash write or erase) done during + * EEPROM formating + */ +HAL_StatusTypeDef EE_Format(void) +{ + ESP_ERROR_CHECK(nvs_flash_erase()); + return HAL_OK; +} + +#else // STM32 FLASH + /* Private typedef -----------------------------------------------------------*/ /* Private define ------------------------------------------------------------*/ /* Private macro -------------------------------------------------------------*/ @@ -729,6 +891,7 @@ static uint16_t EE_PageTransfer(uint16_t VirtAddress, uint16_t Data) /* Return last operation flash status */ return FlashStatus; } +#endif /** * @} diff --git a/Firmware/FFBoard/Src/global_callbacks.cpp b/Firmware/FFBoard/Src/global_callbacks.cpp index 1b8262d3c..f33712cb1 100644 --- a/Firmware/FFBoard/Src/global_callbacks.cpp +++ b/Firmware/FFBoard/Src/global_callbacks.cpp @@ -58,9 +58,11 @@ extern ADC_HandleTypeDef hadc3; * Callback after an adc finished conversion */ void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){ +#ifndef HW_ESP32SX //Pulse braking mosfet if internal voltage is higher than supply. if(hadc == &VSENSE_HADC) brakeCheck(); +#endif uint8_t chans = 0; volatile uint32_t* buf = getAnalogBuffer(hadc,&chans); @@ -76,16 +78,21 @@ void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){ * Note: this is normally generated in the main.c * A call to HAL_TIM_PeriodElapsedCallback_CPP must be added there instead! */ +#ifndef HW_ESP32SX __weak void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef* htim){ HAL_TIM_PeriodElapsedCallback_CPP(htim); } - +#endif void HAL_TIM_PeriodElapsedCallback_CPP(TIM_HandleTypeDef* htim) { for(TimerHandler* c : TimerHandler::timerHandlers){ c->timerElapsed(htim); } } +#ifdef __cplusplus +extern "C" { +#endif + /** * Callback for GPIO interrupts */ @@ -107,7 +114,7 @@ void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart){ } } -#ifdef CANBUS +#if defined(CANBUS) && !defined(HW_ESP32SX) // CAN uint8_t canRxBuf0[8]; @@ -253,7 +260,9 @@ void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c){ c->I2cError(hi2c); } } - +#ifdef __cplusplus +} +#endif //void HAL_I2C_MemRxCpltCallback(I2C_HandleTypeDef *hi2c); //void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c); @@ -365,6 +374,32 @@ void tud_resume_cb(){ mainclass->usbResume(); } +#ifdef HW_ESP32SX +extern "C" volatile uint32_t* getAnalogBuffer(ADC_HandleTypeDef* hadc,uint8_t* chans) +{ + uint8_t result[12] = {0}; + uint32_t ret_num = 0; + esp_err_t ret = adc_digi_read_bytes(result, 12, &ret_num, ADC_MAX_DELAY); + if (ret == ESP_OK || ret == ESP_ERR_INVALID_STATE) + { + for (size_t i = 0; i < ret_num; i+=2) + { + adc_digi_output_data_t *p = (adc_digi_output_data_t *)&result[i]; +#ifdef CONFIG_IDF_TARGET_ESP32S3 + ADC1_BUF[p->type2.channel] = p->type2.data; +#elif defined CONFIG_IDF_TARGET_ESP32S2 + ADC1_BUF[p->type1.channel] = p->type1.data; +#endif + } + ESP_LOGV(__FUNCTION__, "[%d] Channel: 0|%04d, 1|%04d, 2|%04d", ret_num, + ADC1_BUF[0], + ADC1_BUF[1], + ADC1_BUF[2]); + } + *chans = ADC1_CHANNELS; + return ADC1_BUF; +} +#else volatile uint32_t* getAnalogBuffer(ADC_HandleTypeDef* hadc,uint8_t* chans){ #ifdef ADC1_CHANNELS @@ -389,6 +424,7 @@ volatile uint32_t* getAnalogBuffer(ADC_HandleTypeDef* hadc,uint8_t* chans){ #endif return NULL; } +#endif void startADC(){ #ifdef ADC1_CHANNELS diff --git a/Firmware/FFBoard/Src/voltagesense.cpp b/Firmware/FFBoard/Src/voltagesense.cpp index f9feb2244..d26422558 100644 --- a/Firmware/FFBoard/Src/voltagesense.cpp +++ b/Firmware/FFBoard/Src/voltagesense.cpp @@ -40,11 +40,19 @@ void setupBrakePin(uint32_t vdiffAct,uint32_t vdiffDeact,uint32_t vMax){ } uint16_t getIntV(){ +#ifdef HW_ESP32SX + return 20000; // ESP32S2 don't have enough gpio to measure v_int +#else return VSENSE_ADC_BUF[ADC_CHAN_VINT] * vSenseMult; +#endif } uint16_t getExtV(){ +#ifdef HW_ESP32SX + return 20000; // ESP32S2 don't have enough gpio to measure v_ext +#else return VSENSE_ADC_BUF[ADC_CHAN_VEXT] * vSenseMult; +#endif } void brakeCheck(){ diff --git a/Firmware/FFBoard/UserExtensions/Inc/ADS111X.h b/Firmware/FFBoard/UserExtensions/Inc/ADS111X.h index 3989a602d..c755b6cab 100644 --- a/Firmware/FFBoard/UserExtensions/Inc/ADS111X.h +++ b/Firmware/FFBoard/UserExtensions/Inc/ADS111X.h @@ -19,6 +19,14 @@ #ifdef I2C_PORT +#ifdef HW_ESP32SX +#define ADS111X_ANALOG_THREAD_MEM 1024 +#define ADS111X_ANALOG_THREAD_PRIO (25*25/56) +#else +#define ADS111X_ANALOG_THREAD_MEM 64 +#define ADS111X_ANALOG_THREAD_PRIO 25 +#endif + class ADS111X : public I2CDevice { public: diff --git a/Firmware/FFBoard/UserExtensions/Inc/EncoderBissC.h b/Firmware/FFBoard/UserExtensions/Inc/EncoderBissC.h index 1fc47fab9..7118656be 100644 --- a/Firmware/FFBoard/UserExtensions/Inc/EncoderBissC.h +++ b/Firmware/FFBoard/UserExtensions/Inc/EncoderBissC.h @@ -20,6 +20,14 @@ #include "semaphore.hpp" #include "array" +#ifdef HW_ESP32SX +#define ENCODERBISSC_THREAD_MEM 1024 +#define ENCODERBISSC_THREAD_PRIO (42*25/56) +#else +#define ENCODERBISSC_THREAD_MEM 64 +#define ENCODERBISSC_THREAD_PRIO 42 +#endif + class EncoderBissC: public Encoder, public SPIDevice , public CommandHandler,cpp_freertos::Thread,public PersistentStorage { public: diff --git a/Firmware/FFBoard/UserExtensions/Inc/FFBHIDMain.h b/Firmware/FFBoard/UserExtensions/Inc/FFBHIDMain.h index 9107fc4e9..8e762363c 100644 --- a/Firmware/FFBoard/UserExtensions/Inc/FFBHIDMain.h +++ b/Firmware/FFBoard/UserExtensions/Inc/FFBHIDMain.h @@ -33,6 +33,14 @@ #include "thread.hpp" +#ifdef HW_ESP32SX +#define FFBHIDMAIN_THREAD_MEM 4096 +#define FFBHIDMAIN_THREAD_PRIO (30*25/56) +#else +#define FFBHIDMAIN_THREAD_MEM 256 +#define FFBHIDMAIN_THREAD_PRIO 30 +#endif + class FFBHIDMain: public FFBoardMain, public cpp_freertos::Thread, PersistentStorage,ExtiHandler,public UsbHidHandler, ErrorHandler{ enum class FFBWheel_commands : uint32_t{ ffbactive,axes,btntypes,lsbtn,addbtn,aintypes,lsain,addain,hidrate,hidsendspd,estop,cfrate diff --git a/Firmware/FFBoard/UserExtensions/Inc/MtEncoderSPI.h b/Firmware/FFBoard/UserExtensions/Inc/MtEncoderSPI.h index ef1b26a97..197c45b3b 100644 --- a/Firmware/FFBoard/UserExtensions/Inc/MtEncoderSPI.h +++ b/Firmware/FFBoard/UserExtensions/Inc/MtEncoderSPI.h @@ -18,6 +18,14 @@ #define MAGNTEK_READ 0x80 +#ifdef HW_ESP32SX +#define MTENCODERSPI_THREAD_MEM 4096 +#define MTENCODERSPI_THREAD_PRIO (42*25/56) +#else +#define MTENCODERSPI_THREAD_MEM 256 +#define MTENCODERSPI_THREAD_PRIO 42 +#endif + class MtEncoderSPI: public Encoder, public SPIDevice, public PersistentStorage, public CommandHandler,cpp_freertos::Thread{ enum class MtEncoderSPI_commands : uint32_t{ cspin,pos,errors diff --git a/Firmware/FFBoard/UserExtensions/Inc/ODriveCAN.h b/Firmware/FFBoard/UserExtensions/Inc/ODriveCAN.h index 6685474cc..480754971 100644 --- a/Firmware/FFBoard/UserExtensions/Inc/ODriveCAN.h +++ b/Firmware/FFBoard/UserExtensions/Inc/ODriveCAN.h @@ -17,8 +17,14 @@ #include "PersistentStorage.h" #ifdef ODRIVE + +#ifdef HW_ESP32SX +#define ODRIVE_THREAD_MEM 2048 +#define ODRIVE_THREAD_PRIO 25*25/56 +#else #define ODRIVE_THREAD_MEM 256 #define ODRIVE_THREAD_PRIO 25 // Must be higher than main thread +#endif enum class ODriveState : uint32_t {AXIS_STATE_UNDEFINED=0,AXIS_STATE_IDLE=1,AXIS_STATE_STARTUP_SEQUENCE=2,AXIS_STATE_FULL_CALIBRATION_SEQUENCE=3,AXIS_STATE_MOTOR_CALIBRATION=4,AXIS_STATE_ENCODER_INDEX_SEARCH=6,AXIS_STATE_ENCODER_OFFSET_CALIBRATION=7,AXIS_STATE_CLOSED_LOOP_CONTROL=8,AXIS_STATE_LOCKIN_SPIN=9,AXIS_STATE_ENCODER_DIR_FIND=10,AXIS_STATE_HOMING=11,AXIS_STATE_ENCODER_HALL_POLARITY_CALIBRATION=12,AXIS_STATE_ENCODER_HALL_PHASE_CALIBRATION=13}; enum class ODriveControlMode : uint32_t {CONTROL_MODE_VOLTAGE_CONTROL = 0,CONTROL_MODE_TORQUE_CONTROL = 1,CONTROL_MODE_VELOCITY_CONTROL = 2,CONTROL_MODE_POSITION_CONTROL = 3}; diff --git a/Firmware/FFBoard/UserExtensions/Inc/PCF8574.h b/Firmware/FFBoard/UserExtensions/Inc/PCF8574.h index 7ec1faa4f..8eb760468 100644 --- a/Firmware/FFBoard/UserExtensions/Inc/PCF8574.h +++ b/Firmware/FFBoard/UserExtensions/Inc/PCF8574.h @@ -19,6 +19,15 @@ #include "PersistentStorage.h" #include "thread.hpp" #ifdef I2C_PORT + +#ifdef HW_ESP32SX +#define PCF8574BUTTONS_THREAD_MEM 1024 +#define PCF8574BUTTONS_THREAD_PRIO (20*25/56) +#else +#define PCF8574BUTTONS_THREAD_MEM 64 +#define PCF8574BUTTONS_THREAD_PRIO 20 +#endif + class PCF8574 : public I2CDevice { public: PCF8574(I2CPort &port); diff --git a/Firmware/FFBoard/UserExtensions/Inc/TMC4671.h b/Firmware/FFBoard/UserExtensions/Inc/TMC4671.h index a28c994a7..9f4411acf 100644 --- a/Firmware/FFBoard/UserExtensions/Inc/TMC4671.h +++ b/Firmware/FFBoard/UserExtensions/Inc/TMC4671.h @@ -28,8 +28,15 @@ #include "Filters.h" #define SPITIMEOUT 500 + +#ifdef HW_ESP32SX +#define TMC_THREAD_MEM 4096 +#define TMC_THREAD_PRIO 25*25/56 +#else #define TMC_THREAD_MEM 256 #define TMC_THREAD_PRIO 25 // Must be higher than main thread +#endif + #define TMC_ADCOFFSETFAIL 5000 // How much offset from 0x7fff to allow before a calibration is failed extern SPI_HandleTypeDef HSPIDRV; diff --git a/Firmware/FFBoard/UserExtensions/Inc/VescCAN.h b/Firmware/FFBoard/UserExtensions/Inc/VescCAN.h index 4eb8c2509..48859460c 100644 --- a/Firmware/FFBoard/UserExtensions/Inc/VescCAN.h +++ b/Firmware/FFBoard/UserExtensions/Inc/VescCAN.h @@ -18,8 +18,15 @@ #include #ifdef VESC + +#ifdef HW_ESP32SX +#define VESC_THREAD_MEM (1024*3) +#define VESC_THREAD_PRIO 25*25/56 +#else #define VESC_THREAD_MEM 512 #define VESC_THREAD_PRIO 25 // Must be higher than main thread +#endif + #define BUFFER_RX_SIZE 32 #define FW_MIN_RELEASE ((5 << 16) | (3 << 8) | 51) diff --git a/Firmware/FFBoard/UserExtensions/Src/ADS111X.cpp b/Firmware/FFBoard/UserExtensions/Src/ADS111X.cpp index 25e1b10b7..4c2bf8625 100644 --- a/Firmware/FFBoard/UserExtensions/Src/ADS111X.cpp +++ b/Firmware/FFBoard/UserExtensions/Src/ADS111X.cpp @@ -174,7 +174,7 @@ const std::array,4> minMaxValAddr = { }; -ADS111X_AnalogSource::ADS111X_AnalogSource() : ADS111X(i2cport) , CommandHandler("adsAnalog", CLSID_ANALOG_ADS111X, 0),AnalogAxisProcessing(4,this,this, true,true,true,true), Thread("ads111x", 64, 25) { +ADS111X_AnalogSource::ADS111X_AnalogSource() : ADS111X(i2cport) , CommandHandler("adsAnalog", CLSID_ANALOG_ADS111X, 0),AnalogAxisProcessing(4,this,this, true,true,true,true), Thread("ads111x", ADS111X_ANALOG_THREAD_MEM, ADS111X_ANALOG_THREAD_PRIO) { CommandHandler::registerCommands(); setAxes(axes,differentialMode); registerCommand("inputs", ADS111X_AnalogSource_commands::axes, "Amount of inputs (1-4 or 1-2 if differential)",CMDFLAG_GET | CMDFLAG_SET); diff --git a/Firmware/FFBoard/UserExtensions/Src/EncoderBissC.cpp b/Firmware/FFBoard/UserExtensions/Src/EncoderBissC.cpp index ddc111c5d..cbc6ccf23 100644 --- a/Firmware/FFBoard/UserExtensions/Src/EncoderBissC.cpp +++ b/Firmware/FFBoard/UserExtensions/Src/EncoderBissC.cpp @@ -26,7 +26,7 @@ std::array EncoderBissC::tableCRC6n __attribute__((section (".ccmram EncoderBissC::EncoderBissC() : SPIDevice(ENCODER_SPI_PORT, ENCODER_SPI_PORT.getCsPins()[0]), CommandHandler("bissenc",CLSID_ENCODER_BISS,0), - cpp_freertos::Thread("BISSENC",64,42) { + cpp_freertos::Thread("BISSENC",ENCODERBISSC_THREAD_MEM,ENCODERBISSC_THREAD_PRIO) { EncoderBissC::inUse = true; diff --git a/Firmware/FFBoard/UserExtensions/Src/EncoderLocal.cpp b/Firmware/FFBoard/UserExtensions/Src/EncoderLocal.cpp index 8fdd35583..015371d77 100644 --- a/Firmware/FFBoard/UserExtensions/Src/EncoderLocal.cpp +++ b/Firmware/FFBoard/UserExtensions/Src/EncoderLocal.cpp @@ -20,12 +20,15 @@ const ClassIdentifier EncoderLocal::getInfo(){ EncoderLocal::EncoderLocal() : CommandHandler("localenc",CLSID_ENCODER_LOCAL) { EncoderLocal::inUse = true; this->restoreFlash(); +#ifdef HW_ESP32SX + glue_pcnt_init(); +#else this->htim = &TIM_ENC; HAL_TIM_Base_Start_IT(htim); // May immediately call overflow. Initialize count again this->htim->Instance->CNT = 0x7fff; pos = 0; this->htim->Instance->CR1 = 1; - +#endif if(!useIndex) setPos(0); @@ -40,7 +43,11 @@ void EncoderLocal::registerCommands(){ EncoderLocal::~EncoderLocal() { EncoderLocal::inUse = false; +#ifdef HW_ESP32SX + glue_pcnt_deinit(); +#else this->htim->Instance->CR1 = 0; +#endif } @@ -64,7 +71,12 @@ void EncoderLocal::restoreFlash(){ } int32_t EncoderLocal::getTimerCount(){ +#ifdef HW_ESP32SX + FFB_LOGW("Unsupport %s", __FUNCTION__); + return 0; +#else return (int32_t)htim->Instance->CNT - (int32_t)0x7fff; +#endif } EncoderType EncoderLocal::getEncoderType(){ @@ -73,10 +85,18 @@ EncoderType EncoderLocal::getEncoderType(){ int32_t EncoderLocal::getPos(){ +#ifdef HW_ESP32SX + pos += glue_pcnt_get_delta_value(); + return pos; +#else return getTimerCount() + pos; +#endif } void EncoderLocal::setPos(int32_t pos){ +#ifdef HW_ESP32SX + this->pos=pos; +#else int32_t cnt = getTimerCount(); this->pos = pos - cnt; @@ -86,16 +106,23 @@ void EncoderLocal::setPos(int32_t pos){ } //this->pos = pos - cnt; //htim->Instance->CNT = 0x7fff; // Reset //pos+0x7fff; - +#endif } void EncoderLocal::setPeriod(uint32_t period){ +#ifdef HW_ESP32SX + FFB_LOGW("Unsupport %s", __FUNCTION__); +#else this->htim->Instance->ARR = period-1; +#endif } void EncoderLocal::exti(uint16_t GPIO_Pin){ +#ifdef HW_ESP32SX + FFB_LOGW("Unsupport %s", __FUNCTION__); +#else if(GPIO_Pin == ENCODER_Z_Pin){ if(HAL_GPIO_ReadPin(ENCODER_Z_GPIO_Port, ENCODER_Z_Pin) == GPIO_PIN_RESET){ // Encoder Z pin activated @@ -110,6 +137,7 @@ void EncoderLocal::exti(uint16_t GPIO_Pin){ } } +#endif } void EncoderLocal::timerElapsed(TIM_HandleTypeDef* htim){ @@ -119,11 +147,15 @@ void EncoderLocal::timerElapsed(TIM_HandleTypeDef* htim){ } void EncoderLocal::overflowCallback(){ +#ifdef HW_ESP32SX + // nothing to do +#else if(htim->Instance->CNT > this->htim->Instance->ARR/2){ pos -= htim->Instance->ARR+1; }else{ pos += htim->Instance->ARR+1; } +#endif } void EncoderLocal::setCpr(uint32_t cpr){ diff --git a/Firmware/FFBoard/UserExtensions/Src/FFBHIDMain.cpp b/Firmware/FFBoard/UserExtensions/Src/FFBHIDMain.cpp index 795b82cbd..961c16237 100644 --- a/Firmware/FFBoard/UserExtensions/Src/FFBHIDMain.cpp +++ b/Firmware/FFBoard/UserExtensions/Src/FFBHIDMain.cpp @@ -22,7 +22,6 @@ #include "ADS111X.h" #include "cmsis_os.h" -extern osThreadId_t defaultTaskHandle; ////////////////////////////////////////////// /* @@ -69,7 +68,7 @@ const std::vector> analog_sources = * setFFBEffectsCalc must be called in constructor of derived class to finish the setup */ FFBHIDMain::FFBHIDMain(uint8_t axisCount) : - Thread("FFBMAIN", 256, 30),axisCount(axisCount),btn_chooser(button_sources),analog_chooser(analog_sources) + Thread("FFBMAIN", FFBHIDMAIN_THREAD_MEM, FFBHIDMAIN_THREAD_PRIO),axisCount(axisCount),btn_chooser(button_sources),analog_chooser(analog_sources) { restoreFlash(); // Load parameters diff --git a/Firmware/FFBoard/UserExtensions/Src/MotorPWM.cpp b/Firmware/FFBoard/UserExtensions/Src/MotorPWM.cpp index bb2b4619c..1677f572a 100644 --- a/Firmware/FFBoard/UserExtensions/Src/MotorPWM.cpp +++ b/Firmware/FFBoard/UserExtensions/Src/MotorPWM.cpp @@ -156,6 +156,13 @@ void MotorPWM::setPwmSpeed(SpeedPWM_DRV spd){ this->pwmspeed = spd; tFreq = (float)(timerConfig.timerFreq/1000000)/(float)(prescaler+1); +#ifdef HW_ESP32SX + glue_ledc_config((ledc_channel_t)timerConfig.channel_1, LEDC_TIMER_11_BIT, timerConfig.timerFreq/(prescaler+1)/period); + glue_ledc_config((ledc_channel_t)timerConfig.channel_2, LEDC_TIMER_11_BIT, timerConfig.timerFreq/(prescaler+1)/period); + glue_ledc_config((ledc_channel_t)timerConfig.channel_3, LEDC_TIMER_11_BIT, timerConfig.timerFreq/(prescaler+1)/period); + glue_ledc_config((ledc_channel_t)timerConfig.channel_4, LEDC_TIMER_11_BIT, timerConfig.timerFreq/(prescaler+1)/period); + period = 1UL << LEDC_TIMER_11_BIT; +#else pwmInitTimer(timerConfig.timer, timerConfig.channel_1,period,prescaler); pwmInitTimer(timerConfig.timer, timerConfig.channel_2,period,prescaler); pwmInitTimer(timerConfig.timer, timerConfig.channel_3,period,prescaler); @@ -164,6 +171,7 @@ void MotorPWM::setPwmSpeed(SpeedPWM_DRV spd){ // setPWM_HAL(0, timer, channel_1, period); // pwmInitTimer(timer, channel_2,period,prescaler); // setPWM_HAL(0, timer, channel_2, period); +#endif turn(0); } } @@ -172,6 +180,17 @@ void MotorPWM::setPwmSpeed(SpeedPWM_DRV spd){ * Updates pwm pulse length */ void MotorPWM::setPWM(uint32_t value,uint8_t ccr){ +#ifdef HW_ESP32SX + if(ccr == 1){ + glue_ledc_set_duty(LEDC_CHANNEL_0, value); // Set next CCR for channel 1 + }else if(ccr == 2){ + glue_ledc_set_duty(LEDC_CHANNEL_1, value); // Set next CCR for channel 2 + }else if(ccr == 3){ + glue_ledc_set_duty(LEDC_CHANNEL_2, value); // Set next CCR for channel 3 + }else if(ccr == 4){ + glue_ledc_set_duty(LEDC_CHANNEL_3, value); // Set next CCR for channel 4 + } +#else if(ccr == 1){ timerConfig.timer->Instance->CCR1 = value; // Set next CCR for channel 1 }else if(ccr == 2){ @@ -181,7 +200,7 @@ void MotorPWM::setPWM(uint32_t value,uint8_t ccr){ }else if(ccr == 4){ timerConfig.timer->Instance->CCR4 = value; // Set next CCR for channel 4 } - +#endif } SpeedPWM_DRV MotorPWM::getPwmSpeed(){ @@ -277,7 +296,7 @@ CommandStatus MotorPWM::command(const ParsedCommand& cmd,std::vectorInstance->ARR = period; @@ -296,7 +315,6 @@ void pwmInitTimer(TIM_HandleTypeDef* timer,uint32_t channel,uint32_t period,uint //setPWM_HAL(0,timer,channel,period); HAL_TIM_PWM_Start(timer, channel); pwmTimMutex.Unlock(); - } @@ -314,4 +332,6 @@ void setPWM_HAL(uint32_t value,TIM_HandleTypeDef* timer,uint32_t channel,uint32_ HAL_TIM_PWM_ConfigChannel(timer, &sConfigOC, channel); HAL_TIM_PWM_Start(timer, channel); } +#endif // end of HW_ESP32SX + #endif diff --git a/Firmware/FFBoard/UserExtensions/Src/MtEncoderSPI.cpp b/Firmware/FFBoard/UserExtensions/Src/MtEncoderSPI.cpp index 20ce36135..581daa699 100644 --- a/Firmware/FFBoard/UserExtensions/Src/MtEncoderSPI.cpp +++ b/Firmware/FFBoard/UserExtensions/Src/MtEncoderSPI.cpp @@ -18,7 +18,7 @@ const ClassIdentifier MtEncoderSPI::getInfo(){ return info; } -MtEncoderSPI::MtEncoderSPI() : SPIDevice(ENCODER_SPI_PORT,ENCODER_SPI_PORT.getFreeCsPins()[0]), CommandHandler("mtenc",CLSID_ENCODER_MTSPI,0),cpp_freertos::Thread("MTENC",256,42) { +MtEncoderSPI::MtEncoderSPI() : SPIDevice(ENCODER_SPI_PORT,ENCODER_SPI_PORT.getFreeCsPins()[0]), CommandHandler("mtenc",CLSID_ENCODER_MTSPI,0),cpp_freertos::Thread("MTENC",MTENCODERSPI_THREAD_MEM,MTENCODERSPI_THREAD_PRIO) { MtEncoderSPI::inUse = true; this->spiConfig.peripheral.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; // 4 = 10MHz 8 = 5MHz this->spiConfig.peripheral.FirstBit = SPI_FIRSTBIT_MSB; diff --git a/Firmware/FFBoard/UserExtensions/Src/PCF8574.cpp b/Firmware/FFBoard/UserExtensions/Src/PCF8574.cpp index fda28269f..5c375b695 100644 --- a/Firmware/FFBoard/UserExtensions/Src/PCF8574.cpp +++ b/Firmware/FFBoard/UserExtensions/Src/PCF8574.cpp @@ -68,7 +68,7 @@ const ClassIdentifier PCF8574Buttons::getInfo(){ } -PCF8574Buttons::PCF8574Buttons() : PCF8574(i2cport) , CommandHandler("pcfbtn", CLSID_BTN_PCF, 0), Thread("pcfbtn", 64, 20) { +PCF8574Buttons::PCF8574Buttons() : PCF8574(i2cport) , CommandHandler("pcfbtn", CLSID_BTN_PCF, 0), Thread("pcfbtn", PCF8574BUTTONS_THREAD_MEM, PCF8574BUTTONS_THREAD_PRIO) { CommandHandler::registerCommands(); ButtonSource::btnnum=8; registerCommand("btnnum", PCF8574Buttons_commands::btnnum, "Amount of buttons",CMDFLAG_GET | CMDFLAG_SET); diff --git a/Firmware/FFBoard/UserExtensions/Src/TMC4671.cpp b/Firmware/FFBoard/UserExtensions/Src/TMC4671.cpp index 2badfe365..fc95c13f1 100644 --- a/Firmware/FFBoard/UserExtensions/Src/TMC4671.cpp +++ b/Firmware/FFBoard/UserExtensions/Src/TMC4671.cpp @@ -9,7 +9,6 @@ #ifdef TMC4671DRIVER #include "ledEffects.h" #include "voltagesense.h" -#include "stm32f4xx_hal_spi.h" #include #include #include "ErrorHandler.h" diff --git a/Firmware/FFBoard/UserExtensions/Src/VescCAN.cpp b/Firmware/FFBoard/UserExtensions/Src/VescCAN.cpp index 4cb9e15f4..2dd4ca7fb 100644 --- a/Firmware/FFBoard/UserExtensions/Src/VescCAN.cpp +++ b/Firmware/FFBoard/UserExtensions/Src/VescCAN.cpp @@ -558,6 +558,15 @@ void VescCAN::decode_buffer(uint8_t *buffer, uint8_t len) { } +#ifdef HW_ESP32SX +extern "C" void glue_can_receive_msg(CAN_HandleTypeDef *hcan, uint8_t *rxBuf, CAN_RxHeaderTypeDef *rxHeader) +{ + for(CanHandler* c : CanHandler::canHandlers){ + c->canRxPendCallback(hcan,rxBuf,rxHeader,CAN_RX_FIFO0); + } +} +#endif + /* * Process the received message on the CAN bus. * Message have several struct, depends on message type, check the comm_can.c and diff --git a/Firmware/FFBoard/UserExtensions/Src/usb_descriptors.cpp b/Firmware/FFBoard/UserExtensions/Src/usb_descriptors.cpp index df7cb284d..c7046cd05 100644 --- a/Firmware/FFBoard/UserExtensions/Src/usb_descriptors.cpp +++ b/Firmware/FFBoard/UserExtensions/Src/usb_descriptors.cpp @@ -6,7 +6,6 @@ */ #include "tusb.h" #include "usb_descriptors.h" -#include "usbd.h" #include "stm32f4xx_hal.h" #include "main.h" #include "usb_hid_ffb_desc.h" diff --git a/Firmware/Targets/ESP32SX/.gitignore b/Firmware/Targets/ESP32SX/.gitignore new file mode 100644 index 000000000..76831bdb2 --- /dev/null +++ b/Firmware/Targets/ESP32SX/.gitignore @@ -0,0 +1,10 @@ +/*Debug/ +**/Release/ +**/.settings/ +*.launch +.vscode +.metadata +.mxproject +/build/ +sdkconfig.old +sdkconfig diff --git a/Firmware/Targets/ESP32SX/CMakeLists.txt b/Firmware/Targets/ESP32SX/CMakeLists.txt new file mode 100644 index 000000000..9d0c21325 --- /dev/null +++ b/Firmware/Targets/ESP32SX/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) +add_compile_options(-fdiagnostics-color=always) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(openffboard) diff --git a/Firmware/Targets/ESP32SX/README.md b/Firmware/Targets/ESP32SX/README.md new file mode 100644 index 000000000..d6396c068 --- /dev/null +++ b/Firmware/Targets/ESP32SX/README.md @@ -0,0 +1,95 @@ +## OpenFFBoard for ESP32S2/ESP32S3 + +ESP32-S serial chips allows all of us to DIY at a lower cost. You can get the same fantastic experience as STM32 in some functions. See this [PR](https://github.com/Ultrawipf/OpenFFBoard/pull/46) for supported features and progress. + +| Supported Targets | Status | +| ----------------- | ------------------- | +| ESP32-S2 | Basically OK | +| ESP32-S3 | Testing is required | + +### Hardware + +Any ESP board that supports USB is OK, but you need enough experience to handle the connection with peripherals. + +[openffboard-esp32](https://github.com/TDA-2030/OpenFFBoard/tree/openffb/esp32s2%2Bvesc/openffboard-esp32s2) is a simple board for ESP32S2 and ESp32S3. It is designed with [KiCad](https://www.kicad.org/) and realizes most of the functions of OpenFFBoard. You may start your journey from this board. + +#### Pin Assignment + +| OpenFFBoard signals | ESP32S2 PINs | ESP32S3 PINs | +| ------------------- | ------------ | ------------ | +| Switch | IO0 | IO0 | +| AIN0 | IO1 | IO1 | +| AIN1 | IO2 | IO2 | +| AIN2 | IO3 | IO3 | +| ENCODER_A | IO4 | IO4 | +| PWM1 | IO5 | IO5 | +| ENCODER_B | IO6 | IO6 | +| PWM2 | IO7 | IO7 | +| DIN3 | IO8 | IO8 | +| DIN2 | IO9 | IO9 | +| DIN1 | IO10 | IO10 | +| DIN0 | IO11 | IO11 | +| GPIO_DRV | IO12 | IO12 | +| DRV_ENABLE | IO13 | IO13 | +| DRV_FLAG | IO14 | IO14 | +| ENCODER_Z | IO15 | IO15 | +| PWM3 | IO16 | IO16 | +| PWM4 | IO17 | IO17 | +| USB_D- | IO19 | IO19 | +| USB_D+ | IO20 | IO20 | +| BRAKE_CTRL | IO21 | IO21 | +| SPI1_CS2 | IO33 | IO47 | +| SPI1_CS3 | IO34 | IO48 | +| CAN_RX | IO35 | IO35 | +| CAN_TX | IO36 | IO36 | +| CAN_S | IO37 | IO37 | +| SPI1_MISO | IO38 | IO38 | +| SPI1_MOSI | IO39 | IO39 | +| LED_ERR | IO40 | IO40 | +| LED_CLIP | IO41 | IO41 | +| LED_SYS | IO42 | IO42 | +| SPI1_CS1 | IO45 | IO45 | +| SPI1_SCK | IO46 | IO46 | + + + +### Quick Download Firmware + +`Firmware/Targets/ESP32SX/firmware` contains a set of compiled firmware. You can write it from 0x00 address to esp32s2/esp32s3. + +1. First connect IO0 to GND, and then connect USB to the computer + +2. Download [flash_download_tool](https://www.espressif.com/en/support/download/other-tools) from Espressif + +3. Run the `flash_download_tool` + + ![download_1](https://s1.328888.xyz/2022/04/29/AHV7v.png) + + ![download_2](https://s1.328888.xyz/2022/04/29/AHF2i.png) + + Complete the configuration as shown in the figure, and finally click the `START` button. Please select the correct COM port before downloading. + +### How to Build + +1. Checkout esp-idf to commit: `c29343eb94d` + +``` bash +cd esp-idf +git fetch +git checkout c29343eb94d +git submodule update --init --recursive +``` + +2. Build the project and flash it to the board, then run monitor tool to view serial output: + +``` bash +cd /OpenFFBoard/Firmware/Targets/ESP32SX +idf.py set-target esp32s2 +idf.py -p PORT flash monitor +``` + +(Replace PORT with the name of the serial port to use.) + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. \ No newline at end of file diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/CMakeLists.txt b/Firmware/Targets/ESP32SX/components/tinyusb/CMakeLists.txt new file mode 100644 index 000000000..f30a66490 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/CMakeLists.txt @@ -0,0 +1,84 @@ +idf_build_get_property(target IDF_TARGET) + +set(srcs) +set(includes_public) +set(includes_private) +set(compile_options) + +if(CONFIG_TINYUSB) + if(target STREQUAL "esp32s3") + set(tusb_mcu "OPT_MCU_ESP32S3") + set(tusb_family "esp32sx") + elseif(target STREQUAL "esp32s2") + set(tusb_mcu "OPT_MCU_ESP32S2") + set(tusb_family "esp32sx") + else() + # CONFIG_TINYUSB dependency has been garanteed by Kconfig logic, + # So it's not possible that cmake goes here + message(FATAL_ERROR "TinyUSB is not support on ${target}.") + return() + endif() + + list(APPEND compile_options + "-DCFG_TUSB_MCU=${tusb_mcu}" + "-DCFG_TUSB_DEBUG=${CONFIG_TINYUSB_DEBUG_LEVEL}" + ) + + idf_component_get_property(freertos_component_dir freertos COMPONENT_DIR) + + list(APPEND includes_private + # "tinyusb/hw/bsp/" + "tinyusb/src/" + "tinyusb/src/device" + # "additions/include_private" + ) + + list(APPEND includes_public + "tinyusb/src/" + "additions/include" + "tinyusb/src/class/cdc" + "tinyusb/src/class/hid" + "tinyusb/src/class/midi" + # The FreeRTOS API include convention in tinyusb is different from esp-idf + "${freertos_component_dir}/include/freertos" + ) + + list(APPEND srcs + "tinyusb/src/portable/espressif/${tusb_family}/dcd_${tusb_family}.c" + "tinyusb/src/class/cdc/cdc_device.c" + "tinyusb/src/class/hid/hid_device.c" + "tinyusb/src/class/midi/midi_device.c" + "tinyusb/src/class/msc/msc_device.c" + "tinyusb/src/class/vendor/vendor_device.c" + "tinyusb/src/common/tusb_fifo.c" + "tinyusb/src/device/usbd_control.c" + "tinyusb/src/device/usbd.c" + "tinyusb/src/tusb.c" + # "additions/src/descriptors_control.c" + # "additions/src/tinyusb.c" + # "additions/src/tusb_tasks.c" + # "additions/src/usb_descriptors.c" + ) + + # when no builtin class driver is enabled, an uint8_t data compared with `BUILTIN_DRIVER_COUNT` will always be false + set_source_files_properties("tinyusb/src/device/usbd.c" PROPERTIES COMPILE_FLAGS "-Wno-type-limits") + + if(CONFIG_TINYUSB_CDC_ENABLED) + list(APPEND srcs + # "additions/src/cdc.c" + # "additions/src/tusb_cdc_acm.c" + # "additions/src/tusb_console.c" + # "additions/src/vfs_tinyusb.c" + ) + endif() # CONFIG_TINYUSB_CDC_ENABLED +endif() # CONFIG_TINYUSB + +idf_component_register(SRCS ${srcs} + INCLUDE_DIRS ${includes_public} + PRIV_INCLUDE_DIRS ${includes_private} + PRIV_REQUIRES "vfs" "usb" + ) + +if(CONFIG_TINYUSB) + target_compile_options(${COMPONENT_LIB} PRIVATE ${compile_options}) +endif() diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/Kconfig b/Firmware/Targets/ESP32SX/components/tinyusb/Kconfig new file mode 100644 index 000000000..192f7741f --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/Kconfig @@ -0,0 +1,192 @@ +menu "TinyUSB Stack" + visible if USB_OTG_SUPPORTED + + config TINYUSB + bool "Use TinyUSB Stack" + depends on USB_OTG_SUPPORTED + default n + help + Enable TinyUSB stack support. + Note that, esp-idf only uses the device stack provided by TinyUSB. + + if TINYUSB + config TINYUSB_DEBUG_LEVEL + int "TinyUSB log level (0-3)" + default 0 + range 0 3 + help + Specify verbosity of TinyUSB log output. + + menu "TinyUSB task configuration" + config TINYUSB_NO_DEFAULT_TASK + bool "Do not create a TinyUSB task" + default n + help + This option allows to not create the FreeRTOS task during the driver initialization. + User will have to handle TinyUSB events manually. + + config TINYUSB_TASK_PRIORITY + int "TinyUSB task priority" + default 5 + depends on !TINYUSB_NO_DEFAULT_TASK + help + Set the priority of the default TinyUSB main task. + + config TINYUSB_TASK_STACK_SIZE + int "TinyUSB task stack size (bytes)" + default 4096 + depends on !TINYUSB_NO_DEFAULT_TASK + help + Set the stack size of the default TinyUSB main task. + endmenu + + menu "Descriptor configuration" + config TINYUSB_DESC_USE_ESPRESSIF_VID + bool "VID: Use Espressif's vendor ID" + default y + help + Enable this option, USB device will use Espressif's vendor ID as its VID. + This is helpful at product develop stage. + + config TINYUSB_DESC_CUSTOM_VID + hex "VID: Custom vendor ID" + default 0x1234 + depends on !TINYUSB_DESC_USE_ESPRESSIF_VID + help + Custom Vendor ID. + + config TINYUSB_DESC_USE_DEFAULT_PID + bool "PID: Use a default PID assigned to TinyUSB" + default y + help + Default TinyUSB PID assigning uses values 0x4000...0x4007. + + config TINYUSB_DESC_CUSTOM_PID + hex "PID: Custom product ID" + default 0x5678 + depends on !TINYUSB_DESC_USE_DEFAULT_PID + help + Custom Product ID. + + config TINYUSB_DESC_BCD_DEVICE + hex "bcdDevice" + default 0x0100 + help + Version of the firmware of the USB device. + + config TINYUSB_DESC_MANUFACTURER_STRING + string "Manufacturer name" + default "Espressif Systems" + help + Name of the manufacturer of the USB device. + + config TINYUSB_DESC_PRODUCT_STRING + string "Product name" + default "Espressif Device" + help + Name of the USB device. + + config TINYUSB_DESC_SERIAL_STRING + string "Serial string" + default "123456" + help + Serial number of the USB device. + + config TINYUSB_DESC_CDC_STRING + depends on TINYUSB_CDC_ENABLED + string "CDC Device String" + default "Espressif CDC Device" + help + Name of the CDC device. + + config TINYUSB_DESC_MSC_STRING + depends on TINYUSB_MSC_ENABLED + string "MSC Device String" + default "Espressif MSC Device" + help + Name of the MSC device. + + config TINYUSB_DESC_HID_STRING + depends on TINYUSB_HID_ENABLED + string "HID Device String" + default "Espressif HID Device" + help + Name of the HID device + endmenu # "Descriptor configuration" + + menu "Massive Storage Class (MSC)" + config TINYUSB_MSC_ENABLED + bool "Enable TinyUSB MSC feature" + default n + help + Enable TinyUSB MSC feature. + + config TINYUSB_MSC_BUFSIZE + depends on TINYUSB_MSC_ENABLED + int "MSC FIFO size" + default 512 + help + MSC FIFO size, in bytes. + endmenu # "Massive Storage Class" + + menu "HID configuration" + config TINYUSB_HID_ENABLED + bool "Enable TinyUSB HID feature" + default n + help + Enable TinyUSB HID feature. + + config TINYUSB_HID_BUFSIZE + depends on TINYUSB_HID_ENABLED + int "HID Buffer size" + default 64 + help + in bytes + endmenu # "HID configuration" + + menu "Communication Device Class (CDC)" + config TINYUSB_CDC_ENABLED + bool "Enable TinyUSB CDC feature" + default n + help + Enable TinyUSB CDC feature. + + config TINYUSB_CDC_RX_BUFSIZE + depends on TINYUSB_CDC_ENABLED + int "CDC FIFO size of RX channel" + default 64 + help + CDC FIFO size of RX channel. + + config TINYUSB_CDC_TX_BUFSIZE + depends on TINYUSB_CDC_ENABLED + int "CDC FIFO size of TX channel" + default 64 + help + CDC FIFO size of TX channel. + endmenu # "Communication Device Class" + + menu "USB MIDI" + config TINYUSB_MIDI_ENABLED + bool "Enable TinyUSB MIDI feature" + default n + help + Enable TinyUSB MIDI feature. + + config TINYUSB_MIDI_RX_BUFSIZE + depends on TINYUSB_MIDI_ENABLED + int "MIDI FIFO size of RX channel" + default 64 + help + MIDI FIFO size of RX channel. + + config TINYUSB_MIDI_TX_BUFSIZE + depends on TINYUSB_MIDI_ENABLED + int "MIDI FIFO size of TX channel" + default 64 + help + MIDI FIFO size of TX channel. + endmenu + endif # TINYUSB + +endmenu # "TinyUSB Stack" diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/additions/include/tinyusb.h b/Firmware/Targets/ESP32SX/components/tinyusb/additions/include/tinyusb.h new file mode 100644 index 000000000..aaa48fa72 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/additions/include/tinyusb.h @@ -0,0 +1,102 @@ +// Copyright 2020 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include "tusb.h" +#include "tusb_option.h" +#include "tusb_config.h" +#include "tinyusb_types.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* tinyusb uses buffers with type of uint8_t[] but in our driver we are reading them as a 32-bit word */ +#if (CFG_TUD_ENDPOINT0_SIZE < 4) +# define CFG_TUD_ENDPOINT0_SIZE 4 +# warning "CFG_TUD_ENDPOINT0_SIZE was too low and was set to 4" +#endif + +#if TUSB_OPT_DEVICE_ENABLED + +# if CFG_TUD_HID +# if (CFG_TUD_HID_BUFSIZE < 4) +# define CFG_TUD_HID_BUFSIZE 4 +# warning "CFG_TUD_HID_BUFSIZE was too low and was set to 4" +# endif +# endif + +# if CFG_TUD_CDC +# if (CFG_TUD_CDC_EP_BUFSIZE < 4) +# define CFG_TUD_CDC_EP_BUFSIZE 4 +# warning "CFG_TUD_CDC_EP_BUFSIZE was too low and was set to 4" +# endif +# endif + +# if CFG_TUD_MSC +# if (CFG_TUD_MSC_BUFSIZE < 4) +# define CFG_TUD_MSC_BUFSIZE 4 +# warning "CFG_TUD_MSC_BUFSIZE was too low and was set to 4" +# endif +# endif + +# if CFG_TUD_MIDI +# if (CFG_TUD_MIDI_EPSIZE < 4) +# define CFG_TUD_MIDI_EPSIZE 4 +# warning "CFG_TUD_MIDI_EPSIZE was too low and was set to 4" +# endif +# endif + +# if CFG_TUD_CUSTOM_CLASS +# warning "Please check that the buffer is more then 4 bytes" +# endif +#endif + +/** + * @brief Configuration structure of the tinyUSB core + */ +typedef struct { + tusb_desc_device_t *descriptor; /*!< Pointer to a device descriptor */ + const char **string_descriptor; /*!< Pointer to an array of string descriptors */ + bool external_phy; /*!< Should USB use an external PHY */ +} tinyusb_config_t; + +/** + * @brief This is an all-in-one helper function, including: + * 1. USB device driver initialization + * 2. Descriptors preparation + * 3. TinyUSB stack initialization + * 4. Creates and start a task to handle usb events + * + * @note Don't change Custom descriptor, but if it has to be done, + * Suggest to define as follows in order to match the Interface Association Descriptor (IAD): + * bDeviceClass = TUSB_CLASS_MISC, + * bDeviceSubClass = MISC_SUBCLASS_COMMON, + * + * @param config tinyusb stack specific configuration + * @retval ESP_ERR_INVALID_ARG Install driver and tinyusb stack failed because of invalid argument + * @retval ESP_FAIL Install driver and tinyusb stack failed because of internal error + * @retval ESP_OK Install driver and tinyusb stack successfully + */ +esp_err_t tinyusb_driver_install(const tinyusb_config_t *config); + +// TODO esp_err_t tinyusb_driver_uninstall(void); (IDF-1474) + +#ifdef __cplusplus +} +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/additions/include/tusb_config.h b/Firmware/Targets/ESP32SX/components/tinyusb/additions/include/tusb_config.h new file mode 100644 index 000000000..bb035a1e3 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/additions/include/tusb_config.h @@ -0,0 +1,101 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org), + * Additions Copyright (c) 2020, Espressif Systems (Shanghai) PTE LTD + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#pragma once + +#include "tusb_option.h" +#include "sdkconfig.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef CONFIG_TINYUSB_CDC_ENABLED +# define CONFIG_TINYUSB_CDC_ENABLED 0 +#endif + +#ifndef CONFIG_TINYUSB_MSC_ENABLED +# define CONFIG_TINYUSB_MSC_ENABLED 0 +#endif + +#ifndef CONFIG_TINYUSB_HID_ENABLED +# define CONFIG_TINYUSB_HID_ENABLED 0 +#endif + +#ifndef CONFIG_TINYUSB_MIDI_ENABLED +# define CONFIG_TINYUSB_MIDI_ENABLED 0 +#endif + +#ifndef CONFIG_TINYUSB_CUSTOM_CLASS_ENABLED +# define CONFIG_TINYUSB_CUSTOM_CLASS_ENABLED 0 +#endif + +#define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE | OPT_MODE_FULL_SPEED +#define CFG_TUSB_OS OPT_OS_FREERTOS + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +# define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +# define CFG_TUSB_MEM_ALIGN TU_ATTR_ALIGNED(4) +#endif + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +// CDC FIFO size of TX and RX +#define CFG_TUD_CDC_RX_BUFSIZE CONFIG_TINYUSB_CDC_RX_BUFSIZE +#define CFG_TUD_CDC_TX_BUFSIZE CONFIG_TINYUSB_CDC_TX_BUFSIZE + +// MSC Buffer size of Device Mass storage +#define CFG_TUD_MSC_BUFSIZE CONFIG_TINYUSB_MSC_BUFSIZE + +// MIDI FIFO size of TX and RX +#define CFG_TUD_MIDI_RX_BUFSIZE CONFIG_TINYUSB_MIDI_RX_BUFSIZE +#define CFG_TUD_MIDI_TX_BUFSIZE CONFIG_TINYUSB_MIDI_TX_BUFSIZE + +// HID buffer size Should be sufficient to hold ID (if any) + Data +#define CFG_TUD_HID_BUFSIZE CONFIG_TINYUSB_HID_BUFSIZE + +// Enabled device class driver +#define CFG_TUD_CDC CONFIG_TINYUSB_CDC_ENABLED +#define CFG_TUD_MSC CONFIG_TINYUSB_MSC_ENABLED +#define CFG_TUD_HID CONFIG_TINYUSB_HID_ENABLED +#define CFG_TUD_MIDI CONFIG_TINYUSB_MIDI_ENABLED +#define CFG_TUD_CUSTOM_CLASS CONFIG_TINYUSB_CUSTOM_CLASS_ENABLED + +#ifdef __cplusplus +} +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/sdkconfig.rename b/Firmware/Targets/ESP32SX/components/tinyusb/sdkconfig.rename new file mode 100644 index 000000000..6aea8579a --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/sdkconfig.rename @@ -0,0 +1,22 @@ +# sdkconfig replacement configurations for deprecated options formatted as +# CONFIG_DEPRECATED_OPTION CONFIG_NEW_OPTION +CONFIG_USB_ENABLED CONFIG_TINYUSB +CONFIG_USB_DO_NOT_CREATE_TASK CONFIG_TINYUSB_NO_DEFAULT_TASK +CONFIG_USB_TASK_PRIORITY CONFIG_TINYUSB_TASK_PRIORITY +CONFIG_USB_DESC_USE_ESPRESSIF_VID CONFIG_TINYUSB_DESC_USE_ESPRESSIF_VID +CONFIG_USB_DESC_CUSTOM_VID CONFIG_TINYUSB_DESC_CUSTOM_VID +CONFIG_USB_DESC_USE_DEFAULT_PID CONFIG_TINYUSB_DESC_USE_DEFAULT_PID +CONFIG_USB_DESC_CUSTOM_PID CONFIG_TINYUSB_DESC_CUSTOM_PID +CONFIG_USB_DESC_BCDDEVICE CONFIG_TINYUSB_DESC_BCD_DEVICE +CONFIG_USB_DESC_MANUFACTURER_STRING CONFIG_TINYUSB_DESC_MANUFACTURER_STRING +CONFIG_USB_DESC_PRODUCT_STRING CONFIG_TINYUSB_DESC_PRODUCT_STRING +CONFIG_USB_DESC_SERIAL_STRING CONFIG_TINYUSB_DESC_SERIAL_STRING +CONFIG_USB_DESC_CDC_STRING CONFIG_TINYUSB_DESC_CDC_STRING +CONFIG_USB_DESC_MSC_STRING CONFIG_TINYUSB_DESC_MSC_STRING +CONFIG_USB_DESC_HID_STRING CONFIG_TINYUSB_DESC_HID_STRING +CONFIG_USB_MSC_ENABLED CONFIG_TINYUSB_MSC_ENABLED +CONFIG_USB_MSC_BUFSIZE CONFIG_TINYUSB_MSC_BUFSIZE +CONFIG_USB_CDC_ENABLED CONFIG_TINYUSB_CDC_ENABLED +CONFIG_USB_CDC_RX_BUFSIZE CONFIG_TINYUSB_CDC_RX_BUFSIZE +CONFIG_USB_CDC_TX_BUFSIZE CONFIG_TINYUSB_CDC_TX_BUFSIZE +CONFIG_USB_DEBUG_LEVEL CONFIG_TINYUSB_DEBUG_LEVEL diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/LICENSE b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/LICENSE new file mode 100644 index 000000000..ddd4ab410 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018, hathach (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. diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/README.rst b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/README.rst new file mode 100644 index 000000000..5b994c357 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/README.rst @@ -0,0 +1,146 @@ +.. figure:: docs/assets/logo.svg + :alt: TinyUSB + +|Build Status| |Documentation Status| |License| + +TinyUSB is an open-source cross-platform USB Host/Device stack for +embedded system, designed to be memory-safe with no dynamic allocation +and thread-safe with all interrupt events are deferred then handled in +the non-ISR task function. + +Please take a look at the online `documentation `__. + +.. figure:: docs/assets/stack.svg + :width: 500px + :alt: stackup + +:: + + . + ├── docs # Documentation + ├── examples # Sample with Makefile build support + ├── hw + │   ├── bsp # Supported boards source files + │   └── mcu # Low level mcu core & peripheral drivers + ├── lib # Sources from 3rd party such as freeRTOS, fatfs ... + ├── src # All sources files for TinyUSB stack itself. + ├── test # Unit tests for the stack + └── tools # Files used internally + +Supported MCUs +============== + +The stack supports the following MCUs: + +- **Allwinner:** F1C100s/F1C200s +- **Broadcom:** BCM2837, BCM2711 +- **Dialog:** DA1469x +- **Espressif:** ESP32-S2, ESP32-S3 +- **GigaDevice:** GD32VF103 +- **Infineon:** XMC4500 +- **MicroChip:** SAMD11, SAMD21, SAMD51, SAME5x, SAMG55, SAML21, SAML22, SAME7x +- **NordicSemi:** nRF52833, nRF52840, nRF5340 +- **Nuvoton:** NUC120, NUC121/NUC125, NUC126, NUC505 +- **NXP:** + + - iMX RT Series: RT1011, RT1015, RT1021, RT1052, RT1062, RT1064 + - Kinetis: KL25, K32L2 + - LPC Series: 11u, 13, 15, 17, 18, 40, 43, 51u, 54, 55 + +- **Raspberry Pi:** RP2040 +- **Renesas:** RX63N, RX65N, RX72N +- **Silabs:** EFM32GG +- **Sony:** CXD56 +- **ST:** STM32 series: F0, F1, F2, F3, F4, F7, H7, G4, L0, L1, L4, L4+ +- **TI:** MSP430, MSP432E4, TM4C123 +- **ValentyUSB:** eptri + +Here is the list of `Supported Devices`_ that can be used with provided examples. + +Device Stack +============ + +Supports multiple device configurations by dynamically changing USB descriptors, low power functions such like suspend, resume, and remote wakeup. The following device classes are supported: + +- Audio Class 2.0 (UAC2) +- Bluetooth Host Controller Interface (BTH HCI) +- Communication Device Class (CDC) +- Device Firmware Update (DFU): DFU mode (WIP) and Runtime +- Human Interface Device (HID): Generic (In & Out), Keyboard, Mouse, Gamepad etc ... +- Mass Storage Class (MSC): with multiple LUNs +- Musical Instrument Digital Interface (MIDI) +- Network with RNDIS, Ethernet Control Model (ECM), Network Control Model (NCM) +- Test and Measurement Class (USBTMC) +- Video class 1.5 (UVC): work in progress +- Vendor-specific class support with generic In & Out endpoints. Can be used with MS OS 2.0 compatible descriptor to load winUSB driver without INF file. +- `WebUSB `__ with vendor-specific class + +If you have a special requirement, `usbd_app_driver_get_cb()` can be used to write your own class driver without modifying the stack. Here is how the RPi team added their reset interface `raspberrypi/pico-sdk#197 `_ + +Host Stack +========== + +- Human Interface Device (HID): Keyboard, Mouse, Generic +- Mass Storage Class (MSC) +- Hub currently only supports 1 level of hub (due to my laziness) + +OS Abstraction layer +==================== + +TinyUSB is completely thread-safe by pushing all Interrupt Service Request (ISR) events into a central queue, then processing them later in the non-ISR context task function. It also uses semaphore/mutex to access shared resources such as Communication Device Class (CDC) FIFO. Therefore the stack needs to use some of the OS's basic APIs. Following OSes are already supported out of the box. + +- **No OS** +- **FreeRTOS** +- `RT-Thread `_: `repo `_ +- **Mynewt** Due to the newt package build system, Mynewt examples are better to be on its `own repo `_ + +Local Docs +========== + +- Info + + - `Uses`_ + - `Changelog`_ + - `Contributors`_ + +- `Reference`_ + + - `Supported Devices`_ + - `Getting Started`_ + - `Concurrency`_ + +- `Contributing`_ + + - `Code of Conduct`_ + - `Structure`_ + - `Porting`_ + +License +======= + +All TinyUSB sources in the ``src`` folder are licensed under MIT +license, the `Full license is here `__. However, each file can be +individually licensed especially those in ``lib`` and ``hw/mcu`` folder. +Please make sure you understand all the license term for files you use +in your project. + + +.. |Build Status| image:: https://github.com/hathach/tinyusb/workflows/Build/badge.svg + :target: https://github.com/hathach/tinyusb/actions +.. |Documentation Status| image:: https://readthedocs.org/projects/tinyusb/badge/?version=latest + :target: https://docs.tinyusb.org/en/latest/?badge=latest +.. |License| image:: https://img.shields.io/badge/license-MIT-brightgreen.svg + :target: https://opensource.org/licenses/MIT + + +.. _Uses: docs/info/uses.rst +.. _Changelog: docs/info/changelog.rst +.. _Contributors: CONTRIBUTORS.rst +.. _Reference: docs/reference/index.rst +.. _Supported Devices: docs/reference/supported.rst +.. _Getting Started: docs/reference/getting_started.rst +.. _Concurrency: docs/reference/concurrency.rst +.. _Contributing: docs/contributing/index.rst +.. _Code of Conduct: CODE_OF_CONDUCT.rst +.. _Structure: docs/contributing/structure.rst +.. _Porting: docs/contributing/porting.rst diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/audio/audio.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/audio/audio.h new file mode 100644 index 000000000..6f9c1a6b5 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/audio/audio.h @@ -0,0 +1,933 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * Copyright (c) 2020 Reinhard Panhuber + * + * 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 group_class + * \defgroup ClassDriver_Audio Audio + * Currently only MIDI subclass is supported + * @{ */ + +#ifndef _TUSB_AUDIO_H__ +#define _TUSB_AUDIO_H__ + +#include "common/tusb_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/// Audio Device Class Codes + +/// A.2 - Audio Function Subclass Codes +typedef enum +{ + AUDIO_FUNCTION_SUBCLASS_UNDEFINED = 0x00, +} audio_function_subclass_type_t; + +/// A.3 - Audio Function Protocol Codes +typedef enum +{ + AUDIO_FUNC_PROTOCOL_CODE_UNDEF = 0x00, + AUDIO_FUNC_PROTOCOL_CODE_V2 = 0x20, ///< Version 2.0 +} audio_function_protocol_code_t; + +/// A.5 - Audio Interface Subclass Codes +typedef enum +{ + AUDIO_SUBCLASS_UNDEFINED = 0x00, + AUDIO_SUBCLASS_CONTROL , ///< Audio Control + AUDIO_SUBCLASS_STREAMING , ///< Audio Streaming + AUDIO_SUBCLASS_MIDI_STREAMING , ///< MIDI Streaming +} audio_subclass_type_t; + +/// A.6 - Audio Interface Protocol Codes +typedef enum +{ + AUDIO_INT_PROTOCOL_CODE_UNDEF = 0x00, + AUDIO_INT_PROTOCOL_CODE_V2 = 0x20, ///< Version 2.0 +} audio_interface_protocol_code_t; + +/// A.7 - Audio Function Category Codes +typedef enum +{ + AUDIO_FUNC_UNDEF = 0x00, + AUDIO_FUNC_DESKTOP_SPEAKER = 0x01, + AUDIO_FUNC_HOME_THEATER = 0x02, + AUDIO_FUNC_MICROPHONE = 0x03, + AUDIO_FUNC_HEADSET = 0x04, + AUDIO_FUNC_TELEPHONE = 0x05, + AUDIO_FUNC_CONVERTER = 0x06, + AUDIO_FUNC_SOUND_RECODER = 0x07, + AUDIO_FUNC_IO_BOX = 0x08, + AUDIO_FUNC_MUSICAL_INSTRUMENT = 0x09, + AUDIO_FUNC_PRO_AUDIO = 0x0A, + AUDIO_FUNC_AUDIO_VIDEO = 0x0B, + AUDIO_FUNC_CONTROL_PANEL = 0x0C, + AUDIO_FUNC_OTHER = 0xFF, +} audio_function_code_t; + +/// A.9 - Audio Class-Specific AC Interface Descriptor Subtypes UAC2 +typedef enum +{ + AUDIO_CS_AC_INTERFACE_AC_DESCRIPTOR_UNDEF = 0x00, + AUDIO_CS_AC_INTERFACE_HEADER = 0x01, + AUDIO_CS_AC_INTERFACE_INPUT_TERMINAL = 0x02, + AUDIO_CS_AC_INTERFACE_OUTPUT_TERMINAL = 0x03, + AUDIO_CS_AC_INTERFACE_MIXER_UNIT = 0x04, + AUDIO_CS_AC_INTERFACE_SELECTOR_UNIT = 0x05, + AUDIO_CS_AC_INTERFACE_FEATURE_UNIT = 0x06, + AUDIO_CS_AC_INTERFACE_EFFECT_UNIT = 0x07, + AUDIO_CS_AC_INTERFACE_PROCESSING_UNIT = 0x08, + AUDIO_CS_AC_INTERFACE_EXTENSION_UNIT = 0x09, + AUDIO_CS_AC_INTERFACE_CLOCK_SOURCE = 0x0A, + AUDIO_CS_AC_INTERFACE_CLOCK_SELECTOR = 0x0B, + AUDIO_CS_AC_INTERFACE_CLOCK_MULTIPLIER = 0x0C, + AUDIO_CS_AC_INTERFACE_SAMPLE_RATE_CONVERTER = 0x0D, +} audio_cs_ac_interface_subtype_t; + +/// A.10 - Audio Class-Specific AS Interface Descriptor Subtypes UAC2 +typedef enum +{ + AUDIO_CS_AS_INTERFACE_AS_DESCRIPTOR_UNDEF = 0x00, + AUDIO_CS_AS_INTERFACE_AS_GENERAL = 0x01, + AUDIO_CS_AS_INTERFACE_FORMAT_TYPE = 0x02, + AUDIO_CS_AS_INTERFACE_ENCODER = 0x03, + AUDIO_CS_AS_INTERFACE_DECODER = 0x04, +} audio_cs_as_interface_subtype_t; + +/// A.11 - Effect Unit Effect Types +typedef enum +{ + AUDIO_EFFECT_TYPE_UNDEF = 0x00, + AUDIO_EFFECT_TYPE_PARAM_EQ_SECTION = 0x01, + AUDIO_EFFECT_TYPE_REVERBERATION = 0x02, + AUDIO_EFFECT_TYPE_MOD_DELAY = 0x03, + AUDIO_EFFECT_TYPE_DYN_RANGE_COMP = 0x04, +} audio_effect_unit_effect_type_t; + +/// A.12 - Processing Unit Process Types +typedef enum +{ + AUDIO_PROCESS_TYPE_UNDEF = 0x00, + AUDIO_PROCESS_TYPE_UP_DOWN_MIX = 0x01, + AUDIO_PROCESS_TYPE_DOLBY_PROLOGIC = 0x02, + AUDIO_PROCESS_TYPE_STEREO_EXTENDER = 0x03, +} audio_processing_unit_process_type_t; + +/// A.13 - Audio Class-Specific EP Descriptor Subtypes UAC2 +typedef enum +{ + AUDIO_CS_EP_SUBTYPE_UNDEF = 0x00, + AUDIO_CS_EP_SUBTYPE_GENERAL = 0x01, +} audio_cs_ep_subtype_t; + +/// A.14 - Audio Class-Specific Request Codes +typedef enum +{ + AUDIO_CS_REQ_UNDEF = 0x00, + AUDIO_CS_REQ_CUR = 0x01, + AUDIO_CS_REQ_RANGE = 0x02, + AUDIO_CS_REQ_MEM = 0x03, +} audio_cs_req_t; + +/// A.17 - Control Selector Codes + +/// A.17.1 - Clock Source Control Selectors +typedef enum +{ + AUDIO_CS_CTRL_UNDEF = 0x00, + AUDIO_CS_CTRL_SAM_FREQ = 0x01, + AUDIO_CS_CTRL_CLK_VALID = 0x02, +} audio_clock_src_control_selector_t; + +/// A.17.2 - Clock Selector Control Selectors +typedef enum +{ + AUDIO_CX_CTRL_UNDEF = 0x00, + AUDIO_CX_CTRL_CONTROL = 0x01, +} audio_clock_sel_control_selector_t; + +/// A.17.3 - Clock Multiplier Control Selectors +typedef enum +{ + AUDIO_CM_CTRL_UNDEF = 0x00, + AUDIO_CM_CTRL_NUMERATOR_CONTROL = 0x01, + AUDIO_CM_CTRL_DENOMINATOR_CONTROL = 0x02, +} audio_clock_mul_control_selector_t; + +/// A.17.4 - Terminal Control Selectors +typedef enum +{ + AUDIO_TE_CTRL_UNDEF = 0x00, + AUDIO_TE_CTRL_COPY_PROTECT = 0x01, + AUDIO_TE_CTRL_CONNECTOR = 0x02, + AUDIO_TE_CTRL_OVERLOAD = 0x03, + AUDIO_TE_CTRL_CLUSTER = 0x04, + AUDIO_TE_CTRL_UNDERFLOW = 0x05, + AUDIO_TE_CTRL_OVERFLOW = 0x06, + AUDIO_TE_CTRL_LATENCY = 0x07, +} audio_terminal_control_selector_t; + +/// A.17.5 - Mixer Control Selectors +typedef enum +{ + AUDIO_MU_CTRL_UNDEF = 0x00, + AUDIO_MU_CTRL_MIXER = 0x01, + AUDIO_MU_CTRL_CLUSTER = 0x02, + AUDIO_MU_CTRL_UNDERFLOW = 0x03, + AUDIO_MU_CTRL_OVERFLOW = 0x04, + AUDIO_MU_CTRL_LATENCY = 0x05, +} audio_mixer_control_selector_t; + +/// A.17.6 - Selector Control Selectors +typedef enum +{ + AUDIO_SU_CTRL_UNDEF = 0x00, + AUDIO_SU_CTRL_SELECTOR = 0x01, + AUDIO_SU_CTRL_LATENCY = 0x02, +} audio_sel_control_selector_t; + +/// A.17.7 - Feature Unit Control Selectors +typedef enum +{ + AUDIO_FU_CTRL_UNDEF = 0x00, + AUDIO_FU_CTRL_MUTE = 0x01, + AUDIO_FU_CTRL_VOLUME = 0x02, + AUDIO_FU_CTRL_BASS = 0x03, + AUDIO_FU_CTRL_MID = 0x04, + AUDIO_FU_CTRL_TREBLE = 0x05, + AUDIO_FU_CTRL_GRAPHIC_EQUALIZER = 0x06, + AUDIO_FU_CTRL_AGC = 0x07, + AUDIO_FU_CTRL_DELAY = 0x08, + AUDIO_FU_CTRL_BASS_BOOST = 0x09, + AUDIO_FU_CTRL_LOUDNESS = 0x0A, + AUDIO_FU_CTRL_INPUT_GAIN = 0x0B, + AUDIO_FU_CTRL_GAIN_PAD = 0x0C, + AUDIO_FU_CTRL_INVERTER = 0x0D, + AUDIO_FU_CTRL_UNDERFLOW = 0x0E, + AUDIO_FU_CTRL_OVERVLOW = 0x0F, + AUDIO_FU_CTRL_LATENCY = 0x10, +} audio_feature_unit_control_selector_t; + +/// A.17.8 Effect Unit Control Selectors + +/// A.17.8.1 Parametric Equalizer Section Effect Unit Control Selectors +typedef enum +{ + AUDIO_PE_CTRL_UNDEF = 0x00, + AUDIO_PE_CTRL_ENABLE = 0x01, + AUDIO_PE_CTRL_CENTERFREQ = 0x02, + AUDIO_PE_CTRL_QFACTOR = 0x03, + AUDIO_PE_CTRL_GAIN = 0x04, + AUDIO_PE_CTRL_UNDERFLOW = 0x05, + AUDIO_PE_CTRL_OVERFLOW = 0x06, + AUDIO_PE_CTRL_LATENCY = 0x07, +} audio_parametric_equalizer_control_selector_t; + +/// A.17.8.2 Reverberation Effect Unit Control Selectors +typedef enum +{ + AUDIO_RV_CTRL_UNDEF = 0x00, + AUDIO_RV_CTRL_ENABLE = 0x01, + AUDIO_RV_CTRL_TYPE = 0x02, + AUDIO_RV_CTRL_LEVEL = 0x03, + AUDIO_RV_CTRL_TIME = 0x04, + AUDIO_RV_CTRL_FEEDBACK = 0x05, + AUDIO_RV_CTRL_PREDELAY = 0x06, + AUDIO_RV_CTRL_DENSITY = 0x07, + AUDIO_RV_CTRL_HIFREQ_ROLLOFF = 0x08, + AUDIO_RV_CTRL_UNDERFLOW = 0x09, + AUDIO_RV_CTRL_OVERFLOW = 0x0A, + AUDIO_RV_CTRL_LATENCY = 0x0B, +} audio_reverberation_effect_control_selector_t; + +/// A.17.8.3 Modulation Delay Effect Unit Control Selectors +typedef enum +{ + AUDIO_MD_CTRL_UNDEF = 0x00, + AUDIO_MD_CTRL_ENABLE = 0x01, + AUDIO_MD_CTRL_BALANCE = 0x02, + AUDIO_MD_CTRL_RATE = 0x03, + AUDIO_MD_CTRL_DEPTH = 0x04, + AUDIO_MD_CTRL_TIME = 0x05, + AUDIO_MD_CTRL_FEEDBACK = 0x06, + AUDIO_MD_CTRL_UNDERFLOW = 0x07, + AUDIO_MD_CTRL_OVERFLOW = 0x08, + AUDIO_MD_CTRL_LATENCY = 0x09, +} audio_modulation_delay_control_selector_t; + +/// A.17.8.4 Dynamic Range Compressor Effect Unit Control Selectors +typedef enum +{ + AUDIO_DR_CTRL_UNDEF = 0x00, + AUDIO_DR_CTRL_ENABLE = 0x01, + AUDIO_DR_CTRL_COMPRESSION_RATE = 0x02, + AUDIO_DR_CTRL_MAXAMPL = 0x03, + AUDIO_DR_CTRL_THRESHOLD = 0x04, + AUDIO_DR_CTRL_ATTACK_TIME = 0x05, + AUDIO_DR_CTRL_RELEASE_TIME = 0x06, + AUDIO_DR_CTRL_UNDERFLOW = 0x07, + AUDIO_DR_CTRL_OVERFLOW = 0x08, + AUDIO_DR_CTRL_LATENCY = 0x09, +} audio_dynamic_range_compression_control_selector_t; + +/// A.17.9 Processing Unit Control Selectors + +/// A.17.9.1 Up/Down-mix Processing Unit Control Selectors +typedef enum +{ + AUDIO_UD_CTRL_UNDEF = 0x00, + AUDIO_UD_CTRL_ENABLE = 0x01, + AUDIO_UD_CTRL_MODE_SELECT = 0x02, + AUDIO_UD_CTRL_CLUSTER = 0x03, + AUDIO_UD_CTRL_UNDERFLOW = 0x04, + AUDIO_UD_CTRL_OVERFLOW = 0x05, + AUDIO_UD_CTRL_LATENCY = 0x06, +} audio_up_down_mix_control_selector_t; + +/// A.17.9.2 Dolby Prologic â„¢ Processing Unit Control Selectors +typedef enum +{ + AUDIO_DP_CTRL_UNDEF = 0x00, + AUDIO_DP_CTRL_ENABLE = 0x01, + AUDIO_DP_CTRL_MODE_SELECT = 0x02, + AUDIO_DP_CTRL_CLUSTER = 0x03, + AUDIO_DP_CTRL_UNDERFLOW = 0x04, + AUDIO_DP_CTRL_OVERFLOW = 0x05, + AUDIO_DP_CTRL_LATENCY = 0x06, +} audio_dolby_prologic_control_selector_t; + +/// A.17.9.3 Stereo Extender Processing Unit Control Selectors +typedef enum +{ + AUDIO_ST_EXT_CTRL_UNDEF = 0x00, + AUDIO_ST_EXT_CTRL_ENABLE = 0x01, + AUDIO_ST_EXT_CTRL_WIDTH = 0x02, + AUDIO_ST_EXT_CTRL_UNDERFLOW = 0x03, + AUDIO_ST_EXT_CTRL_OVERFLOW = 0x04, + AUDIO_ST_EXT_CTRL_LATENCY = 0x05, +} audio_stereo_extender_control_selector_t; + +/// A.17.10 Extension Unit Control Selectors +typedef enum +{ + AUDIO_XU_CTRL_UNDEF = 0x00, + AUDIO_XU_CTRL_ENABLE = 0x01, + AUDIO_XU_CTRL_CLUSTER = 0x02, + AUDIO_XU_CTRL_UNDERFLOW = 0x03, + AUDIO_XU_CTRL_OVERFLOW = 0x04, + AUDIO_XU_CTRL_LATENCY = 0x05, +} audio_extension_unit_control_selector_t; + +/// A.17.11 AudioStreaming Interface Control Selectors +typedef enum +{ + AUDIO_AS_CTRL_UNDEF = 0x00, + AUDIO_AS_CTRL_ACT_ALT_SETTING = 0x01, + AUDIO_AS_CTRL_VAL_ALT_SETTINGS = 0x02, + AUDIO_AS_CTRL_AUDIO_DATA_FORMAT = 0x03, +} audio_audiostreaming_interface_control_selector_t; + +/// A.17.12 Encoder Control Selectors +typedef enum +{ + AUDIO_EN_CTRL_UNDEF = 0x00, + AUDIO_EN_CTRL_BIT_RATE = 0x01, + AUDIO_EN_CTRL_QUALITY = 0x02, + AUDIO_EN_CTRL_VBR = 0x03, + AUDIO_EN_CTRL_TYPE = 0x04, + AUDIO_EN_CTRL_UNDERFLOW = 0x05, + AUDIO_EN_CTRL_OVERFLOW = 0x06, + AUDIO_EN_CTRL_ENCODER_ERROR = 0x07, + AUDIO_EN_CTRL_PARAM1 = 0x08, + AUDIO_EN_CTRL_PARAM2 = 0x09, + AUDIO_EN_CTRL_PARAM3 = 0x0A, + AUDIO_EN_CTRL_PARAM4 = 0x0B, + AUDIO_EN_CTRL_PARAM5 = 0x0C, + AUDIO_EN_CTRL_PARAM6 = 0x0D, + AUDIO_EN_CTRL_PARAM7 = 0x0E, + AUDIO_EN_CTRL_PARAM8 = 0x0F, +} audio_encoder_control_selector_t; + +/// A.17.13 Decoder Control Selectors + +/// A.17.13.1 MPEG Decoder Control Selectors +typedef enum +{ + AUDIO_MPD_CTRL_UNDEF = 0x00, + AUDIO_MPD_CTRL_DUAL_CHANNEL = 0x01, + AUDIO_MPD_CTRL_SECOND_STEREO = 0x02, + AUDIO_MPD_CTRL_MULTILINGUAL = 0x03, + AUDIO_MPD_CTRL_DYN_RANGE = 0x04, + AUDIO_MPD_CTRL_SCALING = 0x05, + AUDIO_MPD_CTRL_HILO_SCALING = 0x06, + AUDIO_MPD_CTRL_UNDERFLOW = 0x07, + AUDIO_MPD_CTRL_OVERFLOW = 0x08, + AUDIO_MPD_CTRL_DECODER_ERROR = 0x09, +} audio_MPEG_decoder_control_selector_t; + +/// A.17.13.2 AC-3 Decoder Control Selectors +typedef enum +{ + AUDIO_AD_CTRL_UNDEF = 0x00, + AUDIO_AD_CTRL_MODE = 0x01, + AUDIO_AD_CTRL_DYN_RANGE = 0x02, + AUDIO_AD_CTRL_SCALING = 0x03, + AUDIO_AD_CTRL_HILO_SCALING = 0x04, + AUDIO_AD_CTRL_UNDERFLOW = 0x05, + AUDIO_AD_CTRL_OVERFLOW = 0x06, + AUDIO_AD_CTRL_DECODER_ERROR = 0x07, +} audio_AC3_decoder_control_selector_t; + +/// A.17.13.3 WMA Decoder Control Selectors +typedef enum +{ + AUDIO_WD_CTRL_UNDEF = 0x00, + AUDIO_WD_CTRL_UNDERFLOW = 0x01, + AUDIO_WD_CTRL_OVERFLOW = 0x02, + AUDIO_WD_CTRL_DECODER_ERROR = 0x03, +} audio_WMA_decoder_control_selector_t; + +/// A.17.13.4 DTS Decoder Control Selectors +typedef enum +{ + AUDIO_DD_CTRL_UNDEF = 0x00, + AUDIO_DD_CTRL_UNDERFLOW = 0x01, + AUDIO_DD_CTRL_OVERFLOW = 0x02, + AUDIO_DD_CTRL_DECODER_ERROR = 0x03, +} audio_DTS_decoder_control_selector_t; + +/// A.17.14 Endpoint Control Selectors +typedef enum +{ + AUDIO_EP_CTRL_UNDEF = 0x00, + AUDIO_EP_CTRL_PITCH = 0x01, + AUDIO_EP_CTRL_DATA_OVERRUN = 0x02, + AUDIO_EP_CTRL_DATA_UNDERRUN = 0x03, +} audio_EP_control_selector_t; + +/// Terminal Types + +/// 2.1 - Audio Class-Terminal Types UAC2 +typedef enum +{ + AUDIO_TERM_TYPE_USB_UNDEFINED = 0x0100, + AUDIO_TERM_TYPE_USB_STREAMING = 0x0101, + AUDIO_TERM_TYPE_USB_VENDOR_SPEC = 0x01FF, +} audio_terminal_type_t; + +/// 2.2 - Audio Class-Input Terminal Types UAC2 +typedef enum +{ + AUDIO_TERM_TYPE_IN_UNDEFINED = 0x0200, + AUDIO_TERM_TYPE_IN_GENERIC_MIC = 0x0201, + AUDIO_TERM_TYPE_IN_DESKTOP_MIC = 0x0202, + AUDIO_TERM_TYPE_IN_PERSONAL_MIC = 0x0203, + AUDIO_TERM_TYPE_IN_OMNI_MIC = 0x0204, + AUDIO_TERM_TYPE_IN_ARRAY_MIC = 0x0205, + AUDIO_TERM_TYPE_IN_PROC_ARRAY_MIC = 0x0206, +} audio_terminal_input_type_t; + +/// 2.3 - Audio Class-Output Terminal Types UAC2 +typedef enum +{ + AUDIO_TERM_TYPE_OUT_UNDEFINED = 0x0300, + AUDIO_TERM_TYPE_OUT_GENERIC_SPEAKER = 0x0301, + AUDIO_TERM_TYPE_OUT_HEADPHONES = 0x0302, + AUDIO_TERM_TYPE_OUT_HEAD_MNT_DISP_AUIDO = 0x0303, + AUDIO_TERM_TYPE_OUT_DESKTOP_SPEAKER = 0x0304, + AUDIO_TERM_TYPE_OUT_ROOM_SPEAKER = 0x0305, + AUDIO_TERM_TYPE_OUT_COMMUNICATION_SPEAKER = 0x0306, + AUDIO_TERM_TYPE_OUT_LOW_FRQ_EFFECTS_SPEAKER = 0x0307, +} audio_terminal_output_type_t; + +/// Rest is yet to be implemented + +/// Additional Audio Device Class Codes - Source: Audio Data Formats + +/// A.1 - Audio Class-Format Type Codes UAC2 +typedef enum +{ + AUDIO_FORMAT_TYPE_UNDEFINED = 0x00, + AUDIO_FORMAT_TYPE_I = 0x01, + AUDIO_FORMAT_TYPE_II = 0x02, + AUDIO_FORMAT_TYPE_III = 0x03, + AUDIO_FORMAT_TYPE_IV = 0x04, + AUDIO_EXT_FORMAT_TYPE_I = 0x81, + AUDIO_EXT_FORMAT_TYPE_II = 0x82, + AUDIO_EXT_FORMAT_TYPE_III = 0x83, +} audio_format_type_t; + +// A.2.1 - Audio Class-Audio Data Format Type I UAC2 +typedef enum +{ + AUDIO_DATA_FORMAT_TYPE_I_PCM = (uint32_t) (1 << 0), + AUDIO_DATA_FORMAT_TYPE_I_PCM8 = (uint32_t) (1 << 1), + AUDIO_DATA_FORMAT_TYPE_I_IEEE_FLOAT = (uint32_t) (1 << 2), + AUDIO_DATA_FORMAT_TYPE_I_ALAW = (uint32_t) (1 << 3), + AUDIO_DATA_FORMAT_TYPE_I_MULAW = (uint32_t) (1 << 4), + AUDIO_DATA_FORMAT_TYPE_I_RAW_DATA = 0x80000000, +} audio_data_format_type_I_t; + +/// All remaining definitions are taken from the descriptor descriptions in the UAC2 main specification + +/// Audio Class-Control Values UAC2 +typedef enum +{ + AUDIO_CTRL_NONE = 0x00, ///< No Host access + AUDIO_CTRL_R = 0x01, ///< Host read access only + AUDIO_CTRL_RW = 0x03, ///< Host read write access +} audio_control_t; + +/// Audio Class-Specific AC Interface Descriptor Controls UAC2 +typedef enum +{ + AUDIO_CS_AS_INTERFACE_CTRL_LATENCY_POS = 0, +} audio_cs_ac_interface_control_pos_t; + +/// Audio Class-Specific AS Interface Descriptor Controls UAC2 +typedef enum +{ + AUDIO_CS_AS_INTERFACE_CTRL_ACTIVE_ALT_SET_POS = 0, + AUDIO_CS_AS_INTERFACE_CTRL_VALID_ALT_SET_POS = 2, +} audio_cs_as_interface_control_pos_t; + +/// Audio Class-Specific AS Isochronous Data EP Attributes UAC2 +typedef enum +{ + AUDIO_CS_AS_ISO_DATA_EP_ATT_MAX_PACKETS_ONLY = 0x80, + AUDIO_CS_AS_ISO_DATA_EP_ATT_NON_MAX_PACKETS_OK = 0x00, +} audio_cs_as_iso_data_ep_attribute_t; + +/// Audio Class-Specific AS Isochronous Data EP Controls UAC2 +typedef enum +{ + AUDIO_CS_AS_ISO_DATA_EP_CTRL_PITCH_POS = 0, + AUDIO_CS_AS_ISO_DATA_EP_CTRL_DATA_OVERRUN_POS = 2, + AUDIO_CS_AS_ISO_DATA_EP_CTRL_DATA_UNDERRUN_POS = 4, +} audio_cs_as_iso_data_ep_control_pos_t; + +/// Audio Class-Specific AS Isochronous Data EP Lock Delay Units UAC2 +typedef enum +{ + AUDIO_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_UNDEFINED = 0x00, + AUDIO_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_MILLISEC = 0x01, + AUDIO_CS_AS_ISO_DATA_EP_LOCK_DELAY_UNIT_PCM_SAMPLES = 0x02, +} audio_cs_as_iso_data_ep_lock_delay_unit_t; + +/// Audio Class-Clock Source Attributes UAC2 +typedef enum +{ + AUDIO_CLOCK_SOURCE_ATT_EXT_CLK = 0x00, + AUDIO_CLOCK_SOURCE_ATT_INT_FIX_CLK = 0x01, + AUDIO_CLOCK_SOURCE_ATT_INT_VAR_CLK = 0x02, + AUDIO_CLOCK_SOURCE_ATT_INT_PRO_CLK = 0x03, + AUDIO_CLOCK_SOURCE_ATT_CLK_SYC_SOF = 0x04, +} audio_clock_source_attribute_t; + +/// Audio Class-Clock Source Controls UAC2 +typedef enum +{ + AUDIO_CLOCK_SOURCE_CTRL_CLK_FRQ_POS = 0, + AUDIO_CLOCK_SOURCE_CTRL_CLK_VAL_POS = 2, +} audio_clock_source_control_pos_t; + +/// Audio Class-Clock Selector Controls UAC2 +typedef enum +{ + AUDIO_CLOCK_SELECTOR_CTRL_POS = 0, +} audio_clock_selector_control_pos_t; + +/// Audio Class-Clock Multiplier Controls UAC2 +typedef enum +{ + AUDIO_CLOCK_MULTIPLIER_CTRL_NUMERATOR_POS = 0, + AUDIO_CLOCK_MULTIPLIER_CTRL_DENOMINATOR_POS = 2, +} audio_clock_multiplier_control_pos_t; + +/// Audio Class-Input Terminal Controls UAC2 +typedef enum +{ + AUDIO_IN_TERM_CTRL_CPY_PROT_POS = 0, + AUDIO_IN_TERM_CTRL_CONNECTOR_POS = 2, + AUDIO_IN_TERM_CTRL_OVERLOAD_POS = 4, + AUDIO_IN_TERM_CTRL_CLUSTER_POS = 6, + AUDIO_IN_TERM_CTRL_UNDERFLOW_POS = 8, + AUDIO_IN_TERM_CTRL_OVERFLOW_POS = 10, +} audio_terminal_input_control_pos_t; + +/// Audio Class-Output Terminal Controls UAC2 +typedef enum +{ + AUDIO_OUT_TERM_CTRL_CPY_PROT_POS = 0, + AUDIO_OUT_TERM_CTRL_CONNECTOR_POS = 2, + AUDIO_OUT_TERM_CTRL_OVERLOAD_POS = 4, + AUDIO_OUT_TERM_CTRL_UNDERFLOW_POS = 6, + AUDIO_OUT_TERM_CTRL_OVERFLOW_POS = 8, +} audio_terminal_output_control_pos_t; + +/// Audio Class-Feature Unit Controls UAC2 +typedef enum +{ + AUDIO_FEATURE_UNIT_CTRL_MUTE_POS = 0, + AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS = 2, + AUDIO_FEATURE_UNIT_CTRL_BASS_POS = 4, + AUDIO_FEATURE_UNIT_CTRL_MID_POS = 6, + AUDIO_FEATURE_UNIT_CTRL_TREBLE_POS = 8, + AUDIO_FEATURE_UNIT_CTRL_GRAPHIC_EQU_POS = 10, + AUDIO_FEATURE_UNIT_CTRL_AGC_POS = 12, + AUDIO_FEATURE_UNIT_CTRL_DELAY_POS = 14, + AUDIO_FEATURE_UNIT_CTRL_BASS_BOOST_POS = 16, + AUDIO_FEATURE_UNIT_CTRL_LOUDNESS_POS = 18, + AUDIO_FEATURE_UNIT_CTRL_INPUT_GAIN_POS = 20, + AUDIO_FEATURE_UNIT_CTRL_INPUT_GAIN_PAD_POS = 22, + AUDIO_FEATURE_UNIT_CTRL_PHASE_INV_POS = 24, + AUDIO_FEATURE_UNIT_CTRL_UNDERFLOW_POS = 26, + AUDIO_FEATURE_UNIT_CTRL_OVERFLOW_POS = 28, +} audio_feature_unit_control_pos_t; + +/// Audio Class-Audio Channel Configuration UAC2 +typedef enum +{ + AUDIO_CHANNEL_CONFIG_NON_PREDEFINED = 0x00000000, + AUDIO_CHANNEL_CONFIG_FRONT_LEFT = 0x00000001, + AUDIO_CHANNEL_CONFIG_FRONT_RIGHT = 0x00000002, + AUDIO_CHANNEL_CONFIG_FRONT_CENTER = 0x00000004, + AUDIO_CHANNEL_CONFIG_LOW_FRQ_EFFECTS = 0x00000008, + AUDIO_CHANNEL_CONFIG_BACK_LEFT = 0x00000010, + AUDIO_CHANNEL_CONFIG_BACK_RIGHT = 0x00000020, + AUDIO_CHANNEL_CONFIG_FRONT_LEFT_OF_CENTER = 0x00000040, + AUDIO_CHANNEL_CONFIG_FRONT_RIGHT_OF_CENTER = 0x00000080, + AUDIO_CHANNEL_CONFIG_BACK_CENTER = 0x00000100, + AUDIO_CHANNEL_CONFIG_SIDE_LEFT = 0x00000200, + AUDIO_CHANNEL_CONFIG_SIDE_RIGHT = 0x00000400, + AUDIO_CHANNEL_CONFIG_TOP_CENTER = 0x00000800, + AUDIO_CHANNEL_CONFIG_TOP_FRONT_LEFT = 0x00001000, + AUDIO_CHANNEL_CONFIG_TOP_FRONT_CENTER = 0x00002000, + AUDIO_CHANNEL_CONFIG_TOP_FRONT_RIGHT = 0x00004000, + AUDIO_CHANNEL_CONFIG_TOP_BACK_LEFT = 0x00008000, + AUDIO_CHANNEL_CONFIG_TOP_BACK_CENTER = 0x00010000, + AUDIO_CHANNEL_CONFIG_TOP_BACK_RIGHT = 0x00020000, + AUDIO_CHANNEL_CONFIG_TOP_FRONT_LEFT_OF_CENTER = 0x00040000, + AUDIO_CHANNEL_CONFIG_TOP_FRONT_RIGHT_OF_CENTER = 0x00080000, + AUDIO_CHANNEL_CONFIG_LEFT_LOW_FRQ_EFFECTS = 0x00100000, + AUDIO_CHANNEL_CONFIG_RIGHT_LOW_FRQ_EFFECTS = 0x00200000, + AUDIO_CHANNEL_CONFIG_TOP_SIDE_LEFT = 0x00400000, + AUDIO_CHANNEL_CONFIG_TOP_SIDE_RIGHT = 0x00800000, + AUDIO_CHANNEL_CONFIG_BOTTOM_CENTER = 0x01000000, + AUDIO_CHANNEL_CONFIG_BACK_LEFT_OF_CENTER = 0x02000000, + AUDIO_CHANNEL_CONFIG_BACK_RIGHT_OF_CENTER = 0x04000000, + AUDIO_CHANNEL_CONFIG_RAW_DATA = 0x80000000, +} audio_channel_config_t; + +/// AUDIO Channel Cluster Descriptor (4.1) +typedef struct TU_ATTR_PACKED { + uint8_t bNrChannels; ///< Number of channels currently connected. + audio_channel_config_t bmChannelConfig; ///< Bitmap according to 'audio_channel_config_t' with a 1 set if channel is connected and 0 else. In case channels are non-predefined ignore them here (see UAC2 specification 4.1 Audio Channel Cluster Descriptor. + uint8_t iChannelNames; ///< Index of a string descriptor, describing the name of the first inserted channel with a non-predefined spatial location. +} audio_desc_channel_cluster_t; + +/// AUDIO Class-Specific AC Interface Header Descriptor (4.7.2) +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor in bytes: 9. + uint8_t bDescriptorType ; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType ; ///< Descriptor SubType. Value: AUDIO_CS_AC_INTERFACE_HEADER. + uint16_t bcdADC ; ///< Audio Device Class Specification Release Number in Binary-Coded Decimal. Value: U16_TO_U8S_LE(0x0200). + uint8_t bCategory ; ///< Constant, indicating the primary use of this audio function, as intended by the manufacturer. See: audio_function_t. + uint16_t wTotalLength ; ///< Total number of bytes returned for the class-specific AudioControl interface descriptor. Includes the combined length of this descriptor header and all Clock Source, Unit and Terminal descriptors. + uint8_t bmControls ; ///< See: audio_cs_ac_interface_control_pos_t. +} audio_desc_cs_ac_interface_t; + +/// AUDIO Clock Source Descriptor (4.7.2.1) +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor in bytes: 8. + uint8_t bDescriptorType ; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType ; ///< Descriptor SubType. Value: AUDIO_CS_AC_INTERFACE_CLOCK_SOURCE. + uint8_t bClockID ; ///< Constant uniquely identifying the Clock Source Entity within the audio function. This value is used in all requests to address this Entity. + uint8_t bmAttributes ; ///< See: audio_clock_source_attribute_t. + uint8_t bmControls ; ///< See: audio_clock_source_control_pos_t. + uint8_t bAssocTerminal ; ///< Terminal ID of the Terminal that is associated with this Clock Source. + uint8_t iClockSource ; ///< Index of a string descriptor, describing the Clock Source Entity. +} audio_desc_clock_source_t; + +/// AUDIO Clock Selector Descriptor (4.7.2.2) for ONE pin +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor, in bytes: 7+p. + uint8_t bDescriptorType ; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType ; ///< Descriptor SubType. Value: AUDIO_CS_AC_INTERFACE_CLOCK_SELECTOR. + uint8_t bClockID ; ///< Constant uniquely identifying the Clock Selector Entity within the audio function. This value is used in all requests to address this Entity. + uint8_t bNrInPins ; ///< Number of Input Pins of this Unit: p = 1 thus bNrInPins = 1. + uint8_t baCSourceID ; ///< ID of the Clock Entity to which the first Clock Input Pin of this Clock Selector Entity is connected.. + uint8_t bmControls ; ///< See: audio_clock_selector_control_pos_t. + uint8_t iClockSource ; ///< Index of a string descriptor, describing the Clock Selector Entity. +} audio_desc_clock_selector_t; + +/// AUDIO Clock Selector Descriptor (4.7.2.2) for multiple pins +#define audio_desc_clock_selector_n_t(source_num) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength ; \ + uint8_t bDescriptorType ; \ + uint8_t bDescriptorSubType ; \ + uint8_t bClockID ; \ + uint8_t bNrInPins ; \ + struct TU_ATTR_PACKED { \ + uint8_t baSourceID ; \ + } sourceID[source_num] ; \ + uint8_t bmControls ; \ + uint8_t iClockSource ; \ +} + +/// AUDIO Clock Multiplier Descriptor (4.7.2.3) +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor, in bytes: 7. + uint8_t bDescriptorType ; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType ; ///< Descriptor SubType. Value: AUDIO_CS_AC_INTERFACE_CLOCK_MULTIPLIER. + uint8_t bClockID ; ///< Constant uniquely identifying the Clock Multiplier Entity within the audio function. This value is used in all requests to address this Entity. + uint8_t bCSourceID ; ///< ID of the Clock Entity to which the last Clock Input Pin of this Clock Selector Entity is connected. + uint8_t bmControls ; ///< See: audio_clock_multiplier_control_pos_t. + uint8_t iClockSource ; ///< Index of a string descriptor, describing the Clock Multiplier Entity. +} audio_desc_clock_multiplier_t; + +/// AUDIO Input Terminal Descriptor(4.7.2.4) +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor, in bytes: 17. + uint8_t bDescriptorType ; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType ; ///< Descriptor SubType. Value: AUDIO_CS_AC_INTERFACE_INPUT_TERMINAL. + uint16_t wTerminalType ; ///< Constant characterizing the type of Terminal. See: audio_terminal_type_t for USB streaming and audio_terminal_input_type_t for other input types. + uint8_t bAssocTerminal ; ///< ID of the Output Terminal to which this Input Terminal is associated. + uint8_t bCSourceID ; ///< ID of the Clock Entity to which this Input Terminal is connected. + uint8_t bNrChannels ; ///< Number of logical output channels in the Terminal’s output audio channel cluster. + uint32_t bmChannelConfig ; ///< Describes the spatial location of the logical channels. See:audio_channel_config_t. + uint16_t bmControls ; ///< See: audio_terminal_input_control_pos_t. + uint8_t iTerminal ; ///< Index of a string descriptor, describing the Input Terminal. +} audio_desc_input_terminal_t; + +/// AUDIO Output Terminal Descriptor(4.7.2.5) +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor, in bytes: 12. + uint8_t bDescriptorType ; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType ; ///< Descriptor SubType. Value: AUDIO_CS_AC_INTERFACE_OUTPUT_TERMINAL. + uint8_t bTerminalID ; ///< Constant uniquely identifying the Terminal within the audio function. This value is used in all requests to address this Terminal. + uint16_t wTerminalType ; ///< Constant characterizing the type of Terminal. See: audio_terminal_type_t for USB streaming and audio_terminal_output_type_t for other output types. + uint8_t bAssocTerminal ; ///< Constant, identifying the Input Terminal to which this Output Terminal is associated. + uint8_t bSourceID ; ///< ID of the Unit or Terminal to which this Terminal is connected. + uint8_t bCSourceID ; ///< ID of the Clock Entity to which this Output Terminal is connected. + uint16_t bmControls ; ///< See: audio_terminal_output_type_t. + uint8_t iTerminal ; ///< Index of a string descriptor, describing the Output Terminal. +} audio_desc_output_terminal_t; + +/// AUDIO Feature Unit Descriptor(4.7.2.8) for ONE channel +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor, in bytes: 14. + uint8_t bDescriptorType ; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType ; ///< Descriptor SubType. Value: AUDIO_CS_AC_INTERFACE_FEATURE_UNIT. + uint8_t bUnitID ; ///< Constant uniquely identifying the Unit within the audio function. This value is used in all requests to address this Unit. + uint8_t bSourceID ; ///< ID of the Unit or Terminal to which this Feature Unit is connected. + struct TU_ATTR_PACKED { + uint32_t bmaControls ; ///< See: audio_feature_unit_control_pos_t. Controls0 is master channel 0 (always present) and Controls1 is logical channel 1. + } controls[2] ; + uint8_t iTerminal ; ///< Index of a string descriptor, describing this Feature Unit. +} audio_desc_feature_unit_t; + +/// AUDIO Feature Unit Descriptor(4.7.2.8) for multiple channels +#define audio_desc_feature_unit_n_t(ch_num)\ + struct TU_ATTR_PACKED { \ + uint8_t bLength ; /* 6+(ch_num+1)*4 */\ + uint8_t bDescriptorType ; \ + uint8_t bDescriptorSubType ; \ + uint8_t bUnitID ; \ + uint8_t bSourceID ; \ + struct TU_ATTR_PACKED { \ + uint32_t bmaControls ; \ + } controls[ch_num+1] ; \ + uint8_t iTerminal ; \ +} + +/// AUDIO Class-Specific AS Interface Descriptor(4.9.2) +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor, in bytes: 16. + uint8_t bDescriptorType ; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType ; ///< Descriptor SubType. Value: AUDIO_CS_AS_INTERFACE_AS_GENERAL. + uint8_t bTerminalLink ; ///< The Terminal ID of the Terminal to which this interface is connected. + uint8_t bmControls ; ///< See: audio_cs_as_interface_control_pos_t. + uint8_t bFormatType ; ///< Constant identifying the Format Type the AudioStreaming interface is using. See: audio_format_type_t. + uint32_t bmFormats ; ///< The Audio Data Format(s) that can be used to communicate with this interface.See: audio_data_format_type_I_t. + uint8_t bNrChannels ; ///< Number of physical channels in the AS Interface audio channel cluster. + uint32_t bmChannelConfig ; ///< Describes the spatial location of the physical channels. See: audio_channel_config_t. + uint8_t iChannelNames ; ///< Index of a string descriptor, describing the name of the first physical channel. +} audio_desc_cs_as_interface_t; + +/// AUDIO Type I Format Type Descriptor(2.3.1.6 - Audio Formats) +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor, in bytes: 6. + uint8_t bDescriptorType ; ///< Descriptor Type. Value: TUSB_DESC_CS_INTERFACE. + uint8_t bDescriptorSubType ; ///< Descriptor SubType. Value: AUDIO_CS_AS_INTERFACE_FORMAT_TYPE. + uint8_t bFormatType ; ///< Constant identifying the Format Type the AudioStreaming interface is using. Value: AUDIO_FORMAT_TYPE_I. + uint8_t bSubslotSize ; ///< The number of bytes occupied by one audio subslot. Can be 1, 2, 3 or 4. + uint8_t bBitResolution ; ///< The number of effectively used bits from the available bits in an audio subslot. +} audio_desc_type_I_format_t; + +/// AUDIO Class-Specific AS Isochronous Audio Data Endpoint Descriptor(4.10.1.2) +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor, in bytes: 8. + uint8_t bDescriptorType ; ///< Descriptor Type. Value: TUSB_DESC_CS_ENDPOINT. + uint8_t bDescriptorSubType ; ///< Descriptor SubType. Value: AUDIO_CS_EP_SUBTYPE_GENERAL. + uint8_t bmAttributes ; ///< See: audio_cs_as_iso_data_ep_attribute_t. + uint8_t bmControls ; ///< See: audio_cs_as_iso_data_ep_control_pos_t. + uint8_t bLockDelayUnits ; ///< Indicates the units used for the wLockDelay field. See: audio_cs_as_iso_data_ep_lock_delay_unit_t. + uint16_t wLockDelay ; ///< Indicates the time it takes this endpoint to reliably lock its internal clock recovery circuitry. Units used depend on the value of the bLockDelayUnits field. +} audio_desc_cs_as_iso_data_ep_t; + +// 5.2.2 Control Request Layout +typedef struct TU_ATTR_PACKED +{ + union + { + struct TU_ATTR_PACKED + { + uint8_t recipient : 5; ///< Recipient type tusb_request_recipient_t. + uint8_t type : 2; ///< Request type tusb_request_type_t. + uint8_t direction : 1; ///< Direction type. tusb_dir_t + } bmRequestType_bit; + + uint8_t bmRequestType; + }; + + uint8_t bRequest; ///< Request type audio_cs_req_t + uint8_t bChannelNumber; + uint8_t bControlSelector; + union + { + uint8_t bInterface; + uint8_t bEndpoint; + }; + uint8_t bEntityID; + uint16_t wLength; +} audio_control_request_t; + +//// 5.2.3 Control Request Parameter Block Layout + +// 5.2.3.1 1-byte Control CUR Parameter Block +typedef struct TU_ATTR_PACKED +{ + int8_t bCur ; ///< The setting for the CUR attribute of the addressed Control +} audio_control_cur_1_t; + +// 5.2.3.2 2-byte Control CUR Parameter Block +typedef struct TU_ATTR_PACKED +{ + int16_t bCur ; ///< The setting for the CUR attribute of the addressed Control +} audio_control_cur_2_t; + +// 5.2.3.3 4-byte Control CUR Parameter Block +typedef struct TU_ATTR_PACKED +{ + int32_t bCur ; ///< The setting for the CUR attribute of the addressed Control +} audio_control_cur_4_t; + +// Use the following ONLY for RECEIVED data - compiler does not know how many subranges are defined! Use the one below for predefined lengths - or if you know what you are doing do what you like +// 5.2.3.1 1-byte Control RANGE Parameter Block +typedef struct TU_ATTR_PACKED { + uint16_t wNumSubRanges; + struct TU_ATTR_PACKED { + int8_t bMin ; /*The setting for the MIN attribute of the nth subrange of the addressed Control*/ + int8_t bMax ; /*The setting for the MAX attribute of the nth subrange of the addressed Control*/ + uint8_t bRes ; /*The setting for the RES attribute of the nth subrange of the addressed Control*/ + } subrange[] ; +} audio_control_range_1_t; + +// 5.2.3.2 2-byte Control RANGE Parameter Block +typedef struct TU_ATTR_PACKED { + uint16_t wNumSubRanges; + struct TU_ATTR_PACKED { + int16_t bMin ; /*The setting for the MIN attribute of the nth subrange of the addressed Control*/ + int16_t bMax ; /*The setting for the MAX attribute of the nth subrange of the addressed Control*/ + uint16_t bRes ; /*The setting for the RES attribute of the nth subrange of the addressed Control*/ + } subrange[] ; +} audio_control_range_2_t; + +// 5.2.3.3 4-byte Control RANGE Parameter Block +typedef struct TU_ATTR_PACKED { + uint16_t wNumSubRanges; + struct TU_ATTR_PACKED { + int32_t bMin ; /*The setting for the MIN attribute of the nth subrange of the addressed Control*/ + int32_t bMax ; /*The setting for the MAX attribute of the nth subrange of the addressed Control*/ + uint32_t bRes ; /*The setting for the RES attribute of the nth subrange of the addressed Control*/ + } subrange[] ; +} audio_control_range_4_t; + +// 5.2.3.1 1-byte Control RANGE Parameter Block +#define audio_control_range_1_n_t(numSubRanges) \ + struct TU_ATTR_PACKED { \ + uint16_t wNumSubRanges; \ + struct TU_ATTR_PACKED { \ + int8_t bMin ; /*The setting for the MIN attribute of the nth subrange of the addressed Control*/\ + int8_t bMax ; /*The setting for the MAX attribute of the nth subrange of the addressed Control*/\ + uint8_t bRes ; /*The setting for the RES attribute of the nth subrange of the addressed Control*/\ + } subrange[numSubRanges] ; \ +} + +/// 5.2.3.2 2-byte Control RANGE Parameter Block +#define audio_control_range_2_n_t(numSubRanges) \ + struct TU_ATTR_PACKED { \ + uint16_t wNumSubRanges; \ + struct TU_ATTR_PACKED { \ + int16_t bMin ; /*The setting for the MIN attribute of the nth subrange of the addressed Control*/\ + int16_t bMax ; /*The setting for the MAX attribute of the nth subrange of the addressed Control*/\ + uint16_t bRes ; /*The setting for the RES attribute of the nth subrange of the addressed Control*/\ + } subrange[numSubRanges]; \ +} + +// 5.2.3.3 4-byte Control RANGE Parameter Block +#define audio_control_range_4_n_t(numSubRanges) \ + struct TU_ATTR_PACKED { \ + uint16_t wNumSubRanges; \ + struct TU_ATTR_PACKED { \ + int32_t bMin ; /*The setting for the MIN attribute of the nth subrange of the addressed Control*/\ + int32_t bMax ; /*The setting for the MAX attribute of the nth subrange of the addressed Control*/\ + uint32_t bRes ; /*The setting for the RES attribute of the nth subrange of the addressed Control*/\ + } subrange[numSubRanges]; \ +} + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/audio/audio_device.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/audio/audio_device.c new file mode 100644 index 000000000..d21980060 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/audio/audio_device.c @@ -0,0 +1,2294 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Reinhard Panhuber, Jerzy Kasenberg + * + * 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. + */ + +/* + * This driver supports at most one out EP, one in EP, one control EP, and one feedback EP and one alternative interface other than zero. Hence, only one input terminal and one output terminal are support, if you need more adjust the driver! + * It supports multiple TX and RX channels. + * + * In case you need more alternate interfaces, you need to define additional defines for this specific alternate interface. Just define them and set them in the set_interface function. + * + * There are three data flow structures currently implemented, where at least one SW-FIFO is used to decouple the asynchronous processes MCU vs. host + * + * 1. Input data -> SW-FIFO -> MCU USB + * + * The most easiest version, available in case the target MCU can handle the software FIFO (SW-FIFO) and if it is implemented in the device driver (if yes then dcd_edpt_xfer_fifo() is available) + * + * 2. Input data -> SW-FIFO -> Linear buffer -> MCU USB + * + * In case the target MCU can not handle a SW-FIFO, a linear buffer is used. This uses the default function dcd_edpt_xfer(). In this case more memory is required. + * + * 3. (Input data 1 | Input data 2 | ... | Input data N) -> (SW-FIFO 1 | SW-FIFO 2 | ... | SW-FIFO N) -> Linear buffer -> MCU USB + * + * This case is used if you have more channels which need to be combined into one stream. Every channel has its own SW-FIFO. All data is encoded into an Linear buffer. + * + * The same holds in the RX case. + * + * */ + +#include "tusb_option.h" + +#if (TUSB_OPT_DEVICE_ENABLED && CFG_TUD_AUDIO) + +//--------------------------------------------------------------------+ +// INCLUDE +//--------------------------------------------------------------------+ +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "audio_device.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + +// Use ring buffer if it's available, some MCUs need extra RAM requirements +#ifndef TUD_AUDIO_PREFER_RING_BUFFER +#if CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX +#define TUD_AUDIO_PREFER_RING_BUFFER 0 +#else +#define TUD_AUDIO_PREFER_RING_BUFFER 1 +#endif +#endif + +// Linear buffer in case target MCU is not capable of handling a ring buffer FIFO e.g. no hardware buffer +// is available or driver is would need to be changed dramatically + +// Only STM32 synopsys and dcd_transdimension use non-linear buffer for now +// Synopsys detection copied from dcd_synopsys.c (refactor later on) +#if defined (STM32F105x8) || defined (STM32F105xB) || defined (STM32F105xC) || \ + defined (STM32F107xB) || defined (STM32F107xC) +#define STM32F1_SYNOPSYS +#endif + +#if defined (STM32L475xx) || defined (STM32L476xx) || \ + defined (STM32L485xx) || defined (STM32L486xx) || defined (STM32L496xx) || \ + defined (STM32L4R5xx) || defined (STM32L4R7xx) || defined (STM32L4R9xx) || \ + defined (STM32L4S5xx) || defined (STM32L4S7xx) || defined (STM32L4S9xx) +#define STM32L4_SYNOPSYS +#endif + +#if (CFG_TUSB_MCU == OPT_MCU_STM32F1 && defined(STM32F1_SYNOPSYS)) || \ + CFG_TUSB_MCU == OPT_MCU_STM32F2 || \ + CFG_TUSB_MCU == OPT_MCU_STM32F4 || \ + CFG_TUSB_MCU == OPT_MCU_STM32F7 || \ + CFG_TUSB_MCU == OPT_MCU_STM32H7 || \ + (CFG_TUSB_MCU == OPT_MCU_STM32L4 && defined(STM32L4_SYNOPSYS)) || \ + CFG_TUSB_MCU == OPT_MCU_RX63X || \ + CFG_TUSB_MCU == OPT_MCU_RX65X || \ + CFG_TUSB_MCU == OPT_MCU_RX72N || \ + CFG_TUSB_MCU == OPT_MCU_GD32VF103 || \ + CFG_TUSB_MCU == OPT_MCU_LPC18XX || \ + CFG_TUSB_MCU == OPT_MCU_LPC43XX || \ + CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || \ + CFG_TUSB_MCU == OPT_MCU_MSP432E4 +#if TUD_AUDIO_PREFER_RING_BUFFER +#define USE_LINEAR_BUFFER 0 +#else +#define USE_LINEAR_BUFFER 1 +#endif +#else +#define USE_LINEAR_BUFFER 1 +#endif + +// Declaration of buffers + +// Check for maximum supported numbers +#if CFG_TUD_AUDIO > 3 +#error Maximum number of audio functions restricted to three! +#endif + +// EP IN software buffers and mutexes +#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING +#if CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ > 0 +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t audio_ep_in_sw_buf_1[CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ]; +#if CFG_FIFO_MUTEX +osal_mutex_def_t ep_in_ff_mutex_wr_1; // No need for read mutex as only USB driver reads from FIFO +#endif +#endif // CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ > 0 +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ > 0 +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t audio_ep_in_sw_buf_2[CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ]; +#if CFG_FIFO_MUTEX +osal_mutex_def_t ep_in_ff_mutex_wr_2; // No need for read mutex as only USB driver reads from FIFO +#endif +#endif // CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ > 0 +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ > 0 +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t audio_ep_in_sw_buf_3[CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ]; +#if CFG_FIFO_MUTEX +osal_mutex_def_t ep_in_ff_mutex_wr_3; // No need for read mutex as only USB driver reads from FIFO +#endif +#endif // CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ > 0 +#endif // CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING + +// Linear buffer TX in case: +// - target MCU is not capable of handling a ring buffer FIFO e.g. no hardware buffer is available or driver is would need to be changed dramatically OR +// - the software encoding is used - in this case the linear buffers serve as a target memory where logical channels are encoded into +#if CFG_TUD_AUDIO_ENABLE_EP_IN && (USE_LINEAR_BUFFER || CFG_TUD_AUDIO_ENABLE_ENCODING) +#if CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX > 0 +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t lin_buf_in_1[CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX]; +#endif +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_IN_SZ_MAX > 0 +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t lin_buf_in_2[CFG_TUD_AUDIO_FUNC_2_EP_IN_SZ_MAX]; +#endif +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_IN_SZ_MAX > 0 +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t lin_buf_in_3[CFG_TUD_AUDIO_FUNC_3_EP_IN_SZ_MAX]; +#endif +#endif // CFG_TUD_AUDIO_ENABLE_EP_IN && (USE_LINEAR_BUFFER || CFG_TUD_AUDIO_ENABLE_DECODING) + +// EP OUT software buffers and mutexes +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING +#if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ > 0 +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t audio_ep_out_sw_buf_1[CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ]; +#if CFG_FIFO_MUTEX +osal_mutex_def_t ep_out_ff_mutex_rd_1; // No need for write mutex as only USB driver writes into FIFO +#endif +#endif // CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ > 0 +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ > 0 +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t audio_ep_out_sw_buf_2[CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ]; +#if CFG_FIFO_MUTEX +osal_mutex_def_t ep_out_ff_mutex_rd_2; // No need for write mutex as only USB driver writes into FIFO +#endif +#endif // CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ > 0 +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ > 0 +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t audio_ep_out_sw_buf_3[CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ]; +#if CFG_FIFO_MUTEX +osal_mutex_def_t ep_out_ff_mutex_rd_3; // No need for write mutex as only USB driver writes into FIFO +#endif +#endif // CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ > 0 +#endif // CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING + +// Linear buffer RX in case: +// - target MCU is not capable of handling a ring buffer FIFO e.g. no hardware buffer is available or driver is would need to be changed dramatically OR +// - the software encoding is used - in this case the linear buffers serve as a target memory where logical channels are encoded into +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && (USE_LINEAR_BUFFER || CFG_TUD_AUDIO_ENABLE_DECODING) +#if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX > 0 +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t lin_buf_out_1[CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX]; +#endif +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_OUT_SZ_MAX > 0 +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t lin_buf_out_2[CFG_TUD_AUDIO_FUNC_2_EP_OUT_SZ_MAX]; +#endif +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX > 0 +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t lin_buf_out_3[CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX]; +#endif +#endif // CFG_TUD_AUDIO_ENABLE_EP_OUT && (USE_LINEAR_BUFFER || CFG_TUD_AUDIO_ENABLE_DECODING) + +// Control buffers +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t ctrl_buf_1[CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ]; +#if CFG_TUD_AUDIO > 1 +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t ctrl_buf_2[CFG_TUD_AUDIO_FUNC_2_CTRL_BUF_SZ]; +#endif +#if CFG_TUD_AUDIO > 2 +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t ctrl_buf_3[CFG_TUD_AUDIO_FUNC_3_CTRL_BUF_SZ]; +#endif + +// Active alternate setting of interfaces +uint8_t alt_setting_1[CFG_TUD_AUDIO_FUNC_1_N_AS_INT]; +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_N_AS_INT > 0 +uint8_t alt_setting_2[CFG_TUD_AUDIO_FUNC_2_N_AS_INT]; +#endif +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_N_AS_INT > 0 +uint8_t alt_setting_3[CFG_TUD_AUDIO_FUNC_3_N_AS_INT]; +#endif + +// Software encoding/decoding support FIFOs +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING +#if CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ > 0 +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t tx_supp_ff_buf_1[CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ]; +tu_fifo_t tx_supp_ff_1[CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO]; +#if CFG_FIFO_MUTEX +osal_mutex_def_t tx_supp_ff_mutex_wr_1[CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO]; // No need for read mutex as only USB driver reads from FIFO +#endif +#endif +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ > 0 +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t tx_supp_ff_buf_2[CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ]; +tu_fifo_t tx_supp_ff_2[CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO]; +#if CFG_FIFO_MUTEX +osal_mutex_def_t tx_supp_ff_mutex_wr_2[CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO]; // No need for read mutex as only USB driver reads from FIFO +#endif +#endif +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ > 0 +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t tx_supp_ff_buf_3[CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ]; +tu_fifo_t tx_supp_ff_3[CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO]; +#if CFG_FIFO_MUTEX +osal_mutex_def_t tx_supp_ff_mutex_wr_3[CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO]; // No need for read mutex as only USB driver reads from FIFO +#endif +#endif +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING +#if CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ > 0 +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t rx_supp_ff_buf_1[CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ]; +tu_fifo_t rx_supp_ff_1[CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO]; +#if CFG_FIFO_MUTEX +osal_mutex_def_t rx_supp_ff_mutex_rd_1[CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO]; // No need for write mutex as only USB driver writes into FIFO +#endif +#endif +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ > 0 +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t rx_supp_ff_buf_2[CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ]; +tu_fifo_t rx_supp_ff_2[CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO]; +#if CFG_FIFO_MUTEX +osal_mutex_def_t rx_supp_ff_mutex_rd_2[CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO]; // No need for write mutex as only USB driver writes into FIFO +#endif +#endif +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ > 0 +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t rx_supp_ff_buf_3[CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO][CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ]; +tu_fifo_t rx_supp_ff_3[CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO]; +#if CFG_FIFO_MUTEX +osal_mutex_def_t rx_supp_ff_mutex_rd_3[CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO]; // No need for write mutex as only USB driver writes into FIFO +#endif +#endif +#endif + +typedef struct +{ + uint8_t rhport; + uint8_t const * p_desc; // Pointer pointing to Standard AC Interface Descriptor(4.7.1) - Audio Control descriptor defining audio function + +#if CFG_TUD_AUDIO_ENABLE_EP_IN + uint8_t ep_in; // TX audio data EP. + uint16_t ep_in_sz; // Current size of TX EP + uint8_t ep_in_as_intf_num; // Corresponding Standard AS Interface Descriptor (4.9.1) belonging to output terminal to which this EP belongs - 0 is invalid (this fits to UAC2 specification since AS interfaces can not have interface number equal to zero) +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + uint8_t ep_out; // Incoming (into uC) audio data EP. + uint16_t ep_out_sz; // Current size of RX EP + uint8_t ep_out_as_intf_num; // Corresponding Standard AS Interface Descriptor (4.9.1) belonging to input terminal to which this EP belongs - 0 is invalid (this fits to UAC2 specification since AS interfaces can not have interface number equal to zero) + +#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + uint8_t ep_fb; // Feedback EP. +#endif + +#endif + +#if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN + uint8_t ep_int_ctr; // Audio control interrupt EP. +#endif + + /*------------- From this point, data is not cleared by bus reset -------------*/ + + uint16_t desc_length; // Length of audio function descriptor + + // Buffer for control requests + uint8_t * ctrl_buf; + uint8_t ctrl_buf_sz; + + // Current active alternate settings + uint8_t * alt_setting; // We need to save the current alternate setting this way, because it is possible that there are AS interfaces which do not have an EP! + + // EP Transfer buffers and FIFOs +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +#if !CFG_TUD_AUDIO_ENABLE_DECODING + tu_fifo_t ep_out_ff; +#endif + +#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + uint32_t fb_val; // Feedback value for asynchronous mode (in 16.16 format). +#endif +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING + tu_fifo_t ep_in_ff; +#endif + + // Audio control interrupt buffer - no FIFO - 6 Bytes according to UAC 2 specification (p. 74) +#if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN + CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN uint8_t ep_int_ctr_buf[CFG_TUD_AUDIO_INT_CTR_EP_IN_SW_BUFFER_SIZE]; +#endif + + // Decoding parameters - parameters are set when alternate AS interface is set by host + // Coding is currently only supported for EP. Software coding corresponding to AS interfaces without EPs are not supported currently. +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING + audio_format_type_t format_type_rx; + uint8_t n_channels_rx; + +#if CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING + audio_data_format_type_I_t format_type_I_rx; + uint8_t n_bytes_per_sampe_rx; + uint8_t n_channels_per_ff_rx; + uint8_t n_ff_used_rx; +#endif +#endif + + // Encoding parameters - parameters are set when alternate AS interface is set by host +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING + audio_format_type_t format_type_tx; + uint8_t n_channels_tx; + +#if CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING + audio_data_format_type_I_t format_type_I_tx; + uint8_t n_bytes_per_sampe_tx; + uint8_t n_channels_per_ff_tx; + uint8_t n_ff_used_tx; +#endif +#endif + + // Support FIFOs for software encoding and decoding +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING + tu_fifo_t * rx_supp_ff; + uint8_t n_rx_supp_ff; + uint16_t rx_supp_ff_sz_max; +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING + tu_fifo_t * tx_supp_ff; + uint8_t n_tx_supp_ff; + uint16_t tx_supp_ff_sz_max; +#endif + + // Linear buffer in case target MCU is not capable of handling a ring buffer FIFO e.g. no hardware buffer is available or driver is would need to be changed dramatically OR the support FIFOs are used +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && (USE_LINEAR_BUFFER || CFG_TUD_AUDIO_ENABLE_DECODING) + uint8_t * lin_buf_out; +#define USE_LINEAR_BUFFER_RX 1 +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && (USE_LINEAR_BUFFER || CFG_TUD_AUDIO_ENABLE_ENCODING) + uint8_t * lin_buf_in; +#define USE_LINEAR_BUFFER_TX 1 +#endif + +} audiod_function_t; + +#ifndef USE_LINEAR_BUFFER_TX +#define USE_LINEAR_BUFFER_TX 0 +#endif + +#ifndef USE_LINEAR_BUFFER_RX +#define USE_LINEAR_BUFFER_RX 0 +#endif + +#define ITF_MEM_RESET_SIZE offsetof(audiod_function_t, ctrl_buf) + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +CFG_TUSB_MEM_SECTION audiod_function_t _audiod_fct[CFG_TUD_AUDIO]; + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +static bool audiod_rx_done_cb(uint8_t rhport, audiod_function_t* audio, uint16_t n_bytes_received); +#endif + +#if CFG_TUD_AUDIO_ENABLE_DECODING && CFG_TUD_AUDIO_ENABLE_EP_OUT +static bool audiod_decode_type_I_pcm(uint8_t rhport, audiod_function_t* audio, uint16_t n_bytes_received); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN +static bool audiod_tx_done_cb(uint8_t rhport, audiod_function_t* audio); +#endif + +#if CFG_TUD_AUDIO_ENABLE_ENCODING && CFG_TUD_AUDIO_ENABLE_EP_IN +static uint16_t audiod_encode_type_I_pcm(uint8_t rhport, audiod_function_t* audio); +#endif + +static bool audiod_get_interface(uint8_t rhport, tusb_control_request_t const * p_request); +static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const * p_request); + +static bool audiod_get_AS_interface_index_global(uint8_t itf, uint8_t *func_id, uint8_t *idxItf, uint8_t const **pp_desc_int); +static bool audiod_get_AS_interface_index(uint8_t itf, audiod_function_t * audio, uint8_t *idxItf, uint8_t const **pp_desc_int); +static bool audiod_verify_entity_exists(uint8_t itf, uint8_t entityID, uint8_t *func_id); +static bool audiod_verify_itf_exists(uint8_t itf, uint8_t *func_id); +static bool audiod_verify_ep_exists(uint8_t ep, uint8_t *func_id); +static uint8_t audiod_get_audio_fct_idx(audiod_function_t * audio); + +#if CFG_TUD_AUDIO_ENABLE_ENCODING || CFG_TUD_AUDIO_ENABLE_DECODING +static void audiod_parse_for_AS_params(audiod_function_t* audio, uint8_t const * p_desc, uint8_t const * p_desc_end, uint8_t const as_itf); + +static inline uint8_t tu_desc_subtype(void const* desc) +{ + return ((uint8_t const*) desc)[2]; +} +#endif + +bool tud_audio_n_mounted(uint8_t func_id) +{ + TU_VERIFY(func_id < CFG_TUD_AUDIO); + audiod_function_t* audio = &_audiod_fct[func_id]; + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + if (audio->ep_out == 0) return false; +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN + if (audio->ep_in == 0) return false; +#endif + +#if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN + if (audio->ep_int_ctr == 0) return false; +#endif + +#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + if (audio->ep_fb == 0) return false; +#endif + + return true; +} + +//--------------------------------------------------------------------+ +// READ API +//--------------------------------------------------------------------+ + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING + +uint16_t tud_audio_n_available(uint8_t func_id) +{ + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); + return tu_fifo_count(&_audiod_fct[func_id].ep_out_ff); +} + +uint16_t tud_audio_n_read(uint8_t func_id, void* buffer, uint16_t bufsize) +{ + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); + return tu_fifo_read_n(&_audiod_fct[func_id].ep_out_ff, buffer, bufsize); +} + +bool tud_audio_n_clear_ep_out_ff(uint8_t func_id) +{ + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); + return tu_fifo_clear(&_audiod_fct[func_id].ep_out_ff); +} + +tu_fifo_t* tud_audio_n_get_ep_out_ff(uint8_t func_id) +{ + if(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL) return &_audiod_fct[func_id].ep_out_ff; + return NULL; +} + +#endif + +#if CFG_TUD_AUDIO_ENABLE_DECODING && CFG_TUD_AUDIO_ENABLE_EP_OUT +// Delete all content in the support RX FIFOs +bool tud_audio_n_clear_rx_support_ff(uint8_t func_id, uint8_t ff_idx) +{ + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL && ff_idx < _audiod_fct[func_id].n_rx_supp_ff); + return tu_fifo_clear(&_audiod_fct[func_id].rx_supp_ff[ff_idx]); +} + +uint16_t tud_audio_n_available_support_ff(uint8_t func_id, uint8_t ff_idx) +{ + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL && ff_idx < _audiod_fct[func_id].n_rx_supp_ff); + return tu_fifo_count(&_audiod_fct[func_id].rx_supp_ff[ff_idx]); +} + +uint16_t tud_audio_n_read_support_ff(uint8_t func_id, uint8_t ff_idx, void* buffer, uint16_t bufsize) +{ + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL && ff_idx < _audiod_fct[func_id].n_rx_supp_ff); + return tu_fifo_read_n(&_audiod_fct[func_id].rx_supp_ff[ff_idx], buffer, bufsize); +} + +tu_fifo_t* tud_audio_n_get_rx_support_ff(uint8_t func_id, uint8_t ff_idx) +{ + if(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL && ff_idx < _audiod_fct[func_id].n_rx_supp_ff) return &_audiod_fct[func_id].rx_supp_ff[ff_idx]; + return NULL; +} +#endif + +// This function is called once an audio packet is received by the USB and is responsible for putting data from USB memory into EP_OUT_FIFO (or support FIFOs + decoding of received stream into audio channels). +// If you prefer your own (more efficient) implementation suiting your purpose set CFG_TUD_AUDIO_ENABLE_DECODING = 0. + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + +static bool audiod_rx_done_cb(uint8_t rhport, audiod_function_t* audio, uint16_t n_bytes_received) +{ + uint8_t idxItf; + uint8_t const *dummy2; + uint8_t idx_audio_fct = 0; + + if (tud_audio_rx_done_pre_read_cb || tud_audio_rx_done_post_read_cb) + { + idx_audio_fct = audiod_get_audio_fct_idx(audio); + TU_VERIFY(audiod_get_AS_interface_index(audio->ep_out_as_intf_num, audio, &idxItf, &dummy2)); + } + + // Call a weak callback here - a possibility for user to get informed an audio packet was received and data gets now loaded into EP FIFO (or decoded into support RX software FIFO) + if (tud_audio_rx_done_pre_read_cb) TU_VERIFY(tud_audio_rx_done_pre_read_cb(rhport, n_bytes_received, idx_audio_fct, audio->ep_out, audio->alt_setting[idxItf])); + +#if CFG_TUD_AUDIO_ENABLE_DECODING && CFG_TUD_AUDIO_ENABLE_EP_OUT + + switch (audio->format_type_rx) + { + case AUDIO_FORMAT_TYPE_UNDEFINED: + // INDIVIDUAL DECODING PROCEDURE REQUIRED HERE! + TU_LOG2(" Desired CFG_TUD_AUDIO_FORMAT encoding not implemented!\r\n"); + TU_BREAKPOINT(); + break; + + case AUDIO_FORMAT_TYPE_I: + + switch (audio->format_type_I_tx) + { + case AUDIO_DATA_FORMAT_TYPE_I_PCM: + TU_VERIFY(audiod_decode_type_I_pcm(rhport, audio, n_bytes_received)); + break; + + default: + // DESIRED CFG_TUD_AUDIO_FORMAT_TYPE_I_RX NOT IMPLEMENTED! + TU_LOG2(" Desired CFG_TUD_AUDIO_FORMAT_TYPE_I_RX encoding not implemented!\r\n"); + TU_BREAKPOINT(); + break; + } + break; + + default: + // Desired CFG_TUD_AUDIO_FORMAT_TYPE_RX not implemented! + TU_LOG2(" Desired CFG_TUD_AUDIO_FORMAT_TYPE_RX not implemented!\r\n"); + TU_BREAKPOINT(); + break; + } + + // Prepare for next transmission + TU_VERIFY(usbd_edpt_xfer(rhport, audio->ep_out, audio->lin_buf_out, audio->ep_out_sz), false); + +#else + +#if USE_LINEAR_BUFFER_RX + // Data currently is in linear buffer, copy into EP OUT FIFO + TU_VERIFY(tu_fifo_write_n(&audio->ep_out_ff, audio->lin_buf_out, n_bytes_received)); + + // Schedule for next receive + TU_VERIFY(usbd_edpt_xfer(rhport, audio->ep_out, audio->lin_buf_out, audio->ep_out_sz), false); +#else + // Data is already placed in EP FIFO, schedule for next receive + TU_VERIFY(usbd_edpt_xfer_fifo(rhport, audio->ep_out, &audio->ep_out_ff, audio->ep_out_sz), false); +#endif + +#endif + + // Call a weak callback here - a possibility for user to get informed decoding was completed + if (tud_audio_rx_done_post_read_cb) TU_VERIFY(tud_audio_rx_done_post_read_cb(rhport, n_bytes_received, idx_audio_fct, audio->ep_out, audio->alt_setting[idxItf])); + + return true; +} + +#endif //CFG_TUD_AUDIO_ENABLE_EP_OUT + +// The following functions are used in case CFG_TUD_AUDIO_ENABLE_DECODING != 0 +#if CFG_TUD_AUDIO_ENABLE_DECODING && CFG_TUD_AUDIO_ENABLE_EP_OUT + +// Decoding according to 2.3.1.5 Audio Streams + +// Helper function +static inline uint8_t * audiod_interleaved_copy_bytes_fast_decode(uint16_t const nBytesToCopy, void * dst, uint8_t * dst_end, uint8_t * src, uint8_t const n_ff_used) +{ + + // This function is an optimized version of + // while((uint8_t *)dst < dst_end) + // { + // memcpy(dst, src, nBytesToCopy); + // dst = (uint8_t *)dst + nBytesToCopy; + // src += nBytesToCopy * n_ff_used; + // } + + // Optimize for fast half word copies + typedef struct{ + uint16_t val; + } __attribute((__packed__)) unaligned_uint16_t; + + // Optimize for fast word copies + typedef struct{ + uint32_t val; + } __attribute((__packed__)) unaligned_uint32_t; + + switch (nBytesToCopy) + { + case 1: + while((uint8_t *)dst < dst_end) + { + *(uint8_t *)dst++ = *src; + src += n_ff_used; + } + break; + + case 2: + while((uint8_t *)dst < dst_end) + { + *(unaligned_uint16_t*)dst = *(unaligned_uint16_t*)src; + dst += 2; + src += 2 * n_ff_used; + } + break; + + case 3: + while((uint8_t *)dst < dst_end) + { + // memcpy(dst, src, 3); + // dst = (uint8_t *)dst + 3; + // src += 3 * n_ff_used; + + // TODO: Is there a faster way to copy 3 bytes? + *(uint8_t *)dst++ = *src++; + *(uint8_t *)dst++ = *src++; + *(uint8_t *)dst++ = *src++; + + src += 3 * (n_ff_used - 1); + } + break; + + case 4: + while((uint8_t *)dst < dst_end) + { + *(unaligned_uint32_t*)dst = *(unaligned_uint32_t*)src; + dst += 4; + src += 4 * n_ff_used; + } + break; + } + + return src; +} + +static bool audiod_decode_type_I_pcm(uint8_t rhport, audiod_function_t* audio, uint16_t n_bytes_received) +{ + (void) rhport; + + // Determine amount of samples + uint8_t const n_ff_used = audio->n_ff_used_rx; + uint16_t const nBytesPerFFToRead = n_bytes_received / n_ff_used; + uint8_t cnt_ff; + + // Decode + uint8_t * src; + uint8_t * dst_end; + + tu_fifo_buffer_info_t info; + + for (cnt_ff = 0; cnt_ff < n_ff_used; cnt_ff++) + { + tu_fifo_get_write_info(&audio->rx_supp_ff[cnt_ff], &info); + + if (info.len_lin != 0) + { + info.len_lin = tu_min16(nBytesPerFFToRead, info.len_lin); + src = &audio->lin_buf_out[cnt_ff*audio->n_channels_per_ff_rx * audio->n_bytes_per_sampe_rx]; + dst_end = info.ptr_lin + info.len_lin; + src = audiod_interleaved_copy_bytes_fast_decode(audio->n_bytes_per_sampe_rx, info.ptr_lin, dst_end, src, n_ff_used); + + // Handle wrapped part of FIFO + info.len_wrap = tu_min16(nBytesPerFFToRead - info.len_lin, info.len_wrap); + if (info.len_wrap != 0) + { + dst_end = info.ptr_wrap + info.len_wrap; + audiod_interleaved_copy_bytes_fast_decode(audio->n_bytes_per_sampe_rx, info.ptr_wrap, dst_end, src, n_ff_used); + } + tu_fifo_advance_write_pointer(&audio->rx_supp_ff[cnt_ff], info.len_lin + info.len_wrap); + } + } + + // Number of bytes should be a multiple of CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX * CFG_TUD_AUDIO_N_CHANNELS_RX but checking makes no sense - no way to correct it + // TU_VERIFY(cnt != n_bytes); + + return true; +} +#endif //CFG_TUD_AUDIO_ENABLE_DECODING + +//--------------------------------------------------------------------+ +// WRITE API +//--------------------------------------------------------------------+ + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING + +/** + * \brief Write data to EP in buffer + * + * Write data to buffer. If it is full, new data can be inserted once a transmit was scheduled. See audiod_tx_done_cb(). + * If TX FIFOs are used, this function is not available in order to not let the user mess up the encoding process. + * + * \param[in] func_id: Index of audio function interface + * \param[in] data: Pointer to data array to be copied from + * \param[in] len: # of array elements to copy + * \return Number of bytes actually written + */ +uint16_t tud_audio_n_write(uint8_t func_id, const void * data, uint16_t len) +{ + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); + return tu_fifo_write_n(&_audiod_fct[func_id].ep_in_ff, data, len); +} + +bool tud_audio_n_clear_ep_in_ff(uint8_t func_id) // Delete all content in the EP IN FIFO +{ + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); + return tu_fifo_clear(&_audiod_fct[func_id].ep_in_ff); +} + +tu_fifo_t* tud_audio_n_get_ep_in_ff(uint8_t func_id) +{ + if(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL) return &_audiod_fct[func_id].ep_in_ff; + return NULL; +} + +#endif + +#if CFG_TUD_AUDIO_ENABLE_ENCODING && CFG_TUD_AUDIO_ENABLE_EP_IN + +uint16_t tud_audio_n_flush_tx_support_ff(uint8_t func_id) // Force all content in the support TX FIFOs to be written into linear buffer and schedule a transmit +{ + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); + audiod_function_t* audio = &_audiod_fct[func_id]; + + uint16_t n_bytes_copied = tu_fifo_count(&audio->tx_supp_ff[0]); + + TU_VERIFY(audiod_tx_done_cb(audio->rhport, audio)); + + n_bytes_copied -= tu_fifo_count(&audio->tx_supp_ff[0]); + n_bytes_copied = n_bytes_copied*audio->tx_supp_ff[0].item_size; + + return n_bytes_copied; +} + +bool tud_audio_n_clear_tx_support_ff(uint8_t func_id, uint8_t ff_idx) +{ + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL && ff_idx < _audiod_fct[func_id].n_tx_supp_ff); + return tu_fifo_clear(&_audiod_fct[func_id].tx_supp_ff[ff_idx]); +} + +uint16_t tud_audio_n_write_support_ff(uint8_t func_id, uint8_t ff_idx, const void * data, uint16_t len) +{ + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL && ff_idx < _audiod_fct[func_id].n_tx_supp_ff); + return tu_fifo_write_n(&_audiod_fct[func_id].tx_supp_ff[ff_idx], data, len); +} + +tu_fifo_t* tud_audio_n_get_tx_support_ff(uint8_t func_id, uint8_t ff_idx) +{ + if(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL && ff_idx < _audiod_fct[func_id].n_tx_supp_ff) return &_audiod_fct[func_id].tx_supp_ff[ff_idx]; + return NULL; +} + +#endif + + +#if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN + +// If no interrupt transmit is pending bytes get written into buffer and a transmit is scheduled - once transmit completed tud_audio_int_ctr_done_cb() is called in inform user +uint16_t tud_audio_int_ctr_n_write(uint8_t func_id, uint8_t const* buffer, uint16_t len) +{ + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); + + // We write directly into the EP's buffer - abort if previous transfer not complete + TU_VERIFY(!usbd_edpt_busy(_audiod_fct[func_id].rhport, _audiod_fct[func_id].ep_int_ctr)); + + // Check length + TU_VERIFY(len <= CFG_TUD_AUDIO_INT_CTR_EP_IN_SW_BUFFER_SIZE); + + memcpy(_audiod_fct[func_id].ep_int_ctr_buf, buffer, len); + + // Schedule transmit + TU_VERIFY(usbd_edpt_xfer(_audiod_fct[func_id].rhport, _audiod_fct[func_id].ep_int_ctr, _audiod_fct[func_id].ep_int_ctr_buf, len)); + + return true; +} + +#endif + + +// This function is called once a transmit of an audio packet was successfully completed. Here, we encode samples and place it in IN EP's buffer for next transmission. +// If you prefer your own (more efficient) implementation suiting your purpose set CFG_TUD_AUDIO_ENABLE_ENCODING = 0 and use tud_audio_n_write. + +// n_bytes_copied - Informs caller how many bytes were loaded. In case n_bytes_copied = 0, a ZLP is scheduled to inform host no data is available for current frame. +#if CFG_TUD_AUDIO_ENABLE_EP_IN +static bool audiod_tx_done_cb(uint8_t rhport, audiod_function_t * audio) +{ + uint8_t idxItf; + uint8_t const *dummy2; + + uint8_t idx_audio_fct = audiod_get_audio_fct_idx(audio); + TU_VERIFY(audiod_get_AS_interface_index(audio->ep_in_as_intf_num, audio, &idxItf, &dummy2)); + + // Only send something if current alternate interface is not 0 as in this case nothing is to be sent due to UAC2 specifications + if (audio->alt_setting[idxItf] == 0) return false; + + // Call a weak callback here - a possibility for user to get informed former TX was completed and data gets now loaded into EP in buffer (in case FIFOs are used) or + // if no FIFOs are used the user may use this call back to load its data into the EP IN buffer by use of tud_audio_n_write_ep_in_buffer(). + if (tud_audio_tx_done_pre_load_cb) TU_VERIFY(tud_audio_tx_done_pre_load_cb(rhport, idx_audio_fct, audio->ep_in, audio->alt_setting[idxItf])); + + // Send everything in ISO EP FIFO + uint16_t n_bytes_tx; + + // If support FIFOs are used, encode and schedule transmit +#if CFG_TUD_AUDIO_ENABLE_ENCODING && CFG_TUD_AUDIO_ENABLE_EP_IN + switch (audio->format_type_tx) + { + case AUDIO_FORMAT_TYPE_UNDEFINED: + // INDIVIDUAL ENCODING PROCEDURE REQUIRED HERE! + TU_LOG2(" Desired CFG_TUD_AUDIO_FORMAT encoding not implemented!\r\n"); + TU_BREAKPOINT(); + n_bytes_tx = 0; + break; + + case AUDIO_FORMAT_TYPE_I: + + switch (audio->format_type_I_tx) + { + case AUDIO_DATA_FORMAT_TYPE_I_PCM: + + n_bytes_tx = audiod_encode_type_I_pcm(rhport, audio); + break; + + default: + // YOUR ENCODING IS REQUIRED HERE! + TU_LOG2(" Desired CFG_TUD_AUDIO_FORMAT_TYPE_I_TX encoding not implemented!\r\n"); + TU_BREAKPOINT(); + n_bytes_tx = 0; + break; + } + break; + + default: + // Desired CFG_TUD_AUDIO_FORMAT_TYPE_TX not implemented! + TU_LOG2(" Desired CFG_TUD_AUDIO_FORMAT_TYPE_TX not implemented!\r\n"); + TU_BREAKPOINT(); + n_bytes_tx = 0; + break; + } + + TU_VERIFY(usbd_edpt_xfer(rhport, audio->ep_in, audio->lin_buf_in, n_bytes_tx)); + +#else + // No support FIFOs, if no linear buffer required schedule transmit, else put data into linear buffer and schedule + + n_bytes_tx = tu_min16(tu_fifo_count(&audio->ep_in_ff), audio->ep_in_sz); // Limit up to max packet size, more can not be done for ISO + +#if USE_LINEAR_BUFFER_TX + tu_fifo_read_n(&audio->ep_in_ff, audio->lin_buf_in, n_bytes_tx); + TU_VERIFY(usbd_edpt_xfer(rhport, audio->ep_in, audio->lin_buf_in, n_bytes_tx)); +#else + // Send everything in ISO EP FIFO + TU_VERIFY(usbd_edpt_xfer_fifo(rhport, audio->ep_in, &audio->ep_in_ff, n_bytes_tx)); +#endif + +#endif + + // Call a weak callback here - a possibility for user to get informed former TX was completed and how many bytes were loaded for the next frame + if (tud_audio_tx_done_post_load_cb) TU_VERIFY(tud_audio_tx_done_post_load_cb(rhport, n_bytes_tx, idx_audio_fct, audio->ep_in, audio->alt_setting[idxItf])); + + return true; +} + +#endif //CFG_TUD_AUDIO_ENABLE_EP_IN + +#if CFG_TUD_AUDIO_ENABLE_ENCODING && CFG_TUD_AUDIO_ENABLE_EP_IN +// Take samples from the support buffer and encode them into the IN EP software FIFO +// Returns number of bytes written into linear buffer + +/* 2.3.1.7.1 PCM Format +The PCM (Pulse Coded Modulation) format is the most commonly used audio format to represent audio +data streams. The audio data is not compressed and uses a signed two’s-complement fixed point format. It +is left-justified (the sign bit is the Msb) and data is padded with trailing zeros to fill the remaining unused +bits of the subslot. The binary point is located to the right of the sign bit so that all values lie within the +range [-1, +1) + */ + +/* + * This function encodes channels saved within the support FIFOs into one stream by interleaving the PCM samples + * in the support FIFOs according to 2.3.1.5 Audio Streams. It does not control justification (left or right) and + * does not change the number of bytes per sample. + * */ + +// Helper function +static inline uint8_t * audiod_interleaved_copy_bytes_fast_encode(uint16_t const nBytesToCopy, uint8_t * src, uint8_t * src_end, uint8_t * dst, uint8_t const n_ff_used) +{ + // Optimize for fast half word copies + typedef struct{ + uint16_t val; + } __attribute((__packed__)) unaligned_uint16_t; + + // Optimize for fast word copies + typedef struct{ + uint32_t val; + } __attribute((__packed__)) unaligned_uint32_t; + + switch (nBytesToCopy) + { + case 1: + while(src < src_end) + { + *dst = *src++; + dst += n_ff_used; + } + break; + + case 2: + while(src < src_end) + { + *(unaligned_uint16_t*)dst = *(unaligned_uint16_t*)src; + src += 2; + dst += 2 * n_ff_used; + } + break; + + case 3: + while(src < src_end) + { + // memcpy(dst, src, 3); + // src = (uint8_t *)src + 3; + // dst += 3 * n_ff_used; + + // TODO: Is there a faster way to copy 3 bytes? + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + + dst += 3 * (n_ff_used - 1); + } + break; + + case 4: + while(src < src_end) + { + *(unaligned_uint32_t*)dst = *(unaligned_uint32_t*)src; + src += 4; + dst += 4 * n_ff_used; + } + break; + } + + return dst; +} + +static uint16_t audiod_encode_type_I_pcm(uint8_t rhport, audiod_function_t* audio) +{ + // This function relies on the fact that the length of the support FIFOs was configured to be a multiple of the active sample size in bytes s.t. no sample is split within a wrap + // This is ensured within set_interface, where the FIFOs are reconfigured according to this size + + // We encode directly into IN EP's linear buffer - abort if previous transfer not complete + TU_VERIFY(!usbd_edpt_busy(rhport, audio->ep_in)); + + // Determine amount of samples + uint8_t const n_ff_used = audio->n_ff_used_tx; + uint16_t const nBytesToCopy = audio->n_channels_per_ff_tx * audio->n_bytes_per_sampe_tx; + uint16_t const capPerFF = audio->ep_in_sz / n_ff_used; // Sample capacity per FIFO in bytes + uint16_t nBytesPerFFToSend = tu_fifo_count(&audio->tx_supp_ff[0]); + uint8_t cnt_ff; + + for (cnt_ff = 1; cnt_ff < n_ff_used; cnt_ff++) + { + uint16_t const count = tu_fifo_count(&audio->tx_supp_ff[cnt_ff]); + if (count < nBytesPerFFToSend) + { + nBytesPerFFToSend = count; + } + } + + // Check if there is enough + if (nBytesPerFFToSend == 0) return 0; + + // Limit to maximum sample number - THIS IS A POSSIBLE ERROR SOURCE IF TOO MANY SAMPLE WOULD NEED TO BE SENT BUT CAN NOT! + nBytesPerFFToSend = tu_min16(nBytesPerFFToSend, capPerFF); + + // Round to full number of samples (flooring) + nBytesPerFFToSend = (nBytesPerFFToSend / nBytesToCopy) * nBytesToCopy; + + // Encode + uint8_t * dst; + uint8_t * src_end; + + tu_fifo_buffer_info_t info; + + for (cnt_ff = 0; cnt_ff < n_ff_used; cnt_ff++) + { + dst = &audio->lin_buf_in[cnt_ff*audio->n_channels_per_ff_tx*audio->n_bytes_per_sampe_tx]; + + tu_fifo_get_read_info(&audio->tx_supp_ff[cnt_ff], &info); + + if (info.len_lin != 0) + { + info.len_lin = tu_min16(nBytesPerFFToSend, info.len_lin); // Limit up to desired length + src_end = (uint8_t *)info.ptr_lin + info.len_lin; + dst = audiod_interleaved_copy_bytes_fast_encode(audio->n_bytes_per_sampe_tx, info.ptr_lin, src_end, dst, n_ff_used); + + // Limit up to desired length + info.len_wrap = tu_min16(nBytesPerFFToSend - info.len_lin, info.len_wrap); + + // Handle wrapped part of FIFO + if (info.len_wrap != 0) + { + src_end = (uint8_t *)info.ptr_wrap + info.len_wrap; + audiod_interleaved_copy_bytes_fast_encode(audio->n_bytes_per_sampe_tx, info.ptr_wrap, src_end, dst, n_ff_used); + } + + tu_fifo_advance_read_pointer(&audio->tx_supp_ff[cnt_ff], info.len_lin + info.len_wrap); + } + } + + return nBytesPerFFToSend * n_ff_used; +} +#endif //CFG_TUD_AUDIO_ENABLE_ENCODING + +// This function is called once a transmit of a feedback packet was successfully completed. Here, we get the next feedback value to be sent + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP +static inline bool audiod_fb_send(uint8_t rhport, audiod_function_t *audio) +{ + return usbd_edpt_xfer(rhport, audio->ep_fb, (uint8_t *) &audio->fb_val, 4); +} +#endif + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void audiod_init(void) +{ + tu_memclr(_audiod_fct, sizeof(_audiod_fct)); + + for(uint8_t i=0; ictrl_buf = ctrl_buf_1; + audio->ctrl_buf_sz = CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ; + break; +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_CTRL_BUF_SZ > 0 + case 1: + audio->ctrl_buf = ctrl_buf_2; + audio->ctrl_buf_sz = CFG_TUD_AUDIO_FUNC_2_CTRL_BUF_SZ; + break; +#endif +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_CTRL_BUF_SZ > 0 + case 2: + audio->ctrl_buf = ctrl_buf_3; + audio->ctrl_buf_sz = CFG_TUD_AUDIO_FUNC_3_CTRL_BUF_SZ; + break; +#endif + } + + // Initialize active alternate interface buffers + switch (i) + { +#if CFG_TUD_AUDIO_FUNC_1_N_AS_INT > 0 + case 0: + audio->alt_setting = alt_setting_1; + break; +#endif +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_N_AS_INT > 0 + case 1: + audio->alt_setting = alt_setting_2; + break; +#endif +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_N_AS_INT > 0 + case 2: + audio->alt_setting = alt_setting_3; + break; +#endif + } + + // Initialize IN EP FIFO if required +#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING + + switch (i) + { +#if CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ > 0 + case 0: + tu_fifo_config(&audio->ep_in_ff, audio_ep_in_sw_buf_1, CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ, 1, true); +#if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&audio->ep_in_ff, osal_mutex_create(&ep_in_ff_mutex_wr_1), NULL); +#endif + break; +#endif +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ > 0 + case 1: + tu_fifo_config(&audio->ep_in_ff, audio_ep_in_sw_buf_2, CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ, 1, true); +#if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&audio->ep_in_ff, osal_mutex_create(&ep_in_ff_mutex_wr_2), NULL); +#endif + break; +#endif +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ > 0 + case 2: + tu_fifo_config(&audio->ep_in_ff, audio_ep_in_sw_buf_3, CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ, 1, true); +#if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&audio->ep_in_ff, osal_mutex_create(&ep_in_ff_mutex_wr_3), NULL); +#endif + break; +#endif + } +#endif // CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING + + // Initialize linear buffers +#if USE_LINEAR_BUFFER_TX + switch (i) + { +#if CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX > 0 + case 0: + audio->lin_buf_in = lin_buf_in_1; + break; +#endif +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_IN_SZ_MAX > 0 + case 1: + audio->lin_buf_in = lin_buf_in_2; + break; +#endif +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_IN_SZ_MAX > 0 + case 2: + audio->lin_buf_in = lin_buf_in_3; + break; +#endif + } +#endif // USE_LINEAR_BUFFER_TX + + // Initialize OUT EP FIFO if required +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING + + switch (i) + { +#if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ > 0 + case 0: + tu_fifo_config(&audio->ep_out_ff, audio_ep_out_sw_buf_1, CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ, 1, true); +#if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&audio->ep_out_ff, NULL, osal_mutex_create(&ep_out_ff_mutex_rd_1)); +#endif + break; +#endif +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ > 0 + case 1: + tu_fifo_config(&audio->ep_out_ff, audio_ep_out_sw_buf_2, CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ, 1, true); +#if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&audio->ep_out_ff, NULL, osal_mutex_create(&ep_out_ff_mutex_rd_2)); +#endif + break; +#endif +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ > 0 + case 2: + tu_fifo_config(&audio->ep_out_ff, audio_ep_out_sw_buf_3, CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ, 1, true); +#if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&audio->ep_out_ff, NULL, osal_mutex_create(&ep_out_ff_mutex_rd_3)); +#endif + break; +#endif + } +#endif // CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING + + // Initialize linear buffers +#if USE_LINEAR_BUFFER_RX + switch (i) + { +#if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX > 0 + case 0: + audio->lin_buf_out = lin_buf_out_1; + break; +#endif +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_EP_OUT_SZ_MAX > 0 + case 1: + audio->lin_buf_out = lin_buf_out_2; + break; +#endif +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX > 0 + case 2: + audio->lin_buf_out = lin_buf_out_3; + break; +#endif + } +#endif // USE_LINEAR_BUFFER_TX + + // Initialize TX support FIFOs if required +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING + + switch (i) + { +#if CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ > 0 + case 0: + audio->tx_supp_ff = tx_supp_ff_1; + audio->n_tx_supp_ff = CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO; + audio->tx_supp_ff_sz_max = CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ; + for (uint8_t cnt = 0; cnt < CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO; cnt++) + { + tu_fifo_config(&tx_supp_ff_1[cnt], tx_supp_ff_buf_1[cnt], CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ, 1, true); +#if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&tx_supp_ff_1[cnt], osal_mutex_create(&tx_supp_ff_mutex_wr_1[cnt]), NULL); +#endif + } + + break; +#endif // CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ > 0 + +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ > 0 + case 1: + audio->tx_supp_ff = tx_supp_ff_2; + audio->n_tx_supp_ff = CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO; + audio->tx_supp_ff_sz_max = CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ; + for (uint8_t cnt = 0; cnt < CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO; cnt++) + { + tu_fifo_config(&tx_supp_ff_2[cnt], tx_supp_ff_buf_2[cnt], CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ, 1, true); +#if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&tx_supp_ff_2[cnt], osal_mutex_create(&tx_supp_ff_mutex_wr_2[cnt]), NULL); +#endif + } + + break; +#endif // CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ > 0 + +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ > 0 + case 2: + audio->tx_supp_ff = tx_supp_ff_3; + audio->n_tx_supp_ff = CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO; + audio->tx_supp_ff_sz_max = CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ; + for (uint8_t cnt = 0; cnt < CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO; cnt++) + { + tu_fifo_config(&tx_supp_ff_3[cnt], tx_supp_ff_buf_3[cnt], CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ, 1, true); +#if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&tx_supp_ff_3[cnt], osal_mutex_create(&tx_supp_ff_mutex_wr_3[cnt]), NULL); +#endif + } + + break; +#endif // CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ > 0 + } +#endif // CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING + + // Set encoding parameters for Type_I formats +#if CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING + switch (i) + { +#if CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ > 0 + case 0: + audio->n_channels_per_ff_tx = CFG_TUD_AUDIO_FUNC_1_CHANNEL_PER_FIFO_TX; + break; +#endif +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ > 0 + case 1: + audio->n_channels_per_ff_tx = CFG_TUD_AUDIO_FUNC_2_CHANNEL_PER_FIFO_TX; + break; +#endif +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ > 0 + case 2: + audio->n_channels_per_ff_tx = CFG_TUD_AUDIO_FUNC_3_CHANNEL_PER_FIFO_TX; + break; +#endif + } +#endif // CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING + + // Initialize RX support FIFOs if required +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING + + switch (i) + { +#if CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ > 0 + case 0: + audio->rx_supp_ff = rx_supp_ff_1; + audio->n_rx_supp_ff = CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO; + audio->rx_supp_ff_sz_max = CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ; + for (uint8_t cnt = 0; cnt < CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO; cnt++) + { + tu_fifo_config(&rx_supp_ff_1[cnt], rx_supp_ff_buf_1[cnt], CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ, 1, true); +#if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&rx_supp_ff_1[cnt], osal_mutex_create(&rx_supp_ff_mutex_rd_1[cnt]), NULL); +#endif + } + + break; +#endif // CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ > 0 + +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ > 0 + case 1: + audio->rx_supp_ff = rx_supp_ff_2; + audio->n_rx_supp_ff = CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO; + audio->rx_supp_ff_sz_max = CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ; + for (uint8_t cnt = 0; cnt < CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO; cnt++) + { + tu_fifo_config(&rx_supp_ff_2[cnt], rx_supp_ff_buf_2[cnt], CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ, 1, true); +#if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&rx_supp_ff_2[cnt], osal_mutex_create(&rx_supp_ff_mutex_rd_2[cnt]), NULL); +#endif + } + + break; +#endif // CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ > 0 + +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ > 0 + case 2: + audio->rx_supp_ff = rx_supp_ff_3; + audio->n_rx_supp_ff = CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO; + audio->rx_supp_ff_sz_max = CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ; + for (uint8_t cnt = 0; cnt < CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO; cnt++) + { + tu_fifo_config(&rx_supp_ff_3[cnt], rx_supp_ff_buf_3[cnt], CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ, 1, true); +#if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&rx_supp_ff_3[cnt], osal_mutex_create(&rx_supp_ff_mutex_rd_3[cnt]), NULL); +#endif + } + + break; +#endif // CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ > 0 + } +#endif // CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING + + // Set encoding parameters for Type_I formats +#if CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING + switch (i) + { +#if CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ > 0 + case 0: + audio->n_channels_per_ff_rx = CFG_TUD_AUDIO_FUNC_1_CHANNEL_PER_FIFO_RX; + break; +#endif +#if CFG_TUD_AUDIO > 1 && CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ > 0 + case 1: + audio->n_channels_per_ff_rx = CFG_TUD_AUDIO_FUNC_2_CHANNEL_PER_FIFO_RX; + break; +#endif +#if CFG_TUD_AUDIO > 2 && CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ > 0 + case 2: + audio->n_channels_per_ff_rx = CFG_TUD_AUDIO_FUNC_3_CHANNEL_PER_FIFO_RX; + break; +#endif + } +#endif // CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING + } +} + +void audiod_reset(uint8_t rhport) +{ + (void) rhport; + + for(uint8_t i=0; iep_in_ff); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING + tu_fifo_clear(&audio->ep_out_ff); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING + for (uint8_t cnt = 0; cnt < audio->n_tx_supp_ff; cnt++) + { + tu_fifo_clear(&audio->tx_supp_ff[cnt]); + } +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING + for (uint8_t cnt = 0; cnt < audio->n_rx_supp_ff; cnt++) + { + tu_fifo_clear(&audio->rx_supp_ff[cnt]); + } +#endif + } +} + +uint16_t audiod_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) +{ + (void) max_len; + + TU_VERIFY ( TUSB_CLASS_AUDIO == itf_desc->bInterfaceClass && + AUDIO_SUBCLASS_CONTROL == itf_desc->bInterfaceSubClass); + + // Verify version is correct - this check can be omitted + TU_VERIFY(itf_desc->bInterfaceProtocol == AUDIO_INT_PROTOCOL_CODE_V2); + + // Verify interrupt control EP is enabled if demanded by descriptor - this should be best some static check however - this check can be omitted + if (itf_desc->bNumEndpoints == 1) // 0 or 1 EPs are allowed + { + TU_VERIFY(CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN > 0); + } + + // Alternate setting MUST be zero - this check can be omitted + TU_VERIFY(itf_desc->bAlternateSetting == 0); + + // Find available audio driver interface + uint8_t i; + for (i = 0; i < CFG_TUD_AUDIO; i++) + { + if (!_audiod_fct[i].p_desc) + { + _audiod_fct[i].p_desc = (uint8_t const *)itf_desc; // Save pointer to AC descriptor which is by specification always the first one + _audiod_fct[i].rhport = rhport; + + // Setup descriptor lengths + switch (i) + { + case 0: + _audiod_fct[i].desc_length = CFG_TUD_AUDIO_FUNC_1_DESC_LEN; + break; +#if CFG_TUD_AUDIO > 1 + case 1: + _audiod_fct[i].desc_length = CFG_TUD_AUDIO_FUNC_2_DESC_LEN; + break; +#endif +#if CFG_TUD_AUDIO > 2 + case 2: + _audiod_fct[i].desc_length = CFG_TUD_AUDIO_FUNC_3_DESC_LEN; + break; +#endif + } + + break; + } + } + + // Verify we found a free one + TU_ASSERT( i < CFG_TUD_AUDIO ); + + // This is all we need so far - the EPs are setup by a later set_interface request (as per UAC2 specification) + uint16_t drv_len = _audiod_fct[i].desc_length - TUD_AUDIO_DESC_IAD_LEN; // - TUD_AUDIO_DESC_IAD_LEN since tinyUSB already handles the IAD descriptor + + return drv_len; +} + +static bool audiod_get_interface(uint8_t rhport, tusb_control_request_t const * p_request) +{ + uint8_t const itf = tu_u16_low(p_request->wIndex); + + // Find index of audio streaming interface + uint8_t func_id, idxItf; + uint8_t const *dummy; + + TU_VERIFY(audiod_get_AS_interface_index_global(itf, &func_id, &idxItf, &dummy)); + TU_VERIFY(tud_control_xfer(rhport, p_request, &_audiod_fct[func_id].alt_setting[idxItf], 1)); + + TU_LOG2(" Get itf: %u - current alt: %u\r\n", itf, _audiod_fct[func_id].alt_setting[idxItf]); + + return true; +} + +static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const * p_request) +{ + (void) rhport; + + // Here we need to do the following: + + // 1. Find the audio driver assigned to the given interface to be set + // Since one audio driver interface has to be able to cover an unknown number of interfaces (AC, AS + its alternate settings), the best memory efficient way to solve this is to always search through the descriptors. + // The audio driver is mapped to an audio function by a reference pointer to the corresponding AC interface of this audio function which serves as a starting point for searching + + // 2. Close EPs which are currently open + // To do so it is not necessary to know the current active alternate interface since we already save the current EP addresses - we simply close them + + // 3. Open new EP + + uint8_t const itf = tu_u16_low(p_request->wIndex); + uint8_t const alt = tu_u16_low(p_request->wValue); + + TU_LOG2(" Set itf: %u - alt: %u\r\n", itf, alt); + + // Find index of audio streaming interface and index of interface + uint8_t func_id, idxItf; + uint8_t const *p_desc; + TU_VERIFY(audiod_get_AS_interface_index_global(itf, &func_id, &idxItf, &p_desc)); + + audiod_function_t* audio = &_audiod_fct[func_id]; + + // Look if there is an EP to be closed - for this driver, there are only 3 possible EPs which may be closed (only AS related EPs can be closed, AC EP (if present) is always open) +#if CFG_TUD_AUDIO_ENABLE_EP_IN + if (audio->ep_in_as_intf_num == itf) + { + audio->ep_in_as_intf_num = 0; + usbd_edpt_close(rhport, audio->ep_in); + + // Clear FIFOs, since data is no longer valid +#if !CFG_TUD_AUDIO_ENABLE_ENCODING + tu_fifo_clear(&audio->ep_in_ff); +#else + for (uint8_t cnt = 0; cnt < audio->n_tx_supp_ff; cnt++) + { + tu_fifo_clear(&audio->tx_supp_ff[cnt]); + } +#endif + + // Invoke callback - can be used to stop data sampling + if (tud_audio_set_itf_close_EP_cb) TU_VERIFY(tud_audio_set_itf_close_EP_cb(rhport, p_request)); + + audio->ep_in = 0; // Necessary? + + } +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + if (audio->ep_out_as_intf_num == itf) + { + audio->ep_out_as_intf_num = 0; + usbd_edpt_close(rhport, audio->ep_out); + + // Clear FIFOs, since data is no longer valid +#if !CFG_TUD_AUDIO_ENABLE_DECODING + tu_fifo_clear(&audio->ep_out_ff); +#else + for (uint8_t cnt = 0; cnt < audio->n_rx_supp_ff; cnt++) + { + tu_fifo_clear(&audio->rx_supp_ff[cnt]); + } +#endif + + // Invoke callback - can be used to stop data sampling + if (tud_audio_set_itf_close_EP_cb) TU_VERIFY(tud_audio_set_itf_close_EP_cb(rhport, p_request)); + + audio->ep_out = 0; // Necessary? + + // Close corresponding feedback EP +#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + usbd_edpt_close(rhport, audio->ep_fb); + audio->ep_fb = 0; // Necessary? +#endif + } +#endif + + // Save current alternative interface setting + audio->alt_setting[idxItf] = alt; + + // Open new EP if necessary - EPs are only to be closed or opened for AS interfaces - Look for AS interface with correct alternate interface + // Get pointer at end + uint8_t const *p_desc_end = audio->p_desc + audio->desc_length - TUD_AUDIO_DESC_IAD_LEN; + + // p_desc starts at required interface with alternate setting zero + while (p_desc < p_desc_end) + { + // Find correct interface + if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE && ((tusb_desc_interface_t const * )p_desc)->bInterfaceNumber == itf && ((tusb_desc_interface_t const * )p_desc)->bAlternateSetting == alt) + { +#if CFG_TUD_AUDIO_ENABLE_ENCODING || CFG_TUD_AUDIO_ENABLE_DECODING + uint8_t const * p_desc_parse_for_params = p_desc; +#endif + // From this point forward follow the EP descriptors associated to the current alternate setting interface - Open EPs if necessary + uint8_t foundEPs = 0, nEps = ((tusb_desc_interface_t const * )p_desc)->bNumEndpoints; + while (foundEPs < nEps && p_desc < p_desc_end) + { + if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT) + { + tusb_desc_endpoint_t const* desc_ep = (tusb_desc_endpoint_t const *) p_desc; + TU_ASSERT(usbd_edpt_open(rhport, desc_ep)); + + uint8_t const ep_addr = desc_ep->bEndpointAddress; + + //TODO: We need to set EP non busy since this is not taken care of right now in ep_close() - THIS IS A WORKAROUND! + usbd_edpt_clear_stall(rhport, ep_addr); + +#if CFG_TUD_AUDIO_ENABLE_EP_IN + if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN && desc_ep->bmAttributes.usage == 0x00) // Check if usage is data EP + { + // Save address + audio->ep_in = ep_addr; + audio->ep_in_as_intf_num = itf; + audio->ep_in_sz = tu_edpt_packet_size(desc_ep); + + // If software encoding is enabled, parse for the corresponding parameters - doing this here means only AS interfaces with EPs get scanned for parameters +#if CFG_TUD_AUDIO_ENABLE_ENCODING + audiod_parse_for_AS_params(audio, p_desc_parse_for_params, p_desc_end, itf); + + // Reconfigure size of support FIFOs - this is necessary to avoid samples to get split in case of a wrap +#if CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING + const uint16_t active_fifo_depth = (audio->tx_supp_ff_sz_max / audio->n_bytes_per_sampe_tx) * audio->n_bytes_per_sampe_tx; + for (uint8_t cnt = 0; cnt < audio->n_tx_supp_ff; cnt++) + { + tu_fifo_config(&audio->tx_supp_ff[cnt], audio->tx_supp_ff[cnt].buffer, active_fifo_depth, 1, true); + } + audio->n_ff_used_tx = audio->n_channels_tx / audio->n_channels_per_ff_tx; + TU_ASSERT( audio->n_ff_used_tx <= audio->n_tx_supp_ff ); +#endif + +#endif + // Invoke callback - can be used to trigger data sampling if not already running + if (tud_audio_set_itf_cb) TU_VERIFY(tud_audio_set_itf_cb(rhport, p_request)); + + // Schedule first transmit if alternate interface is not zero i.e. streaming is disabled - in case no sample data is available a ZLP is loaded + // It is necessary to trigger this here since the refill is done with an RX FIFO empty interrupt which can only trigger if something was in there + TU_VERIFY(audiod_tx_done_cb(rhport, &_audiod_fct[func_id])); + } +#endif // CFG_TUD_AUDIO_ENABLE_EP_IN + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + + if (tu_edpt_dir(ep_addr) == TUSB_DIR_OUT) // Checking usage not necessary + { + // Save address + audio->ep_out = ep_addr; + audio->ep_out_as_intf_num = itf; + audio->ep_out_sz = tu_edpt_packet_size(desc_ep); + +#if CFG_TUD_AUDIO_ENABLE_DECODING + audiod_parse_for_AS_params(audio, p_desc_parse_for_params, p_desc_end, itf); + + // Reconfigure size of support FIFOs - this is necessary to avoid samples to get split in case of a wrap +#if CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING + const uint16_t active_fifo_depth = (audio->rx_supp_ff_sz_max / audio->n_bytes_per_sampe_rx) * audio->n_bytes_per_sampe_rx; + for (uint8_t cnt = 0; cnt < audio->n_rx_supp_ff; cnt++) + { + tu_fifo_config(&audio->rx_supp_ff[cnt], audio->rx_supp_ff[cnt].buffer, active_fifo_depth, 1, true); + } + audio->n_ff_used_rx = audio->n_channels_rx / audio->n_channels_per_ff_rx; + TU_ASSERT( audio->n_ff_used_rx <= audio->n_rx_supp_ff ); +#endif +#endif + +#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + // In case of asynchronous EP, call Cb after ep_fb is set + if ( !(desc_ep->bmAttributes.sync == 0x01 && audio->ep_fb == 0) ) + { + if (tud_audio_set_itf_cb) TU_VERIFY(tud_audio_set_itf_cb(rhport, p_request)); + } +#else + // Invoke callback + if (tud_audio_set_itf_cb) TU_VERIFY(tud_audio_set_itf_cb(rhport, p_request)); +#endif + // Prepare for incoming data +#if USE_LINEAR_BUFFER_RX + TU_VERIFY(usbd_edpt_xfer(rhport, audio->ep_out, audio->lin_buf_out, audio->ep_out_sz), false); +#else + TU_VERIFY(usbd_edpt_xfer_fifo(rhport, audio->ep_out, &audio->ep_out_ff, audio->ep_out_sz), false); +#endif + } + +#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN && desc_ep->bmAttributes.usage == 1) // Check if usage is explicit data feedback + { + audio->ep_fb = ep_addr; + + // Invoke callback after ep_out is set + if (audio->ep_out != 0) + { + if (tud_audio_set_itf_cb) TU_VERIFY(tud_audio_set_itf_cb(rhport, p_request)); + } + } +#endif +#endif // CFG_TUD_AUDIO_ENABLE_EP_OUT + + foundEPs += 1; + } + p_desc = tu_desc_next(p_desc); + } + + TU_VERIFY(foundEPs == nEps); + + // We are done - abort loop + break; + } + + // Moving forward + p_desc = tu_desc_next(p_desc); + } + + tud_control_status(rhport, p_request); + + return true; +} + +// Invoked when class request DATA stage is finished. +// return false to stall control EP (e.g Host send non-sense DATA) +static bool audiod_control_complete(uint8_t rhport, tusb_control_request_t const * p_request) +{ + // Handle audio class specific set requests + if(p_request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS && p_request->bmRequestType_bit.direction == TUSB_DIR_OUT) + { + uint8_t func_id; + + switch (p_request->bmRequestType_bit.recipient) + { + case TUSB_REQ_RCPT_INTERFACE: + { + uint8_t itf = TU_U16_LOW(p_request->wIndex); + uint8_t entityID = TU_U16_HIGH(p_request->wIndex); + + if (entityID != 0) + { + if (tud_audio_set_req_entity_cb) + { + // Check if entity is present and get corresponding driver index + TU_VERIFY(audiod_verify_entity_exists(itf, entityID, &func_id)); + + // Invoke callback + return tud_audio_set_req_entity_cb(rhport, p_request, _audiod_fct[func_id].ctrl_buf); + } + else + { + TU_LOG2(" No entity set request callback available!\r\n"); + return false; // In case no callback function is present or request can not be conducted we stall it + } + } + else + { + if (tud_audio_set_req_itf_cb) + { + // Find index of audio driver structure and verify interface really exists + TU_VERIFY(audiod_verify_itf_exists(itf, &func_id)); + + // Invoke callback + return tud_audio_set_req_itf_cb(rhport, p_request, _audiod_fct[func_id].ctrl_buf); + } + else + { + TU_LOG2(" No interface set request callback available!\r\n"); + return false; // In case no callback function is present or request can not be conducted we stall it + } + } + } + break; + + case TUSB_REQ_RCPT_ENDPOINT: + { + uint8_t ep = TU_U16_LOW(p_request->wIndex); + + if (tud_audio_set_req_ep_cb) + { + // Check if entity is present and get corresponding driver index + TU_VERIFY(audiod_verify_ep_exists(ep, &func_id)); + + // Invoke callback + return tud_audio_set_req_ep_cb(rhport, p_request, _audiod_fct[func_id].ctrl_buf); + } + else + { + TU_LOG2(" No EP set request callback available!\r\n"); + return false; // In case no callback function is present or request can not be conducted we stall it + } + } + break; + // Unknown/Unsupported recipient + default: TU_BREAKPOINT(); return false; + } + } + return true; +} + +// Handle class control request +// return false to stall control endpoint (e.g unsupported request) +static bool audiod_control_request(uint8_t rhport, tusb_control_request_t const * p_request) +{ + (void) rhport; + + // Handle standard requests - standard set requests usually have no data stage so we also handle set requests here + if (p_request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD) + { + switch (p_request->bRequest) + { + case TUSB_REQ_GET_INTERFACE: + return audiod_get_interface(rhport, p_request); + + case TUSB_REQ_SET_INTERFACE: + return audiod_set_interface(rhport, p_request); + + // Unknown/Unsupported request + default: TU_BREAKPOINT(); return false; + } + } + + // Handle class requests + if (p_request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS) + { + uint8_t itf = TU_U16_LOW(p_request->wIndex); + uint8_t func_id; + + // Conduct checks which depend on the recipient + switch (p_request->bmRequestType_bit.recipient) + { + case TUSB_REQ_RCPT_INTERFACE: + { + uint8_t entityID = TU_U16_HIGH(p_request->wIndex); + + // Verify if entity is present + if (entityID != 0) + { + // Find index of audio driver structure and verify entity really exists + TU_VERIFY(audiod_verify_entity_exists(itf, entityID, &func_id)); + + // In case we got a get request invoke callback - callback needs to answer as defined in UAC2 specification page 89 - 5. Requests + if (p_request->bmRequestType_bit.direction == TUSB_DIR_IN) + { + if (tud_audio_get_req_entity_cb) + { + return tud_audio_get_req_entity_cb(rhport, p_request); + } + else + { + TU_LOG2(" No entity get request callback available!\r\n"); + return false; // Stall + } + } + } + else + { + // Find index of audio driver structure and verify interface really exists + TU_VERIFY(audiod_verify_itf_exists(itf, &func_id)); + + // In case we got a get request invoke callback - callback needs to answer as defined in UAC2 specification page 89 - 5. Requests + if (p_request->bmRequestType_bit.direction == TUSB_DIR_IN) + { + if (tud_audio_get_req_itf_cb) + { + return tud_audio_get_req_itf_cb(rhport, p_request); + } + else + { + TU_LOG2(" No interface get request callback available!\r\n"); + return false; // Stall + } + } + } + } + break; + + case TUSB_REQ_RCPT_ENDPOINT: + { + uint8_t ep = TU_U16_LOW(p_request->wIndex); + + // Find index of audio driver structure and verify EP really exists + TU_VERIFY(audiod_verify_ep_exists(ep, &func_id)); + + // In case we got a get request invoke callback - callback needs to answer as defined in UAC2 specification page 89 - 5. Requests + if (p_request->bmRequestType_bit.direction == TUSB_DIR_IN) + { + if (tud_audio_get_req_ep_cb) + { + return tud_audio_get_req_ep_cb(rhport, p_request); + } + else + { + TU_LOG2(" No EP get request callback available!\r\n"); + return false; // Stall + } + } + } + break; + + // Unknown/Unsupported recipient + default: TU_LOG2(" Unsupported recipient: %d\r\n", p_request->bmRequestType_bit.recipient); TU_BREAKPOINT(); return false; + } + + // If we end here, the received request is a set request - we schedule a receive for the data stage and return true here. We handle the rest later in audiod_control_complete() once the data stage was finished + TU_VERIFY(tud_control_xfer(rhport, p_request, _audiod_fct[func_id].ctrl_buf, _audiod_fct[func_id].ctrl_buf_sz)); + return true; + } + + // There went something wrong - unsupported control request type + TU_BREAKPOINT(); + return false; +} + +bool audiod_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) +{ + if ( stage == CONTROL_STAGE_SETUP ) + { + return audiod_control_request(rhport, request); + } + else if ( stage == CONTROL_STAGE_DATA ) + { + return audiod_control_complete(rhport, request); + } + + return true; +} + +bool audiod_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) +{ + (void) result; + (void) xferred_bytes; + + // Search for interface belonging to given end point address and proceed as required + uint8_t func_id; + for (func_id = 0; func_id < CFG_TUD_AUDIO; func_id++) + { + +#if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN + + // Data transmission of control interrupt finished + if (_audiod_fct[func_id].ep_int_ctr == ep_addr) + { + // According to USB2 specification, maximum payload of interrupt EP is 8 bytes on low speed, 64 bytes on full speed, and 1024 bytes on high speed (but only if an alternate interface other than 0 is used - see specification p. 49) + // In case there is nothing to send we have to return a NAK - this is taken care of by PHY ??? + // In case of an erroneous transmission a retransmission is conducted - this is taken care of by PHY ??? + + // I assume here, that things above are handled by PHY + // All transmission is done - what remains to do is to inform job was completed + + if (tud_audio_int_ctr_done_cb) TU_VERIFY(tud_audio_int_ctr_done_cb(rhport, (uint16_t) xferred_bytes)); + } + +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN + + // Data transmission of audio packet finished + if (_audiod_fct[func_id].ep_in == ep_addr && _audiod_fct[func_id].alt_setting != 0) + { + // USB 2.0, section 5.6.4, third paragraph, states "An isochronous endpoint must specify its required bus access period. However, an isochronous endpoint must be prepared to handle poll rates faster than the one specified." + // That paragraph goes on to say "An isochronous IN endpoint must return a zero-length packet whenever data is requested at a faster interval than the specified interval and data is not available." + // This can only be solved reliably if we load a ZLP after every IN transmission since we can not say if the host requests samples earlier than we declared! Once all samples are collected we overwrite the loaded ZLP. + + // Check if there is data to load into EPs buffer - if not load it with ZLP + // Be aware - we as a device are not able to know if the host polls for data with a faster rate as we stated this in the descriptors. Therefore we always have to put something into the EPs buffer. However, once we did that, there is no way of aborting this or replacing what we put into the buffer before! + // This is the only place where we can fill something into the EPs buffer! + + // Load new data + TU_VERIFY(audiod_tx_done_cb(rhport, &_audiod_fct[func_id])); + + // Transmission of ZLP is done by audiod_tx_done_cb() + return true; + } +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + + // New audio packet received + if (_audiod_fct[func_id].ep_out == ep_addr) + { + TU_VERIFY(audiod_rx_done_cb(rhport, &_audiod_fct[func_id], (uint16_t) xferred_bytes)); + return true; + } + + +#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + // Transmission of feedback EP finished + if (_audiod_fct[func_id].ep_fb == ep_addr) + { + if (tud_audio_fb_done_cb) TU_VERIFY(tud_audio_fb_done_cb(rhport)); + + // Schedule a transmit with the new value if EP is not busy + if (!usbd_edpt_busy(rhport, _audiod_fct[func_id].ep_fb)) + { + // Schedule next transmission - value is changed bytud_audio_n_fb_set() in the meantime or the old value gets sent + return audiod_fb_send(rhport, &_audiod_fct[func_id]); + } + } +#endif +#endif + } + + return false; +} + +bool tud_audio_buffer_and_schedule_control_xfer(uint8_t rhport, tusb_control_request_t const * p_request, void* data, uint16_t len) +{ + // Handles only sending of data not receiving + if (p_request->bmRequestType_bit.direction == TUSB_DIR_OUT) return false; + + // Get corresponding driver index + uint8_t func_id; + uint8_t itf = TU_U16_LOW(p_request->wIndex); + + // Conduct checks which depend on the recipient + switch (p_request->bmRequestType_bit.recipient) + { + case TUSB_REQ_RCPT_INTERFACE: + { + uint8_t entityID = TU_U16_HIGH(p_request->wIndex); + + // Verify if entity is present + if (entityID != 0) + { + // Find index of audio driver structure and verify entity really exists + TU_VERIFY(audiod_verify_entity_exists(itf, entityID, &func_id)); + } + else + { + // Find index of audio driver structure and verify interface really exists + TU_VERIFY(audiod_verify_itf_exists(itf, &func_id)); + } + } + break; + + case TUSB_REQ_RCPT_ENDPOINT: + { + uint8_t ep = TU_U16_LOW(p_request->wIndex); + + // Find index of audio driver structure and verify EP really exists + TU_VERIFY(audiod_verify_ep_exists(ep, &func_id)); + } + break; + + // Unknown/Unsupported recipient + default: TU_LOG2(" Unsupported recipient: %d\r\n", p_request->bmRequestType_bit.recipient); TU_BREAKPOINT(); return false; + } + + // Crop length + if (len > _audiod_fct[func_id].ctrl_buf_sz) len = _audiod_fct[func_id].ctrl_buf_sz; + + // Copy into buffer + memcpy((void *)_audiod_fct[func_id].ctrl_buf, data, (size_t)len); + + // Schedule transmit + return tud_control_xfer(rhport, p_request, (void*)_audiod_fct[func_id].ctrl_buf, len); +} + +// This helper function finds for a given audio function and AS interface number the index of the attached driver structure, the index of the interface in the audio function +// (e.g. the std. AS interface with interface number 15 is the first AS interface for the given audio function and thus gets index zero), and +// finally a pointer to the std. AS interface, where the pointer always points to the first alternate setting i.e. alternate interface zero. +static bool audiod_get_AS_interface_index(uint8_t itf, audiod_function_t * audio, uint8_t *idxItf, uint8_t const **pp_desc_int) +{ + if (audio->p_desc) + { + // Get pointer at end + uint8_t const *p_desc_end = audio->p_desc + audio->desc_length - TUD_AUDIO_DESC_IAD_LEN; + + // Advance past AC descriptors + uint8_t const *p_desc = tu_desc_next(audio->p_desc); + p_desc += ((audio_desc_cs_ac_interface_t const *)p_desc)->wTotalLength; + + uint8_t tmp = 0; + while (p_desc < p_desc_end) + { + // We assume the number of alternate settings is increasing thus we return the index of alternate setting zero! + if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE && ((tusb_desc_interface_t const * )p_desc)->bAlternateSetting == 0) + { + if (((tusb_desc_interface_t const * )p_desc)->bInterfaceNumber == itf) + { + *idxItf = tmp; + *pp_desc_int = p_desc; + return true; + } + // Increase index, bytes read, and pointer + tmp++; + } + p_desc = tu_desc_next(p_desc); + } + } + return false; +} + +// This helper function finds for a given AS interface number the index of the attached driver structure, the index of the interface in the audio function +// (e.g. the std. AS interface with interface number 15 is the first AS interface for the given audio function and thus gets index zero), and +// finally a pointer to the std. AS interface, where the pointer always points to the first alternate setting i.e. alternate interface zero. +static bool audiod_get_AS_interface_index_global(uint8_t itf, uint8_t *func_id, uint8_t *idxItf, uint8_t const **pp_desc_int) +{ + // Loop over audio driver interfaces + uint8_t i; + for (i = 0; i < CFG_TUD_AUDIO; i++) + { + if (audiod_get_AS_interface_index(itf, &_audiod_fct[i], idxItf, pp_desc_int)) + { + *func_id = i; + return true; + } + } + + return false; +} + +// Verify an entity with the given ID exists and returns also the corresponding driver index +static bool audiod_verify_entity_exists(uint8_t itf, uint8_t entityID, uint8_t *func_id) +{ + uint8_t i; + for (i = 0; i < CFG_TUD_AUDIO; i++) + { + // Look for the correct driver by checking if the unique standard AC interface number fits + if (_audiod_fct[i].p_desc && ((tusb_desc_interface_t const *)_audiod_fct[i].p_desc)->bInterfaceNumber == itf) + { + // Get pointers after class specific AC descriptors and end of AC descriptors - entities are defined in between + uint8_t const *p_desc = tu_desc_next(_audiod_fct[i].p_desc); // Points to CS AC descriptor + uint8_t const *p_desc_end = ((audio_desc_cs_ac_interface_t const *)p_desc)->wTotalLength + p_desc; + p_desc = tu_desc_next(p_desc); // Get past CS AC descriptor + + while (p_desc < p_desc_end) + { + if (p_desc[3] == entityID) // Entity IDs are always at offset 3 + { + *func_id = i; + return true; + } + p_desc = tu_desc_next(p_desc); + } + } + } + return false; +} + +static bool audiod_verify_itf_exists(uint8_t itf, uint8_t *func_id) +{ + uint8_t i; + for (i = 0; i < CFG_TUD_AUDIO; i++) + { + if (_audiod_fct[i].p_desc) + { + // Get pointer at beginning and end + uint8_t const *p_desc = _audiod_fct[i].p_desc; + uint8_t const *p_desc_end = _audiod_fct[i].p_desc + _audiod_fct[i].desc_length - TUD_AUDIO_DESC_IAD_LEN; + + while (p_desc < p_desc_end) + { + if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE && ((tusb_desc_interface_t const *)_audiod_fct[i].p_desc)->bInterfaceNumber == itf) + { + *func_id = i; + return true; + } + p_desc = tu_desc_next(p_desc); + } + } + } + return false; +} + +static bool audiod_verify_ep_exists(uint8_t ep, uint8_t *func_id) +{ + uint8_t i; + for (i = 0; i < CFG_TUD_AUDIO; i++) + { + if (_audiod_fct[i].p_desc) + { + // Get pointer at end + uint8_t const *p_desc_end = _audiod_fct[i].p_desc + _audiod_fct[i].desc_length; + + // Advance past AC descriptors - EP we look for are streaming EPs + uint8_t const *p_desc = tu_desc_next(_audiod_fct[i].p_desc); + p_desc += ((audio_desc_cs_ac_interface_t const *)p_desc)->wTotalLength; + + while (p_desc < p_desc_end) + { + if (tu_desc_type(p_desc) == TUSB_DESC_ENDPOINT && ((tusb_desc_endpoint_t const * )p_desc)->bEndpointAddress == ep) + { + *func_id = i; + return true; + } + p_desc = tu_desc_next(p_desc); + } + } + } + return false; +} + +#if CFG_TUD_AUDIO_ENABLE_ENCODING || CFG_TUD_AUDIO_ENABLE_DECODING +// p_desc points to the AS interface of alternate setting zero +// itf is the interface number of the corresponding interface - we check if the interface belongs to EP in or EP out to see if it is a TX or RX parameter +// Currently, only AS interfaces with an EP (in or out) are supposed to be parsed for! +static void audiod_parse_for_AS_params(audiod_function_t* audio, uint8_t const * p_desc, uint8_t const * p_desc_end, uint8_t const as_itf) +{ + p_desc = tu_desc_next(p_desc); // Exclude standard AS interface descriptor of current alternate interface descriptor + + while (p_desc < p_desc_end) + { + // Abort if follow up descriptor is a new standard interface descriptor - indicates the last AS descriptor was already finished + if (tu_desc_type(p_desc) == TUSB_DESC_INTERFACE) break; + + // Look for a Class-Specific AS Interface Descriptor(4.9.2) to verify format type and format and also to get number of physical channels + if (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE && tu_desc_subtype(p_desc) == AUDIO_CS_AS_INTERFACE_AS_GENERAL) + { +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_EP_OUT + if (as_itf != audio->ep_in_as_intf_num && as_itf != audio->ep_out_as_intf_num) break; // Abort loop, this interface has no EP, this driver does not support this currently +#endif +#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_EP_OUT + if (as_itf != audio->ep_in_as_intf_num) break; +#endif +#if !CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_EP_OUT + if (as_itf != audio->ep_out_as_intf_num) break; +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN + if (as_itf == audio->ep_in_as_intf_num) + { + audio->n_channels_tx = ((audio_desc_cs_as_interface_t const * )p_desc)->bNrChannels; + audio->format_type_tx = (audio_format_type_t)(((audio_desc_cs_as_interface_t const * )p_desc)->bFormatType); + +#if CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING + audio->format_type_I_tx = (audio_data_format_type_I_t)(((audio_desc_cs_as_interface_t const * )p_desc)->bmFormats); +#endif + } +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + if (as_itf == audio->ep_out_as_intf_num) + { + audio->n_channels_rx = ((audio_desc_cs_as_interface_t const * )p_desc)->bNrChannels; + audio->format_type_rx = ((audio_desc_cs_as_interface_t const * )p_desc)->bFormatType; +#if CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING + audio->format_type_I_rx = ((audio_desc_cs_as_interface_t const * )p_desc)->bmFormats; +#endif + } +#endif + } + + // Look for a Type I Format Type Descriptor(2.3.1.6 - Audio Formats) +#if CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING || CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING + if (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE && tu_desc_subtype(p_desc) == AUDIO_CS_AS_INTERFACE_FORMAT_TYPE && ((audio_desc_type_I_format_t const * )p_desc)->bFormatType == AUDIO_FORMAT_TYPE_I) + { +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_EP_OUT + if (as_itf != audio->ep_in_as_intf_num && as_itf != audio->ep_out_as_intf_num) break; // Abort loop, this interface has no EP, this driver does not support this currently +#endif +#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_EP_OUT + if (as_itf != audio->ep_in_as_intf_num) break; +#endif +#if !CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_EP_OUT + if (as_itf != audio->ep_out_as_intf_num) break; +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN + if (as_itf == audio->ep_in_as_intf_num) + { + audio->n_bytes_per_sampe_tx = ((audio_desc_type_I_format_t const * )p_desc)->bSubslotSize; + } +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT + if (as_itf == audio->ep_out_as_intf_num) + { + audio->n_bytes_per_sampe_rx = ((audio_desc_type_I_format_t const * )p_desc)->bSubslotSize; + } +#endif + } +#endif + + // Other format types are not supported yet + + p_desc = tu_desc_next(p_desc); + } +} +#endif + +#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP + +bool tud_audio_n_fb_set(uint8_t func_id, uint32_t feedback) +{ + TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL); + + // Format the feedback value +#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_FORMAT_CORRECTION + if ( TUSB_SPEED_FULL == tud_speed_get() ) + { + uint8_t * fb = (uint8_t *) &_audiod_fct[func_id].fb_val; + + // For FS format is 10.14 + *(fb++) = (feedback >> 2) & 0xFF; + *(fb++) = (feedback >> 10) & 0xFF; + *(fb++) = (feedback >> 18) & 0xFF; + // 4th byte is needed to work correctly with MS Windows + *fb = 0; + }else +#else + { + // Send value as-is, caller will choose the appropriate format + _audiod_fct[func_id].fb_val = feedback; + } +#endif + + // Schedule a transmit with the new value if EP is not busy - this triggers repetitive scheduling of the feedback value + if (!usbd_edpt_busy(_audiod_fct[func_id].rhport, _audiod_fct[func_id].ep_fb)) + { + return audiod_fb_send(_audiod_fct[func_id].rhport, &_audiod_fct[func_id]); + } + + return true; +} +#endif + +// No security checks here - internal function only which should always succeed +uint8_t audiod_get_audio_fct_idx(audiod_function_t * audio) +{ + for (uint8_t cnt=0; cnt < CFG_TUD_AUDIO; cnt++) + { + if (&_audiod_fct[cnt] == audio) return cnt; + } + return 0; +} + +#endif //TUSB_OPT_DEVICE_ENABLED && CFG_TUD_AUDIO diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/audio/audio_device.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/audio/audio_device.h new file mode 100644 index 000000000..f406cf281 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/audio/audio_device.h @@ -0,0 +1,637 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Ha Thach (tinyusb.org) + * Copyright (c) 2020 Reinhard Panhuber + * + * 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_AUDIO_DEVICE_H_ +#define _TUSB_AUDIO_DEVICE_H_ + +#include "audio.h" + +//--------------------------------------------------------------------+ +// Class Driver Configuration +//--------------------------------------------------------------------+ + +// All sizes are in bytes! + +#ifndef CFG_TUD_AUDIO_FUNC_1_DESC_LEN +#error You must tell the driver the length of the audio function descriptor including IAD descriptor +#endif +#if CFG_TUD_AUDIO > 1 +#ifndef CFG_TUD_AUDIO_FUNC_2_DESC_LEN +#error You must tell the driver the length of the audio function descriptor including IAD descriptor +#endif +#endif +#if CFG_TUD_AUDIO > 2 +#ifndef CFG_TUD_AUDIO_FUNC_3_DESC_LEN +#error You must tell the driver the length of the audio function descriptor including IAD descriptor +#endif +#endif + +// Number of Standard AS Interface Descriptors (4.9.1) defined per audio function - this is required to be able to remember the current alternate settings of these interfaces +#ifndef CFG_TUD_AUDIO_FUNC_1_N_AS_INT +#error You must tell the driver the number of Standard AS Interface Descriptors you have defined in the audio function descriptor! +#endif +#if CFG_TUD_AUDIO > 1 +#ifndef CFG_TUD_AUDIO_FUNC_2_N_AS_INT +#error You must tell the driver the number of Standard AS Interface Descriptors you have defined in the audio function descriptor! +#endif +#endif +#if CFG_TUD_AUDIO > 2 +#ifndef CFG_TUD_AUDIO_FUNC_3_N_AS_INT +#error You must tell the driver the number of Standard AS Interface Descriptors you have defined in the audio function descriptor! +#endif +#endif + +// Size of control buffer used to receive and send control messages via EP0 - has to be big enough to hold your biggest request structure e.g. range requests with multiple intervals defined or cluster descriptors +#ifndef CFG_TUD_AUDIO_FUNC_1_CTRL_BUF_SZ +#error You must define an audio class control request buffer size! +#endif + +#if CFG_TUD_AUDIO > 1 +#ifndef CFG_TUD_AUDIO_FUNC_2_CTRL_BUF_SZ +#error You must define an audio class control request buffer size! +#endif +#endif + +#if CFG_TUD_AUDIO > 2 +#ifndef CFG_TUD_AUDIO_FUNC_3_CTRL_BUF_SZ +#error You must define an audio class control request buffer size! +#endif +#endif + +// End point sizes IN BYTES - Limits: Full Speed <= 1023, High Speed <= 1024 +#ifndef CFG_TUD_AUDIO_ENABLE_EP_IN +#define CFG_TUD_AUDIO_ENABLE_EP_IN 0 // TX +#endif + +#ifndef CFG_TUD_AUDIO_ENABLE_EP_OUT +#define CFG_TUD_AUDIO_ENABLE_EP_OUT 0 // RX +#endif + +// Maximum EP sizes for all alternate AS interface settings - used for checks and buffer allocation +#if CFG_TUD_AUDIO_ENABLE_EP_IN +#ifndef CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX +#error You must tell the driver the biggest EP IN size! +#endif +#if CFG_TUD_AUDIO > 1 +#ifndef CFG_TUD_AUDIO_FUNC_2_EP_IN_SZ_MAX +#error You must tell the driver the biggest EP IN size! +#endif +#endif +#if CFG_TUD_AUDIO > 2 +#ifndef CFG_TUD_AUDIO_FUNC_3_EP_IN_SZ_MAX +#error You must tell the driver the biggest EP IN size! +#endif +#endif +#endif // CFG_TUD_AUDIO_ENABLE_EP_IN + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +#ifndef CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX +#error You must tell the driver the biggest EP OUT size! +#endif +#if CFG_TUD_AUDIO > 1 +#ifndef CFG_TUD_AUDIO_FUNC_2_EP_OUT_SZ_MAX +#error You must tell the driver the biggest EP OUT size! +#endif +#endif +#if CFG_TUD_AUDIO > 2 +#ifndef CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX +#error You must tell the driver the biggest EP OUT size! +#endif +#endif +#endif // CFG_TUD_AUDIO_ENABLE_EP_OUT + +// Software EP FIFO buffer sizes - must be >= max EP SIZEs! +#ifndef CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ +#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ 0 +#endif +#ifndef CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ +#define CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ 0 +#endif +#ifndef CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ +#define CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ 0 +#endif + +#ifndef CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ +#define CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ 0 +#endif +#ifndef CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ +#define CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ 0 +#endif +#ifndef CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ +#define CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ 0 +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN +#if CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ < CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX +#error EP software buffer size MUST BE at least as big as maximum EP size +#endif + +#if CFG_TUD_AUDIO > 1 +#if CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ < CFG_TUD_AUDIO_FUNC_2_EP_IN_SZ_MAX +#error EP software buffer size MUST BE at least as big as maximum EP size +#endif +#endif + +#if CFG_TUD_AUDIO > 2 +#if CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ < CFG_TUD_AUDIO_FUNC_3_EP_IN_SZ_MAX +#error EP software buffer size MUST BE at least as big as maximum EP size +#endif +#endif +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +#if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ < CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX +#error EP software buffer size MUST BE at least as big as maximum EP size +#endif + +#if CFG_TUD_AUDIO > 1 +#if CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ < CFG_TUD_AUDIO_FUNC_2_EP_OUT_SZ_MAX +#error EP software buffer size MUST BE at least as big as maximum EP size +#endif +#endif + +#if CFG_TUD_AUDIO > 2 +#if CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ < CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX +#error EP software buffer size MUST BE at least as big as maximum EP size +#endif +#endif +#endif + +// Enable/disable feedback EP (required for asynchronous RX applications) +#ifndef CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP +#define CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP 0 // Feedback - 0 or 1 +#endif + +// Enable/disable conversion from 16.16 to 10.14 format on full-speed devices. See tud_audio_n_fb_set(). +#ifndef CFG_TUD_AUDIO_ENABLE_FEEDBACK_FORMAT_CORRECTION +#define CFG_TUD_AUDIO_ENABLE_FEEDBACK_FORMAT_CORRECTION 0 // 0 or 1 +#endif + +// Audio interrupt control EP size - disabled if 0 +#ifndef CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN +#define CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN 0 // Audio interrupt control - if required - 6 Bytes according to UAC 2 specification (p. 74) +#endif + +#ifndef CFG_TUD_AUDIO_INT_CTR_EP_IN_SW_BUFFER_SIZE +#define CFG_TUD_AUDIO_INT_CTR_EP_IN_SW_BUFFER_SIZE 6 // Buffer size of audio control interrupt EP - 6 Bytes according to UAC 2 specification (p. 74) +#endif + +// Use software encoding/decoding + +// The software coding feature of the driver is not mandatory. It is useful if, for instance, you have two I2S streams which need to be interleaved +// into a single PCM stream as SAMPLE_1 | SAMPLE_2 | SAMPLE_3 | SAMPLE_4. +// +// Currently, only PCM type I encoding/decoding is supported! +// +// If the coding feature is to be used, support FIFOs need to be configured. Their sizes and numbers are defined below. + +// Encoding/decoding is done in software and thus time consuming. If you can encode/decode your stream more efficiently do not use the +// support FIFOs but write/read directly into/from the EP_X_SW_BUFFER_FIFOs using +// - tud_audio_n_write() or +// - tud_audio_n_read(). +// To write/read to/from the support FIFOs use +// - tud_audio_n_write_support_ff() or +// - tud_audio_n_read_support_ff(). +// +// The encoding/decoding format type done is defined below. +// +// The encoding/decoding starts when the private callback functions +// - audio_tx_done_cb() +// - audio_rx_done_cb() +// are invoked. If support FIFOs are used, the corresponding encoding/decoding functions are called from there. +// Once encoding/decoding is done the result is put directly into the EP_X_SW_BUFFER_FIFOs. You can use the public callback functions +// - tud_audio_tx_done_pre_load_cb() or tud_audio_tx_done_post_load_cb() +// - tud_audio_rx_done_pre_read_cb() or tud_audio_rx_done_post_read_cb() +// if you want to get informed what happened. +// +// If you don't use the support FIFOs you may use the public callback functions +// - tud_audio_tx_done_pre_load_cb() or tud_audio_tx_done_post_load_cb() +// - tud_audio_rx_done_pre_read_cb() or tud_audio_rx_done_post_read_cb() +// to write/read from/into the EP_X_SW_BUFFER_FIFOs at the right time. +// +// If you need a different encoding which is not support so far implement it in the +// - audio_tx_done_cb() +// - audio_rx_done_cb() +// functions. + +// Enable encoding/decodings - for these to work, support FIFOs need to be setup in appropriate numbers and size +// The actual coding parameters of active AS alternate interface is parsed from the descriptors + +// The item size of the FIFO is always fixed to one i.e. bytes! Furthermore, the actively used FIFO depth is reconfigured such that the depth is a multiple of the current sample size in order to avoid samples to get split up in case of a wrap in the FIFO ring buffer (depth = (max_depth / sampe_sz) * sampe_sz)! +// This is important to remind in case you use DMAs! If the sample sizes changes, the DMA MUST BE RECONFIGURED just like the FIFOs for a different depth!!! + +// For PCM encoding/decoding + +#ifndef CFG_TUD_AUDIO_ENABLE_ENCODING +#define CFG_TUD_AUDIO_ENABLE_ENCODING 0 +#endif + +#ifndef CFG_TUD_AUDIO_ENABLE_DECODING +#define CFG_TUD_AUDIO_ENABLE_DECODING 0 +#endif + +// This enabling allows to save the current coding parameters e.g. # of bytes per sample etc. - TYPE_I includes common PCM encoding +#ifndef CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING +#define CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING 0 +#endif + +#ifndef CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING +#define CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING 0 +#endif + +// Type I Coding parameters not given within UAC2 descriptors +// It would be possible to allow for a more flexible setting and not fix this parameter as done below. However, this is most often not needed and kept for later if really necessary. The more flexible setting could be implemented within set_interface(), however, how the values are saved per alternate setting is to be determined! +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING && CFG_TUD_AUDIO_ENABLE_TYPE_I_ENCODING +#ifndef CFG_TUD_AUDIO_FUNC_1_CHANNEL_PER_FIFO_TX +#error You must tell the driver the number of channels per FIFO for the interleaved encoding! E.g. for an I2S interface having two channels, CHANNEL_PER_FIFO = 2 as the I2S stream having two channels is usually saved within one FIFO +#endif +#if CFG_TUD_AUDIO > 1 +#ifndef CFG_TUD_AUDIO_FUNC_2_CHANNEL_PER_FIFO_TX +#error You must tell the driver the number of channels per FIFO for the interleaved encoding! E.g. for an I2S interface having two channels, CHANNEL_PER_FIFO = 2 as the I2S stream having two channels is usually saved within one FIFO +#endif +#endif +#if CFG_TUD_AUDIO > 2 +#ifndef CFG_TUD_AUDIO_FUNC_3_CHANNEL_PER_FIFO_TX +#error You must tell the driver the number of channels per FIFO for the interleaved encoding! E.g. for an I2S interface having two channels, CHANNEL_PER_FIFO = 2 as the I2S stream having two channels is usually saved within one FIFO +#endif +#endif +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING && CFG_TUD_AUDIO_ENABLE_TYPE_I_DECODING +#ifndef CFG_TUD_AUDIO_FUNC_1_CHANNEL_PER_FIFO_RX +#error You must tell the driver the number of channels per FIFO for the interleaved encoding! E.g. for an I2S interface having two channels, CHANNEL_PER_FIFO = 2 as the I2S stream having two channels is usually saved within one FIFO +#endif +#if CFG_TUD_AUDIO > 1 +#ifndef CFG_TUD_AUDIO_FUNC_2_CHANNEL_PER_FIFO_RX +#error You must tell the driver the number of channels per FIFO for the interleaved encoding! E.g. for an I2S interface having two channels, CHANNEL_PER_FIFO = 2 as the I2S stream having two channels is usually saved within one FIFO +#endif +#endif +#if CFG_TUD_AUDIO > 2 +#ifndef CFG_TUD_AUDIO_FUNC_3_CHANNEL_PER_FIFO_RX +#error You must tell the driver the number of channels per FIFO for the interleaved encoding! E.g. for an I2S interface having two channels, CHANNEL_PER_FIFO = 2 as the I2S stream having two channels is usually saved within one FIFO +#endif +#endif +#endif + +// Remaining types not support so far + +// Number of support FIFOs to set up - multiple channels can be handled by one FIFO - very common is two channels per FIFO stemming from one I2S interface +#ifndef CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO +#define CFG_TUD_AUDIO_FUNC_1_N_TX_SUPP_SW_FIFO 0 +#endif +#ifndef CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO +#define CFG_TUD_AUDIO_FUNC_2_N_TX_SUPP_SW_FIFO 0 +#endif +#ifndef CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO +#define CFG_TUD_AUDIO_FUNC_3_N_TX_SUPP_SW_FIFO 0 +#endif + +#ifndef CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO +#define CFG_TUD_AUDIO_FUNC_1_N_RX_SUPP_SW_FIFO 0 +#endif +#ifndef CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO +#define CFG_TUD_AUDIO_FUNC_2_N_RX_SUPP_SW_FIFO 0 +#endif +#ifndef CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO +#define CFG_TUD_AUDIO_FUNC_3_N_RX_SUPP_SW_FIFO 0 +#endif + +// Size of support FIFOs IN BYTES - if size > 0 there are as many FIFOs set up as CFG_TUD_AUDIO_FUNC_X_N_TX_SUPP_SW_FIFO and CFG_TUD_AUDIO_FUNC_X_N_RX_SUPP_SW_FIFO +#ifndef CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ +#define CFG_TUD_AUDIO_FUNC_1_TX_SUPP_SW_FIFO_SZ 0 // FIFO size - minimum size: ceil(f_s/1000) * max(# of TX channels) / (# of TX support FIFOs) * max(# of bytes per sample) +#endif +#ifndef CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ +#define CFG_TUD_AUDIO_FUNC_2_TX_SUPP_SW_FIFO_SZ 0 +#endif +#ifndef CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ +#define CFG_TUD_AUDIO_FUNC_3_TX_SUPP_SW_FIFO_SZ 0 +#endif + +#ifndef CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ +#define CFG_TUD_AUDIO_FUNC_1_RX_SUPP_SW_FIFO_SZ 0 // FIFO size - minimum size: ceil(f_s/1000) * max(# of RX channels) / (# of RX support FIFOs) * max(# of bytes per sample) +#endif +#ifndef CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ +#define CFG_TUD_AUDIO_FUNC_2_RX_SUPP_SW_FIFO_SZ 0 +#endif +#ifndef CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ +#define CFG_TUD_AUDIO_FUNC_3_RX_SUPP_SW_FIFO_SZ 0 +#endif + +//static_assert(sizeof(tud_audio_desc_lengths) != CFG_TUD_AUDIO, "Supply audio function descriptor pack length!"); + +// Supported types of this driver: +// AUDIO_DATA_FORMAT_TYPE_I_PCM - Required definitions: CFG_TUD_AUDIO_N_CHANNELS and CFG_TUD_AUDIO_BYTES_PER_CHANNEL + +#ifdef __cplusplus +extern "C" { +#endif + +/** \addtogroup AUDIO_Serial Serial + * @{ + * \defgroup AUDIO_Serial_Device Device + * @{ */ + +//--------------------------------------------------------------------+ +// Application API (Multiple Interfaces) +// CFG_TUD_AUDIO > 1 +//--------------------------------------------------------------------+ +bool tud_audio_n_mounted (uint8_t func_id); + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING +uint16_t tud_audio_n_available (uint8_t func_id); +uint16_t tud_audio_n_read (uint8_t func_id, void* buffer, uint16_t bufsize); +bool tud_audio_n_clear_ep_out_ff (uint8_t func_id); // Delete all content in the EP OUT FIFO +tu_fifo_t* tud_audio_n_get_ep_out_ff (uint8_t func_id); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING +bool tud_audio_n_clear_rx_support_ff (uint8_t func_id, uint8_t ff_idx); // Delete all content in the support RX FIFOs +uint16_t tud_audio_n_available_support_ff (uint8_t func_id, uint8_t ff_idx); +uint16_t tud_audio_n_read_support_ff (uint8_t func_id, uint8_t ff_idx, void* buffer, uint16_t bufsize); +tu_fifo_t* tud_audio_n_get_rx_support_ff (uint8_t func_id, uint8_t ff_idx); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING +uint16_t tud_audio_n_write (uint8_t func_id, const void * data, uint16_t len); +bool tud_audio_n_clear_ep_in_ff (uint8_t func_id); // Delete all content in the EP IN FIFO +tu_fifo_t* tud_audio_n_get_ep_in_ff (uint8_t func_id); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING +uint16_t tud_audio_n_flush_tx_support_ff (uint8_t func_id); // Force all content in the support TX FIFOs to be written into EP SW FIFO +bool tud_audio_n_clear_tx_support_ff (uint8_t func_id, uint8_t ff_idx); +uint16_t tud_audio_n_write_support_ff (uint8_t func_id, uint8_t ff_idx, const void * data, uint16_t len); +tu_fifo_t* tud_audio_n_get_tx_support_ff (uint8_t func_id, uint8_t ff_idx); +#endif + +#if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN +uint16_t tud_audio_int_ctr_n_write (uint8_t func_id, uint8_t const* buffer, uint16_t len); +#endif + +//--------------------------------------------------------------------+ +// Application API (Interface0) +//--------------------------------------------------------------------+ + +static inline bool tud_audio_mounted (void); + +// RX API + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING +static inline uint16_t tud_audio_available (void); +static inline bool tud_audio_clear_ep_out_ff (void); // Delete all content in the EP OUT FIFO +static inline uint16_t tud_audio_read (void* buffer, uint16_t bufsize); +static inline tu_fifo_t* tud_audio_get_ep_out_ff (void); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING +static inline bool tud_audio_clear_rx_support_ff (uint8_t ff_idx); +static inline uint16_t tud_audio_available_support_ff (uint8_t ff_idx); +static inline uint16_t tud_audio_read_support_ff (uint8_t ff_idx, void* buffer, uint16_t bufsize); +static inline tu_fifo_t* tud_audio_get_rx_support_ff (uint8_t ff_idx); +#endif + +// TX API + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING +static inline uint16_t tud_audio_write (const void * data, uint16_t len); +static inline bool tud_audio_clear_ep_in_ff (void); +static inline tu_fifo_t* tud_audio_get_ep_in_ff (void); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING +static inline uint16_t tud_audio_flush_tx_support_ff (void); +static inline uint16_t tud_audio_clear_tx_support_ff (uint8_t ff_idx); +static inline uint16_t tud_audio_write_support_ff (uint8_t ff_idx, const void * data, uint16_t len); +static inline tu_fifo_t* tud_audio_get_tx_support_ff (uint8_t ff_idx); +#endif + +// INT CTR API + +#if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN +static inline uint16_t tud_audio_int_ctr_write (uint8_t const* buffer, uint16_t len); +#endif + +// Buffer control EP data and schedule a transmit +// This function is intended to be used if you do not have a persistent buffer or memory location available (e.g. non-local variables) and need to answer onto a +// get request. This function buffers your answer request frame into the control buffer of the corresponding audio driver and schedules a transmit for sending it. +// Since transmission is triggered via interrupts, a persistent memory location is required onto which the buffer pointer in pointing. If you already have such +// available you may directly use 'tud_control_xfer(...)'. In this case data does not need to be copied into an additional buffer and you save some time. +// If the request's wLength is zero, a status packet is sent instead. +bool tud_audio_buffer_and_schedule_control_xfer(uint8_t rhport, tusb_control_request_t const * p_request, void* data, uint16_t len); + +//--------------------------------------------------------------------+ +// Application Callback API (weak is optional) +//--------------------------------------------------------------------+ + +#if CFG_TUD_AUDIO_ENABLE_EP_IN +TU_ATTR_WEAK bool tud_audio_tx_done_pre_load_cb(uint8_t rhport, uint8_t func_id, uint8_t ep_in, uint8_t cur_alt_setting); +TU_ATTR_WEAK bool tud_audio_tx_done_post_load_cb(uint8_t rhport, uint16_t n_bytes_copied, uint8_t func_id, uint8_t ep_in, uint8_t cur_alt_setting); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT +TU_ATTR_WEAK bool tud_audio_rx_done_pre_read_cb(uint8_t rhport, uint16_t n_bytes_received, uint8_t func_id, uint8_t ep_out, uint8_t cur_alt_setting); +TU_ATTR_WEAK bool tud_audio_rx_done_post_read_cb(uint8_t rhport, uint16_t n_bytes_received, uint8_t func_id, uint8_t ep_out, uint8_t cur_alt_setting); +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP +TU_ATTR_WEAK bool tud_audio_fb_done_cb(uint8_t rhport); + +// This function is used to provide data rate feedback from an asynchronous sink. Feedback value will be sent at FB endpoint interval till it's changed. +// +// The feedback format is specified to be 16.16 for HS and 10.14 for FS devices (see Universal Serial Bus Specification Revision 2.0 5.12.4.2). By default, +// the choice of format is left to the caller and feedback argument is sent as-is. If CFG_TUD_AUDIO_ENABLE_FEEDBACK_FORMAT_CORRECTION is set, then tinyusb +// expects 16.16 format and handles the conversion to 10.14 on FS. +// +// Note that due to a bug in its USB Audio 2.0 driver, Windows currently requires 16.16 format for _all_ USB 2.0 devices. On Linux and macOS it seems the +// driver can work with either format. So a good compromise is to keep format correction disabled and stick to 16.16 format. +bool tud_audio_n_fb_set(uint8_t func_id, uint32_t feedback); +static inline bool tud_audio_fb_set(uint32_t feedback); +#endif + +#if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN +TU_ATTR_WEAK bool tud_audio_int_ctr_done_cb(uint8_t rhport, uint16_t n_bytes_copied); +#endif + +// Invoked when audio set interface request received +TU_ATTR_WEAK bool tud_audio_set_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request); + +// Invoked when audio set interface request received which closes an EP +TU_ATTR_WEAK bool tud_audio_set_itf_close_EP_cb(uint8_t rhport, tusb_control_request_t const * p_request); + +// Invoked when audio class specific set request received for an EP +TU_ATTR_WEAK bool tud_audio_set_req_ep_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff); + +// Invoked when audio class specific set request received for an interface +TU_ATTR_WEAK bool tud_audio_set_req_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff); + +// Invoked when audio class specific set request received for an entity +TU_ATTR_WEAK bool tud_audio_set_req_entity_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff); + +// Invoked when audio class specific get request received for an EP +TU_ATTR_WEAK bool tud_audio_get_req_ep_cb(uint8_t rhport, tusb_control_request_t const * p_request); + +// Invoked when audio class specific get request received for an interface +TU_ATTR_WEAK bool tud_audio_get_req_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request); + +// Invoked when audio class specific get request received for an entity +TU_ATTR_WEAK bool tud_audio_get_req_entity_cb(uint8_t rhport, tusb_control_request_t const * p_request); + +//--------------------------------------------------------------------+ +// Inline Functions +//--------------------------------------------------------------------+ + +static inline bool tud_audio_mounted(void) +{ + return tud_audio_n_mounted(0); +} + +// RX API + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && !CFG_TUD_AUDIO_ENABLE_DECODING + +static inline uint16_t tud_audio_available(void) +{ + return tud_audio_n_available(0); +} + +static inline uint16_t tud_audio_read(void* buffer, uint16_t bufsize) +{ + return tud_audio_n_read(0, buffer, bufsize); +} + +static inline bool tud_audio_clear_ep_out_ff(void) +{ + return tud_audio_n_clear_ep_out_ff(0); +} + +static inline tu_fifo_t* tud_audio_get_ep_out_ff(void) +{ + return tud_audio_n_get_ep_out_ff(0); +} + +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_DECODING + +static inline bool tud_audio_clear_rx_support_ff(uint8_t ff_idx) +{ + return tud_audio_n_clear_rx_support_ff(0, ff_idx); +} + +static inline uint16_t tud_audio_available_support_ff(uint8_t ff_idx) +{ + return tud_audio_n_available_support_ff(0, ff_idx); +} + +static inline uint16_t tud_audio_read_support_ff(uint8_t ff_idx, void* buffer, uint16_t bufsize) +{ + return tud_audio_n_read_support_ff(0, ff_idx, buffer, bufsize); +} + +static inline tu_fifo_t* tud_audio_get_rx_support_ff(uint8_t ff_idx) +{ + return tud_audio_n_get_rx_support_ff(0, ff_idx); +} + +#endif + +// TX API + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && !CFG_TUD_AUDIO_ENABLE_ENCODING + +static inline uint16_t tud_audio_write(const void * data, uint16_t len) +{ + return tud_audio_n_write(0, data, len); +} + +static inline bool tud_audio_clear_ep_in_ff(void) +{ + return tud_audio_n_clear_ep_in_ff(0); +} + +static inline tu_fifo_t* tud_audio_get_ep_in_ff(void) +{ + return tud_audio_n_get_ep_in_ff(0); +} + +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_IN && CFG_TUD_AUDIO_ENABLE_ENCODING + +static inline uint16_t tud_audio_flush_tx_support_ff(void) +{ + return tud_audio_n_flush_tx_support_ff(0); +} + +static inline uint16_t tud_audio_clear_tx_support_ff(uint8_t ff_idx) +{ + return tud_audio_n_clear_tx_support_ff(0, ff_idx); +} + +static inline uint16_t tud_audio_write_support_ff(uint8_t ff_idx, const void * data, uint16_t len) +{ + return tud_audio_n_write_support_ff(0, ff_idx, data, len); +} + +static inline tu_fifo_t* tud_audio_get_tx_support_ff(uint8_t ff_idx) +{ + return tud_audio_n_get_tx_support_ff(0, ff_idx); +} + +#endif + +#if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN +static inline uint16_t tud_audio_int_ctr_write(uint8_t const* buffer, uint16_t len) +{ + return tud_audio_int_ctr_n_write(0, buffer, len); +} +#endif + +#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP +static inline bool tud_audio_fb_set(uint32_t feedback) +{ + return tud_audio_n_fb_set(0, feedback); +} +#endif + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void audiod_init (void); +void audiod_reset (uint8_t rhport); +uint16_t audiod_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool audiod_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); +bool audiod_xfer_cb (uint8_t rhport, uint8_t edpt_addr, xfer_result_t result, uint32_t xferred_bytes); + +#ifdef __cplusplus +} +#endif + +#endif /* _TUSB_AUDIO_DEVICE_H_ */ + +/** @} */ +/** @} */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/bth/bth_device.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/bth/bth_device.c new file mode 100755 index 000000000..8ef609622 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/bth/bth_device.c @@ -0,0 +1,258 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Jerzy Kasenberg + * + * 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 (TUSB_OPT_DEVICE_ENABLED && CFG_TUD_BTH) + +//--------------------------------------------------------------------+ +// INCLUDE +//--------------------------------------------------------------------+ +#include "bth_device.h" +#include + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +typedef struct +{ + uint8_t itf_num; + uint8_t ep_ev; + uint8_t ep_acl_in; + uint8_t ep_acl_out; + uint8_t ep_voice[2]; // Not used yet + uint8_t ep_voice_size[2][CFG_TUD_BTH_ISO_ALT_COUNT]; + + // Endpoint Transfer buffer + CFG_TUSB_MEM_ALIGN bt_hci_cmd_t hci_cmd; + CFG_TUSB_MEM_ALIGN uint8_t epout_buf[CFG_TUD_BTH_DATA_EPSIZE]; + +} btd_interface_t; + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +CFG_TUSB_MEM_SECTION btd_interface_t _btd_itf; + +static bool bt_tx_data(uint8_t ep, void *data, uint16_t len) +{ + // skip if previous transfer not complete + TU_VERIFY(!usbd_edpt_busy(TUD_OPT_RHPORT, ep)); + + TU_ASSERT(usbd_edpt_xfer(TUD_OPT_RHPORT, ep, data, len)); + + return true; +} + +//--------------------------------------------------------------------+ +// READ API +//--------------------------------------------------------------------+ + + +//--------------------------------------------------------------------+ +// WRITE API +//--------------------------------------------------------------------+ + +bool tud_bt_event_send(void *event, uint16_t event_len) +{ + return bt_tx_data(_btd_itf.ep_ev, event, event_len); +} + +bool tud_bt_acl_data_send(void *event, uint16_t event_len) +{ + return bt_tx_data(_btd_itf.ep_acl_in, event, event_len); +} + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void btd_init(void) +{ + tu_memclr(&_btd_itf, sizeof(_btd_itf)); +} + +void btd_reset(uint8_t rhport) +{ + (void)rhport; +} + +uint16_t btd_open(uint8_t rhport, tusb_desc_interface_t const *itf_desc, uint16_t max_len) +{ + tusb_desc_endpoint_t const *desc_ep; + uint16_t drv_len = 0; + // Size of single alternative of ISO interface + const uint16_t iso_alt_itf_size = sizeof(tusb_desc_interface_t) + 2 * sizeof(tusb_desc_endpoint_t); + // Size of hci interface + const uint16_t hci_itf_size = sizeof(tusb_desc_interface_t) + 3 * sizeof(tusb_desc_endpoint_t); + // Ensure this is BT Primary Controller + TU_VERIFY(TUSB_CLASS_WIRELESS_CONTROLLER == itf_desc->bInterfaceClass && + TUD_BT_APP_SUBCLASS == itf_desc->bInterfaceSubClass && + TUD_BT_PROTOCOL_PRIMARY_CONTROLLER == itf_desc->bInterfaceProtocol, 0); + + TU_ASSERT(itf_desc->bNumEndpoints == 3 && max_len >= hci_itf_size); + + _btd_itf.itf_num = itf_desc->bInterfaceNumber; + + desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc); + + TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && TUSB_XFER_INTERRUPT == desc_ep->bmAttributes.xfer, 0); + TU_ASSERT(usbd_edpt_open(rhport, desc_ep), 0); + _btd_itf.ep_ev = desc_ep->bEndpointAddress; + + // Open endpoint pair + TU_ASSERT(usbd_open_edpt_pair(rhport, tu_desc_next(desc_ep), 2, TUSB_XFER_BULK, &_btd_itf.ep_acl_out, + &_btd_itf.ep_acl_in), 0); + + itf_desc = (tusb_desc_interface_t const *)tu_desc_next(tu_desc_next(tu_desc_next(desc_ep))); + + // Prepare for incoming data from host + TU_ASSERT(usbd_edpt_xfer(rhport, _btd_itf.ep_acl_out, _btd_itf.epout_buf, CFG_TUD_BTH_DATA_EPSIZE), 0); + + drv_len = hci_itf_size; + + // Ensure this is still BT Primary Controller + TU_ASSERT(TUSB_CLASS_WIRELESS_CONTROLLER == itf_desc->bInterfaceClass && + TUD_BT_APP_SUBCLASS == itf_desc->bInterfaceSubClass && + TUD_BT_PROTOCOL_PRIMARY_CONTROLLER == itf_desc->bInterfaceProtocol, 0); + TU_ASSERT(itf_desc->bNumEndpoints == 2 && max_len >= iso_alt_itf_size + drv_len); + + uint8_t dir; + + desc_ep = (tusb_desc_endpoint_t const *)tu_desc_next(itf_desc); + TU_ASSERT(itf_desc->bAlternateSetting < CFG_TUD_BTH_ISO_ALT_COUNT, 0); + TU_ASSERT(desc_ep->bDescriptorType == TUSB_DESC_ENDPOINT, 0); + dir = tu_edpt_dir(desc_ep->bEndpointAddress); + _btd_itf.ep_voice[dir] = desc_ep->bEndpointAddress; + // Store endpoint size for alternative + _btd_itf.ep_voice_size[dir][itf_desc->bAlternateSetting] = (uint8_t) tu_edpt_packet_size(desc_ep); + + desc_ep = (tusb_desc_endpoint_t const *)tu_desc_next(desc_ep); + TU_ASSERT(desc_ep->bDescriptorType == TUSB_DESC_ENDPOINT, 0); + dir = tu_edpt_dir(desc_ep->bEndpointAddress); + _btd_itf.ep_voice[dir] = desc_ep->bEndpointAddress; + // Store endpoint size for alternative + _btd_itf.ep_voice_size[dir][itf_desc->bAlternateSetting] = (uint8_t) tu_edpt_packet_size(desc_ep); + drv_len += iso_alt_itf_size; + + for (int i = 1; i < CFG_TUD_BTH_ISO_ALT_COUNT && drv_len + iso_alt_itf_size <= max_len; ++i) { + // Make sure rest of alternatives matches + itf_desc = (tusb_desc_interface_t const *)tu_desc_next(desc_ep); + if (itf_desc->bDescriptorType != TUSB_DESC_INTERFACE || + TUSB_CLASS_WIRELESS_CONTROLLER != itf_desc->bInterfaceClass || + TUD_BT_APP_SUBCLASS != itf_desc->bInterfaceSubClass || + TUD_BT_PROTOCOL_PRIMARY_CONTROLLER != itf_desc->bInterfaceProtocol) + { + // Not an Iso interface instance + break; + } + TU_ASSERT(itf_desc->bAlternateSetting < CFG_TUD_BTH_ISO_ALT_COUNT, 0); + + desc_ep = (tusb_desc_endpoint_t const *)tu_desc_next(itf_desc); + dir = tu_edpt_dir(desc_ep->bEndpointAddress); + // Verify that alternative endpoint are same as first ones + TU_ASSERT(desc_ep->bDescriptorType == TUSB_DESC_ENDPOINT && + _btd_itf.ep_voice[dir] == desc_ep->bEndpointAddress, 0); + _btd_itf.ep_voice_size[dir][itf_desc->bAlternateSetting] = (uint8_t) tu_edpt_packet_size(desc_ep); + + desc_ep = (tusb_desc_endpoint_t const *)tu_desc_next(desc_ep); + dir = tu_edpt_dir(desc_ep->bEndpointAddress); + // Verify that alternative endpoint are same as first ones + TU_ASSERT(desc_ep->bDescriptorType == TUSB_DESC_ENDPOINT && + _btd_itf.ep_voice[dir] == desc_ep->bEndpointAddress, 0); + _btd_itf.ep_voice_size[dir][itf_desc->bAlternateSetting] = (uint8_t) tu_edpt_packet_size(desc_ep); + drv_len += iso_alt_itf_size; + } + + return drv_len; +} + +// 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 btd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request) +{ + (void)rhport; + + if ( stage == CONTROL_STAGE_SETUP ) + { + if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS && + request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_DEVICE) + { + // HCI command packet addressing for single function Primary Controllers + TU_VERIFY(request->bRequest == 0 && request->wValue == 0 && request->wIndex == 0); + } + else if (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE) + { + if (request->bRequest == TUSB_REQ_SET_INTERFACE && _btd_itf.itf_num + 1 == request->wIndex) + { + // TODO: Set interface it would involve changing size of endpoint size + } + else + { + // HCI command packet for Primary Controller function in a composite device + TU_VERIFY(request->bRequest == 0 && request->wValue == 0 && request->wIndex == _btd_itf.itf_num); + } + } + else return false; + + return tud_control_xfer(rhport, request, &_btd_itf.hci_cmd, sizeof(_btd_itf.hci_cmd)); + } + else if ( stage == CONTROL_STAGE_DATA ) + { + // Handle class request only + TU_VERIFY(request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS); + + if (tud_bt_hci_cmd_cb) tud_bt_hci_cmd_cb(&_btd_itf.hci_cmd, tu_min16(request->wLength, sizeof(_btd_itf.hci_cmd))); + } + + return true; +} + +bool btd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) +{ + (void)result; + + // received new data from host + if (ep_addr == _btd_itf.ep_acl_out) + { + if (tud_bt_acl_data_received_cb) tud_bt_acl_data_received_cb(_btd_itf.epout_buf, xferred_bytes); + + // prepare for next data + TU_ASSERT(usbd_edpt_xfer(rhport, _btd_itf.ep_acl_out, _btd_itf.epout_buf, CFG_TUD_BTH_DATA_EPSIZE)); + } + else if (ep_addr == _btd_itf.ep_ev) + { + if (tud_bt_event_sent_cb) tud_bt_event_sent_cb((uint16_t)xferred_bytes); + } + else if (ep_addr == _btd_itf.ep_acl_in) + { + if (tud_bt_acl_data_sent_cb) tud_bt_acl_data_sent_cb((uint16_t)xferred_bytes); + } + + return true; +} + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/bth/bth_device.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/bth/bth_device.h new file mode 100755 index 000000000..1b90d0915 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/bth/bth_device.h @@ -0,0 +1,109 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Jerzy Kasenberg + * + * 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_BTH_DEVICE_H_ +#define _TUSB_BTH_DEVICE_H_ + +#include +#include + +//--------------------------------------------------------------------+ +// Class Driver Configuration +//--------------------------------------------------------------------+ +#ifndef CFG_TUD_BTH_EVENT_EPSIZE +#define CFG_TUD_BTH_EVENT_EPSIZE 16 +#endif +#ifndef CFG_TUD_BTH_DATA_EPSIZE +#define CFG_TUD_BTH_DATA_EPSIZE 64 +#endif + +typedef struct TU_ATTR_PACKED +{ + uint16_t op_code; + uint8_t param_length; + uint8_t param[255]; +} bt_hci_cmd_t; + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Application Callback API (weak is optional) +//--------------------------------------------------------------------+ + +// Invoked when HCI command was received over USB from Bluetooth host. +// Detailed format is described in Bluetooth core specification Vol 2, +// Part E, 5.4.1. +// Length of the command is from 3 bytes (2 bytes for OpCode, +// 1 byte for parameter total length) to 258. +TU_ATTR_WEAK void tud_bt_hci_cmd_cb(void *hci_cmd, size_t cmd_len); + +// Invoked when ACL data was received over USB from Bluetooth host. +// Detailed format is described in Bluetooth core specification Vol 2, +// Part E, 5.4.2. +// Length is from 4 bytes, (12 bits for Handle, 4 bits for flags +// and 16 bits for data total length) to endpoint size. +TU_ATTR_WEAK void tud_bt_acl_data_received_cb(void *acl_data, uint16_t data_len); + +// Called when event sent with tud_bt_event_send() was delivered to BT stack. +// Controller can release/reuse buffer with Event packet at this point. +TU_ATTR_WEAK void tud_bt_event_sent_cb(uint16_t sent_bytes); + +// Called when ACL data that was sent with tud_bt_acl_data_send() +// was delivered to BT stack. +// Controller can release/reuse buffer with ACL packet at this point. +TU_ATTR_WEAK void tud_bt_acl_data_sent_cb(uint16_t sent_bytes); + +// Bluetooth controller calls this function when it wants to send even packet +// as described in Bluetooth core specification Vol 2, Part E, 5.4.4. +// Event has at least 2 bytes, first is Event code second contains parameter +// total length. Controller can release/reuse event memory after +// tud_bt_event_sent_cb() is called. +bool tud_bt_event_send(void *event, uint16_t event_len); + +// Bluetooth controller calls this to send ACL data packet +// as described in Bluetooth core specification Vol 2, Part E, 5.4.2 +// Minimum length is 4 bytes, (12 bits for Handle, 4 bits for flags +// and 16 bits for data total length). Upper limit is not limited +// to endpoint size since buffer is allocate by controller +// and must not be reused till tud_bt_acl_data_sent_cb() is called. +bool tud_bt_acl_data_send(void *acl_data, uint16_t data_len); + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void btd_init (void); +void btd_reset (uint8_t rhport); +uint16_t btd_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool btd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const *request); +bool btd_xfer_cb (uint8_t rhport, uint8_t edpt_addr, xfer_result_t result, uint32_t xferred_bytes); + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_BTH_DEVICE_H_ */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc.h new file mode 100644 index 000000000..e345139ea --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc.h @@ -0,0 +1,409 @@ +/* + * 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 group_class + * \defgroup ClassDriver_CDC Communication Device Class (CDC) + * Currently only Abstract Control Model subclass is supported + * @{ */ + +#ifndef _TUSB_CDC_H__ +#define _TUSB_CDC_H__ + +#include "common/tusb_common.h" + +#ifdef __cplusplus + extern "C" { +#endif + +/** \defgroup ClassDriver_CDC_Common Common Definitions + * @{ */ + +// TODO remove +/// CDC Pipe ID, used to indicate which pipe the API is addressing to (Notification, Out, In) +typedef enum +{ + CDC_PIPE_NOTIFICATION , ///< Notification pipe + CDC_PIPE_DATA_IN , ///< Data in pipe + CDC_PIPE_DATA_OUT , ///< Data out pipe + CDC_PIPE_ERROR , ///< Invalid Pipe ID +}cdc_pipeid_t; + +//--------------------------------------------------------------------+ +// CDC Communication Interface Class +//--------------------------------------------------------------------+ + +/// Communication Interface Subclass Codes +typedef enum +{ + CDC_COMM_SUBCLASS_DIRECT_LINE_CONTROL_MODEL = 0x01 , ///< Direct Line Control Model [USBPSTN1.2] + CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL = 0x02 , ///< Abstract Control Model [USBPSTN1.2] + CDC_COMM_SUBCLASS_TELEPHONE_CONTROL_MODEL = 0x03 , ///< Telephone Control Model [USBPSTN1.2] + CDC_COMM_SUBCLASS_MULTICHANNEL_CONTROL_MODEL = 0x04 , ///< Multi-Channel Control Model [USBISDN1.2] + CDC_COMM_SUBCLASS_CAPI_CONTROL_MODEL = 0x05 , ///< CAPI Control Model [USBISDN1.2] + CDC_COMM_SUBCLASS_ETHERNET_CONTROL_MODEL = 0x06 , ///< Ethernet Networking Control Model [USBECM1.2] + CDC_COMM_SUBCLASS_ATM_NETWORKING_CONTROL_MODEL = 0x07 , ///< ATM Networking Control Model [USBATM1.2] + CDC_COMM_SUBCLASS_WIRELESS_HANDSET_CONTROL_MODEL = 0x08 , ///< Wireless Handset Control Model [USBWMC1.1] + CDC_COMM_SUBCLASS_DEVICE_MANAGEMENT = 0x09 , ///< Device Management [USBWMC1.1] + CDC_COMM_SUBCLASS_MOBILE_DIRECT_LINE_MODEL = 0x0A , ///< Mobile Direct Line Model [USBWMC1.1] + CDC_COMM_SUBCLASS_OBEX = 0x0B , ///< OBEX [USBWMC1.1] + CDC_COMM_SUBCLASS_ETHERNET_EMULATION_MODEL = 0x0C , ///< Ethernet Emulation Model [USBEEM1.0] + CDC_COMM_SUBCLASS_NETWORK_CONTROL_MODEL = 0x0D ///< Network Control Model [USBNCM1.0] +} cdc_comm_sublcass_type_t; + +/// Communication Interface Protocol Codes +typedef enum +{ + CDC_COMM_PROTOCOL_NONE = 0x00 , ///< No specific protocol + CDC_COMM_PROTOCOL_ATCOMMAND = 0x01 , ///< AT Commands: V.250 etc + CDC_COMM_PROTOCOL_ATCOMMAND_PCCA_101 = 0x02 , ///< AT Commands defined by PCCA-101 + CDC_COMM_PROTOCOL_ATCOMMAND_PCCA_101_AND_ANNEXO = 0x03 , ///< AT Commands defined by PCCA-101 & Annex O + CDC_COMM_PROTOCOL_ATCOMMAND_GSM_707 = 0x04 , ///< AT Commands defined by GSM 07.07 + CDC_COMM_PROTOCOL_ATCOMMAND_3GPP_27007 = 0x05 , ///< AT Commands defined by 3GPP 27.007 + CDC_COMM_PROTOCOL_ATCOMMAND_CDMA = 0x06 , ///< AT Commands defined by TIA for CDMA + CDC_COMM_PROTOCOL_ETHERNET_EMULATION_MODEL = 0x07 ///< Ethernet Emulation Model +} cdc_comm_protocol_type_t; + +//------------- SubType Descriptor in COMM Functional Descriptor -------------// +/// Communication Interface SubType Descriptor +typedef enum +{ + CDC_FUNC_DESC_HEADER = 0x00 , ///< Header Functional Descriptor, which marks the beginning of the concatenated set of functional descriptors for the interface. + CDC_FUNC_DESC_CALL_MANAGEMENT = 0x01 , ///< Call Management Functional Descriptor. + CDC_FUNC_DESC_ABSTRACT_CONTROL_MANAGEMENT = 0x02 , ///< Abstract Control Management Functional Descriptor. + CDC_FUNC_DESC_DIRECT_LINE_MANAGEMENT = 0x03 , ///< Direct Line Management Functional Descriptor. + CDC_FUNC_DESC_TELEPHONE_RINGER = 0x04 , ///< Telephone Ringer Functional Descriptor. + CDC_FUNC_DESC_TELEPHONE_CALL_AND_LINE_STATE_REPORTING_CAPACITY = 0x05 , ///< Telephone Call and Line State Reporting Capabilities Functional Descriptor. + CDC_FUNC_DESC_UNION = 0x06 , ///< Union Functional Descriptor + CDC_FUNC_DESC_COUNTRY_SELECTION = 0x07 , ///< Country Selection Functional Descriptor + CDC_FUNC_DESC_TELEPHONE_OPERATIONAL_MODES = 0x08 , ///< Telephone Operational ModesFunctional Descriptor + CDC_FUNC_DESC_USB_TERMINAL = 0x09 , ///< USB Terminal Functional Descriptor + CDC_FUNC_DESC_NETWORK_CHANNEL_TERMINAL = 0x0A , ///< Network Channel Terminal Descriptor + CDC_FUNC_DESC_PROTOCOL_UNIT = 0x0B , ///< Protocol Unit Functional Descriptor + CDC_FUNC_DESC_EXTENSION_UNIT = 0x0C , ///< Extension Unit Functional Descriptor + CDC_FUNC_DESC_MULTICHANEL_MANAGEMENT = 0x0D , ///< Multi-Channel Management Functional Descriptor + CDC_FUNC_DESC_CAPI_CONTROL_MANAGEMENT = 0x0E , ///< CAPI Control Management Functional Descriptor + CDC_FUNC_DESC_ETHERNET_NETWORKING = 0x0F , ///< Ethernet Networking Functional Descriptor + CDC_FUNC_DESC_ATM_NETWORKING = 0x10 , ///< ATM Networking Functional Descriptor + CDC_FUNC_DESC_WIRELESS_HANDSET_CONTROL_MODEL = 0x11 , ///< Wireless Handset Control Model Functional Descriptor + CDC_FUNC_DESC_MOBILE_DIRECT_LINE_MODEL = 0x12 , ///< Mobile Direct Line Model Functional Descriptor + CDC_FUNC_DESC_MOBILE_DIRECT_LINE_MODEL_DETAIL = 0x13 , ///< MDLM Detail Functional Descriptor + CDC_FUNC_DESC_DEVICE_MANAGEMENT_MODEL = 0x14 , ///< Device Management Model Functional Descriptor + CDC_FUNC_DESC_OBEX = 0x15 , ///< OBEX Functional Descriptor + CDC_FUNC_DESC_COMMAND_SET = 0x16 , ///< Command Set Functional Descriptor + CDC_FUNC_DESC_COMMAND_SET_DETAIL = 0x17 , ///< Command Set Detail Functional Descriptor + CDC_FUNC_DESC_TELEPHONE_CONTROL_MODEL = 0x18 , ///< Telephone Control Model Functional Descriptor + CDC_FUNC_DESC_OBEX_SERVICE_IDENTIFIER = 0x19 , ///< OBEX Service Identifier Functional Descriptor + CDC_FUNC_DESC_NCM = 0x1A , ///< NCM Functional Descriptor +}cdc_func_desc_type_t; + +//--------------------------------------------------------------------+ +// CDC Data Interface Class +//--------------------------------------------------------------------+ + +// SUBCLASS code of Data Interface is not used and should/must be zero + +// Data Interface Protocol Codes +typedef enum{ + CDC_DATA_PROTOCOL_ISDN_BRI = 0x30, ///< Physical interface protocol for ISDN BRI + CDC_DATA_PROTOCOL_HDLC = 0x31, ///< HDLC + CDC_DATA_PROTOCOL_TRANSPARENT = 0x32, ///< Transparent + CDC_DATA_PROTOCOL_Q921_MANAGEMENT = 0x50, ///< Management protocol for Q.921 data link protocol + CDC_DATA_PROTOCOL_Q921_DATA_LINK = 0x51, ///< Data link protocol for Q.931 + CDC_DATA_PROTOCOL_Q921_TEI_MULTIPLEXOR = 0x52, ///< TEI-multiplexor for Q.921 data link protocol + CDC_DATA_PROTOCOL_V42BIS_DATA_COMPRESSION = 0x90, ///< Data compression procedures + CDC_DATA_PROTOCOL_EURO_ISDN = 0x91, ///< Euro-ISDN protocol control + CDC_DATA_PROTOCOL_V24_RATE_ADAPTION_TO_ISDN = 0x92, ///< V.24 rate adaptation to ISDN + CDC_DATA_PROTOCOL_CAPI_COMMAND = 0x93, ///< CAPI Commands + CDC_DATA_PROTOCOL_HOST_BASED_DRIVER = 0xFD, ///< Host based driver. Note: This protocol code should only be used in messages between host and device to identify the host driver portion of a protocol stack. + CDC_DATA_PROTOCOL_IN_PROTOCOL_UNIT_FUNCTIONAL_DESCRIPTOR = 0xFE ///< The protocol(s) are described using a ProtocolUnit Functional Descriptors on Communications Class Interface +}cdc_data_protocol_type_t; + +//--------------------------------------------------------------------+ +// Management Element Request (Control Endpoint) +//--------------------------------------------------------------------+ + +/// Communication Interface Management Element Request Codes +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, + CDC_REQUEST_GET_COMM_FEATURE = 0x03, + CDC_REQUEST_CLEAR_COMM_FEATURE = 0x04, + + CDC_REQUEST_SET_AUX_LINE_STATE = 0x10, + CDC_REQUEST_SET_HOOK_STATE = 0x11, + CDC_REQUEST_PULSE_SETUP = 0x12, + CDC_REQUEST_SEND_PULSE = 0x13, + CDC_REQUEST_SET_PULSE_TIME = 0x14, + CDC_REQUEST_RING_AUX_JACK = 0x15, + + CDC_REQUEST_SET_LINE_CODING = 0x20, + CDC_REQUEST_GET_LINE_CODING = 0x21, + CDC_REQUEST_SET_CONTROL_LINE_STATE = 0x22, + CDC_REQUEST_SEND_BREAK = 0x23, + + CDC_REQUEST_SET_RINGER_PARMS = 0x30, + CDC_REQUEST_GET_RINGER_PARMS = 0x31, + CDC_REQUEST_SET_OPERATION_PARMS = 0x32, + CDC_REQUEST_GET_OPERATION_PARMS = 0x33, + CDC_REQUEST_SET_LINE_PARMS = 0x34, + CDC_REQUEST_GET_LINE_PARMS = 0x35, + CDC_REQUEST_DIAL_DIGITS = 0x36, + CDC_REQUEST_SET_UNIT_PARAMETER = 0x37, + CDC_REQUEST_GET_UNIT_PARAMETER = 0x38, + CDC_REQUEST_CLEAR_UNIT_PARAMETER = 0x39, + CDC_REQUEST_GET_PROFILE = 0x3A, + + CDC_REQUEST_SET_ETHERNET_MULTICAST_FILTERS = 0x40, + CDC_REQUEST_SET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x41, + CDC_REQUEST_GET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x42, + CDC_REQUEST_SET_ETHERNET_PACKET_FILTER = 0x43, + CDC_REQUEST_GET_ETHERNET_STATISTIC = 0x44, + + CDC_REQUEST_SET_ATM_DATA_FORMAT = 0x50, + CDC_REQUEST_GET_ATM_DEVICE_STATISTICS = 0x51, + CDC_REQUEST_SET_ATM_DEFAULT_VC = 0x52, + CDC_REQUEST_GET_ATM_VC_STATISTICS = 0x53, + + CDC_REQUEST_MDLM_SEMANTIC_MODEL = 0x60, +}cdc_management_request_t; + +//--------------------------------------------------------------------+ +// Management Elemenent Notification (Notification Endpoint) +//--------------------------------------------------------------------+ + +/// 6.3 Notification Codes +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, + CDC_NOTIF_RING_DETECT = 0x09, + CDC_NOTIF_SERIAL_STATE = 0x20, + CDC_NOTIF_CALL_STATE_CHANGE = 0x28, + CDC_NOTIF_LINE_STATE_CHANGE = 0x29, + CDC_NOTIF_CONNECTION_SPEED_CHANGE = 0x2A, ///< This notification allows the device to inform the host-networking driver that a change in either the upstream or the downstream bit rate of the connection has occurred + CDC_NOTIF_MDLM_SEMANTIC_MODEL_NOTIFICATION = 0x40, +}cdc_notification_request_t; + +//--------------------------------------------------------------------+ +// Class Specific Functional Descriptor (Communication Interface) +//--------------------------------------------------------------------+ + +// Start of all packed definitions for compiler without per-type packed +TU_ATTR_PACKED_BEGIN +TU_ATTR_BIT_FIELD_ORDER_BEGIN + +/// Header Functional Descriptor (Communication Interface) +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific + uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUNC_DESC_ + uint16_t bcdCDC ; ///< CDC release number in Binary-Coded Decimal +}cdc_desc_func_header_t; + +/// Union Functional Descriptor (Communication Interface) +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific + uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ + uint8_t bControlInterface ; ///< Interface number of Communication Interface + uint8_t bSubordinateInterface ; ///< Array of Interface number of Data Interface +}cdc_desc_func_union_t; + +#define cdc_desc_func_union_n_t(no_slave)\ + struct TU_ATTR_PACKED { \ + uint8_t bLength ;\ + uint8_t bDescriptorType ;\ + uint8_t bDescriptorSubType ;\ + uint8_t bControlInterface ;\ + uint8_t bSubordinateInterface[no_slave] ;\ +} + +/// Country Selection Functional Descriptor (Communication Interface) +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific + uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ + uint8_t iCountryCodeRelDate ; ///< Index of a string giving the release date for the implemented ISO 3166 Country Codes. + uint16_t wCountryCode ; ///< Country code in the format as defined in [ISO3166], release date as specified inoffset 3 for the first supported country. +}cdc_desc_func_country_selection_t; + +#define cdc_desc_func_country_selection_n_t(no_country) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength ;\ + uint8_t bDescriptorType ;\ + uint8_t bDescriptorSubType ;\ + uint8_t iCountryCodeRelDate ;\ + uint16_t wCountryCode[no_country] ;\ +} + +//--------------------------------------------------------------------+ +// PUBLIC SWITCHED TELEPHONE NETWORK (PSTN) SUBCLASS +//--------------------------------------------------------------------+ + +/// \brief Call Management Functional Descriptor +/// \details This functional descriptor describes the processing of calls for the Communications Class interface. +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific + uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ + + struct { + uint8_t handle_call : 1; ///< 0 - Device sends/receives call management information only over the Communications Class interface. 1 - Device can send/receive call management information over a Data Class interface. + uint8_t send_recv_call : 1; ///< 0 - Device does not handle call management itself. 1 - Device handles call management itself. + uint8_t TU_RESERVED : 6; + } bmCapabilities; + + uint8_t bDataInterface; +}cdc_desc_func_call_management_t; + +typedef struct TU_ATTR_PACKED +{ + uint8_t support_comm_request : 1; ///< Device supports the request combination of Set_Comm_Feature, Clear_Comm_Feature, and Get_Comm_Feature. + uint8_t support_line_request : 1; ///< Device supports the request combination of Set_Line_Coding, Set_Control_Line_State, Get_Line_Coding, and the notification Serial_State. + uint8_t support_send_break : 1; ///< Device supports the request Send_Break + uint8_t support_notification_network_connection : 1; ///< Device supports the notification Network_Connection. + uint8_t TU_RESERVED : 4; +}cdc_acm_capability_t; + +TU_VERIFY_STATIC(sizeof(cdc_acm_capability_t) == 1, "mostly problem with compiler"); + +/// Abstract Control Management Functional Descriptor +/// This functional descriptor describes the commands supported by by the Communications Class interface with SubClass code of \ref CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific + uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ + cdc_acm_capability_t bmCapabilities ; +}cdc_desc_func_acm_t; + +/// \brief Direct Line Management Functional Descriptor +/// \details This functional descriptor describes the commands supported by the Communications Class interface with SubClass code of \ref CDC_FUNC_DESC_DIRECT_LINE_MANAGEMENT +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific + uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ + struct { + uint8_t require_pulse_setup : 1; ///< Device requires extra Pulse_Setup request during pulse dialing sequence to disengage holding circuit. + uint8_t support_aux_request : 1; ///< Device supports the request combination of Set_Aux_Line_State, Ring_Aux_Jack, and notification Aux_Jack_Hook_State. + uint8_t support_pulse_request : 1; ///< Device supports the request combination of Pulse_Setup, Send_Pulse, and Set_Pulse_Time. + uint8_t TU_RESERVED : 5; + } bmCapabilities; +}cdc_desc_func_direct_line_management_t; + +/// \brief Telephone Ringer Functional Descriptor +/// \details The Telephone Ringer functional descriptor describes the ringer capabilities supported by the Communications Class interface, +/// with the SubClass code of \ref CDC_COMM_SUBCLASS_TELEPHONE_CONTROL_MODEL +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific + uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ + uint8_t bRingerVolSteps ; + uint8_t bNumRingerPatterns ; +}cdc_desc_func_telephone_ringer_t; + +/// \brief Telephone Operational Modes Functional Descriptor +/// \details The Telephone Operational Modes functional descriptor describes the operational modes supported by +/// the Communications Class interface, with the SubClass code of \ref CDC_COMM_SUBCLASS_TELEPHONE_CONTROL_MODEL +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific + uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ + struct { + uint8_t simple_mode : 1; + uint8_t standalone_mode : 1; + uint8_t computer_centric_mode : 1; + uint8_t TU_RESERVED : 5; + } bmCapabilities; +}cdc_desc_func_telephone_operational_modes_t; + +/// \brief Telephone Call and Line State Reporting Capabilities Descriptor +/// \details The Telephone Call and Line State Reporting Capabilities functional descriptor describes the abilities of a +/// telephone device to report optional call and line states. +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific + uint8_t bDescriptorSubType ; ///< Descriptor SubType one of above CDC_FUCN_DESC_ + struct { + uint32_t interrupted_dialtone : 1; ///< 0 : Reports only dialtone (does not differentiate between normal and interrupted dialtone). 1 : Reports interrupted dialtone in addition to normal dialtone + uint32_t ringback_busy_fastbusy : 1; ///< 0 : Reports only dialing state. 1 : Reports ringback, busy, and fast busy states. + uint32_t caller_id : 1; ///< 0 : Does not report caller ID. 1 : Reports caller ID information. + uint32_t incoming_distinctive : 1; ///< 0 : Reports only incoming ringing. 1 : Reports incoming distinctive ringing patterns. + uint32_t dual_tone_multi_freq : 1; ///< 0 : Cannot report dual tone multi-frequency (DTMF) digits input remotely over the telephone line. 1 : Can report DTMF digits input remotely over the telephone line. + uint32_t line_state_change : 1; ///< 0 : Does not support line state change notification. 1 : Does support line state change notification + uint32_t TU_RESERVED : 26; + } bmCapabilities; +}cdc_desc_func_telephone_call_state_reporting_capabilities_t; + +// TODO remove +static inline uint8_t cdc_functional_desc_typeof(uint8_t const * p_desc) +{ + return p_desc[2]; +} + +//--------------------------------------------------------------------+ +// Requests +//--------------------------------------------------------------------+ +typedef struct TU_ATTR_PACKED +{ + uint32_t bit_rate; + uint8_t stop_bits; ///< 0: 1 stop bit - 1: 1.5 stop bits - 2: 2 stop bits + uint8_t parity; ///< 0: None - 1: Odd - 2: Even - 3: Mark - 4: Space + uint8_t data_bits; ///< can be 5, 6, 7, 8 or 16 +} cdc_line_coding_t; + +TU_VERIFY_STATIC(sizeof(cdc_line_coding_t) == 7, "size is not correct"); + +typedef struct TU_ATTR_PACKED +{ + uint16_t dte_is_present : 1; ///< Indicates to DCE if DTE is presentor not. This signal corresponds to V.24 signal 108/2 and RS-232 signal DTR. + uint16_t half_duplex_carrier_control : 1; + uint16_t : 14; +} cdc_line_control_state_t; + +TU_VERIFY_STATIC(sizeof(cdc_line_control_state_t) == 2, "size is not correct"); + +TU_ATTR_PACKED_END // End of all packed definitions +TU_ATTR_BIT_FIELD_ORDER_END + +#ifdef __cplusplus + } +#endif + +#endif + +/** @} */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_device.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_device.c new file mode 100644 index 000000000..08f2af253 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_device.c @@ -0,0 +1,486 @@ +/* + * 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 (TUSB_OPT_DEVICE_ENABLED && CFG_TUD_CDC) + +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "cdc_device.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +enum +{ + BULK_PACKET_SIZE = (TUD_OPT_HIGH_SPEED ? 512 : 64) +}; + +typedef struct +{ + uint8_t itf_num; + uint8_t ep_notif; + uint8_t ep_in; + uint8_t ep_out; + + // Bit 0: DTR (Data Terminal Ready), Bit 1: RTS (Request to Send) + uint8_t line_state; + + /*------------- From this point, data is not cleared by bus reset -------------*/ + char wanted_char; + cdc_line_coding_t line_coding; + + // FIFO + tu_fifo_t rx_ff; + tu_fifo_t tx_ff; + + uint8_t rx_ff_buf[CFG_TUD_CDC_RX_BUFSIZE]; + uint8_t tx_ff_buf[CFG_TUD_CDC_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_CDC_EP_BUFSIZE]; + CFG_TUSB_MEM_ALIGN uint8_t epin_buf[CFG_TUD_CDC_EP_BUFSIZE]; + +}cdcd_interface_t; + +#define ITF_MEM_RESET_SIZE offsetof(cdcd_interface_t, wanted_char) + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +CFG_TUSB_MEM_SECTION static cdcd_interface_t _cdcd_itf[CFG_TUD_CDC]; + +static bool _prep_out_transaction (cdcd_interface_t* p_cdc) +{ + uint8_t const rhport = TUD_OPT_RHPORT; + uint16_t available = tu_fifo_remaining(&p_cdc->rx_ff); + + // Prepare for incoming data but only allow what we can store in the ring buffer. + // TODO Actually we can still carry out the transfer, keeping count of received bytes + // and slowly move it to the FIFO when read(). + // This pre-check reduces endpoint claiming + TU_VERIFY(available >= sizeof(p_cdc->epout_buf)); + + // claim endpoint + TU_VERIFY(usbd_edpt_claim(rhport, p_cdc->ep_out)); + + // fifo can be changed before endpoint is claimed + available = tu_fifo_remaining(&p_cdc->rx_ff); + + if ( available >= sizeof(p_cdc->epout_buf) ) + { + return usbd_edpt_xfer(rhport, p_cdc->ep_out, p_cdc->epout_buf, sizeof(p_cdc->epout_buf)); + }else + { + // Release endpoint since we don't make any transfer + usbd_edpt_release(rhport, p_cdc->ep_out); + + return false; + } +} + +//--------------------------------------------------------------------+ +// APPLICATION API +//--------------------------------------------------------------------+ +bool tud_cdc_n_connected(uint8_t itf) +{ + // DTR (bit 0) active is considered as connected + return tud_ready() && tu_bit_test(_cdcd_itf[itf].line_state, 0); +} + +uint8_t tud_cdc_n_get_line_state (uint8_t itf) +{ + return _cdcd_itf[itf].line_state; +} + +void tud_cdc_n_get_line_coding (uint8_t itf, cdc_line_coding_t* coding) +{ + (*coding) = _cdcd_itf[itf].line_coding; +} + +void tud_cdc_n_set_wanted_char (uint8_t itf, char wanted) +{ + _cdcd_itf[itf].wanted_char = wanted; +} + + +//--------------------------------------------------------------------+ +// READ API +//--------------------------------------------------------------------+ +uint32_t tud_cdc_n_available(uint8_t itf) +{ + return tu_fifo_count(&_cdcd_itf[itf].rx_ff); +} + +uint32_t tud_cdc_n_read(uint8_t itf, void* buffer, uint32_t bufsize) +{ + cdcd_interface_t* p_cdc = &_cdcd_itf[itf]; + uint32_t num_read = tu_fifo_read_n(&p_cdc->rx_ff, buffer, bufsize); + _prep_out_transaction(p_cdc); + return num_read; +} + +bool tud_cdc_n_peek(uint8_t itf, uint8_t* chr) +{ + return tu_fifo_peek(&_cdcd_itf[itf].rx_ff, chr); +} + +void tud_cdc_n_read_flush (uint8_t itf) +{ + cdcd_interface_t* p_cdc = &_cdcd_itf[itf]; + tu_fifo_clear(&p_cdc->rx_ff); + _prep_out_transaction(p_cdc); +} + +//--------------------------------------------------------------------+ +// WRITE API +//--------------------------------------------------------------------+ +uint32_t tud_cdc_n_write(uint8_t itf, void const* buffer, uint32_t bufsize) +{ + cdcd_interface_t* p_cdc = &_cdcd_itf[itf]; + uint16_t ret = tu_fifo_write_n(&p_cdc->tx_ff, buffer, bufsize); + + // flush if queue more than packet size + if ( tu_fifo_count(&p_cdc->tx_ff) >= BULK_PACKET_SIZE ) + { + tud_cdc_n_write_flush(itf); + } + + return ret; +} + +uint32_t tud_cdc_n_write_flush (uint8_t itf) +{ + cdcd_interface_t* p_cdc = &_cdcd_itf[itf]; + + // Skip if usb is not ready yet + TU_VERIFY( tud_ready(), 0 ); + + // No data to send + if ( !tu_fifo_count(&p_cdc->tx_ff) ) return 0; + + uint8_t const rhport = TUD_OPT_RHPORT; + + // Claim the endpoint + TU_VERIFY( usbd_edpt_claim(rhport, p_cdc->ep_in), 0 ); + + // Pull data from FIFO + uint16_t const count = tu_fifo_read_n(&p_cdc->tx_ff, p_cdc->epin_buf, sizeof(p_cdc->epin_buf)); + + if ( count ) + { + TU_ASSERT( usbd_edpt_xfer(rhport, p_cdc->ep_in, p_cdc->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_cdc->ep_in); + return 0; + } +} + +uint32_t tud_cdc_n_write_available (uint8_t itf) +{ + return tu_fifo_remaining(&_cdcd_itf[itf].tx_ff); +} + +bool tud_cdc_n_write_clear (uint8_t itf) +{ + return tu_fifo_clear(&_cdcd_itf[itf].tx_ff); +} + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void cdcd_init(void) +{ + tu_memclr(_cdcd_itf, sizeof(_cdcd_itf)); + + for(uint8_t i=0; iwanted_char = (char) -1; + + // default line coding is : stop bit = 1, parity = none, data bits = 8 + p_cdc->line_coding.bit_rate = 115200; + p_cdc->line_coding.stop_bits = 0; + p_cdc->line_coding.parity = 0; + p_cdc->line_coding.data_bits = 8; + + // Config RX fifo + tu_fifo_config(&p_cdc->rx_ff, p_cdc->rx_ff_buf, TU_ARRAY_SIZE(p_cdc->rx_ff_buf), 1, false); + + // Config TX fifo as overwritable at initialization and will be changed to non-overwritable + // if terminal supports DTR bit. Without DTR we do not know if data is actually polled by terminal. + // 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); + +#if CFG_FIFO_MUTEX + 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); +#endif + } +} + +void cdcd_reset(uint8_t rhport) +{ + (void) rhport; + + for(uint8_t i=0; irx_ff); + tu_fifo_clear(&p_cdc->tx_ff); + tu_fifo_set_overwritable(&p_cdc->tx_ff, true); + } +} + +uint16_t cdcd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) +{ + // Only support ACM subclass + TU_VERIFY( TUSB_CLASS_CDC == itf_desc->bInterfaceClass && + CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL == itf_desc->bInterfaceSubClass, 0); + + // Find available interface + cdcd_interface_t * p_cdc = NULL; + for(uint8_t cdc_id=0; cdc_iditf_num = itf_desc->bInterfaceNumber; + + uint16_t drv_len = sizeof(tusb_desc_interface_t); + uint8_t const * p_desc = tu_desc_next( itf_desc ); + + // Communication Functional Descriptors + while ( TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && drv_len <= max_len ) + { + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } + + if ( TUSB_DESC_ENDPOINT == tu_desc_type(p_desc) ) + { + // notification endpoint + tusb_desc_endpoint_t const * desc_ep = (tusb_desc_endpoint_t const *) p_desc; + + TU_ASSERT( usbd_edpt_open(rhport, desc_ep), 0 ); + p_cdc->ep_notif = desc_ep->bEndpointAddress; + + drv_len += tu_desc_len(p_desc); + 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 + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + + // Open endpoint pair + TU_ASSERT( usbd_open_edpt_pair(rhport, p_desc, 2, TUSB_XFER_BULK, &p_cdc->ep_out, &p_cdc->ep_in), 0 ); + + drv_len += 2*sizeof(tusb_desc_endpoint_t); + } + + // Prepare for incoming data + _prep_out_transaction(p_cdc); + + return drv_len; +} + +// 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 cdcd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) +{ + // Handle class request only + TU_VERIFY(request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS); + + uint8_t itf = 0; + cdcd_interface_t* p_cdc = _cdcd_itf; + + // Identify which interface to use + for ( ; ; itf++, p_cdc++) + { + if (itf >= TU_ARRAY_SIZE(_cdcd_itf)) return false; + + if ( p_cdc->itf_num == request->wIndex ) break; + } + + switch ( request->bRequest ) + { + case CDC_REQUEST_SET_LINE_CODING: + if (stage == CONTROL_STAGE_SETUP) + { + TU_LOG2(" Set Line Coding\r\n"); + tud_control_xfer(rhport, request, &p_cdc->line_coding, sizeof(cdc_line_coding_t)); + } + else if ( stage == CONTROL_STAGE_ACK) + { + if ( tud_cdc_line_coding_cb ) tud_cdc_line_coding_cb(itf, &p_cdc->line_coding); + } + break; + + case CDC_REQUEST_GET_LINE_CODING: + if (stage == CONTROL_STAGE_SETUP) + { + TU_LOG2(" Get Line Coding\r\n"); + tud_control_xfer(rhport, request, &p_cdc->line_coding, sizeof(cdc_line_coding_t)); + } + break; + + case CDC_REQUEST_SET_CONTROL_LINE_STATE: + if (stage == CONTROL_STAGE_SETUP) + { + tud_control_status(rhport, request); + } + else if (stage == CONTROL_STAGE_ACK) + { + // CDC PSTN v1.2 section 6.3.12 + // Bit 0: Indicates if DTE is present or not. + // This signal corresponds to V.24 signal 108/2 and RS-232 signal DTR (Data Terminal Ready) + // Bit 1: Carrier control for half-duplex modems. + // This signal corresponds to V.24 signal 105 and RS-232 signal RTS (Request to Send) + bool const dtr = tu_bit_test(request->wValue, 0); + bool const rts = tu_bit_test(request->wValue, 1); + + p_cdc->line_state = (uint8_t) request->wValue; + + // Disable fifo overwriting if DTR bit is set + tu_fifo_set_overwritable(&p_cdc->tx_ff, !dtr); + + TU_LOG2(" Set Control Line State: DTR = %d, RTS = %d\r\n", dtr, rts); + + // Invoke callback + if ( tud_cdc_line_state_cb ) tud_cdc_line_state_cb(itf, dtr, rts); + } + break; + case CDC_REQUEST_SEND_BREAK: + if (stage == CONTROL_STAGE_SETUP) + { + tud_control_status(rhport, request); + } + else if (stage == CONTROL_STAGE_ACK) + { + TU_LOG2(" Send Break\r\n"); + if ( tud_cdc_send_break_cb ) tud_cdc_send_break_cb(itf, request->wValue); + } + break; + + default: return false; // stall unsupported request + } + + return true; +} + +bool cdcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) +{ + (void) result; + + uint8_t itf; + cdcd_interface_t* p_cdc; + + // Identify which interface to use + for (itf = 0; itf < CFG_TUD_CDC; itf++) + { + p_cdc = &_cdcd_itf[itf]; + if ( ( ep_addr == p_cdc->ep_out ) || ( ep_addr == p_cdc->ep_in ) ) break; + } + TU_ASSERT(itf < CFG_TUD_CDC); + + // Received new data + if ( ep_addr == p_cdc->ep_out ) + { + tu_fifo_write_n(&p_cdc->rx_ff, &p_cdc->epout_buf, xferred_bytes); + + // Check for wanted char and invoke callback if needed + if ( tud_cdc_rx_wanted_cb && (((signed char) p_cdc->wanted_char) != -1) ) + { + for ( uint32_t i = 0; i < xferred_bytes; i++ ) + { + if ( (p_cdc->wanted_char == p_cdc->epout_buf[i]) && !tu_fifo_empty(&p_cdc->rx_ff) ) + { + tud_cdc_rx_wanted_cb(itf, p_cdc->wanted_char); + } + } + } + + // invoke receive callback (if there is still data) + if (tud_cdc_rx_cb && !tu_fifo_empty(&p_cdc->rx_ff) ) tud_cdc_rx_cb(itf); + + // prepare for OUT transaction + _prep_out_transaction(p_cdc); + } + + // Data sent to host, we continue to fetch from tx fifo to send. + // Note: This will cause incorrect baudrate set in line coding. + // Though maybe the baudrate is not really important !!! + if ( ep_addr == p_cdc->ep_in ) + { + // invoke transmit callback to possibly refill tx fifo + if ( tud_cdc_tx_complete_cb ) tud_cdc_tx_complete_cb(itf); + + if ( 0 == tud_cdc_n_write_flush(itf) ) + { + // If there is no data left, a ZLP should be sent if + // xferred_bytes is multiple of EP Packet size and not zero + if ( !tu_fifo_count(&p_cdc->tx_ff) && xferred_bytes && (0 == (xferred_bytes & (BULK_PACKET_SIZE-1))) ) + { + if ( usbd_edpt_claim(rhport, p_cdc->ep_in) ) + { + usbd_edpt_xfer(rhport, p_cdc->ep_in, NULL, 0); + } + } + } + } + + // nothing to do with notif endpoint for now + + return true; +} + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_device.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_device.h new file mode 100644 index 000000000..fbc7162a3 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_device.h @@ -0,0 +1,260 @@ +/* + * 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_DEVICE_H_ +#define _TUSB_CDC_DEVICE_H_ + +#include "common/tusb_common.h" +#include "cdc.h" + +//--------------------------------------------------------------------+ +// Class Driver Configuration +//--------------------------------------------------------------------+ +#if !defined(CFG_TUD_CDC_EP_BUFSIZE) && defined(CFG_TUD_CDC_EPSIZE) + #warning CFG_TUD_CDC_EPSIZE is renamed to CFG_TUD_CDC_EP_BUFSIZE, please update to use the new name + #define CFG_TUD_CDC_EP_BUFSIZE CFG_TUD_CDC_EPSIZE +#endif + +#ifndef CFG_TUD_CDC_EP_BUFSIZE + #define CFG_TUD_CDC_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +/** \addtogroup CDC_Serial Serial + * @{ + * \defgroup CDC_Serial_Device Device + * @{ */ + +//--------------------------------------------------------------------+ +// Application API (Multiple Ports) +// CFG_TUD_CDC > 1 +//--------------------------------------------------------------------+ + +// Check if terminal is connected to this port +bool tud_cdc_n_connected (uint8_t itf); + +// Get current line state. Bit 0: DTR (Data Terminal Ready), Bit 1: RTS (Request to Send) +uint8_t tud_cdc_n_get_line_state (uint8_t itf); + +// Get current line encoding: bit rate, stop bits parity etc .. +void tud_cdc_n_get_line_coding (uint8_t itf, cdc_line_coding_t* coding); + +// Set special character that will trigger tud_cdc_rx_wanted_cb() callback on receiving +void tud_cdc_n_set_wanted_char (uint8_t itf, char wanted); + +// Get the number of bytes available for reading +uint32_t tud_cdc_n_available (uint8_t itf); + +// Read received bytes +uint32_t tud_cdc_n_read (uint8_t itf, void* buffer, uint32_t bufsize); + +// Read a byte, return -1 if there is none +static inline +int32_t tud_cdc_n_read_char (uint8_t itf); + +// Clear the received FIFO +void tud_cdc_n_read_flush (uint8_t itf); + +// Get a byte from FIFO at the specified position without removing it +bool tud_cdc_n_peek (uint8_t itf, uint8_t* ui8); + +// Write bytes to TX FIFO, data may remain in the FIFO for a while +uint32_t tud_cdc_n_write (uint8_t itf, void const* buffer, uint32_t bufsize); + +// Write a byte +static inline +uint32_t tud_cdc_n_write_char (uint8_t itf, char ch); + +// Write a null-terminated string +static inline +uint32_t tud_cdc_n_write_str (uint8_t itf, char const* str); + +// Force sending data if possible, return number of forced bytes +uint32_t tud_cdc_n_write_flush (uint8_t itf); + +// Return the number of bytes (characters) available for writing to TX FIFO buffer in a single n_write operation. +uint32_t tud_cdc_n_write_available (uint8_t itf); + +// Clear the transmit FIFO +bool tud_cdc_n_write_clear (uint8_t itf); + +//--------------------------------------------------------------------+ +// Application API (Single Port) +//--------------------------------------------------------------------+ +static inline bool tud_cdc_connected (void); +static inline uint8_t tud_cdc_get_line_state (void); +static inline void tud_cdc_get_line_coding (cdc_line_coding_t* coding); +static inline void tud_cdc_set_wanted_char (char wanted); + +static inline uint32_t tud_cdc_available (void); +static inline int32_t tud_cdc_read_char (void); +static inline uint32_t tud_cdc_read (void* buffer, uint32_t bufsize); +static inline void tud_cdc_read_flush (void); +static inline bool tud_cdc_peek (uint8_t* ui8); + +static inline uint32_t tud_cdc_write_char (char ch); +static inline uint32_t tud_cdc_write (void const* buffer, uint32_t bufsize); +static inline uint32_t tud_cdc_write_str (char const* str); +static inline uint32_t tud_cdc_write_flush (void); +static inline uint32_t tud_cdc_write_available (void); +static inline bool tud_cdc_write_clear (void); + +//--------------------------------------------------------------------+ +// Application Callback API (weak is optional) +//--------------------------------------------------------------------+ + +// Invoked when received new data +TU_ATTR_WEAK void tud_cdc_rx_cb(uint8_t itf); + +// Invoked when received `wanted_char` +TU_ATTR_WEAK void tud_cdc_rx_wanted_cb(uint8_t itf, char wanted_char); + +// Invoked when space becomes available in TX buffer +TU_ATTR_WEAK void tud_cdc_tx_complete_cb(uint8_t itf); + +// Invoked when line state DTR & RTS are changed via SET_CONTROL_LINE_STATE +TU_ATTR_WEAK void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts); + +// Invoked when line coding is change via SET_LINE_CODING +TU_ATTR_WEAK void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* p_line_coding); + +// Invoked when received send break +TU_ATTR_WEAK void tud_cdc_send_break_cb(uint8_t itf, uint16_t duration_ms); + +//--------------------------------------------------------------------+ +// Inline Functions +//--------------------------------------------------------------------+ +static inline int32_t tud_cdc_n_read_char (uint8_t itf) +{ + uint8_t ch; + return tud_cdc_n_read(itf, &ch, 1) ? (int32_t) ch : -1; +} + +static inline uint32_t tud_cdc_n_write_char(uint8_t itf, char ch) +{ + return tud_cdc_n_write(itf, &ch, 1); +} + +static inline uint32_t tud_cdc_n_write_str (uint8_t itf, char const* str) +{ + return tud_cdc_n_write(itf, str, strlen(str)); +} + +static inline bool tud_cdc_connected (void) +{ + return tud_cdc_n_connected(0); +} + +static inline uint8_t tud_cdc_get_line_state (void) +{ + return tud_cdc_n_get_line_state(0); +} + +static inline void tud_cdc_get_line_coding (cdc_line_coding_t* coding) +{ + tud_cdc_n_get_line_coding(0, coding); +} + +static inline void tud_cdc_set_wanted_char (char wanted) +{ + tud_cdc_n_set_wanted_char(0, wanted); +} + +static inline uint32_t tud_cdc_available (void) +{ + return tud_cdc_n_available(0); +} + +static inline int32_t tud_cdc_read_char (void) +{ + return tud_cdc_n_read_char(0); +} + +static inline uint32_t tud_cdc_read (void* buffer, uint32_t bufsize) +{ + return tud_cdc_n_read(0, buffer, bufsize); +} + +static inline void tud_cdc_read_flush (void) +{ + tud_cdc_n_read_flush(0); +} + +static inline bool tud_cdc_peek (uint8_t* ui8) +{ + return tud_cdc_n_peek(0, ui8); +} + +static inline uint32_t tud_cdc_write_char (char ch) +{ + return tud_cdc_n_write_char(0, ch); +} + +static inline uint32_t tud_cdc_write (void const* buffer, uint32_t bufsize) +{ + return tud_cdc_n_write(0, buffer, bufsize); +} + +static inline uint32_t tud_cdc_write_str (char const* str) +{ + return tud_cdc_n_write_str(0, str); +} + +static inline uint32_t tud_cdc_write_flush (void) +{ + return tud_cdc_n_write_flush(0); +} + +static inline uint32_t tud_cdc_write_available(void) +{ + return tud_cdc_n_write_available(0); +} + +static inline bool tud_cdc_write_clear(void) +{ + return tud_cdc_n_write_clear(0); +} + +/** @} */ +/** @} */ + +//--------------------------------------------------------------------+ +// INTERNAL USBD-CLASS DRIVER API +//--------------------------------------------------------------------+ +void cdcd_init (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); +bool cdcd_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_CDC_DEVICE_H_ */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_host.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_host.c new file mode 100644 index 000000000..f4fb6c1d6 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_host.c @@ -0,0 +1,249 @@ +/* + * 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 (TUSB_OPT_HOST_ENABLED && CFG_TUH_CDC) + +#include "host/usbh.h" +#include "host/usbh_classdriver.h" + +#include "cdc_host.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +typedef struct { + uint8_t itf_num; + uint8_t itf_protocol; + + uint8_t ep_notif; + uint8_t ep_in; + uint8_t ep_out; + + cdc_acm_capability_t acm_capability; + +} cdch_data_t; + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +static cdch_data_t cdch_data[CFG_TUH_DEVICE_MAX]; + +static inline cdch_data_t* get_itf(uint8_t dev_addr) +{ + return &cdch_data[dev_addr-1]; +} + +bool tuh_cdc_mounted(uint8_t dev_addr) +{ + cdch_data_t* cdc = get_itf(dev_addr); + return cdc->ep_in && cdc->ep_out; +} + +bool tuh_cdc_is_busy(uint8_t dev_addr, cdc_pipeid_t pipeid) +{ + if ( !tuh_cdc_mounted(dev_addr) ) return false; + + cdch_data_t const * p_cdc = get_itf(dev_addr); + + switch (pipeid) + { + case CDC_PIPE_NOTIFICATION: + return usbh_edpt_busy(dev_addr, p_cdc->ep_notif ); + + case CDC_PIPE_DATA_IN: + return usbh_edpt_busy(dev_addr, p_cdc->ep_in ); + + case CDC_PIPE_DATA_OUT: + return usbh_edpt_busy(dev_addr, p_cdc->ep_out ); + + default: + return false; + } +} + +//--------------------------------------------------------------------+ +// APPLICATION API (parameter validation needed) +//--------------------------------------------------------------------+ +bool tuh_cdc_serial_is_mounted(uint8_t dev_addr) +{ + // TODO consider all AT Command as serial candidate + return tuh_cdc_mounted(dev_addr) && + (cdch_data[dev_addr-1].itf_protocol <= CDC_COMM_PROTOCOL_ATCOMMAND_CDMA); +} + +bool tuh_cdc_send(uint8_t dev_addr, void const * p_data, uint32_t length, bool is_notify) +{ + (void) is_notify; + TU_VERIFY( tuh_cdc_mounted(dev_addr) ); + TU_VERIFY( p_data != NULL && length, TUSB_ERROR_INVALID_PARA); + + uint8_t const ep_out = cdch_data[dev_addr-1].ep_out; + if ( usbh_edpt_busy(dev_addr, ep_out) ) return false; + + return usbh_edpt_xfer(dev_addr, ep_out, (void*)(uintptr_t) p_data, length); +} + +bool tuh_cdc_receive(uint8_t dev_addr, void * p_buffer, uint32_t length, bool is_notify) +{ + (void) is_notify; + TU_VERIFY( tuh_cdc_mounted(dev_addr) ); + TU_VERIFY( p_buffer != NULL && length, TUSB_ERROR_INVALID_PARA); + + uint8_t const ep_in = cdch_data[dev_addr-1].ep_in; + if ( usbh_edpt_busy(dev_addr, ep_in) ) return false; + + return usbh_edpt_xfer(dev_addr, ep_in, p_buffer, length); +} + +bool tuh_cdc_set_control_line_state(uint8_t dev_addr, bool dtr, bool rts, tuh_control_complete_cb_t complete_cb) +{ + cdch_data_t const * p_cdc = get_itf(dev_addr); + 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 = (rts ? 2 : 0) | (dtr ? 1 : 0), + .wIndex = p_cdc->itf_num, + .wLength = 0 + }; + + TU_ASSERT( tuh_control_xfer(dev_addr, &request, NULL, complete_cb) ); + return true; +} + +//--------------------------------------------------------------------+ +// USBH-CLASS DRIVER API +//--------------------------------------------------------------------+ +void cdch_init(void) +{ + tu_memclr(cdch_data, sizeof(cdch_data)); +} + +bool cdch_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *itf_desc, uint16_t max_len) +{ + (void) max_len; + + // Only support ACM subclass + // Protocol 0xFF can be RNDIS device for windows XP + TU_VERIFY( TUSB_CLASS_CDC == itf_desc->bInterfaceClass && + CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL == itf_desc->bInterfaceSubClass && + 0xFF != itf_desc->bInterfaceProtocol); + + cdch_data_t * p_cdc = get_itf(dev_addr); + + p_cdc->itf_num = itf_desc->bInterfaceNumber; + p_cdc->itf_protocol = itf_desc->bInterfaceProtocol; + + //------------- Communication Interface -------------// + uint16_t drv_len = tu_desc_len(itf_desc); + uint8_t const * p_desc = tu_desc_next(itf_desc); + + // Communication Functional Descriptors + while( TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && drv_len <= max_len ) + { + 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; + } + + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } + + if ( TUSB_DESC_ENDPOINT == tu_desc_type(p_desc) ) + { + // notification endpoint + tusb_desc_endpoint_t const * desc_ep = (tusb_desc_endpoint_t const *) p_desc; + + TU_ASSERT( usbh_edpt_open(rhport, dev_addr, desc_ep) ); + p_cdc->ep_notif = desc_ep->bEndpointAddress; + + drv_len += tu_desc_len(p_desc); + 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 + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + + // data endpoints expected to be in pairs + for(uint32_t i=0; i<2; i++) + { + tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) p_desc; + TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && TUSB_XFER_BULK == desc_ep->bmAttributes.xfer); + + TU_ASSERT(usbh_edpt_open(rhport, dev_addr, desc_ep)); + + if ( tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN ) + { + p_cdc->ep_in = desc_ep->bEndpointAddress; + }else + { + p_cdc->ep_out = desc_ep->bEndpointAddress; + } + + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next( p_desc ); + } + } + + return true; +} + +bool cdch_set_config(uint8_t dev_addr, uint8_t itf_num) +{ + (void) dev_addr; (void) itf_num; + return true; +} + +bool cdch_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes) +{ + (void) ep_addr; + tuh_cdc_xfer_isr( dev_addr, event, 0, xferred_bytes ); + return true; +} + +void cdch_close(uint8_t dev_addr) +{ + TU_VERIFY(dev_addr <= CFG_TUH_DEVICE_MAX, ); + + cdch_data_t * p_cdc = get_itf(dev_addr); + tu_memclr(p_cdc, sizeof(cdch_data_t)); +} + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_host.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_host.h new file mode 100644 index 000000000..0d435138b --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_host.h @@ -0,0 +1,134 @@ +/* + * 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 + +//--------------------------------------------------------------------+ +// CDC APPLICATION PUBLIC API +//--------------------------------------------------------------------+ +/** \ingroup ClassDriver_CDC Communication Device Class (CDC) + * \addtogroup CDC_Serial Serial + * @{ + * \defgroup CDC_Serial_Host Host + * @{ */ + +bool tuh_cdc_set_control_line_state(uint8_t dev_addr, bool dtr, bool rts, tuh_control_complete_cb_t complete_cb); + +static inline bool tuh_cdc_connect(uint8_t dev_addr, tuh_control_complete_cb_t complete_cb) +{ + return tuh_cdc_set_control_line_state(dev_addr, true, true, complete_cb); +} + +static inline bool tuh_cdc_disconnect(uint8_t dev_addr, tuh_control_complete_cb_t complete_cb) +{ + return tuh_cdc_set_control_line_state(dev_addr, false, false, complete_cb); +} + +/** \brief Check if device support CDC Serial interface or not + * \param[in] dev_addr device address + * \retval true if device supports + * \retval false if device does not support or is not mounted + */ +bool tuh_cdc_serial_is_mounted(uint8_t dev_addr); + +/** \brief Check if the interface is currently busy or not + * \param[in] dev_addr device address + * \param[in] pipeid value from \ref cdc_pipeid_t to indicate target pipe. + * \retval true if the interface is busy, meaning the stack is still transferring/waiting data from/to device + * \retval false if the interface is not busy, meaning the stack successfully transferred data from/to device + * \note This function is used to check if previous transfer is complete (success or error), so that the next transfer + * can be scheduled. User needs to make sure the corresponding interface is mounted + * (by \ref tuh_cdc_serial_is_mounted) before calling this function. + */ +bool tuh_cdc_is_busy(uint8_t dev_addr, cdc_pipeid_t pipeid); + +/** \brief Perform USB OUT transfer to device + * \param[in] dev_addr device address + * \param[in] p_data Buffer containing data. Must be accessible by USB controller (see \ref CFG_TUSB_MEM_SECTION) + * \param[in] length Number of bytes to be transferred via USB bus + * \retval TUSB_ERROR_NONE on success + * \retval TUSB_ERROR_INTERFACE_IS_BUSY if the interface is already transferring data with device + * \retval TUSB_ERROR_DEVICE_NOT_READY if device is not yet configured (by SET CONFIGURED request) + * \retval TUSB_ERROR_INVALID_PARA if input parameters are not correct + * \note This function is non-blocking and returns immediately. The result of USB transfer will be reported by the + * interface's callback function. \a p_data must be declared with \ref CFG_TUSB_MEM_SECTION. + */ +bool tuh_cdc_send(uint8_t dev_addr, void const * p_data, uint32_t length, bool is_notify); + +/** \brief Perform USB IN transfer to get data from device + * \param[in] dev_addr device address + * \param[in] p_buffer Buffer containing received data. Must be accessible by USB controller (see \ref CFG_TUSB_MEM_SECTION) + * \param[in] length Number of bytes to be transferred via USB bus + * \retval TUSB_ERROR_NONE on success + * \retval TUSB_ERROR_INTERFACE_IS_BUSY if the interface is already transferring data with device + * \retval TUSB_ERROR_DEVICE_NOT_READY if device is not yet configured (by SET CONFIGURED request) + * \retval TUSB_ERROR_INVALID_PARA if input parameters are not correct + * \note This function is non-blocking and returns immediately. The result of USB transfer will be reported by the + * interface's callback function. \a p_data must be declared with \ref CFG_TUSB_MEM_SECTION. + */ +bool tuh_cdc_receive(uint8_t dev_addr, void * p_buffer, uint32_t length, bool is_notify); + +//--------------------------------------------------------------------+ +// CDC APPLICATION CALLBACKS +//--------------------------------------------------------------------+ + +/** \brief Callback function that is invoked when an transferring event occurred + * \param[in] dev_addr Address of device + * \param[in] event an value from \ref xfer_result_t + * \param[in] pipe_id value from \ref cdc_pipeid_t indicate the pipe + * \param[in] xferred_bytes Number of bytes transferred via USB bus + * \note event can be one of following + * - XFER_RESULT_SUCCESS : previously scheduled transfer completes successfully. + * - XFER_RESULT_FAILED : previously scheduled transfer encountered a transaction error. + * - XFER_RESULT_STALLED : previously scheduled transfer is stalled by device. + * \note + */ +void tuh_cdc_xfer_isr(uint8_t dev_addr, xfer_result_t event, cdc_pipeid_t pipe_id, uint32_t xferred_bytes); + +/// @} // group CDC_Serial_Host +/// @} + +//--------------------------------------------------------------------+ +// 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/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_rndis.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_rndis.h new file mode 100644 index 000000000..e0f129fe3 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_rndis.h @@ -0,0 +1,301 @@ +/* + * 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/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_rndis_host.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_rndis_host.c new file mode 100644 index 000000000..cc4ffd1cf --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_rndis_host.c @@ -0,0 +1,279 @@ +/* + * 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 (TUSB_OPT_HOST_ENABLED && CFG_TUH_CDC && CFG_TUH_CDC_RNDIS) + +//--------------------------------------------------------------------+ +// INCLUDE +//--------------------------------------------------------------------+ +#include "common/tusb_common.h" +#include "cdc_host.h" +#include "cdc_rndis_host.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +#define RNDIS_MSG_PAYLOAD_MAX (1024*4) + +CFG_TUSB_MEM_SECTION static uint8_t msg_notification[CFG_TUH_DEVICE_MAX][8]; +CFG_TUSB_MEM_SECTION TU_ATTR_ALIGNED(4) 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 notificaiton 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/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_rndis_host.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_rndis_host.h new file mode 100644 index 000000000..170cb3b0e --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/cdc/cdc_rndis_host.h @@ -0,0 +1,63 @@ +/* + * 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); +tusb_error_t 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/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/dfu/dfu.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/dfu/dfu.h new file mode 100644 index 000000000..114c827b8 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/dfu/dfu.h @@ -0,0 +1,119 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 XMOS LIMITED + * + * 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_DFU_H_ +#define _TUSB_DFU_H_ + +#include "common/tusb_common.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Common Definitions +//--------------------------------------------------------------------+ + +// DFU Protocol +typedef enum +{ + DFU_PROTOCOL_RT = 0x01, + DFU_PROTOCOL_DFU = 0x02, +} dfu_protocol_type_t; + +// DFU Descriptor Type +typedef enum +{ + DFU_DESC_FUNCTIONAL = 0x21, +} dfu_descriptor_type_t; + +// DFU Requests +typedef enum { + DFU_REQUEST_DETACH = 0, + DFU_REQUEST_DNLOAD = 1, + DFU_REQUEST_UPLOAD = 2, + DFU_REQUEST_GETSTATUS = 3, + DFU_REQUEST_CLRSTATUS = 4, + DFU_REQUEST_GETSTATE = 5, + DFU_REQUEST_ABORT = 6, +} dfu_requests_t; + +// DFU States +typedef enum { + APP_IDLE = 0, + APP_DETACH = 1, + DFU_IDLE = 2, + DFU_DNLOAD_SYNC = 3, + DFU_DNBUSY = 4, + DFU_DNLOAD_IDLE = 5, + DFU_MANIFEST_SYNC = 6, + DFU_MANIFEST = 7, + DFU_MANIFEST_WAIT_RESET = 8, + DFU_UPLOAD_IDLE = 9, + DFU_ERROR = 10, +} dfu_state_t; + +// DFU Status +typedef enum { + DFU_STATUS_OK = 0x00, + DFU_STATUS_ERR_TARGET = 0x01, + DFU_STATUS_ERR_FILE = 0x02, + DFU_STATUS_ERR_WRITE = 0x03, + DFU_STATUS_ERR_ERASE = 0x04, + DFU_STATUS_ERR_CHECK_ERASED = 0x05, + DFU_STATUS_ERR_PROG = 0x06, + DFU_STATUS_ERR_VERIFY = 0x07, + DFU_STATUS_ERR_ADDRESS = 0x08, + DFU_STATUS_ERR_NOTDONE = 0x09, + DFU_STATUS_ERR_FIRMWARE = 0x0A, + DFU_STATUS_ERR_VENDOR = 0x0B, + DFU_STATUS_ERR_USBR = 0x0C, + DFU_STATUS_ERR_POR = 0x0D, + DFU_STATUS_ERR_UNKNOWN = 0x0E, + DFU_STATUS_ERR_STALLEDPKT = 0x0F, +} dfu_status_t; + +#define DFU_ATTR_CAN_DOWNLOAD (1u << 0) +#define DFU_ATTR_CAN_UPLOAD (1u << 1) +#define DFU_ATTR_MANIFESTATION_TOLERANT (1u << 2) +#define DFU_ATTR_WILL_DETACH (1u << 3) + +// DFU Status Request Payload +typedef struct TU_ATTR_PACKED +{ + uint8_t bStatus; + uint8_t bwPollTimeout[3]; + uint8_t bState; + uint8_t iString; +} dfu_status_response_t; + +TU_VERIFY_STATIC( sizeof(dfu_status_response_t) == 6, "size is not correct"); + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_DFU_H_ */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/dfu/dfu_device.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/dfu/dfu_device.c new file mode 100644 index 000000000..ddfa608e4 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/dfu/dfu_device.c @@ -0,0 +1,458 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 XMOS LIMITED + * + * 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 (TUSB_OPT_DEVICE_ENABLED && CFG_TUD_DFU) + +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "dfu_device.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +typedef struct +{ + uint8_t attrs; + uint8_t alt; + + dfu_state_t state; + dfu_status_t status; + + bool flashing_in_progress; + uint16_t block; + uint16_t length; + + CFG_TUSB_MEM_ALIGN uint8_t transfer_buf[CFG_TUD_DFU_XFER_BUFSIZE]; +} dfu_state_ctx_t; + +// Only a single dfu state is allowed +CFG_TUSB_MEM_SECTION static dfu_state_ctx_t _dfu_ctx; + +static void reset_state(void) +{ + _dfu_ctx.state = DFU_IDLE; + _dfu_ctx.status = DFU_STATUS_OK; + _dfu_ctx.flashing_in_progress = false; +} + +static bool reply_getstatus(uint8_t rhport, tusb_control_request_t const * request, dfu_state_t state, dfu_status_t status, uint32_t timeout); +static bool process_download_get_status(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); +static bool process_manifest_get_status(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); + +//--------------------------------------------------------------------+ +// Debug +//--------------------------------------------------------------------+ +#if CFG_TUSB_DEBUG >= 2 + +static tu_lookup_entry_t const _dfu_request_lookup[] = +{ + { .key = DFU_REQUEST_DETACH , .data = "DETACH" }, + { .key = DFU_REQUEST_DNLOAD , .data = "DNLOAD" }, + { .key = DFU_REQUEST_UPLOAD , .data = "UPLOAD" }, + { .key = DFU_REQUEST_GETSTATUS , .data = "GETSTATUS" }, + { .key = DFU_REQUEST_CLRSTATUS , .data = "CLRSTATUS" }, + { .key = DFU_REQUEST_GETSTATE , .data = "GETSTATE" }, + { .key = DFU_REQUEST_ABORT , .data = "ABORT" }, +}; + +static tu_lookup_table_t const _dfu_request_table = +{ + .count = TU_ARRAY_SIZE(_dfu_request_lookup), + .items = _dfu_request_lookup +}; + +static tu_lookup_entry_t const _dfu_state_lookup[] = +{ + { .key = APP_IDLE , .data = "APP_IDLE" }, + { .key = APP_DETACH , .data = "APP_DETACH" }, + { .key = DFU_IDLE , .data = "IDLE" }, + { .key = DFU_DNLOAD_SYNC , .data = "DNLOAD_SYNC" }, + { .key = DFU_DNBUSY , .data = "DNBUSY" }, + { .key = DFU_DNLOAD_IDLE , .data = "DNLOAD_IDLE" }, + { .key = DFU_MANIFEST_SYNC , .data = "MANIFEST_SYNC" }, + { .key = DFU_MANIFEST , .data = "MANIFEST" }, + { .key = DFU_MANIFEST_WAIT_RESET , .data = "MANIFEST_WAIT_RESET" }, + { .key = DFU_UPLOAD_IDLE , .data = "UPLOAD_IDLE" }, + { .key = DFU_ERROR , .data = "ERROR" }, +}; + +static tu_lookup_table_t const _dfu_state_table = +{ + .count = TU_ARRAY_SIZE(_dfu_state_lookup), + .items = _dfu_state_lookup +}; + +static tu_lookup_entry_t const _dfu_status_lookup[] = +{ + { .key = DFU_STATUS_OK , .data = "OK" }, + { .key = DFU_STATUS_ERR_TARGET , .data = "errTARGET" }, + { .key = DFU_STATUS_ERR_FILE , .data = "errFILE" }, + { .key = DFU_STATUS_ERR_WRITE , .data = "errWRITE" }, + { .key = DFU_STATUS_ERR_ERASE , .data = "errERASE" }, + { .key = DFU_STATUS_ERR_CHECK_ERASED , .data = "errCHECK_ERASED" }, + { .key = DFU_STATUS_ERR_PROG , .data = "errPROG" }, + { .key = DFU_STATUS_ERR_VERIFY , .data = "errVERIFY" }, + { .key = DFU_STATUS_ERR_ADDRESS , .data = "errADDRESS" }, + { .key = DFU_STATUS_ERR_NOTDONE , .data = "errNOTDONE" }, + { .key = DFU_STATUS_ERR_FIRMWARE , .data = "errFIRMWARE" }, + { .key = DFU_STATUS_ERR_VENDOR , .data = "errVENDOR" }, + { .key = DFU_STATUS_ERR_USBR , .data = "errUSBR" }, + { .key = DFU_STATUS_ERR_POR , .data = "errPOR" }, + { .key = DFU_STATUS_ERR_UNKNOWN , .data = "errUNKNOWN" }, + { .key = DFU_STATUS_ERR_STALLEDPKT , .data = "errSTALLEDPKT" }, +}; + +static tu_lookup_table_t const _dfu_status_table = +{ + .count = TU_ARRAY_SIZE(_dfu_status_lookup), + .items = _dfu_status_lookup +}; + +#endif + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void dfu_moded_reset(uint8_t rhport) +{ + (void) rhport; + + _dfu_ctx.attrs = 0; + _dfu_ctx.alt = 0; + + reset_state(); +} + +void dfu_moded_init(void) +{ + dfu_moded_reset(0); +} + +uint16_t dfu_moded_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) +{ + (void) rhport; + + //------------- Interface (with Alt) descriptor -------------// + uint8_t const itf_num = itf_desc->bInterfaceNumber; + uint8_t alt_count = 0; + + uint16_t drv_len = 0; + while(itf_desc->bInterfaceSubClass == TUD_DFU_APP_SUBCLASS && itf_desc->bInterfaceProtocol == DFU_PROTOCOL_DFU) + { + TU_ASSERT(max_len > drv_len, 0); + + // Alternate must have the same interface number + TU_ASSERT(itf_desc->bInterfaceNumber == itf_num, 0); + + // Alt should increase by one every time + TU_ASSERT(itf_desc->bAlternateSetting == alt_count, 0); + alt_count++; + + drv_len += tu_desc_len(itf_desc); + itf_desc = (tusb_desc_interface_t const *) tu_desc_next(itf_desc); + } + + //------------- DFU Functional descriptor -------------// + tusb_desc_dfu_functional_t const *func_desc = (tusb_desc_dfu_functional_t const *) itf_desc; + TU_ASSERT(tu_desc_type(func_desc) == TUSB_DESC_FUNCTIONAL, 0); + drv_len += sizeof(tusb_desc_dfu_functional_t); + + _dfu_ctx.attrs = func_desc->bAttributes; + + // CFG_TUD_DFU_XFER_BUFSIZE has to be set to the buffer size used in TUD_DFU_DESCRIPTOR + uint16_t const transfer_size = tu_le16toh( tu_unaligned_read16((uint8_t const*) func_desc + offsetof(tusb_desc_dfu_functional_t, wTransferSize)) ); + TU_ASSERT(transfer_size <= CFG_TUD_DFU_XFER_BUFSIZE, drv_len); + + return drv_len; +} + +// 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 dfu_moded_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); + + TU_LOG2(" DFU State : %s, Status: %s\r\n", tu_lookup_find(&_dfu_state_table, _dfu_ctx.state), tu_lookup_find(&_dfu_status_table, _dfu_ctx.status)); + + if ( request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD ) + { + // Standard request include GET/SET_INTERFACE + switch ( request->bRequest ) + { + case TUSB_REQ_SET_INTERFACE: + if ( stage == CONTROL_STAGE_SETUP ) + { + // Switch Alt interface and reset state machine + _dfu_ctx.alt = (uint8_t) request->wValue; + reset_state(); + return tud_control_status(rhport, request); + } + break; + + case TUSB_REQ_GET_INTERFACE: + if(stage == CONTROL_STAGE_SETUP) + { + return tud_control_xfer(rhport, request, &_dfu_ctx.alt, 1); + } + break; + + // unsupported request + default: return false; + } + } + else if ( request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS ) + { + TU_LOG2(" DFU Request: %s\r\n", tu_lookup_find(&_dfu_request_table, request->bRequest)); + + // Class request + switch ( request->bRequest ) + { + case DFU_REQUEST_DETACH: + if ( stage == CONTROL_STAGE_SETUP ) + { + tud_control_status(rhport, request); + } + else if ( stage == CONTROL_STAGE_ACK ) + { + if ( tud_dfu_detach_cb ) tud_dfu_detach_cb(); + } + break; + + case DFU_REQUEST_CLRSTATUS: + if ( stage == CONTROL_STAGE_SETUP ) + { + reset_state(); + tud_control_status(rhport, request); + } + break; + + case DFU_REQUEST_GETSTATE: + if ( stage == CONTROL_STAGE_SETUP ) + { + tud_control_xfer(rhport, request, &_dfu_ctx.state, 1); + } + break; + + case DFU_REQUEST_ABORT: + if ( stage == CONTROL_STAGE_SETUP ) + { + reset_state(); + tud_control_status(rhport, request); + } + else if ( stage == CONTROL_STAGE_ACK ) + { + if ( tud_dfu_abort_cb ) tud_dfu_abort_cb(_dfu_ctx.alt); + } + break; + + case DFU_REQUEST_UPLOAD: + if ( stage == CONTROL_STAGE_SETUP ) + { + TU_VERIFY(_dfu_ctx.attrs & DFU_ATTR_CAN_UPLOAD); + TU_VERIFY(tud_dfu_upload_cb); + TU_VERIFY(request->wLength <= CFG_TUD_DFU_XFER_BUFSIZE); + + uint16_t const xfer_len = tud_dfu_upload_cb(_dfu_ctx.alt, request->wValue, _dfu_ctx.transfer_buf, request->wLength); + + return tud_control_xfer(rhport, request, _dfu_ctx.transfer_buf, xfer_len); + } + break; + + case DFU_REQUEST_DNLOAD: + if ( stage == CONTROL_STAGE_SETUP ) + { + TU_VERIFY(_dfu_ctx.attrs & DFU_ATTR_CAN_DOWNLOAD); + TU_VERIFY(_dfu_ctx.state == DFU_IDLE || _dfu_ctx.state == DFU_DNLOAD_IDLE); + TU_VERIFY(request->wLength <= CFG_TUD_DFU_XFER_BUFSIZE); + + // set to true for both download and manifest + _dfu_ctx.flashing_in_progress = true; + + // save block and length for flashing + _dfu_ctx.block = request->wValue; + _dfu_ctx.length = request->wLength; + + if ( request->wLength ) + { + // Download with payload -> transition to DOWNLOAD SYNC + _dfu_ctx.state = DFU_DNLOAD_SYNC; + return tud_control_xfer(rhport, request, _dfu_ctx.transfer_buf, request->wLength); + } + else + { + // Download is complete -> transition to MANIFEST SYNC + _dfu_ctx.state = DFU_MANIFEST_SYNC; + return tud_control_status(rhport, request); + } + } + break; + + case DFU_REQUEST_GETSTATUS: + switch ( _dfu_ctx.state ) + { + case DFU_DNLOAD_SYNC: + return process_download_get_status(rhport, stage, request); + break; + + case DFU_MANIFEST_SYNC: + return process_manifest_get_status(rhport, stage, request); + break; + + default: + if ( stage == CONTROL_STAGE_SETUP ) return reply_getstatus(rhport, request, _dfu_ctx.state, _dfu_ctx.status, 0); + break; + } + break; + + default: return false; // stall unsupported request + } + }else + { + return false; // unsupported request + } + + return true; +} + +void tud_dfu_finish_flashing(uint8_t status) +{ + _dfu_ctx.flashing_in_progress = false; + + if ( status == DFU_STATUS_OK ) + { + if (_dfu_ctx.state == DFU_DNBUSY) + { + _dfu_ctx.state = DFU_DNLOAD_SYNC; + } + else if (_dfu_ctx.state == DFU_MANIFEST) + { + _dfu_ctx.state = (_dfu_ctx.attrs & DFU_ATTR_MANIFESTATION_TOLERANT) + ? DFU_MANIFEST_SYNC : DFU_MANIFEST_WAIT_RESET; + } + } + else + { + // failed while flashing, move to dfuError + _dfu_ctx.state = DFU_ERROR; + _dfu_ctx.status = (dfu_status_t)status; + } +} + +static bool process_download_get_status(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) +{ + if ( stage == CONTROL_STAGE_SETUP ) + { + // only transition to next state on CONTROL_STAGE_ACK + dfu_state_t next_state; + uint32_t timeout; + + if ( _dfu_ctx.flashing_in_progress ) + { + next_state = DFU_DNBUSY; + timeout = tud_dfu_get_timeout_cb(_dfu_ctx.alt, (uint8_t) next_state); + } + else + { + next_state = DFU_DNLOAD_IDLE; + timeout = 0; + } + + return reply_getstatus(rhport, request, next_state, _dfu_ctx.status, timeout); + } + else if ( stage == CONTROL_STAGE_ACK ) + { + if ( _dfu_ctx.flashing_in_progress ) + { + _dfu_ctx.state = DFU_DNBUSY; + tud_dfu_download_cb(_dfu_ctx.alt, _dfu_ctx.block, _dfu_ctx.transfer_buf, _dfu_ctx.length); + }else + { + _dfu_ctx.state = DFU_DNLOAD_IDLE; + } + } + + return true; +} + +static bool process_manifest_get_status(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) +{ + if ( stage == CONTROL_STAGE_SETUP ) + { + // only transition to next state on CONTROL_STAGE_ACK + dfu_state_t next_state; + uint32_t timeout; + + if ( _dfu_ctx.flashing_in_progress ) + { + next_state = DFU_MANIFEST; + timeout = tud_dfu_get_timeout_cb(_dfu_ctx.alt, next_state); + } + else + { + next_state = DFU_IDLE; + timeout = 0; + } + + return reply_getstatus(rhport, request, next_state, _dfu_ctx.status, timeout); + } + else if ( stage == CONTROL_STAGE_ACK ) + { + if ( _dfu_ctx.flashing_in_progress ) + { + _dfu_ctx.state = DFU_MANIFEST; + tud_dfu_manifest_cb(_dfu_ctx.alt); + } + else + { + _dfu_ctx.state = DFU_IDLE; + } + } + + return true; +} + +static bool reply_getstatus(uint8_t rhport, tusb_control_request_t const * request, dfu_state_t state, dfu_status_t status, uint32_t timeout) +{ + dfu_status_response_t resp; + resp.bStatus = (uint8_t) status; + resp.bwPollTimeout[0] = TU_U32_BYTE0(timeout); + resp.bwPollTimeout[1] = TU_U32_BYTE1(timeout); + resp.bwPollTimeout[2] = TU_U32_BYTE2(timeout); + resp.bState = (uint8_t) state; + resp.iString = 0; + + return tud_control_xfer(rhport, request, &resp, sizeof(dfu_status_response_t)); +} + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/dfu/dfu_device.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/dfu/dfu_device.h new file mode 100644 index 000000000..fecf8596f --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/dfu/dfu_device.h @@ -0,0 +1,98 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 XMOS LIMITED + * + * 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_DFU_DEVICE_H_ +#define _TUSB_DFU_DEVICE_H_ + +#include "dfu.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Class Driver Default Configure & Validation +//--------------------------------------------------------------------+ + +#if !defined(CFG_TUD_DFU_XFER_BUFSIZE) + #error "CFG_TUD_DFU_XFER_BUFSIZE must be defined, it has to be set to the buffer size used in TUD_DFU_DESCRIPTOR" +#endif + +//--------------------------------------------------------------------+ +// Application API +//--------------------------------------------------------------------+ + +// Must be called when the application is done with flashing started by +// tud_dfu_download_cb() and tud_dfu_manifest_cb(). +// status is DFU_STATUS_OK if successful, any other error status will cause state to enter dfuError +void tud_dfu_finish_flashing(uint8_t status); + +//--------------------------------------------------------------------+ +// Application Callback API (weak is optional) +//--------------------------------------------------------------------+ + +// Note: alt is used as the partition number, in order to support multiple partitions like FLASH, EEPROM, etc. + +// Invoked right before tud_dfu_download_cb() (state=DFU_DNBUSY) or tud_dfu_manifest_cb() (state=DFU_MANIFEST) +// Application return timeout in milliseconds (bwPollTimeout) for the next download/manifest operation. +// During this period, USB host won't try to communicate with us. +uint32_t tud_dfu_get_timeout_cb(uint8_t alt, uint8_t state); + +// Invoked when received DFU_DNLOAD (wLength>0) following by DFU_GETSTATUS (state=DFU_DNBUSY) requests +// This callback could be returned before flashing op is complete (async). +// Once finished flashing, application must call tud_dfu_finish_flashing() +void tud_dfu_download_cb (uint8_t alt, uint16_t block_num, uint8_t const *data, uint16_t length); + +// Invoked when download process is complete, received DFU_DNLOAD (wLength=0) following by DFU_GETSTATUS (state=Manifest) +// Application can do checksum, or actual flashing if buffered entire image previously. +// Once finished flashing, application must call tud_dfu_finish_flashing() +void tud_dfu_manifest_cb(uint8_t alt); + +// Invoked when received DFU_UPLOAD request +// Application must populate data with up to length bytes and +// Return the number of written bytes +TU_ATTR_WEAK uint16_t tud_dfu_upload_cb(uint8_t alt, uint16_t block_num, uint8_t* data, uint16_t length); + +// Invoked when a DFU_DETACH request is received +TU_ATTR_WEAK void tud_dfu_detach_cb(void); + +// Invoked when the Host has terminated a download or upload transfer +TU_ATTR_WEAK void tud_dfu_abort_cb(uint8_t alt); + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void dfu_moded_init(void); +void dfu_moded_reset(uint8_t rhport); +uint16_t dfu_moded_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool dfu_moded_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); + + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_DFU_MODE_DEVICE_H_ */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/dfu/dfu_rt_device.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/dfu/dfu_rt_device.c new file mode 100644 index 000000000..afee2aa1f --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/dfu/dfu_rt_device.c @@ -0,0 +1,128 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Sylvain Munaut + * + * 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 (TUSB_OPT_DEVICE_ENABLED && CFG_TUD_DFU_RUNTIME) + +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "dfu_rt_device.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void dfu_rtd_init(void) +{ +} + +void dfu_rtd_reset(uint8_t rhport) +{ + (void) rhport; +} + +uint16_t dfu_rtd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) +{ + (void) rhport; + (void) max_len; + + // Ensure this is DFU Runtime + TU_VERIFY((itf_desc->bInterfaceSubClass == TUD_DFU_APP_SUBCLASS) && + (itf_desc->bInterfaceProtocol == DFU_PROTOCOL_RT), 0); + + uint8_t const * p_desc = tu_desc_next( itf_desc ); + uint16_t drv_len = sizeof(tusb_desc_interface_t); + + if ( TUSB_DESC_FUNCTIONAL == tu_desc_type(p_desc) ) + { + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } + + return drv_len; +} + +// 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 dfu_rtd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) +{ + // nothing to do with DATA or ACK stage + if ( stage != CONTROL_STAGE_SETUP ) return true; + + TU_VERIFY(request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE); + + // dfu-util will try to claim the interface with SET_INTERFACE request before sending DFU request + if ( TUSB_REQ_TYPE_STANDARD == request->bmRequestType_bit.type && + TUSB_REQ_SET_INTERFACE == request->bRequest ) + { + tud_control_status(rhport, request); + return true; + } + + // Handle class request only from here + TU_VERIFY(request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS); + + switch (request->bRequest) + { + case DFU_REQUEST_DETACH: + { + TU_LOG2(" DFU RT Request: DETACH\r\n"); + tud_control_status(rhport, request); + tud_dfu_runtime_reboot_to_dfu_cb(); + } + break; + + case DFU_REQUEST_GETSTATUS: + { + TU_LOG2(" DFU RT Request: GETSTATUS\r\n"); + dfu_status_response_t resp; + // Status = OK, Poll timeout is ignored during RT, State = APP_IDLE, IString = 0 + memset(&resp, 0x00, sizeof(dfu_status_response_t)); + tud_control_xfer(rhport, request, &resp, sizeof(dfu_status_response_t)); + } + break; + + default: + { + TU_LOG2(" DFU RT Unexpected Request: %d\r\n", request->bRequest); + return false; // stall unsupported request + } + } + + return true; +} + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/dfu/dfu_rt_device.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/dfu/dfu_rt_device.h new file mode 100644 index 000000000..babaa8214 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/dfu/dfu_rt_device.h @@ -0,0 +1,54 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Sylvain Munaut + * + * 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_DFU_RT_DEVICE_H_ +#define _TUSB_DFU_RT_DEVICE_H_ + +#include "dfu.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Application Callback API (weak is optional) +//--------------------------------------------------------------------+ +// Invoked when a DFU_DETACH request is received and bitWillDetach is set +void tud_dfu_runtime_reboot_to_dfu_cb(void); + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void dfu_rtd_init(void); +void dfu_rtd_reset(uint8_t rhport); +uint16_t dfu_rtd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool dfu_rtd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_DFU_RT_DEVICE_H_ */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/hid/hid.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/hid/hid.h new file mode 100644 index 000000000..20a5dd7f9 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/hid/hid.h @@ -0,0 +1,1119 @@ +/* + * 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 group_class + * \defgroup ClassDriver_HID Human Interface Device (HID) + * @{ */ + +#ifndef _TUSB_HID_H_ +#define _TUSB_HID_H_ + +#include "common/tusb_common.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Common Definitions +//--------------------------------------------------------------------+ +/** \defgroup ClassDriver_HID_Common Common Definitions + * @{ */ + + /// USB HID Descriptor +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength; /**< Numeric expression that is the total size of the HID descriptor */ + uint8_t bDescriptorType; /**< Constant name specifying type of HID descriptor. */ + + uint16_t bcdHID; /**< Numeric expression identifying the HID Class Specification release */ + uint8_t bCountryCode; /**< Numeric expression identifying country code of the localized hardware. */ + uint8_t bNumDescriptors; /**< Numeric expression specifying the number of class descriptors */ + + uint8_t bReportType; /**< Type of HID class report. */ + uint16_t wReportLength; /**< the total size of the Report descriptor. */ +} tusb_hid_descriptor_hid_t; + +/// HID Subclass +typedef enum +{ + HID_SUBCLASS_NONE = 0, ///< No Subclass + HID_SUBCLASS_BOOT = 1 ///< Boot Interface Subclass +}hid_subclass_enum_t; + +/// HID Interface Protocol +typedef enum +{ + HID_ITF_PROTOCOL_NONE = 0, ///< None + HID_ITF_PROTOCOL_KEYBOARD = 1, ///< Keyboard + HID_ITF_PROTOCOL_MOUSE = 2 ///< Mouse +}hid_interface_protocol_enum_t; + +/// HID Descriptor Type +typedef enum +{ + HID_DESC_TYPE_HID = 0x21, ///< HID Descriptor + HID_DESC_TYPE_REPORT = 0x22, ///< Report Descriptor + HID_DESC_TYPE_PHYSICAL = 0x23 ///< Physical Descriptor +}hid_descriptor_enum_t; + +/// HID Request Report Type +typedef enum +{ + HID_REPORT_TYPE_INVALID = 0, + HID_REPORT_TYPE_INPUT, ///< Input + HID_REPORT_TYPE_OUTPUT, ///< Output + HID_REPORT_TYPE_FEATURE ///< Feature +}hid_report_type_t; + +/// HID Class Specific Control Request +typedef enum +{ + HID_REQ_CONTROL_GET_REPORT = 0x01, ///< Get Report + HID_REQ_CONTROL_GET_IDLE = 0x02, ///< Get Idle + HID_REQ_CONTROL_GET_PROTOCOL = 0x03, ///< Get Protocol + HID_REQ_CONTROL_SET_REPORT = 0x09, ///< Set Report + HID_REQ_CONTROL_SET_IDLE = 0x0a, ///< Set Idle + HID_REQ_CONTROL_SET_PROTOCOL = 0x0b ///< Set Protocol +}hid_request_enum_t; + +/// HID Local Code +typedef enum +{ + HID_LOCAL_NotSupported = 0 , ///< NotSupported + HID_LOCAL_Arabic , ///< Arabic + HID_LOCAL_Belgian , ///< Belgian + HID_LOCAL_Canadian_Bilingual , ///< Canadian_Bilingual + HID_LOCAL_Canadian_French , ///< Canadian_French + HID_LOCAL_Czech_Republic , ///< Czech_Republic + HID_LOCAL_Danish , ///< Danish + HID_LOCAL_Finnish , ///< Finnish + HID_LOCAL_French , ///< French + HID_LOCAL_German , ///< German + HID_LOCAL_Greek , ///< Greek + HID_LOCAL_Hebrew , ///< Hebrew + HID_LOCAL_Hungary , ///< Hungary + HID_LOCAL_International , ///< International + HID_LOCAL_Italian , ///< Italian + HID_LOCAL_Japan_Katakana , ///< Japan_Katakana + HID_LOCAL_Korean , ///< Korean + HID_LOCAL_Latin_American , ///< Latin_American + HID_LOCAL_Netherlands_Dutch , ///< Netherlands/Dutch + HID_LOCAL_Norwegian , ///< Norwegian + HID_LOCAL_Persian_Farsi , ///< Persian (Farsi) + HID_LOCAL_Poland , ///< Poland + HID_LOCAL_Portuguese , ///< Portuguese + HID_LOCAL_Russia , ///< Russia + HID_LOCAL_Slovakia , ///< Slovakia + HID_LOCAL_Spanish , ///< Spanish + HID_LOCAL_Swedish , ///< Swedish + HID_LOCAL_Swiss_French , ///< Swiss/French + HID_LOCAL_Swiss_German , ///< Swiss/German + HID_LOCAL_Switzerland , ///< Switzerland + HID_LOCAL_Taiwan , ///< Taiwan + HID_LOCAL_Turkish_Q , ///< Turkish-Q + HID_LOCAL_UK , ///< UK + HID_LOCAL_US , ///< US + HID_LOCAL_Yugoslavia , ///< Yugoslavia + HID_LOCAL_Turkish_F ///< Turkish-F +} hid_local_enum_t; + +// HID protocol value used by GetProtocol / SetProtocol +typedef enum +{ + HID_PROTOCOL_BOOT = 0, + HID_PROTOCOL_REPORT = 1 +} hid_protocol_mode_enum_t; + +/** @} */ + +//--------------------------------------------------------------------+ +// GAMEPAD +//--------------------------------------------------------------------+ +/** \addtogroup ClassDriver_HID_Gamepad Gamepad + * @{ */ + +/* From https://www.kernel.org/doc/html/latest/input/gamepad.html + ____________________________ __ + / [__ZL__] [__ZR__] \ | + / [__ TL __] [__ TR __] \ | Front Triggers + __/________________________________\__ __| + / _ \ | + / /\ __ (N) \ | + / || __ |MO| __ _ _ \ | Main Pad + | <===DP===> |SE| |ST| (W) -|- (E) | | + \ || ___ ___ _ / | + /\ \/ / \ / \ (S) /\ __| + / \________ | LS | ____ | RS | ________/ \ | +| / \ \___/ / \ \___/ / \ | | Control Sticks +| / \_____/ \_____/ \ | __| +| / \ | + \_____/ \_____/ + + |________|______| |______|___________| + D-Pad Left Right Action Pad + Stick Stick + + |_____________| + Menu Pad + + Most gamepads have the following features: + - Action-Pad 4 buttons in diamonds-shape (on the right side) NORTH, SOUTH, WEST and EAST. + - D-Pad (Direction-pad) 4 buttons (on the left side) that point up, down, left and right. + - Menu-Pad Different constellations, but most-times 2 buttons: SELECT - START. + - Analog-Sticks provide freely moveable sticks to control directions, Analog-sticks may also + provide a digital button if you press them. + - Triggers are located on the upper-side of the pad in vertical direction. The upper buttons + are normally named Left- and Right-Triggers, the lower buttons Z-Left and Z-Right. + - Rumble Many devices provide force-feedback features. But are mostly just simple rumble motors. + */ + +/// HID Gamepad Protocol Report. +typedef struct TU_ATTR_PACKED +{ + int8_t x; ///< Delta x movement of left analog-stick + int8_t y; ///< Delta y movement of left analog-stick + int8_t z; ///< Delta z movement of right analog-joystick + int8_t rz; ///< Delta Rz movement of right analog-joystick + int8_t rx; ///< Delta Rx movement of analog left trigger + int8_t ry; ///< Delta Ry movement of analog right trigger + uint8_t hat; ///< Buttons mask for currently pressed buttons in the DPad/hat + uint32_t buttons; ///< Buttons mask for currently pressed buttons +}hid_gamepad_report_t; + +/// Standard Gamepad Buttons Bitmap +typedef enum +{ + GAMEPAD_BUTTON_0 = TU_BIT(0), + GAMEPAD_BUTTON_1 = TU_BIT(1), + GAMEPAD_BUTTON_2 = TU_BIT(2), + GAMEPAD_BUTTON_3 = TU_BIT(3), + GAMEPAD_BUTTON_4 = TU_BIT(4), + GAMEPAD_BUTTON_5 = TU_BIT(5), + GAMEPAD_BUTTON_6 = TU_BIT(6), + GAMEPAD_BUTTON_7 = TU_BIT(7), + GAMEPAD_BUTTON_8 = TU_BIT(8), + GAMEPAD_BUTTON_9 = TU_BIT(9), + GAMEPAD_BUTTON_10 = TU_BIT(10), + GAMEPAD_BUTTON_11 = TU_BIT(11), + GAMEPAD_BUTTON_12 = TU_BIT(12), + GAMEPAD_BUTTON_13 = TU_BIT(13), + GAMEPAD_BUTTON_14 = TU_BIT(14), + GAMEPAD_BUTTON_15 = TU_BIT(15), + GAMEPAD_BUTTON_16 = TU_BIT(16), + GAMEPAD_BUTTON_17 = TU_BIT(17), + GAMEPAD_BUTTON_18 = TU_BIT(18), + GAMEPAD_BUTTON_19 = TU_BIT(19), + GAMEPAD_BUTTON_20 = TU_BIT(20), + GAMEPAD_BUTTON_21 = TU_BIT(21), + GAMEPAD_BUTTON_22 = TU_BIT(22), + GAMEPAD_BUTTON_23 = TU_BIT(23), + GAMEPAD_BUTTON_24 = TU_BIT(24), + GAMEPAD_BUTTON_25 = TU_BIT(25), + GAMEPAD_BUTTON_26 = TU_BIT(26), + GAMEPAD_BUTTON_27 = TU_BIT(27), + GAMEPAD_BUTTON_28 = TU_BIT(28), + GAMEPAD_BUTTON_29 = TU_BIT(29), + GAMEPAD_BUTTON_30 = TU_BIT(30), + GAMEPAD_BUTTON_31 = TU_BIT(31), +}hid_gamepad_button_bm_t; + +/// Standard Gamepad Buttons Naming from Linux input event codes +/// https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h +#define GAMEPAD_BUTTON_A GAMEPAD_BUTTON_0 +#define GAMEPAD_BUTTON_SOUTH GAMEPAD_BUTTON_0 + +#define GAMEPAD_BUTTON_B GAMEPAD_BUTTON_1 +#define GAMEPAD_BUTTON_EAST GAMEPAD_BUTTON_1 + +#define GAMEPAD_BUTTON_C GAMEPAD_BUTTON_2 + +#define GAMEPAD_BUTTON_X GAMEPAD_BUTTON_3 +#define GAMEPAD_BUTTON_NORTH GAMEPAD_BUTTON_3 + +#define GAMEPAD_BUTTON_Y GAMEPAD_BUTTON_4 +#define GAMEPAD_BUTTON_WEST GAMEPAD_BUTTON_4 + +#define GAMEPAD_BUTTON_Z GAMEPAD_BUTTON_5 +#define GAMEPAD_BUTTON_TL GAMEPAD_BUTTON_6 +#define GAMEPAD_BUTTON_TR GAMEPAD_BUTTON_7 +#define GAMEPAD_BUTTON_TL2 GAMEPAD_BUTTON_8 +#define GAMEPAD_BUTTON_TR2 GAMEPAD_BUTTON_9 +#define GAMEPAD_BUTTON_SELECT GAMEPAD_BUTTON_10 +#define GAMEPAD_BUTTON_START GAMEPAD_BUTTON_11 +#define GAMEPAD_BUTTON_MODE GAMEPAD_BUTTON_12 +#define GAMEPAD_BUTTON_THUMBL GAMEPAD_BUTTON_13 +#define GAMEPAD_BUTTON_THUMBR GAMEPAD_BUTTON_14 + +/// Standard Gamepad HAT/DPAD Buttons (from Linux input event codes) +typedef enum +{ + GAMEPAD_HAT_CENTERED = 0, ///< DPAD_CENTERED + GAMEPAD_HAT_UP = 1, ///< DPAD_UP + GAMEPAD_HAT_UP_RIGHT = 2, ///< DPAD_UP_RIGHT + GAMEPAD_HAT_RIGHT = 3, ///< DPAD_RIGHT + GAMEPAD_HAT_DOWN_RIGHT = 4, ///< DPAD_DOWN_RIGHT + GAMEPAD_HAT_DOWN = 5, ///< DPAD_DOWN + GAMEPAD_HAT_DOWN_LEFT = 6, ///< DPAD_DOWN_LEFT + GAMEPAD_HAT_LEFT = 7, ///< DPAD_LEFT + GAMEPAD_HAT_UP_LEFT = 8, ///< DPAD_UP_LEFT +}hid_gamepad_hat_t; + +/// @} + +//--------------------------------------------------------------------+ +// MOUSE +//--------------------------------------------------------------------+ +/** \addtogroup ClassDriver_HID_Mouse Mouse + * @{ */ + +/// Standard HID Boot Protocol Mouse Report. +typedef struct TU_ATTR_PACKED +{ + uint8_t buttons; /**< buttons mask for currently pressed buttons in the mouse. */ + int8_t x; /**< Current delta x movement of the mouse. */ + int8_t y; /**< Current delta y movement on the mouse. */ + int8_t wheel; /**< Current delta wheel movement on the mouse. */ + int8_t pan; // using AC Pan +} hid_mouse_report_t; + +/// Standard Mouse Buttons Bitmap +typedef enum +{ + MOUSE_BUTTON_LEFT = TU_BIT(0), ///< Left button + MOUSE_BUTTON_RIGHT = TU_BIT(1), ///< Right button + MOUSE_BUTTON_MIDDLE = TU_BIT(2), ///< Middle button + MOUSE_BUTTON_BACKWARD = TU_BIT(3), ///< Backward button, + MOUSE_BUTTON_FORWARD = TU_BIT(4), ///< Forward button, +}hid_mouse_button_bm_t; + +/// @} + +//--------------------------------------------------------------------+ +// Keyboard +//--------------------------------------------------------------------+ +/** \addtogroup ClassDriver_HID_Keyboard Keyboard + * @{ */ + +/// Standard HID Boot Protocol Keyboard Report. +typedef struct TU_ATTR_PACKED +{ + uint8_t modifier; /**< Keyboard modifier (KEYBOARD_MODIFIER_* masks). */ + uint8_t reserved; /**< Reserved for OEM use, always set to 0. */ + uint8_t keycode[6]; /**< Key codes of the currently pressed keys. */ +} hid_keyboard_report_t; + +/// Keyboard modifier codes bitmap +typedef enum +{ + KEYBOARD_MODIFIER_LEFTCTRL = TU_BIT(0), ///< Left Control + KEYBOARD_MODIFIER_LEFTSHIFT = TU_BIT(1), ///< Left Shift + KEYBOARD_MODIFIER_LEFTALT = TU_BIT(2), ///< Left Alt + KEYBOARD_MODIFIER_LEFTGUI = TU_BIT(3), ///< Left Window + KEYBOARD_MODIFIER_RIGHTCTRL = TU_BIT(4), ///< Right Control + KEYBOARD_MODIFIER_RIGHTSHIFT = TU_BIT(5), ///< Right Shift + KEYBOARD_MODIFIER_RIGHTALT = TU_BIT(6), ///< Right Alt + KEYBOARD_MODIFIER_RIGHTGUI = TU_BIT(7) ///< Right Window +}hid_keyboard_modifier_bm_t; + +typedef enum +{ + KEYBOARD_LED_NUMLOCK = TU_BIT(0), ///< Num Lock LED + KEYBOARD_LED_CAPSLOCK = TU_BIT(1), ///< Caps Lock LED + KEYBOARD_LED_SCROLLLOCK = TU_BIT(2), ///< Scroll Lock LED + KEYBOARD_LED_COMPOSE = TU_BIT(3), ///< Composition Mode + KEYBOARD_LED_KANA = TU_BIT(4) ///< Kana mode +}hid_keyboard_led_bm_t; + +/// @} + +//--------------------------------------------------------------------+ +// 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 + + +//--------------------------------------------------------------------+ +// REPORT DESCRIPTOR +//--------------------------------------------------------------------+ + +//------------- ITEM & TAG -------------// +#define HID_REPORT_DATA_0(data) +#define HID_REPORT_DATA_1(data) , data +#define HID_REPORT_DATA_2(data) , U16_TO_U8S_LE(data) +#define HID_REPORT_DATA_3(data) , U32_TO_U8S_LE(data) + +#define HID_REPORT_ITEM(data, tag, type, size) \ + (((tag) << 4) | ((type) << 2) | (size)) HID_REPORT_DATA_##size(data) + +// Report Item Types +enum { + RI_TYPE_MAIN = 0, + RI_TYPE_GLOBAL = 1, + RI_TYPE_LOCAL = 2 +}; + +//------------- Main Items - HID 1.11 section 6.2.2.4 -------------// + +// Report Item Main group +enum { + RI_MAIN_INPUT = 8, + RI_MAIN_OUTPUT = 9, + RI_MAIN_COLLECTION = 10, + RI_MAIN_FEATURE = 11, + RI_MAIN_COLLECTION_END = 12 +}; + +#define HID_INPUT(x) HID_REPORT_ITEM(x, RI_MAIN_INPUT , RI_TYPE_MAIN, 1) +#define HID_OUTPUT(x) HID_REPORT_ITEM(x, RI_MAIN_OUTPUT , RI_TYPE_MAIN, 1) +#define HID_COLLECTION(x) HID_REPORT_ITEM(x, RI_MAIN_COLLECTION , RI_TYPE_MAIN, 1) +#define HID_FEATURE(x) HID_REPORT_ITEM(x, RI_MAIN_FEATURE , RI_TYPE_MAIN, 1) +#define HID_COLLECTION_END HID_REPORT_ITEM(x, RI_MAIN_COLLECTION_END, RI_TYPE_MAIN, 0) + +//------------- Input, Output, Feature - HID 1.11 section 6.2.2.5 -------------// +#define HID_DATA (0<<0) +#define HID_CONSTANT (1<<0) + +#define HID_ARRAY (0<<1) +#define HID_VARIABLE (1<<1) + +#define HID_ABSOLUTE (0<<2) +#define HID_RELATIVE (1<<2) + +#define HID_WRAP_NO (0<<3) +#define HID_WRAP (1<<3) + +#define HID_LINEAR (0<<4) +#define HID_NONLINEAR (1<<4) + +#define HID_PREFERRED_STATE (0<<5) +#define HID_PREFERRED_NO (1<<5) + +#define HID_NO_NULL_POSITION (0<<6) +#define HID_NULL_STATE (1<<6) + +#define HID_NON_VOLATILE (0<<7) +#define HID_VOLATILE (1<<7) + +#define HID_BITFIELD (0<<8) +#define HID_BUFFERED_BYTES (1<<8) + +//------------- Collection Item - HID 1.11 section 6.2.2.6 -------------// +enum { + HID_COLLECTION_PHYSICAL = 0, + HID_COLLECTION_APPLICATION, + HID_COLLECTION_LOGICAL, + HID_COLLECTION_REPORT, + HID_COLLECTION_NAMED_ARRAY, + HID_COLLECTION_USAGE_SWITCH, + HID_COLLECTION_USAGE_MODIFIER +}; + +//------------- Global Items - HID 1.11 section 6.2.2.7 -------------// + +// Report Item Global group +enum { + RI_GLOBAL_USAGE_PAGE = 0, + RI_GLOBAL_LOGICAL_MIN = 1, + RI_GLOBAL_LOGICAL_MAX = 2, + RI_GLOBAL_PHYSICAL_MIN = 3, + RI_GLOBAL_PHYSICAL_MAX = 4, + RI_GLOBAL_UNIT_EXPONENT = 5, + RI_GLOBAL_UNIT = 6, + RI_GLOBAL_REPORT_SIZE = 7, + RI_GLOBAL_REPORT_ID = 8, + RI_GLOBAL_REPORT_COUNT = 9, + RI_GLOBAL_PUSH = 10, + RI_GLOBAL_POP = 11 +}; + +#define HID_USAGE_PAGE(x) HID_REPORT_ITEM(x, RI_GLOBAL_USAGE_PAGE, RI_TYPE_GLOBAL, 1) +#define HID_USAGE_PAGE_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_USAGE_PAGE, RI_TYPE_GLOBAL, n) + +#define HID_LOGICAL_MIN(x) HID_REPORT_ITEM(x, RI_GLOBAL_LOGICAL_MIN, RI_TYPE_GLOBAL, 1) +#define HID_LOGICAL_MIN_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_LOGICAL_MIN, RI_TYPE_GLOBAL, n) + +#define HID_LOGICAL_MAX(x) HID_REPORT_ITEM(x, RI_GLOBAL_LOGICAL_MAX, RI_TYPE_GLOBAL, 1) +#define HID_LOGICAL_MAX_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_LOGICAL_MAX, RI_TYPE_GLOBAL, n) + +#define HID_PHYSICAL_MIN(x) HID_REPORT_ITEM(x, RI_GLOBAL_PHYSICAL_MIN, RI_TYPE_GLOBAL, 1) +#define HID_PHYSICAL_MIN_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_PHYSICAL_MIN, RI_TYPE_GLOBAL, n) + +#define HID_PHYSICAL_MAX(x) HID_REPORT_ITEM(x, RI_GLOBAL_PHYSICAL_MAX, RI_TYPE_GLOBAL, 1) +#define HID_PHYSICAL_MAX_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_PHYSICAL_MAX, RI_TYPE_GLOBAL, n) + +#define HID_UNIT_EXPONENT(x) HID_REPORT_ITEM(x, RI_GLOBAL_UNIT_EXPONENT, RI_TYPE_GLOBAL, 1) +#define HID_UNIT_EXPONENT_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_UNIT_EXPONENT, RI_TYPE_GLOBAL, n) + +#define HID_UNIT(x) HID_REPORT_ITEM(x, RI_GLOBAL_UNIT, RI_TYPE_GLOBAL, 1) +#define HID_UNIT_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_UNIT, RI_TYPE_GLOBAL, n) + +#define HID_REPORT_SIZE(x) HID_REPORT_ITEM(x, RI_GLOBAL_REPORT_SIZE, RI_TYPE_GLOBAL, 1) +#define HID_REPORT_SIZE_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_REPORT_SIZE, RI_TYPE_GLOBAL, n) + +#define HID_REPORT_ID(x) HID_REPORT_ITEM(x, RI_GLOBAL_REPORT_ID, RI_TYPE_GLOBAL, 1), +#define HID_REPORT_ID_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_REPORT_ID, RI_TYPE_GLOBAL, n), + +#define HID_REPORT_COUNT(x) HID_REPORT_ITEM(x, RI_GLOBAL_REPORT_COUNT, RI_TYPE_GLOBAL, 1) +#define HID_REPORT_COUNT_N(x, n) HID_REPORT_ITEM(x, RI_GLOBAL_REPORT_COUNT, RI_TYPE_GLOBAL, n) + +#define HID_PUSH HID_REPORT_ITEM(x, RI_GLOBAL_PUSH, RI_TYPE_GLOBAL, 0) +#define HID_POP HID_REPORT_ITEM(x, RI_GLOBAL_POP, RI_TYPE_GLOBAL, 0) + +//------------- LOCAL ITEMS 6.2.2.8 -------------// + +enum { + RI_LOCAL_USAGE = 0, + RI_LOCAL_USAGE_MIN = 1, + RI_LOCAL_USAGE_MAX = 2, + RI_LOCAL_DESIGNATOR_INDEX = 3, + RI_LOCAL_DESIGNATOR_MIN = 4, + RI_LOCAL_DESIGNATOR_MAX = 5, + // 6 is reserved + RI_LOCAL_STRING_INDEX = 7, + RI_LOCAL_STRING_MIN = 8, + RI_LOCAL_STRING_MAX = 9, + RI_LOCAL_DELIMITER = 10, +}; + +#define HID_USAGE(x) HID_REPORT_ITEM(x, RI_LOCAL_USAGE, RI_TYPE_LOCAL, 1) +#define HID_USAGE_N(x, n) HID_REPORT_ITEM(x, RI_LOCAL_USAGE, RI_TYPE_LOCAL, n) + +#define HID_USAGE_MIN(x) HID_REPORT_ITEM(x, RI_LOCAL_USAGE_MIN, RI_TYPE_LOCAL, 1) +#define HID_USAGE_MIN_N(x, n) HID_REPORT_ITEM(x, RI_LOCAL_USAGE_MIN, RI_TYPE_LOCAL, n) + +#define HID_USAGE_MAX(x) HID_REPORT_ITEM(x, RI_LOCAL_USAGE_MAX, RI_TYPE_LOCAL, 1) +#define HID_USAGE_MAX_N(x, n) HID_REPORT_ITEM(x, RI_LOCAL_USAGE_MAX, RI_TYPE_LOCAL, n) + +//--------------------------------------------------------------------+ +// Usage Table +//--------------------------------------------------------------------+ + +/// 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_VENDOR = 0xFF00 // 0xFF00 - 0xFFFF +}; + +/// HID Usage Table - Table 6: Generic Desktop Page +enum { + HID_USAGE_DESKTOP_POINTER = 0x01, + HID_USAGE_DESKTOP_MOUSE = 0x02, + HID_USAGE_DESKTOP_JOYSTICK = 0x04, + HID_USAGE_DESKTOP_GAMEPAD = 0x05, + HID_USAGE_DESKTOP_KEYBOARD = 0x06, + HID_USAGE_DESKTOP_KEYPAD = 0x07, + HID_USAGE_DESKTOP_MULTI_AXIS_CONTROLLER = 0x08, + HID_USAGE_DESKTOP_TABLET_PC_SYSTEM = 0x09, + HID_USAGE_DESKTOP_X = 0x30, + HID_USAGE_DESKTOP_Y = 0x31, + HID_USAGE_DESKTOP_Z = 0x32, + HID_USAGE_DESKTOP_RX = 0x33, + HID_USAGE_DESKTOP_RY = 0x34, + HID_USAGE_DESKTOP_RZ = 0x35, + HID_USAGE_DESKTOP_SLIDER = 0x36, + HID_USAGE_DESKTOP_DIAL = 0x37, + HID_USAGE_DESKTOP_WHEEL = 0x38, + HID_USAGE_DESKTOP_HAT_SWITCH = 0x39, + HID_USAGE_DESKTOP_COUNTED_BUFFER = 0x3a, + HID_USAGE_DESKTOP_BYTE_COUNT = 0x3b, + HID_USAGE_DESKTOP_MOTION_WAKEUP = 0x3c, + HID_USAGE_DESKTOP_START = 0x3d, + HID_USAGE_DESKTOP_SELECT = 0x3e, + HID_USAGE_DESKTOP_VX = 0x40, + HID_USAGE_DESKTOP_VY = 0x41, + HID_USAGE_DESKTOP_VZ = 0x42, + HID_USAGE_DESKTOP_VBRX = 0x43, + HID_USAGE_DESKTOP_VBRY = 0x44, + HID_USAGE_DESKTOP_VBRZ = 0x45, + HID_USAGE_DESKTOP_VNO = 0x46, + HID_USAGE_DESKTOP_FEATURE_NOTIFICATION = 0x47, + HID_USAGE_DESKTOP_RESOLUTION_MULTIPLIER = 0x48, + HID_USAGE_DESKTOP_SYSTEM_CONTROL = 0x80, + HID_USAGE_DESKTOP_SYSTEM_POWER_DOWN = 0x81, + HID_USAGE_DESKTOP_SYSTEM_SLEEP = 0x82, + HID_USAGE_DESKTOP_SYSTEM_WAKE_UP = 0x83, + HID_USAGE_DESKTOP_SYSTEM_CONTEXT_MENU = 0x84, + HID_USAGE_DESKTOP_SYSTEM_MAIN_MENU = 0x85, + HID_USAGE_DESKTOP_SYSTEM_APP_MENU = 0x86, + HID_USAGE_DESKTOP_SYSTEM_MENU_HELP = 0x87, + HID_USAGE_DESKTOP_SYSTEM_MENU_EXIT = 0x88, + HID_USAGE_DESKTOP_SYSTEM_MENU_SELECT = 0x89, + HID_USAGE_DESKTOP_SYSTEM_MENU_RIGHT = 0x8A, + HID_USAGE_DESKTOP_SYSTEM_MENU_LEFT = 0x8B, + HID_USAGE_DESKTOP_SYSTEM_MENU_UP = 0x8C, + HID_USAGE_DESKTOP_SYSTEM_MENU_DOWN = 0x8D, + HID_USAGE_DESKTOP_SYSTEM_COLD_RESTART = 0x8E, + HID_USAGE_DESKTOP_SYSTEM_WARM_RESTART = 0x8F, + HID_USAGE_DESKTOP_DPAD_UP = 0x90, + HID_USAGE_DESKTOP_DPAD_DOWN = 0x91, + HID_USAGE_DESKTOP_DPAD_RIGHT = 0x92, + HID_USAGE_DESKTOP_DPAD_LEFT = 0x93, + HID_USAGE_DESKTOP_SYSTEM_DOCK = 0xA0, + HID_USAGE_DESKTOP_SYSTEM_UNDOCK = 0xA1, + HID_USAGE_DESKTOP_SYSTEM_SETUP = 0xA2, + HID_USAGE_DESKTOP_SYSTEM_BREAK = 0xA3, + HID_USAGE_DESKTOP_SYSTEM_DEBUGGER_BREAK = 0xA4, + HID_USAGE_DESKTOP_APPLICATION_BREAK = 0xA5, + HID_USAGE_DESKTOP_APPLICATION_DEBUGGER_BREAK = 0xA6, + HID_USAGE_DESKTOP_SYSTEM_SPEAKER_MUTE = 0xA7, + HID_USAGE_DESKTOP_SYSTEM_HIBERNATE = 0xA8, + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_INVERT = 0xB0, + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_INTERNAL = 0xB1, + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_EXTERNAL = 0xB2, + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_BOTH = 0xB3, + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_DUAL = 0xB4, + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_TOGGLE_INT_EXT = 0xB5, + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_SWAP_PRIMARY_SECONDARY = 0xB6, + HID_USAGE_DESKTOP_SYSTEM_DISPLAY_LCD_AUTOSCALE = 0xB7 +}; + + +/// HID Usage Table: Consumer Page (0x0C) +/// Only contains controls that supported by Windows (whole list is too long) +enum +{ + // Generic Control + HID_USAGE_CONSUMER_CONTROL = 0x0001, + + // Power Control + HID_USAGE_CONSUMER_POWER = 0x0030, + HID_USAGE_CONSUMER_RESET = 0x0031, + HID_USAGE_CONSUMER_SLEEP = 0x0032, + + // Screen Brightness + HID_USAGE_CONSUMER_BRIGHTNESS_INCREMENT = 0x006F, + HID_USAGE_CONSUMER_BRIGHTNESS_DECREMENT = 0x0070, + + // These HID usages operate only on mobile systems (battery powered) and + // require Windows 8 (build 8302 or greater). + HID_USAGE_CONSUMER_WIRELESS_RADIO_CONTROLS = 0x000C, + HID_USAGE_CONSUMER_WIRELESS_RADIO_BUTTONS = 0x00C6, + HID_USAGE_CONSUMER_WIRELESS_RADIO_LED = 0x00C7, + HID_USAGE_CONSUMER_WIRELESS_RADIO_SLIDER_SWITCH = 0x00C8, + + // Media Control + HID_USAGE_CONSUMER_PLAY_PAUSE = 0x00CD, + HID_USAGE_CONSUMER_SCAN_NEXT = 0x00B5, + HID_USAGE_CONSUMER_SCAN_PREVIOUS = 0x00B6, + HID_USAGE_CONSUMER_STOP = 0x00B7, + HID_USAGE_CONSUMER_VOLUME = 0x00E0, + HID_USAGE_CONSUMER_MUTE = 0x00E2, + HID_USAGE_CONSUMER_BASS = 0x00E3, + HID_USAGE_CONSUMER_TREBLE = 0x00E4, + HID_USAGE_CONSUMER_BASS_BOOST = 0x00E5, + HID_USAGE_CONSUMER_VOLUME_INCREMENT = 0x00E9, + HID_USAGE_CONSUMER_VOLUME_DECREMENT = 0x00EA, + HID_USAGE_CONSUMER_BASS_INCREMENT = 0x0152, + HID_USAGE_CONSUMER_BASS_DECREMENT = 0x0153, + HID_USAGE_CONSUMER_TREBLE_INCREMENT = 0x0154, + HID_USAGE_CONSUMER_TREBLE_DECREMENT = 0x0155, + + // Application Launcher + HID_USAGE_CONSUMER_AL_CONSUMER_CONTROL_CONFIGURATION = 0x0183, + HID_USAGE_CONSUMER_AL_EMAIL_READER = 0x018A, + HID_USAGE_CONSUMER_AL_CALCULATOR = 0x0192, + HID_USAGE_CONSUMER_AL_LOCAL_BROWSER = 0x0194, + + // Browser/Explorer Specific + HID_USAGE_CONSUMER_AC_SEARCH = 0x0221, + HID_USAGE_CONSUMER_AC_HOME = 0x0223, + HID_USAGE_CONSUMER_AC_BACK = 0x0224, + HID_USAGE_CONSUMER_AC_FORWARD = 0x0225, + HID_USAGE_CONSUMER_AC_STOP = 0x0226, + HID_USAGE_CONSUMER_AC_REFRESH = 0x0227, + HID_USAGE_CONSUMER_AC_BOOKMARKS = 0x022A, + + // Mouse Horizontal scroll + HID_USAGE_CONSUMER_AC_PAN = 0x0238, +}; + +/*-------------------------------------------------------------------- + * ASCII to KEYCODE Conversion + * Expand to array of [128][2] (shift, keycode) + * + * Usage: example to convert input chr into keyboard report (modifier + keycode) + * + * uint8_t const conv_table[128][2] = { HID_ASCII_TO_KEYCODE }; + * + * uint8_t keycode[6] = { 0 }; + * uint8_t modifier = 0; + * + * if ( conv_table[chr][0] ) modifier = KEYBOARD_MODIFIER_LEFTSHIFT; + * keycode[0] = conv_table[chr][1]; + * tud_hid_keyboard_report(report_id, modifier, keycode); + * + *--------------------------------------------------------------------*/ +#define HID_ASCII_TO_KEYCODE \ + {0, 0 }, /* 0x00 Null */ \ + {0, 0 }, /* 0x01 */ \ + {0, 0 }, /* 0x02 */ \ + {0, 0 }, /* 0x03 */ \ + {0, 0 }, /* 0x04 */ \ + {0, 0 }, /* 0x05 */ \ + {0, 0 }, /* 0x06 */ \ + {0, 0 }, /* 0x07 */ \ + {0, HID_KEY_BACKSPACE }, /* 0x08 Backspace */ \ + {0, HID_KEY_TAB }, /* 0x09 Tab */ \ + {0, HID_KEY_ENTER }, /* 0x0A Line Feed */ \ + {0, 0 }, /* 0x0B */ \ + {0, 0 }, /* 0x0C */ \ + {0, HID_KEY_ENTER }, /* 0x0D CR */ \ + {0, 0 }, /* 0x0E */ \ + {0, 0 }, /* 0x0F */ \ + {0, 0 }, /* 0x10 */ \ + {0, 0 }, /* 0x11 */ \ + {0, 0 }, /* 0x12 */ \ + {0, 0 }, /* 0x13 */ \ + {0, 0 }, /* 0x14 */ \ + {0, 0 }, /* 0x15 */ \ + {0, 0 }, /* 0x16 */ \ + {0, 0 }, /* 0x17 */ \ + {0, 0 }, /* 0x18 */ \ + {0, 0 }, /* 0x19 */ \ + {0, 0 }, /* 0x1A */ \ + {0, HID_KEY_ESCAPE }, /* 0x1B Escape */ \ + {0, 0 }, /* 0x1C */ \ + {0, 0 }, /* 0x1D */ \ + {0, 0 }, /* 0x1E */ \ + {0, 0 }, /* 0x1F */ \ + \ + {0, HID_KEY_SPACE }, /* 0x20 */ \ + {1, HID_KEY_1 }, /* 0x21 ! */ \ + {1, HID_KEY_APOSTROPHE }, /* 0x22 " */ \ + {1, HID_KEY_3 }, /* 0x23 # */ \ + {1, HID_KEY_4 }, /* 0x24 $ */ \ + {1, HID_KEY_5 }, /* 0x25 % */ \ + {1, HID_KEY_7 }, /* 0x26 & */ \ + {0, HID_KEY_APOSTROPHE }, /* 0x27 ' */ \ + {1, HID_KEY_9 }, /* 0x28 ( */ \ + {1, HID_KEY_0 }, /* 0x29 ) */ \ + {1, HID_KEY_8 }, /* 0x2A * */ \ + {1, HID_KEY_EQUAL }, /* 0x2B + */ \ + {0, HID_KEY_COMMA }, /* 0x2C , */ \ + {0, HID_KEY_MINUS }, /* 0x2D - */ \ + {0, HID_KEY_PERIOD }, /* 0x2E . */ \ + {0, HID_KEY_SLASH }, /* 0x2F / */ \ + {0, HID_KEY_0 }, /* 0x30 0 */ \ + {0, HID_KEY_1 }, /* 0x31 1 */ \ + {0, HID_KEY_2 }, /* 0x32 2 */ \ + {0, HID_KEY_3 }, /* 0x33 3 */ \ + {0, HID_KEY_4 }, /* 0x34 4 */ \ + {0, HID_KEY_5 }, /* 0x35 5 */ \ + {0, HID_KEY_6 }, /* 0x36 6 */ \ + {0, HID_KEY_7 }, /* 0x37 7 */ \ + {0, HID_KEY_8 }, /* 0x38 8 */ \ + {0, HID_KEY_9 }, /* 0x39 9 */ \ + {1, HID_KEY_SEMICOLON }, /* 0x3A : */ \ + {0, HID_KEY_SEMICOLON }, /* 0x3B ; */ \ + {1, HID_KEY_COMMA }, /* 0x3C < */ \ + {0, HID_KEY_EQUAL }, /* 0x3D = */ \ + {1, HID_KEY_PERIOD }, /* 0x3E > */ \ + {1, HID_KEY_SLASH }, /* 0x3F ? */ \ + \ + {1, HID_KEY_2 }, /* 0x40 @ */ \ + {1, HID_KEY_A }, /* 0x41 A */ \ + {1, HID_KEY_B }, /* 0x42 B */ \ + {1, HID_KEY_C }, /* 0x43 C */ \ + {1, HID_KEY_D }, /* 0x44 D */ \ + {1, HID_KEY_E }, /* 0x45 E */ \ + {1, HID_KEY_F }, /* 0x46 F */ \ + {1, HID_KEY_G }, /* 0x47 G */ \ + {1, HID_KEY_H }, /* 0x48 H */ \ + {1, HID_KEY_I }, /* 0x49 I */ \ + {1, HID_KEY_J }, /* 0x4A J */ \ + {1, HID_KEY_K }, /* 0x4B K */ \ + {1, HID_KEY_L }, /* 0x4C L */ \ + {1, HID_KEY_M }, /* 0x4D M */ \ + {1, HID_KEY_N }, /* 0x4E N */ \ + {1, HID_KEY_O }, /* 0x4F O */ \ + {1, HID_KEY_P }, /* 0x50 P */ \ + {1, HID_KEY_Q }, /* 0x51 Q */ \ + {1, HID_KEY_R }, /* 0x52 R */ \ + {1, HID_KEY_S }, /* 0x53 S */ \ + {1, HID_KEY_T }, /* 0x55 T */ \ + {1, HID_KEY_U }, /* 0x55 U */ \ + {1, HID_KEY_V }, /* 0x56 V */ \ + {1, HID_KEY_W }, /* 0x57 W */ \ + {1, HID_KEY_X }, /* 0x58 X */ \ + {1, HID_KEY_Y }, /* 0x59 Y */ \ + {1, HID_KEY_Z }, /* 0x5A Z */ \ + {0, HID_KEY_BRACKET_LEFT }, /* 0x5B [ */ \ + {0, HID_KEY_BACKSLASH }, /* 0x5C '\' */ \ + {0, HID_KEY_BRACKET_RIGHT }, /* 0x5D ] */ \ + {1, HID_KEY_6 }, /* 0x5E ^ */ \ + {1, HID_KEY_MINUS }, /* 0x5F _ */ \ + \ + {0, HID_KEY_GRAVE }, /* 0x60 ` */ \ + {0, HID_KEY_A }, /* 0x61 a */ \ + {0, HID_KEY_B }, /* 0x62 b */ \ + {0, HID_KEY_C }, /* 0x63 c */ \ + {0, HID_KEY_D }, /* 0x66 d */ \ + {0, HID_KEY_E }, /* 0x65 e */ \ + {0, HID_KEY_F }, /* 0x66 f */ \ + {0, HID_KEY_G }, /* 0x67 g */ \ + {0, HID_KEY_H }, /* 0x68 h */ \ + {0, HID_KEY_I }, /* 0x69 i */ \ + {0, HID_KEY_J }, /* 0x6A j */ \ + {0, HID_KEY_K }, /* 0x6B k */ \ + {0, HID_KEY_L }, /* 0x6C l */ \ + {0, HID_KEY_M }, /* 0x6D m */ \ + {0, HID_KEY_N }, /* 0x6E n */ \ + {0, HID_KEY_O }, /* 0x6F o */ \ + {0, HID_KEY_P }, /* 0x70 p */ \ + {0, HID_KEY_Q }, /* 0x71 q */ \ + {0, HID_KEY_R }, /* 0x72 r */ \ + {0, HID_KEY_S }, /* 0x73 s */ \ + {0, HID_KEY_T }, /* 0x75 t */ \ + {0, HID_KEY_U }, /* 0x75 u */ \ + {0, HID_KEY_V }, /* 0x76 v */ \ + {0, HID_KEY_W }, /* 0x77 w */ \ + {0, HID_KEY_X }, /* 0x78 x */ \ + {0, HID_KEY_Y }, /* 0x79 y */ \ + {0, HID_KEY_Z }, /* 0x7A z */ \ + {1, HID_KEY_BRACKET_LEFT }, /* 0x7B { */ \ + {1, HID_KEY_BACKSLASH }, /* 0x7C | */ \ + {1, HID_KEY_BRACKET_RIGHT }, /* 0x7D } */ \ + {1, HID_KEY_GRAVE }, /* 0x7E ~ */ \ + {0, HID_KEY_DELETE } /* 0x7F Delete */ \ + +/*-------------------------------------------------------------------- + * KEYCODE to Ascii Conversion + * Expand to array of [128][2] (ascii without shift, ascii with shift) + * + * Usage: example to convert ascii from keycode (key) and shift modifier (shift). + * Here we assume key < 128 ( printable ) + * + * uint8_t const conv_table[128][2] = { HID_KEYCODE_TO_ASCII }; + * char ch = shift ? conv_table[chr][1] : conv_table[chr][0]; + * + *--------------------------------------------------------------------*/ +#define HID_KEYCODE_TO_ASCII \ + {0 , 0 }, /* 0x00 */ \ + {0 , 0 }, /* 0x01 */ \ + {0 , 0 }, /* 0x02 */ \ + {0 , 0 }, /* 0x03 */ \ + {'a' , 'A' }, /* 0x04 */ \ + {'b' , 'B' }, /* 0x05 */ \ + {'c' , 'C' }, /* 0x06 */ \ + {'d' , 'D' }, /* 0x07 */ \ + {'e' , 'E' }, /* 0x08 */ \ + {'f' , 'F' }, /* 0x09 */ \ + {'g' , 'G' }, /* 0x0a */ \ + {'h' , 'H' }, /* 0x0b */ \ + {'i' , 'I' }, /* 0x0c */ \ + {'j' , 'J' }, /* 0x0d */ \ + {'k' , 'K' }, /* 0x0e */ \ + {'l' , 'L' }, /* 0x0f */ \ + {'m' , 'M' }, /* 0x10 */ \ + {'n' , 'N' }, /* 0x11 */ \ + {'o' , 'O' }, /* 0x12 */ \ + {'p' , 'P' }, /* 0x13 */ \ + {'q' , 'Q' }, /* 0x14 */ \ + {'r' , 'R' }, /* 0x15 */ \ + {'s' , 'S' }, /* 0x16 */ \ + {'t' , 'T' }, /* 0x17 */ \ + {'u' , 'U' }, /* 0x18 */ \ + {'v' , 'V' }, /* 0x19 */ \ + {'w' , 'W' }, /* 0x1a */ \ + {'x' , 'X' }, /* 0x1b */ \ + {'y' , 'Y' }, /* 0x1c */ \ + {'z' , 'Z' }, /* 0x1d */ \ + {'1' , '!' }, /* 0x1e */ \ + {'2' , '@' }, /* 0x1f */ \ + {'3' , '#' }, /* 0x20 */ \ + {'4' , '$' }, /* 0x21 */ \ + {'5' , '%' }, /* 0x22 */ \ + {'6' , '^' }, /* 0x23 */ \ + {'7' , '&' }, /* 0x24 */ \ + {'8' , '*' }, /* 0x25 */ \ + {'9' , '(' }, /* 0x26 */ \ + {'0' , ')' }, /* 0x27 */ \ + {'\r' , '\r' }, /* 0x28 */ \ + {'\x1b', '\x1b' }, /* 0x29 */ \ + {'\b' , '\b' }, /* 0x2a */ \ + {'\t' , '\t' }, /* 0x2b */ \ + {' ' , ' ' }, /* 0x2c */ \ + {'-' , '_' }, /* 0x2d */ \ + {'=' , '+' }, /* 0x2e */ \ + {'[' , '{' }, /* 0x2f */ \ + {']' , '}' }, /* 0x30 */ \ + {'\\' , '|' }, /* 0x31 */ \ + {'#' , '~' }, /* 0x32 */ \ + {';' , ':' }, /* 0x33 */ \ + {'\'' , '\"' }, /* 0x34 */ \ + {'`' , '~' }, /* 0x35 */ \ + {',' , '<' }, /* 0x36 */ \ + {'.' , '>' }, /* 0x37 */ \ + {'/' , '?' }, /* 0x38 */ \ + \ + {0 , 0 }, /* 0x39 */ \ + {0 , 0 }, /* 0x3a */ \ + {0 , 0 }, /* 0x3b */ \ + {0 , 0 }, /* 0x3c */ \ + {0 , 0 }, /* 0x3d */ \ + {0 , 0 }, /* 0x3e */ \ + {0 , 0 }, /* 0x3f */ \ + {0 , 0 }, /* 0x40 */ \ + {0 , 0 }, /* 0x41 */ \ + {0 , 0 }, /* 0x42 */ \ + {0 , 0 }, /* 0x43 */ \ + {0 , 0 }, /* 0x44 */ \ + {0 , 0 }, /* 0x45 */ \ + {0 , 0 }, /* 0x46 */ \ + {0 , 0 }, /* 0x47 */ \ + {0 , 0 }, /* 0x48 */ \ + {0 , 0 }, /* 0x49 */ \ + {0 , 0 }, /* 0x4a */ \ + {0 , 0 }, /* 0x4b */ \ + {0 , 0 }, /* 0x4c */ \ + {0 , 0 }, /* 0x4d */ \ + {0 , 0 }, /* 0x4e */ \ + {0 , 0 }, /* 0x4f */ \ + {0 , 0 }, /* 0x50 */ \ + {0 , 0 }, /* 0x51 */ \ + {0 , 0 }, /* 0x52 */ \ + {0 , 0 }, /* 0x53 */ \ + \ + {'/' , '/' }, /* 0x54 */ \ + {'*' , '*' }, /* 0x55 */ \ + {'-' , '-' }, /* 0x56 */ \ + {'+' , '+' }, /* 0x57 */ \ + {'\r' , '\r' }, /* 0x58 */ \ + {'1' , 0 }, /* 0x59 */ \ + {'2' , 0 }, /* 0x5a */ \ + {'3' , 0 }, /* 0x5b */ \ + {'4' , 0 }, /* 0x5c */ \ + {'5' , '5' }, /* 0x5d */ \ + {'6' , 0 }, /* 0x5e */ \ + {'7' , 0 }, /* 0x5f */ \ + {'8' , 0 }, /* 0x60 */ \ + {'9' , 0 }, /* 0x61 */ \ + {'0' , 0 }, /* 0x62 */ \ + {'0' , 0 }, /* 0x63 */ \ + {'=' , '=' }, /* 0x67 */ \ + + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_HID_H__ */ + +/// @} diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/hid/hid_device.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/hid/hid_device.c new file mode 100644 index 000000000..588b61254 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/hid/hid_device.c @@ -0,0 +1,417 @@ +/* + * 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 (TUSB_OPT_DEVICE_ENABLED && CFG_TUD_HID) + +//--------------------------------------------------------------------+ +// INCLUDE +//--------------------------------------------------------------------+ +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "hid_device.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +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 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 epin_buf[CFG_TUD_HID_EP_BUFSIZE]; + CFG_TUSB_MEM_ALIGN uint8_t epout_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; +} hidd_interface_t; + +CFG_TUSB_MEM_SECTION 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; + } + + return 0xFF; +} + +//--------------------------------------------------------------------+ +// APPLICATION API +//--------------------------------------------------------------------+ +bool tud_hid_n_ready(uint8_t instance) +{ + uint8_t const ep_in = _hidd_itf[instance].ep_in; + return tud_ready() && (ep_in != 0) && !usbd_edpt_busy(TUD_OPT_RHPORT, ep_in); +} + +bool tud_hid_n_report(uint8_t instance, uint8_t report_id, void const* report, uint8_t len) +{ + uint8_t const rhport = 0; + hidd_interface_t * p_hid = &_hidd_itf[instance]; + + // claim endpoint + TU_VERIFY( usbd_edpt_claim(rhport, p_hid->ep_in) ); + + // prepare data + if (report_id) + { + len = tu_min8(len, CFG_TUD_HID_EP_BUFSIZE-1); + + p_hid->epin_buf[0] = report_id; + memcpy(p_hid->epin_buf+1, report, len); + len++; + }else + { + // If report id = 0, skip ID field + len = tu_min8(len, CFG_TUD_HID_EP_BUFSIZE); + memcpy(p_hid->epin_buf, report, len); + } + + return usbd_edpt_xfer(TUD_OPT_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_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]) +{ + hid_keyboard_report_t report; + + report.modifier = modifier; + report.reserved = 0; + + if ( keycode ) + { + memcpy(report.keycode, keycode, 6); + }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) +{ + hid_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)); +} + +//--------------------------------------------------------------------+ +// USBD-CLASS API +//--------------------------------------------------------------------+ +void hidd_init(void) +{ + hidd_reset(TUD_OPT_RHPORT); +} + +void hidd_reset(uint8_t 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) +{ + TU_VERIFY(TUSB_CLASS_HID == desc_itf->bInterfaceClass, 0); + + // len = interface + hid + n*endpoints + uint16_t const drv_len = 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; + uint8_t hid_id; + for(hid_id=0; hid_idhid_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; + + p_hid->protocol_mode = HID_PROTOCOL_REPORT; // Per Specs: default is report mode + 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)); + + // 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)) ) + { + TU_LOG_FAILED(); + TU_BREAKPOINT(); + } + } + + return drv_len; +} + +// 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) +{ + TU_VERIFY(request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE); + + 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]; + + 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 (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 + { + return false; // stall unsupported request + } + } + } + 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); + + uint8_t* report_buf = p_hid->epin_buf; + uint16_t req_len = tu_min16(request->wLength, CFG_TUD_HID_EP_BUFSIZE); + + 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--; + + 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); + } + 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); + } + 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); + } + 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); + } + 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); + } + break; + + 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); + } + } + break; + + default: return false; // stall unsupported request + } + }else + { + return false; // stall unsupported request + } + + return true; +} + +bool hidd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) +{ + (void) result; + + uint8_t instance = 0; + hidd_interface_t * p_hid = _hidd_itf; + + // Identify which interface to use + 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; + } + TU_ASSERT(instance < CFG_TUD_HID); + + // 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, (uint8_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, xferred_bytes); + TU_ASSERT(usbd_edpt_xfer(rhport, p_hid->ep_out, p_hid->epout_buf, sizeof(p_hid->epout_buf))); + } + + return true; +} + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/hid/hid_device.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/hid/hid_device.h new file mode 100644 index 000000000..078b67349 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/hid/hid_device.h @@ -0,0 +1,393 @@ +/* + * 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_HID_DEVICE_H_ +#define _TUSB_HID_DEVICE_H_ + +#include "hid.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Class Driver Default Configure & Validation +//--------------------------------------------------------------------+ + +#if !defined(CFG_TUD_HID_EP_BUFSIZE) & defined(CFG_TUD_HID_BUFSIZE) + // TODO warn user to use new name later on + // #warning CFG_TUD_HID_BUFSIZE is renamed to CFG_TUD_HID_EP_BUFSIZE, please update to use the new name + #define CFG_TUD_HID_EP_BUFSIZE CFG_TUD_HID_BUFSIZE +#endif + +#ifndef CFG_TUD_HID_EP_BUFSIZE + #define CFG_TUD_HID_EP_BUFSIZE 64 +#endif + +//--------------------------------------------------------------------+ +// Application API (Multiple Instances) +// CFG_TUD_HID > 1 +//--------------------------------------------------------------------+ + +// Check if the interface is ready to use +bool tud_hid_n_ready(uint8_t instance); + +// Get interface supported protocol (bInterfaceProtocol) check out hid_interface_protocol_enum_t for possible values +uint8_t tud_hid_n_interface_protocol(uint8_t instance); + +// Get current active protocol: HID_PROTOCOL_BOOT (0) or HID_PROTOCOL_REPORT (1) +uint8_t tud_hid_n_get_protocol(uint8_t instance); + +// Send report to host +bool tud_hid_n_report(uint8_t instance, uint8_t report_id, void const* report, uint8_t len); + +// KEYBOARD: convenient helper to send keyboard report if application +// use template layout report as defined by hid_keyboard_report_t +bool tud_hid_n_keyboard_report(uint8_t instance, uint8_t report_id, uint8_t modifier, uint8_t keycode[6]); + +// MOUSE: convenient helper to send mouse report if application +// 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); + +// 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); + +//--------------------------------------------------------------------+ +// Application API (Single Port) +//--------------------------------------------------------------------+ +static inline bool tud_hid_ready(void); +static inline uint8_t tud_hid_interface_protocol(void); +static inline uint8_t tud_hid_get_protocol(void); +static inline bool tud_hid_report(uint8_t report_id, void const* report, uint8_t len); +static inline bool tud_hid_keyboard_report(uint8_t report_id, uint8_t modifier, uint8_t keycode[6]); +static inline bool tud_hid_mouse_report(uint8_t report_id, uint8_t buttons, int8_t x, int8_t y, int8_t vertical, int8_t horizontal); +static inline bool tud_hid_gamepad_report(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); + +//--------------------------------------------------------------------+ +// Callbacks (Weak is optional) +//--------------------------------------------------------------------+ + +// Invoked when received GET HID REPORT DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +uint8_t const * tud_hid_descriptor_report_cb(uint8_t instance); + +// Invoked when received GET_REPORT control request +// Application must fill buffer report's content and return its length. +// Return zero will cause the stack to STALL request +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 reqlen); + +// Invoked when received SET_REPORT control request or +// received data on OUT endpoint ( Report ID = 0, Type = 0 ) +void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize); + +// Invoked when received SET_PROTOCOL request +// protocol is either HID_PROTOCOL_BOOT (0) or HID_PROTOCOL_REPORT (1) +TU_ATTR_WEAK void tud_hid_set_protocol_cb(uint8_t instance, uint8_t protocol); + +// Invoked when received SET_IDLE request. return false will stall the request +// - Idle Rate = 0 : only send report if there is changes, i.e skip duplication +// - Idle Rate > 0 : skip duplication, but send at least 1 report every idle rate (in unit of 4 ms). +TU_ATTR_WEAK bool tud_hid_set_idle_cb(uint8_t instance, uint8_t idle_rate); + +// Invoked when sent REPORT successfully to host +// Application can use this to send the next report +// 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, uint8_t len); + + +//--------------------------------------------------------------------+ +// Inline Functions +//--------------------------------------------------------------------+ +static inline bool tud_hid_ready(void) +{ + return tud_hid_n_ready(0); +} + +static inline uint8_t tud_hid_interface_protocol(void) +{ + return tud_hid_n_interface_protocol(0); +} + +static inline uint8_t tud_hid_get_protocol(void) +{ + return tud_hid_n_get_protocol(0); +} + +static inline bool tud_hid_report(uint8_t report_id, void const* report, uint8_t len) +{ + return tud_hid_n_report(0, report_id, report, len); +} + +static inline bool tud_hid_keyboard_report(uint8_t report_id, uint8_t modifier, uint8_t keycode[6]) +{ + return tud_hid_n_keyboard_report(0, report_id, modifier, keycode); +} + +static inline bool tud_hid_mouse_report(uint8_t report_id, uint8_t buttons, int8_t x, int8_t y, int8_t vertical, int8_t horizontal) +{ + return tud_hid_n_mouse_report(0, report_id, buttons, x, y, vertical, horizontal); +} + +static inline bool tud_hid_gamepad_report(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) +{ + return tud_hid_n_gamepad_report(0, report_id, x, y, z, rz, rx, ry, hat, buttons); +} + +/* --------------------------------------------------------------------+ + * HID Report Descriptor Template + * + * Convenient for declaring popular HID device (keyboard, mouse, consumer, + * gamepad etc...). Templates take "HID_REPORT_ID(n)" as input, leave + * empty if multiple reports is not used + * + * - Only 1 report: no parameter + * uint8_t const report_desc[] = { TUD_HID_REPORT_DESC_KEYBOARD() }; + * + * - Multiple Reports: "HID_REPORT_ID(ID)" must be passed to template + * uint8_t const report_desc[] = + * { + * TUD_HID_REPORT_DESC_KEYBOARD( HID_REPORT_ID(1) ) , + * TUD_HID_REPORT_DESC_MOUSE ( HID_REPORT_ID(2) ) + * }; + *--------------------------------------------------------------------*/ + +// Keyboard Report Descriptor Template +#define TUD_HID_REPORT_DESC_KEYBOARD(...) \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_KEYBOARD ) ,\ + HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\ + /* Report ID if any */\ + __VA_ARGS__ \ + /* 8 bits Modifier Keys (Shfit, Control, Alt) */ \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_KEYBOARD ) ,\ + HID_USAGE_MIN ( 224 ) ,\ + HID_USAGE_MAX ( 231 ) ,\ + HID_LOGICAL_MIN ( 0 ) ,\ + HID_LOGICAL_MAX ( 1 ) ,\ + HID_REPORT_COUNT ( 8 ) ,\ + HID_REPORT_SIZE ( 1 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + /* 8 bit reserved */ \ + HID_REPORT_COUNT ( 1 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_INPUT ( HID_CONSTANT ) ,\ + /* Output 5-bit LED Indicator Kana | Compose | ScrollLock | CapsLock | NumLock */ \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_LED ) ,\ + HID_USAGE_MIN ( 1 ) ,\ + HID_USAGE_MAX ( 5 ) ,\ + HID_REPORT_COUNT ( 5 ) ,\ + HID_REPORT_SIZE ( 1 ) ,\ + HID_OUTPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + /* led padding */ \ + HID_REPORT_COUNT ( 1 ) ,\ + HID_REPORT_SIZE ( 3 ) ,\ + HID_OUTPUT ( HID_CONSTANT ) ,\ + /* 6-byte Keycodes */ \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_KEYBOARD ) ,\ + HID_USAGE_MIN ( 0 ) ,\ + HID_USAGE_MAX_N ( 255, 2 ) ,\ + HID_LOGICAL_MIN ( 0 ) ,\ + HID_LOGICAL_MAX_N( 255, 2 ) ,\ + HID_REPORT_COUNT ( 6 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_INPUT ( HID_DATA | HID_ARRAY | HID_ABSOLUTE ) ,\ + HID_COLLECTION_END \ + +// Mouse Report Descriptor Template +#define TUD_HID_REPORT_DESC_MOUSE(...) \ + 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 position [-127, 127] */ \ + HID_USAGE ( HID_USAGE_DESKTOP_X ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_Y ) ,\ + HID_LOGICAL_MIN ( 0x81 ) ,\ + HID_LOGICAL_MAX ( 0x7f ) ,\ + HID_REPORT_COUNT( 2 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_RELATIVE ) ,\ + /* Verital 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 ) ,\ + HID_USAGE ( HID_USAGE_CONSUMER_CONTROL ) ,\ + HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\ + /* Report ID if any */\ + __VA_ARGS__ \ + HID_LOGICAL_MIN ( 0x00 ) ,\ + HID_LOGICAL_MAX_N( 0x03FF, 2 ) ,\ + HID_USAGE_MIN ( 0x00 ) ,\ + HID_USAGE_MAX_N ( 0x03FF, 2 ) ,\ + HID_REPORT_COUNT ( 1 ) ,\ + HID_REPORT_SIZE ( 16 ) ,\ + HID_INPUT ( HID_DATA | HID_ARRAY | HID_ABSOLUTE ) ,\ + HID_COLLECTION_END \ + +/* System Control Report Descriptor Template + * 0x00 - do nothing + * 0x01 - Power Off + * 0x02 - Standby + * 0x03 - Wake Host + */ +#define TUD_HID_REPORT_DESC_SYSTEM_CONTROL(...) \ + 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__ \ + /* 2 bit system power control */ \ + HID_LOGICAL_MIN ( 1 ) ,\ + HID_LOGICAL_MAX ( 3 ) ,\ + HID_REPORT_COUNT ( 1 ) ,\ + HID_REPORT_SIZE ( 2 ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_SYSTEM_POWER_DOWN ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_SYSTEM_SLEEP ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_SYSTEM_WAKE_UP ) ,\ + HID_INPUT ( HID_DATA | HID_ARRAY | HID_ABSOLUTE ) ,\ + /* 6 bit padding */ \ + HID_REPORT_COUNT ( 1 ) ,\ + HID_REPORT_SIZE ( 6 ) ,\ + HID_INPUT ( HID_CONSTANT ) ,\ + HID_COLLECTION_END \ + +// Gamepad Report Descriptor Template +// with 32 buttons, 2 joysticks and 1 hat/dpad with following layout +// | X | Y | Z | Rz | Rx | Ry (1 byte each) | hat/DPAD (1 byte) | Button Map (4 bytes) | +#define TUD_HID_REPORT_DESC_GAMEPAD(...) \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_GAMEPAD ) ,\ + HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\ + /* Report ID if any */\ + __VA_ARGS__ \ + /* 8 bit X, Y, Z, Rz, Rx, Ry (min -127, max 127 ) */ \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_X ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_Y ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_Z ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_RZ ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_RX ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_RY ) ,\ + HID_LOGICAL_MIN ( 0x81 ) ,\ + HID_LOGICAL_MAX ( 0x7f ) ,\ + HID_REPORT_COUNT ( 6 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + /* 8 bit DPad/Hat Button Map */ \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\ + HID_USAGE ( HID_USAGE_DESKTOP_HAT_SWITCH ) ,\ + HID_LOGICAL_MIN ( 1 ) ,\ + HID_LOGICAL_MAX ( 8 ) ,\ + HID_PHYSICAL_MIN ( 0 ) ,\ + HID_PHYSICAL_MAX_N ( 315, 2 ) ,\ + HID_REPORT_COUNT ( 1 ) ,\ + HID_REPORT_SIZE ( 8 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + /* 32 bit Button Map */ \ + HID_USAGE_PAGE ( HID_USAGE_PAGE_BUTTON ) ,\ + HID_USAGE_MIN ( 1 ) ,\ + HID_USAGE_MAX ( 32 ) ,\ + HID_LOGICAL_MIN ( 0 ) ,\ + HID_LOGICAL_MAX ( 1 ) ,\ + HID_REPORT_COUNT ( 32 ) ,\ + HID_REPORT_SIZE ( 1 ) ,\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\ + HID_COLLECTION_END \ + +// HID Generic Input & Output +// - 1st parameter is report size (mandatory) +// - 2nd parameter is report id HID_REPORT_ID(n) (optional) +#define TUD_HID_REPORT_DESC_GENERIC_INOUT(report_size, ...) \ + HID_USAGE_PAGE_N ( HID_USAGE_PAGE_VENDOR, 2 ),\ + HID_USAGE ( 0x01 ),\ + HID_COLLECTION ( HID_COLLECTION_APPLICATION ),\ + /* Report ID if any */\ + __VA_ARGS__ \ + /* Input */ \ + HID_USAGE ( 0x02 ),\ + HID_LOGICAL_MIN ( 0x00 ),\ + HID_LOGICAL_MAX_N ( 0xff, 2 ),\ + HID_REPORT_SIZE ( 8 ),\ + HID_REPORT_COUNT( report_size ),\ + HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + /* Output */ \ + HID_USAGE ( 0x03 ),\ + HID_LOGICAL_MIN ( 0x00 ),\ + HID_LOGICAL_MAX_N ( 0xff, 2 ),\ + HID_REPORT_SIZE ( 8 ),\ + HID_REPORT_COUNT( report_size ),\ + HID_OUTPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ),\ + HID_COLLECTION_END \ + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void hidd_init (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 + +#endif /* _TUSB_HID_DEVICE_H_ */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/hid/hid_host.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/hid/hid_host.c new file mode 100644 index 000000000..f19f1ba81 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/hid/hid_host.c @@ -0,0 +1,636 @@ +/* + * 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 (TUSB_OPT_HOST_ENABLED && CFG_TUH_HID) + +#include "host/usbh.h" +#include "host/usbh_classdriver.h" + +#include "hid_host.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + +typedef struct +{ + uint8_t itf_num; + uint8_t ep_in; + uint8_t ep_out; + + uint8_t itf_protocol; // None, Keyboard, Mouse + uint8_t protocol_mode; // Boot (0) or Report protocol (1) + + uint8_t report_desc_type; + uint16_t report_desc_len; + + uint16_t epin_size; + uint16_t epout_size; + + uint8_t epin_buf[CFG_TUH_HID_EPIN_BUFSIZE]; + uint8_t epout_buf[CFG_TUH_HID_EPOUT_BUFSIZE]; +} hidh_interface_t; + +typedef struct +{ + uint8_t inst_count; + hidh_interface_t instances[CFG_TUH_HID]; +} hidh_device_t; + +static hidh_device_t _hidh_dev[CFG_TUH_DEVICE_MAX]; + +//------------- Internal prototypes -------------// + +// Get HID device & interface +TU_ATTR_ALWAYS_INLINE static inline hidh_device_t* get_dev(uint8_t dev_addr); +TU_ATTR_ALWAYS_INLINE static inline hidh_interface_t* get_instance(uint8_t dev_addr, uint8_t instance); +static uint8_t get_instance_id_by_itfnum(uint8_t dev_addr, uint8_t itf); +static uint8_t get_instance_id_by_epaddr(uint8_t dev_addr, uint8_t ep_addr); + +//--------------------------------------------------------------------+ +// Interface API +//--------------------------------------------------------------------+ + +uint8_t tuh_hid_instance_count(uint8_t dev_addr) +{ + return get_dev(dev_addr)->inst_count; +} + +bool tuh_hid_mounted(uint8_t dev_addr, uint8_t instance) +{ + hidh_interface_t* hid_itf = get_instance(dev_addr, instance); + return (hid_itf->ep_in != 0) || (hid_itf->ep_out != 0); +} + +uint8_t tuh_hid_interface_protocol(uint8_t dev_addr, uint8_t instance) +{ + hidh_interface_t* hid_itf = get_instance(dev_addr, instance); + return hid_itf->itf_protocol; +} + +//--------------------------------------------------------------------+ +// Control Endpoint API +//--------------------------------------------------------------------+ + +uint8_t tuh_hid_get_protocol(uint8_t dev_addr, uint8_t instance) +{ + hidh_interface_t* hid_itf = get_instance(dev_addr, instance); + return hid_itf->protocol_mode; +} + +static bool set_protocol_complete(uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result) +{ + uint8_t const itf_num = (uint8_t) request->wIndex; + uint8_t const instance = get_instance_id_by_itfnum(dev_addr, itf_num); + hidh_interface_t* hid_itf = get_instance(dev_addr, instance); + + if (XFER_RESULT_SUCCESS == result) hid_itf->protocol_mode = (uint8_t) request->wValue; + + if (tuh_hid_set_protocol_complete_cb) + { + tuh_hid_set_protocol_complete_cb(dev_addr, instance, hid_itf->protocol_mode); + } + + return true; +} + +bool tuh_hid_set_protocol(uint8_t dev_addr, uint8_t instance, uint8_t protocol) +{ + hidh_interface_t* hid_itf = get_instance(dev_addr, instance); + TU_VERIFY(hid_itf->itf_protocol != HID_ITF_PROTOCOL_NONE); + + TU_LOG2("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 = hid_itf->itf_num, + .wLength = 0 + }; + + TU_ASSERT( tuh_control_xfer(dev_addr, &request, NULL, set_protocol_complete) ); + return true; +} + +static bool set_report_complete(uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result) +{ + TU_LOG2("HID Set Report complete\r\n"); + + if (tuh_hid_set_report_complete_cb) + { + uint8_t const itf_num = (uint8_t) request->wIndex; + uint8_t const instance = get_instance_id_by_itfnum(dev_addr, itf_num); + + uint8_t const report_type = tu_u16_high(request->wValue); + uint8_t const report_id = tu_u16_low(request->wValue); + + tuh_hid_set_report_complete_cb(dev_addr, instance, report_id, report_type, (result == XFER_RESULT_SUCCESS) ? request->wLength : 0); + } + + return true; +} + +bool tuh_hid_set_report(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t report_type, void* report, uint16_t len) +{ + hidh_interface_t* hid_itf = get_instance(dev_addr, instance); + TU_LOG2("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_u16(report_type, report_id), + .wIndex = hid_itf->itf_num, + .wLength = len + }; + + TU_ASSERT( tuh_control_xfer(dev_addr, &request, report, set_report_complete) ); + return true; +} + +//--------------------------------------------------------------------+ +// Interrupt Endpoint API +//--------------------------------------------------------------------+ + +bool tuh_hid_receive_report(uint8_t dev_addr, uint8_t instance) +{ + hidh_interface_t* hid_itf = get_instance(dev_addr, instance); + + // claim endpoint + TU_VERIFY( usbh_edpt_claim(dev_addr, hid_itf->ep_in) ); + + return usbh_edpt_xfer(dev_addr, hid_itf->ep_in, hid_itf->epin_buf, hid_itf->epin_size); +} + +//bool tuh_n_hid_n_ready(uint8_t dev_addr, uint8_t instance) +//{ +// TU_VERIFY(tuh_n_hid_n_mounted(dev_addr, instance)); +// +// hidh_interface_t* hid_itf = get_instance(dev_addr, instance); +// return !usbh_edpt_busy(dev_addr, hid_itf->ep_in); +//} + +//void tuh_hid_send_report(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t const* report, uint16_t len); + +//--------------------------------------------------------------------+ +// USBH API +//--------------------------------------------------------------------+ +void hidh_init(void) +{ + tu_memclr(_hidh_dev, sizeof(_hidh_dev)); +} + +bool hidh_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) +{ + (void) result; + + uint8_t const dir = tu_edpt_dir(ep_addr); + uint8_t const instance = get_instance_id_by_epaddr(dev_addr, ep_addr); + hidh_interface_t* hid_itf = get_instance(dev_addr, instance); + + if ( dir == TUSB_DIR_IN ) + { + TU_LOG2(" Get Report callback (%u, %u)\r\n", dev_addr, instance); + TU_LOG3_MEM(hid_itf->epin_buf, xferred_bytes, 2); + tuh_hid_report_received_cb(dev_addr, instance, hid_itf->epin_buf, xferred_bytes); + }else + { + if (tuh_hid_report_sent_cb) tuh_hid_report_sent_cb(dev_addr, instance, hid_itf->epout_buf, xferred_bytes); + } + + return true; +} + +void hidh_close(uint8_t dev_addr) +{ + TU_VERIFY(dev_addr <= CFG_TUH_DEVICE_MAX, ); + + hidh_device_t* hid_dev = get_dev(dev_addr); + + if (tuh_hid_umount_cb) + { + for (uint8_t inst = 0; inst < hid_dev->inst_count; inst++ ) tuh_hid_umount_cb(dev_addr, inst); + } + + tu_memclr(hid_dev, sizeof(hidh_device_t)); +} + +//--------------------------------------------------------------------+ +// Enumeration +//--------------------------------------------------------------------+ + +static bool config_set_protocol (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result); +static bool config_get_report_desc (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result); +static bool config_get_report_desc_complete (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result); + +static void config_driver_mount_complete(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len); + +bool hidh_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *desc_itf, uint16_t max_len) +{ + (void) max_len; + + TU_VERIFY(TUSB_CLASS_HID == desc_itf->bInterfaceClass); + + TU_LOG2("HID opening Interface %u (addr = %u)\r\n", desc_itf->bInterfaceNumber, dev_addr); + + // len = interface + hid + n*endpoints + uint16_t const drv_len = 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; + + //------------- 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; + TU_ASSERT(HID_DESC_TYPE_HID == desc_hid->bDescriptorType); + + // not enough interface, try to increase CFG_TUH_HID + // TODO multiple devices + hidh_device_t* hid_dev = get_dev(dev_addr); + TU_ASSERT(hid_dev->inst_count < CFG_TUH_HID, 0); + + //------------- Endpoint Descriptor -------------// + p_desc = tu_desc_next(p_desc); + tusb_desc_endpoint_t const * desc_ep = (tusb_desc_endpoint_t const *) p_desc; + TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType); + + // first endpoint may be OUT, skip to IN endpoint + // TODO also open endpoint OUT + if(tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_OUT) + { + p_desc = tu_desc_next(p_desc); + desc_ep = (tusb_desc_endpoint_t const *) p_desc; + TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType); + } + + TU_ASSERT( usbh_edpt_open(rhport, dev_addr, desc_ep) ); + + hidh_interface_t* hid_itf = get_instance(dev_addr, hid_dev->inst_count); + hid_dev->inst_count++; + + hid_itf->itf_num = desc_itf->bInterfaceNumber; + hid_itf->ep_in = desc_ep->bEndpointAddress; + hid_itf->epin_size = tu_edpt_packet_size(desc_ep); + + // Assume bNumDescriptors = 1 + hid_itf->report_desc_type = desc_hid->bReportType; + hid_itf->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 + hid_itf->protocol_mode = HID_PROTOCOL_BOOT; + if ( HID_SUBCLASS_BOOT == desc_itf->bInterfaceSubClass ) hid_itf->itf_protocol = desc_itf->bInterfaceProtocol; + + return true; +} + +bool hidh_set_config(uint8_t dev_addr, uint8_t itf_num) +{ + uint8_t const instance = get_instance_id_by_itfnum(dev_addr, itf_num); + hidh_interface_t* hid_itf = get_instance(dev_addr, instance); + + // Idle rate = 0 mean only report when there is changes + uint16_t const idle_rate = 0; + + // SET IDLE request, device can stall if not support this request + TU_LOG2("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 = idle_rate, + .wIndex = itf_num, + .wLength = 0 + }; + + TU_ASSERT( tuh_control_xfer(dev_addr, &request, NULL, (hid_itf->itf_protocol != HID_ITF_PROTOCOL_NONE) ? config_set_protocol : config_get_report_desc) ); + + return true; +} + +// Force device to work in BOOT protocol +static bool config_set_protocol(uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result) +{ + // Stall is a valid response for SET_IDLE, therefore we could ignore its result + (void) result; + + uint8_t const itf_num = (uint8_t) request->wIndex; + uint8_t const instance = get_instance_id_by_itfnum(dev_addr, itf_num); + hidh_interface_t* hid_itf = get_instance(dev_addr, instance); + + TU_LOG2("HID Set Protocol to Boot Mode\r\n"); + hid_itf->protocol_mode = HID_PROTOCOL_BOOT; + tusb_control_request_t const new_request = + { + .bmRequestType_bit = + { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_OUT + }, + .bRequest = HID_REQ_CONTROL_SET_PROTOCOL, + .wValue = HID_PROTOCOL_BOOT, + .wIndex = hid_itf->itf_num, + .wLength = 0 + }; + + TU_ASSERT( tuh_control_xfer(dev_addr, &new_request, NULL, config_get_report_desc) ); + return true; +} + +static bool config_get_report_desc(uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result) +{ + // We can be here after SET_IDLE or SET_PROTOCOL (boot device) + // Trigger assert if result is not successful with set protocol + if ( request->bRequest != HID_REQ_CONTROL_SET_IDLE ) + { + TU_ASSERT(result == XFER_RESULT_SUCCESS); + } + + uint8_t const itf_num = (uint8_t) request->wIndex; + uint8_t const instance = get_instance_id_by_itfnum(dev_addr, itf_num); + hidh_interface_t* hid_itf = get_instance(dev_addr, instance); + + // Get Report Descriptor if possible + // using usbh enumeration buffer since report descriptor can be very long + if( hid_itf->report_desc_len > CFG_TUH_ENUMERATION_BUFSIZE ) + { + TU_LOG2("HID Skip Report Descriptor since it is too large %u bytes\r\n", hid_itf->report_desc_len); + + // Driver is mounted without report descriptor + config_driver_mount_complete(dev_addr, instance, NULL, 0); + }else + { + TU_LOG2("HID Get Report Descriptor\r\n"); + tusb_control_request_t const new_request = + { + .bmRequestType_bit = + { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_STANDARD, + .direction = TUSB_DIR_IN + }, + .bRequest = TUSB_REQ_GET_DESCRIPTOR, + .wValue = tu_u16(hid_itf->report_desc_type, 0), + .wIndex = itf_num, + .wLength = hid_itf->report_desc_len + }; + + TU_ASSERT(tuh_control_xfer(dev_addr, &new_request, usbh_get_enum_buf(), config_get_report_desc_complete)); + } + + return true; +} + +static bool config_get_report_desc_complete(uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result) +{ + TU_ASSERT(XFER_RESULT_SUCCESS == result); + + uint8_t const itf_num = (uint8_t) request->wIndex; + uint8_t const instance = get_instance_id_by_itfnum(dev_addr, itf_num); + + uint8_t const* desc_report = usbh_get_enum_buf(); + uint16_t const desc_len = request->wLength; + + config_driver_mount_complete(dev_addr, instance, desc_report, desc_len); + + return true; +} + +static void config_driver_mount_complete(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len) +{ + hidh_interface_t* hid_itf = get_instance(dev_addr, instance); + + // enumeration is complete + tuh_hid_mount_cb(dev_addr, instance, desc_report, desc_len); + + // notify usbh that driver enumeration is complete + usbh_driver_set_config_complete(dev_addr, hid_itf->itf_num); +} + +//--------------------------------------------------------------------+ +// 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) +{ + // Report Item 6.2.2.2 USB HID 1.11 + union TU_ATTR_PACKED + { + uint8_t byte; + 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)); + + uint8_t report_num = 0; + tuh_hid_report_info_t* info = report_info_arr; + + // current parsed report count & size from descriptor +// uint8_t ri_report_count = 0; +// uint8_t ri_report_size = 0; + + uint8_t ri_collection_depth = 0; + + while(desc_len && report_num < arr_count) + { + header.byte = *desc_report++; + desc_len--; + + 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; + + 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; + + case RI_GLOBAL_REPORT_SIZE: +// ri_report_size = data8; + break; + + case RI_GLOBAL_REPORT_COUNT: +// ri_report_count = data8; + 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; + + case RI_TYPE_LOCAL: + 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; + + 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; + + // error + default: break; + } + + desc_report += size; + desc_len -= size; + } + + for ( uint8_t i = 0; i < report_num; i++ ) + { + info = report_info_arr+i; + TU_LOG2("%u: id = %u, usage_page = %u, usage = %u\r\n", i, info->report_id, info->usage_page, info->usage); + } + + return report_num; +} + +//--------------------------------------------------------------------+ +// Helper +//--------------------------------------------------------------------+ + +// Get Device by address +TU_ATTR_ALWAYS_INLINE static inline hidh_device_t* get_dev(uint8_t dev_addr) +{ + return &_hidh_dev[dev_addr-1]; +} + +// Get Interface by instance number +TU_ATTR_ALWAYS_INLINE static inline hidh_interface_t* get_instance(uint8_t dev_addr, uint8_t instance) +{ + return &_hidh_dev[dev_addr-1].instances[instance]; +} + +// Get instance ID by interface number +static uint8_t get_instance_id_by_itfnum(uint8_t dev_addr, uint8_t itf) +{ + for ( uint8_t inst = 0; inst < CFG_TUH_HID; inst++ ) + { + hidh_interface_t *hid = get_instance(dev_addr, inst); + + if ( (hid->itf_num == itf) && (hid->ep_in || hid->ep_out) ) return inst; + } + + return 0xff; +} + +// Get instance ID by endpoint address +static uint8_t get_instance_id_by_epaddr(uint8_t dev_addr, uint8_t ep_addr) +{ + for ( uint8_t inst = 0; inst < CFG_TUH_HID; inst++ ) + { + hidh_interface_t *hid = get_instance(dev_addr, inst); + + if ( (ep_addr == hid->ep_in) || ( ep_addr == hid->ep_out) ) return inst; + } + + return 0xff; +} + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/hid/hid_host.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/hid/hid_host.h new file mode 100644 index 000000000..fe09b03b2 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/hid/hid_host.h @@ -0,0 +1,152 @@ +/* + * 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_HID_HOST_H_ +#define _TUSB_HID_HOST_H_ + +#include "hid.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Class Driver Configuration +//--------------------------------------------------------------------+ + +// TODO Highspeed interrupt can be up to 512 bytes +#ifndef CFG_TUH_HID_EPIN_BUFSIZE +#define CFG_TUH_HID_EPIN_BUFSIZE 64 +#endif + +#ifndef CFG_TUH_HID_EPOUT_BUFSIZE +#define CFG_TUH_HID_EPOUT_BUFSIZE 64 +#endif + + +typedef struct +{ + uint8_t report_id; + uint8_t usage; + uint16_t usage_page; + + // TODO still use the endpoint size for now +// uint8_t in_len; // length of IN report +// uint8_t out_len; // length of OUT report +} tuh_hid_report_info_t; + +//--------------------------------------------------------------------+ +// Interface API +//--------------------------------------------------------------------+ + +// Get the number of HID instances +uint8_t tuh_hid_instance_count(uint8_t dev_addr); + +// Check if HID instance is mounted +bool tuh_hid_mounted(uint8_t dev_addr, uint8_t instance); + +// Get interface supported protocol (bInterfaceProtocol) check out hid_interface_protocol_enum_t for possible values +uint8_t tuh_hid_interface_protocol(uint8_t dev_addr, uint8_t instance); + +// 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; + +//--------------------------------------------------------------------+ +// Control Endpoint API +//--------------------------------------------------------------------+ + +// Get current protocol: HID_PROTOCOL_BOOT (0) or HID_PROTOCOL_REPORT (1) +// Note: Device will be initialized in Boot protocol for simplicity. +// Application can use set_protocol() to switch back to Report protocol. +uint8_t tuh_hid_get_protocol(uint8_t dev_addr, uint8_t instance); + +// Set protocol to HID_PROTOCOL_BOOT (0) or HID_PROTOCOL_REPORT (1) +// 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 instance, uint8_t protocol); + +// Set Report using control endpoint +// report_type is either Intput, Output or Feature, (value from hid_report_type_t) +bool tuh_hid_set_report(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t report_type, void* report, uint16_t len); + +//--------------------------------------------------------------------+ +// Interrupt Endpoint API +//--------------------------------------------------------------------+ + +// Check if the interface is ready to use +//bool tuh_n_hid_n_ready(uint8_t dev_addr, uint8_t instance); + +// Try to receive next report on Interrupt Endpoint. Immediately return +// - true If succeeded, tuh_hid_report_received_cb() callback will be invoked when report is available +// - false if failed to queue the transfer e.g endpoint is busy +bool tuh_hid_receive_report(uint8_t dev_addr, uint8_t instance); + +// Send report using interrupt endpoint +// If report_id > 0 (composite), it will be sent as 1st byte, then report contents. Otherwise only report content is sent. +//void tuh_hid_send_report(uint8_t dev_addr, uint8_t instance, uint8_t report_id, uint8_t const* report, uint16_t len); + +//--------------------------------------------------------------------+ +// Callbacks (Weak is optional) +//--------------------------------------------------------------------+ + +// Invoked when device with hid interface is mounted +// Report descriptor is also available for use. tuh_hid_parse_report_descriptor() +// can be used to parse common/simple enough descriptor. +// Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE, it will be skipped +// therefore report_desc = NULL, desc_len = 0 +void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* report_desc, uint16_t desc_len); + +// Invoked when device with hid interface is un-mounted +TU_ATTR_WEAK void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance); + +// Invoked when received report from device via interrupt endpoint +// Note: if there is report ID (composite), it is 1st byte of report +void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len); + +// 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 instance, uint8_t const* report, 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 instance, uint8_t report_id, uint8_t report_type, uint16_t len); + +// Invoked when Set Protocol request is complete +TU_ATTR_WEAK void tuh_hid_set_protocol_complete_cb(uint8_t dev_addr, uint8_t instance, uint8_t protocol); + +//--------------------------------------------------------------------+ +// 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); + +#ifdef __cplusplus +} +#endif + +#endif /* _TUSB_HID_HOST_H_ */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/midi/midi.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/midi/midi.h new file mode 100644 index 000000000..74dc41749 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/midi/midi.h @@ -0,0 +1,212 @@ +/* + * 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 group_class + * \defgroup ClassDriver_CDC Communication Device Class (CDC) + * Currently only Abstract Control Model subclass is supported + * @{ */ + +#ifndef _TUSB_MIDI_H__ +#define _TUSB_MIDI_H__ + +#include "common/tusb_common.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Class Specific Descriptor +//--------------------------------------------------------------------+ + +typedef enum +{ + MIDI_CS_INTERFACE_HEADER = 0x01, + MIDI_CS_INTERFACE_IN_JACK = 0x02, + MIDI_CS_INTERFACE_OUT_JACK = 0x03, + MIDI_CS_INTERFACE_ELEMENT = 0x04, +} midi_cs_interface_subtype_t; + +typedef enum +{ + MIDI_CS_ENDPOINT_GENERAL = 0x01 +} midi_cs_endpoint_subtype_t; + +typedef enum +{ + MIDI_JACK_EMBEDDED = 0x01, + MIDI_JACK_EXTERNAL = 0x02 +} midi_jack_type_t; + +typedef enum +{ + MIDI_CIN_MISC = 0, + MIDI_CIN_CABLE_EVENT = 1, + MIDI_CIN_SYSCOM_2BYTE = 2, // 2 byte system common message e.g MTC, SongSelect + MIDI_CIN_SYSCOM_3BYTE = 3, // 3 byte system common message e.g SPP + MIDI_CIN_SYSEX_START = 4, // SysEx starts or continue + MIDI_CIN_SYSEX_END_1BYTE = 5, // SysEx ends with 1 data, or 1 byte system common message + MIDI_CIN_SYSEX_END_2BYTE = 6, // SysEx ends with 2 data + MIDI_CIN_SYSEX_END_3BYTE = 7, // SysEx ends with 3 data + MIDI_CIN_NOTE_ON = 8, + MIDI_CIN_NOTE_OFF = 9, + MIDI_CIN_POLY_KEYPRESS = 10, + MIDI_CIN_CONTROL_CHANGE = 11, + MIDI_CIN_PROGRAM_CHANGE = 12, + MIDI_CIN_CHANNEL_PRESSURE = 13, + MIDI_CIN_PITCH_BEND_CHANGE = 14, + MIDI_CIN_1BYTE_DATA = 15 +} midi_code_index_number_t; + +// MIDI 1.0 status byte +enum +{ + //------------- System Exclusive -------------// + MIDI_STATUS_SYSEX_START = 0xF0, + MIDI_STATUS_SYSEX_END = 0xF7, + + //------------- System Common -------------// + MIDI_STATUS_SYSCOM_TIME_CODE_QUARTER_FRAME = 0xF1, + MIDI_STATUS_SYSCOM_SONG_POSITION_POINTER = 0xF2, + MIDI_STATUS_SYSCOM_SONG_SELECT = 0xF3, + // F4, F5 is undefined + MIDI_STATUS_SYSCOM_TUNE_REQUEST = 0xF6, + + //------------- System RealTime -------------// + MIDI_STATUS_SYSREAL_TIMING_CLOCK = 0xF8, + // 0xF9 is undefined + MIDI_STATUS_SYSREAL_START = 0xFA, + MIDI_STATUS_SYSREAL_CONTINUE = 0xFB, + MIDI_STATUS_SYSREAL_STOP = 0xFC, + // 0xFD is undefined + MIDI_STATUS_SYSREAL_ACTIVE_SENSING = 0xFE, + MIDI_STATUS_SYSREAL_SYSTEM_RESET = 0xFF, +}; + +/// MIDI Interface Header Descriptor +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific + uint8_t bDescriptorSubType ; ///< Descriptor SubType + uint16_t bcdMSC ; ///< MidiStreaming SubClass release number in Binary-Coded Decimal + uint16_t wTotalLength ; +} midi_desc_header_t; + +/// MIDI In Jack Descriptor +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific + uint8_t bDescriptorSubType ; ///< Descriptor SubType + uint8_t bJackType ; ///< Embedded or External + uint8_t bJackID ; ///< Unique ID for MIDI IN Jack + uint8_t iJack ; ///< string descriptor +} midi_desc_in_jack_t; + + +/// MIDI Out Jack Descriptor with single pin +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific + uint8_t bDescriptorSubType ; ///< Descriptor SubType + uint8_t bJackType ; ///< Embedded or External + uint8_t bJackID ; ///< Unique ID for MIDI IN Jack + uint8_t bNrInputPins; + + uint8_t baSourceID; + uint8_t baSourcePin; + + uint8_t iJack ; ///< string descriptor +} midi_desc_out_jack_t ; + +/// MIDI Out Jack Descriptor with multiple pins +#define midi_desc_out_jack_n_t(input_num) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength ; \ + uint8_t bDescriptorType ; \ + uint8_t bDescriptorSubType ; \ + uint8_t bJackType ; \ + uint8_t bJackID ; \ + uint8_t bNrInputPins ; \ + struct TU_ATTR_PACKED { \ + uint8_t baSourceID; \ + uint8_t baSourcePin; \ + } pins[input_num]; \ + uint8_t iJack ; \ + } + +/// MIDI Element Descriptor +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor in bytes. + uint8_t bDescriptorType ; ///< Descriptor Type, must be Class-Specific + uint8_t bDescriptorSubType ; ///< Descriptor SubType + uint8_t bElementID; + + uint8_t bNrInputPins; + uint8_t baSourceID; + uint8_t baSourcePin; + + uint8_t bNrOutputPins; + uint8_t bInTerminalLink; + uint8_t bOutTerminalLink; + uint8_t bElCapsSize; + + uint16_t bmElementCaps; + uint8_t iElement; +} midi_desc_element_t; + +/// MIDI Element Descriptor with multiple pins +#define midi_desc_element_n_t(input_num) \ + struct TU_ATTR_PACKED { \ + uint8_t bLength; \ + uint8_t bDescriptorType; \ + uint8_t bDescriptorSubType; \ + uint8_t bElementID; \ + uint8_t bNrInputPins; \ + struct TU_ATTR_PACKED { \ + uint8_t baSourceID; \ + uint8_t baSourcePin; \ + } pins[input_num]; \ + uint8_t bNrOutputPins; \ + uint8_t bInTerminalLink; \ + uint8_t bOutTerminalLink; \ + uint8_t bElCapsSize; \ + uint16_t bmElementCaps; \ + uint8_t iElement; \ + } + +/** @} */ + +#ifdef __cplusplus + } +#endif + +#endif + +/** @} */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/midi/midi_device.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/midi/midi_device.c new file mode 100644 index 000000000..b08b362f9 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/midi/midi_device.c @@ -0,0 +1,545 @@ +/* + * 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 (TUSB_OPT_DEVICE_ENABLED && CFG_TUD_MIDI) + +//--------------------------------------------------------------------+ +// INCLUDE +//--------------------------------------------------------------------+ +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "midi_device.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + +typedef struct +{ + uint8_t buffer[4]; + uint8_t index; + uint8_t total; +}midid_stream_t; + +typedef struct +{ + uint8_t itf_num; + uint8_t ep_in; + uint8_t ep_out; + + // For Stream read()/write() API + // Messages are always 4 bytes long, queue them for reading and writing so the + // callers can use the Stream interface with single-byte read/write calls. + midid_stream_t stream_write; + midid_stream_t stream_read; + + /*------------- From this point, data is not cleared by bus reset -------------*/ + // FIFO + tu_fifo_t rx_ff; + tu_fifo_t tx_ff; + uint8_t rx_ff_buf[CFG_TUD_MIDI_RX_BUFSIZE]; + uint8_t tx_ff_buf[CFG_TUD_MIDI_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_MIDI_EP_BUFSIZE]; + CFG_TUSB_MEM_ALIGN uint8_t epin_buf[CFG_TUD_MIDI_EP_BUFSIZE]; + +} midid_interface_t; + +#define ITF_MEM_RESET_SIZE offsetof(midid_interface_t, rx_ff) + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +CFG_TUSB_MEM_SECTION midid_interface_t _midid_itf[CFG_TUD_MIDI]; + +bool tud_midi_n_mounted (uint8_t itf) +{ + midid_interface_t* midi = &_midid_itf[itf]; + return midi->ep_in && midi->ep_out; +} + +static void _prep_out_transaction (midid_interface_t* p_midi) +{ + uint8_t const rhport = TUD_OPT_RHPORT; + uint16_t available = tu_fifo_remaining(&p_midi->rx_ff); + + // Prepare for incoming data but only allow what we can store in the ring buffer. + // TODO Actually we can still carry out the transfer, keeping count of received bytes + // and slowly move it to the FIFO when read(). + // This pre-check reduces endpoint claiming + TU_VERIFY(available >= sizeof(p_midi->epout_buf), ); + + // claim endpoint + TU_VERIFY(usbd_edpt_claim(rhport, p_midi->ep_out), ); + + // fifo can be changed before endpoint is claimed + available = tu_fifo_remaining(&p_midi->rx_ff); + + if ( available >= sizeof(p_midi->epout_buf) ) { + usbd_edpt_xfer(rhport, p_midi->ep_out, p_midi->epout_buf, sizeof(p_midi->epout_buf)); + }else + { + // Release endpoint since we don't make any transfer + usbd_edpt_release(rhport, p_midi->ep_out); + } +} + +//--------------------------------------------------------------------+ +// READ API +//--------------------------------------------------------------------+ +uint32_t tud_midi_n_available(uint8_t itf, uint8_t cable_num) +{ + (void) cable_num; + + midid_interface_t* midi = &_midid_itf[itf]; + midid_stream_t const* stream = &midi->stream_read; + + // when using with packet API stream total & index are both zero + return tu_fifo_count(&midi->rx_ff) + (stream->total - stream->index); +} + +uint32_t tud_midi_n_stream_read(uint8_t itf, uint8_t cable_num, void* buffer, uint32_t bufsize) +{ + (void) cable_num; + TU_VERIFY(bufsize, 0); + + uint8_t* buf8 = (uint8_t*) buffer; + + midid_interface_t* midi = &_midid_itf[itf]; + midid_stream_t* stream = &midi->stream_read; + + uint32_t total_read = 0; + while( bufsize ) + { + // Get new packet from fifo, then set packet expected bytes + if ( stream->total == 0 ) + { + // return if there is no more data from fifo + if ( !tud_midi_n_packet_read(itf, stream->buffer) ) return total_read; + + uint8_t const code_index = stream->buffer[0] & 0x0f; + + // MIDI 1.0 Table 4-1: Code Index Number Classifications + switch(code_index) + { + case MIDI_CIN_MISC: + case MIDI_CIN_CABLE_EVENT: + // These are reserved and unused, possibly issue somewhere, skip this packet + return 0; + break; + + case MIDI_CIN_SYSEX_END_1BYTE: + case MIDI_CIN_1BYTE_DATA: + stream->total = 1; + break; + + case MIDI_CIN_SYSCOM_2BYTE : + case MIDI_CIN_SYSEX_END_2BYTE : + case MIDI_CIN_PROGRAM_CHANGE : + case MIDI_CIN_CHANNEL_PRESSURE : + stream->total = 2; + break; + + default: + stream->total = 3; + break; + } + } + + // Copy data up to bufsize + uint32_t const count = tu_min32(stream->total - stream->index, bufsize); + + // Skip the header (1st byte) in the buffer + memcpy(buf8, stream->buffer + 1 + stream->index, count); + + total_read += count; + stream->index += count; + buf8 += count; + bufsize -= count; + + // complete current event packet, reset stream + if ( stream->total == stream->index ) + { + stream->index = 0; + stream->total = 0; + } + } + + return total_read; +} + +bool tud_midi_n_packet_read (uint8_t itf, uint8_t packet[4]) +{ + midid_interface_t* midi = &_midid_itf[itf]; + TU_VERIFY(midi->ep_out); + + uint32_t const num_read = tu_fifo_read_n(&midi->rx_ff, packet, 4); + _prep_out_transaction(midi); + return (num_read == 4); +} + +//--------------------------------------------------------------------+ +// WRITE API +//--------------------------------------------------------------------+ + +static uint32_t write_flush(midid_interface_t* midi) +{ + // No data to send + if ( !tu_fifo_count(&midi->tx_ff) ) return 0; + + uint8_t const rhport = TUD_OPT_RHPORT; + + // skip if previous transfer not complete + TU_VERIFY( usbd_edpt_claim(rhport, midi->ep_in), 0 ); + + uint16_t count = tu_fifo_read_n(&midi->tx_ff, midi->epin_buf, CFG_TUD_MIDI_EP_BUFSIZE); + + if (count) + { + TU_ASSERT( usbd_edpt_xfer(rhport, midi->ep_in, midi->epin_buf, count), 0 ); + return count; + }else + { + // Release endpoint since we don't make any transfer + usbd_edpt_release(rhport, midi->ep_in); + return 0; + } +} + +uint32_t tud_midi_n_stream_write(uint8_t itf, uint8_t cable_num, uint8_t const* buffer, uint32_t bufsize) +{ + midid_interface_t* midi = &_midid_itf[itf]; + TU_VERIFY(midi->ep_in, 0); + + midid_stream_t* stream = &midi->stream_write; + + uint32_t i = 0; + while ( (i < bufsize) && (tu_fifo_remaining(&midi->tx_ff) >= 4) ) + { + uint8_t const data = buffer[i]; + i++; + + if ( stream->index == 0 ) + { + //------------- New event packet -------------// + + uint8_t const msg = data >> 4; + + stream->index = 2; + stream->buffer[1] = data; + + // Check to see if we're still in a SysEx transmit. + if ( stream->buffer[0] == MIDI_CIN_SYSEX_START ) + { + if ( data == MIDI_STATUS_SYSEX_END ) + { + stream->buffer[0] = MIDI_CIN_SYSEX_END_1BYTE; + stream->total = 2; + } + else + { + stream->total = 4; + } + } + else if ( (msg >= 0x8 && msg <= 0xB) || msg == 0xE ) + { + // Channel Voice Messages + stream->buffer[0] = (cable_num << 4) | msg; + stream->total = 4; + } + else if ( msg == 0xC || msg == 0xD) + { + // Channel Voice Messages, two-byte variants (Program Change and Channel Pressure) + stream->buffer[0] = (cable_num << 4) | msg; + stream->total = 3; + } + else if ( msg == 0xf ) + { + // System message + if ( data == MIDI_STATUS_SYSEX_START ) + { + stream->buffer[0] = MIDI_CIN_SYSEX_START; + stream->total = 4; + } + else if ( data == MIDI_STATUS_SYSCOM_TIME_CODE_QUARTER_FRAME || data == MIDI_STATUS_SYSCOM_SONG_SELECT ) + { + stream->buffer[0] = MIDI_CIN_SYSCOM_2BYTE; + stream->total = 3; + } + else if ( data == MIDI_STATUS_SYSCOM_SONG_POSITION_POINTER ) + { + stream->buffer[0] = MIDI_CIN_SYSCOM_3BYTE; + stream->total = 4; + } + else + { + stream->buffer[0] = MIDI_CIN_SYSEX_END_1BYTE; + stream->total = 2; + } + } + else + { + // Pack individual bytes if we don't support packing them into words. + stream->buffer[0] = cable_num << 4 | 0xf; + stream->buffer[2] = 0; + stream->buffer[3] = 0; + stream->index = 2; + stream->total = 2; + } + } + else + { + //------------- On-going (buffering) packet -------------// + + TU_ASSERT(stream->index < 4, i); + stream->buffer[stream->index] = data; + stream->index++; + + // See if this byte ends a SysEx. + if ( stream->buffer[0] == MIDI_CIN_SYSEX_START && data == MIDI_STATUS_SYSEX_END ) + { + stream->buffer[0] = MIDI_CIN_SYSEX_START + (stream->index - 1); + stream->total = stream->index; + } + } + + // Send out packet + if ( stream->index == stream->total ) + { + // zeroes unused bytes + for(uint8_t idx = stream->total; idx < 4; idx++) stream->buffer[idx] = 0; + + uint16_t const count = tu_fifo_write_n(&midi->tx_ff, stream->buffer, 4); + + // complete current event packet, reset stream + stream->index = stream->total = 0; + + // FIFO overflown, since we already check fifo remaining. It is probably race condition + TU_ASSERT(count == 4, i); + } + } + + write_flush(midi); + + return i; +} + +bool tud_midi_n_packet_write (uint8_t itf, uint8_t const packet[4]) +{ + midid_interface_t* midi = &_midid_itf[itf]; + TU_VERIFY(midi->ep_in); + + if (tu_fifo_remaining(&midi->tx_ff) < 4) return false; + + tu_fifo_write_n(&midi->tx_ff, packet, 4); + write_flush(midi); + + return true; +} + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void midid_init(void) +{ + tu_memclr(_midid_itf, sizeof(_midid_itf)); + + for(uint8_t i=0; irx_ff, midi->rx_ff_buf, CFG_TUD_MIDI_RX_BUFSIZE, 1, false); // true, true + tu_fifo_config(&midi->tx_ff, midi->tx_ff_buf, CFG_TUD_MIDI_TX_BUFSIZE, 1, false); // OBVS. + + #if CFG_FIFO_MUTEX + tu_fifo_config_mutex(&midi->rx_ff, NULL, osal_mutex_create(&midi->rx_ff_mutex)); + tu_fifo_config_mutex(&midi->tx_ff, osal_mutex_create(&midi->tx_ff_mutex), NULL); + #endif + } +} + +void midid_reset(uint8_t rhport) +{ + (void) rhport; + + for(uint8_t i=0; irx_ff); + tu_fifo_clear(&midi->tx_ff); + } +} + +uint16_t midid_open(uint8_t rhport, tusb_desc_interface_t const * desc_itf, uint16_t max_len) +{ + // 1st Interface is Audio Control v1 + TU_VERIFY(TUSB_CLASS_AUDIO == desc_itf->bInterfaceClass && + AUDIO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass && + AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_itf->bInterfaceProtocol, 0); + + uint16_t drv_len = tu_desc_len(desc_itf); + uint8_t const * p_desc = tu_desc_next(desc_itf); + + // Skip Class Specific descriptors + while ( TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && drv_len <= max_len ) + { + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } + + // 2nd Interface is MIDI Streaming + TU_VERIFY(TUSB_DESC_INTERFACE == tu_desc_type(p_desc), 0); + tusb_desc_interface_t const * desc_midi = (tusb_desc_interface_t const *) p_desc; + + TU_VERIFY(TUSB_CLASS_AUDIO == desc_midi->bInterfaceClass && + AUDIO_SUBCLASS_MIDI_STREAMING == desc_midi->bInterfaceSubClass && + AUDIO_FUNC_PROTOCOL_CODE_UNDEF == desc_midi->bInterfaceProtocol, 0); + + // Find available interface + midid_interface_t * p_midi = NULL; + for(uint8_t i=0; iitf_num = desc_midi->bInterfaceNumber; + (void) p_midi->itf_num; + + // next descriptor + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + + // Find and open endpoint descriptors + uint8_t found_endpoints = 0; + while ( (found_endpoints < desc_midi->bNumEndpoints) && (drv_len <= max_len) ) + { + if ( TUSB_DESC_ENDPOINT == tu_desc_type(p_desc) ) + { + TU_ASSERT(usbd_edpt_open(rhport, (tusb_desc_endpoint_t const *) p_desc), 0); + uint8_t ep_addr = ((tusb_desc_endpoint_t const *) p_desc)->bEndpointAddress; + + if (tu_edpt_dir(ep_addr) == TUSB_DIR_IN) + { + p_midi->ep_in = ep_addr; + } else { + p_midi->ep_out = ep_addr; + } + + // Class Specific MIDI Stream endpoint descriptor + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + + found_endpoints += 1; + } + + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } + + // Prepare for incoming data + _prep_out_transaction(p_midi); + + return drv_len; +} + +// 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 midid_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) +{ + (void) rhport; + (void) stage; + (void) request; + + // driver doesn't support any request yet + return false; +} + +bool midid_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) +{ + (void) result; + (void) rhport; + + uint8_t itf; + midid_interface_t* p_midi; + + // Identify which interface to use + for (itf = 0; itf < CFG_TUD_MIDI; itf++) + { + p_midi = &_midid_itf[itf]; + if ( ( ep_addr == p_midi->ep_out ) || ( ep_addr == p_midi->ep_in ) ) break; + } + TU_ASSERT(itf < CFG_TUD_MIDI); + + // receive new data + if ( ep_addr == p_midi->ep_out ) + { + tu_fifo_write_n(&p_midi->rx_ff, p_midi->epout_buf, xferred_bytes); + + // invoke receive callback if available + if (tud_midi_rx_cb) tud_midi_rx_cb(itf); + + // prepare for next + // TODO for now ep_out is not used by public API therefore there is no race condition, + // and does not need to claim like ep_in + _prep_out_transaction(p_midi); + } + else if ( ep_addr == p_midi->ep_in ) + { + if (0 == write_flush(p_midi)) + { + // If there is no data left, a ZLP should be sent if + // xferred_bytes is multiple of EP size and not zero + if ( !tu_fifo_count(&p_midi->tx_ff) && xferred_bytes && (0 == (xferred_bytes % CFG_TUD_MIDI_EP_BUFSIZE)) ) + { + if ( usbd_edpt_claim(rhport, p_midi->ep_in) ) + { + usbd_edpt_xfer(rhport, p_midi->ep_in, NULL, 0); + } + } + } + } + + return true; +} + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/midi/midi_device.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/midi/midi_device.h new file mode 100644 index 000000000..211edc8d1 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/midi/midi_device.h @@ -0,0 +1,173 @@ +/* + * 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_MIDI_DEVICE_H_ +#define _TUSB_MIDI_DEVICE_H_ + +#include "class/audio/audio.h" +#include "midi.h" + +//--------------------------------------------------------------------+ +// Class Driver Configuration +//--------------------------------------------------------------------+ + +#if !defined(CFG_TUD_MIDI_EP_BUFSIZE) && defined(CFG_TUD_MIDI_EPSIZE) + #warning CFG_TUD_MIDI_EPSIZE is renamed to CFG_TUD_MIDI_EP_BUFSIZE, please update to use the new name + #define CFG_TUD_MIDI_EP_BUFSIZE CFG_TUD_MIDI_EPSIZE +#endif + +#ifndef CFG_TUD_MIDI_EP_BUFSIZE + #define CFG_TUD_MIDI_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +/** \addtogroup MIDI_Serial Serial + * @{ + * \defgroup MIDI_Serial_Device Device + * @{ */ + +//--------------------------------------------------------------------+ +// Application API (Multiple Interfaces) +// CFG_TUD_MIDI > 1 +//--------------------------------------------------------------------+ + +// Check if midi interface is mounted +bool tud_midi_n_mounted (uint8_t itf); + +// Get the number of bytes available for reading +uint32_t tud_midi_n_available (uint8_t itf, uint8_t cable_num); + +// Read byte stream (legacy) +uint32_t tud_midi_n_stream_read (uint8_t itf, uint8_t cable_num, void* buffer, uint32_t bufsize); + +// Write byte Stream (legacy) +uint32_t tud_midi_n_stream_write (uint8_t itf, uint8_t cable_num, uint8_t const* buffer, uint32_t bufsize); + +// Read event packet (4 bytes) +bool tud_midi_n_packet_read (uint8_t itf, uint8_t packet[4]); + +// Write event packet (4 bytes) +bool tud_midi_n_packet_write (uint8_t itf, uint8_t const packet[4]); + +//--------------------------------------------------------------------+ +// Application API (Single Interface) +//--------------------------------------------------------------------+ +static inline bool tud_midi_mounted (void); +static inline uint32_t tud_midi_available (void); + +static inline uint32_t tud_midi_stream_read (void* buffer, uint32_t bufsize); +static inline uint32_t tud_midi_stream_write (uint8_t cable_num, uint8_t const* buffer, uint32_t bufsize); + +static inline bool tud_midi_packet_read (uint8_t packet[4]); +static inline bool tud_midi_packet_write (uint8_t const packet[4]); + +//------------- Deprecated API name -------------// +// TODO remove after 0.10.0 release + +TU_ATTR_DEPRECATED("tud_midi_read() is renamed to tud_midi_stream_read()") +static inline uint32_t tud_midi_read (void* buffer, uint32_t bufsize) +{ + return tud_midi_stream_read(buffer, bufsize); +} + +TU_ATTR_DEPRECATED("tud_midi_write() is renamed to tud_midi_stream_write()") +static inline uint32_t tud_midi_write(uint8_t cable_num, uint8_t const* buffer, uint32_t bufsize) +{ + return tud_midi_stream_write(cable_num, buffer, bufsize); +} + + +TU_ATTR_DEPRECATED("tud_midi_send() is renamed to tud_midi_packet_write()") +static inline bool tud_midi_send(uint8_t packet[4]) +{ + return tud_midi_packet_write(packet); +} + +TU_ATTR_DEPRECATED("tud_midi_receive() is renamed to tud_midi_packet_read()") +static inline bool tud_midi_receive(uint8_t packet[4]) +{ + return tud_midi_packet_read(packet); +} + +//--------------------------------------------------------------------+ +// Application Callback API (weak is optional) +//--------------------------------------------------------------------+ +TU_ATTR_WEAK void tud_midi_rx_cb(uint8_t itf); + +//--------------------------------------------------------------------+ +// Inline Functions +//--------------------------------------------------------------------+ + +static inline bool tud_midi_mounted (void) +{ + return tud_midi_n_mounted(0); +} + +static inline uint32_t tud_midi_available (void) +{ + return tud_midi_n_available(0, 0); +} + +static inline uint32_t tud_midi_stream_read (void* buffer, uint32_t bufsize) +{ + return tud_midi_n_stream_read(0, 0, buffer, bufsize); +} + +static inline uint32_t tud_midi_stream_write (uint8_t cable_num, uint8_t const* buffer, uint32_t bufsize) +{ + return tud_midi_n_stream_write(0, cable_num, buffer, bufsize); +} + +static inline bool tud_midi_packet_read (uint8_t packet[4]) +{ + return tud_midi_n_packet_read(0, packet); +} + +static inline bool tud_midi_packet_write (uint8_t const packet[4]) +{ + return tud_midi_n_packet_write(0, packet); +} + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void midid_init (void); +void midid_reset (uint8_t rhport); +uint16_t midid_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool midid_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); +bool midid_xfer_cb (uint8_t rhport, uint8_t edpt_addr, xfer_result_t result, uint32_t xferred_bytes); + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_MIDI_DEVICE_H_ */ + +/** @} */ +/** @} */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/msc/msc.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/msc/msc.h new file mode 100644 index 000000000..84b6e4d79 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/msc/msc.h @@ -0,0 +1,382 @@ +/* + * 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_H_ +#define _TUSB_MSC_H_ + +#include "common/tusb_common.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Mass Storage Class Constant +//--------------------------------------------------------------------+ +/// MassStorage Subclass +typedef enum +{ + MSC_SUBCLASS_RBC = 1 , ///< Reduced Block Commands (RBC) T10 Project 1240-D + MSC_SUBCLASS_SFF_MMC , ///< SFF-8020i, MMC-2 (ATAPI). Typically used by a CD/DVD device + MSC_SUBCLASS_QIC , ///< QIC-157. Typically used by a tape device + MSC_SUBCLASS_UFI , ///< UFI. Typically used by Floppy Disk Drive (FDD) device + MSC_SUBCLASS_SFF , ///< SFF-8070i. Can be used by Floppy Disk Drive (FDD) device + MSC_SUBCLASS_SCSI ///< SCSI transparent command set +}msc_subclass_type_t; + +enum { + MSC_CBW_SIGNATURE = 0x43425355, ///< Constant value of 43425355h (little endian) + MSC_CSW_SIGNATURE = 0x53425355 ///< Constant value of 53425355h (little endian) +}; + +/// \brief MassStorage Protocol. +/// \details CBI only approved to use with full-speed floopy disk & should not used with highspeed or device other than floopy +typedef enum +{ + MSC_PROTOCOL_CBI = 0 , ///< Control/Bulk/Interrupt protocol (with command completion interrupt) + MSC_PROTOCOL_CBI_NO_INTERRUPT = 1 , ///< Control/Bulk/Interrupt protocol (without command completion interrupt) + MSC_PROTOCOL_BOT = 0x50 ///< Bulk-Only Transport +}msc_protocol_type_t; + +/// MassStorage Class-Specific Control Request +typedef enum +{ + MSC_REQ_GET_MAX_LUN = 254, ///< The Get Max LUN device request is used to determine the number of logical units supported by the device. Logical Unit Numbers on the device shall be numbered contiguously starting from LUN 0 to a maximum LUN of 15 + MSC_REQ_RESET = 255 ///< This request is used to reset the mass storage device and its associated interface. This class-specific request shall ready the device for the next CBW from the host. +}msc_request_type_t; + +/// \brief Command Block Status Values +/// \details Indicates the success or failure of the command. The device shall set this byte to zero if the command completed +/// successfully. A non-zero value shall indicate a failure during command execution according to the following +typedef enum +{ + MSC_CSW_STATUS_PASSED = 0 , ///< MSC_CSW_STATUS_PASSED + MSC_CSW_STATUS_FAILED , ///< MSC_CSW_STATUS_FAILED + MSC_CSW_STATUS_PHASE_ERROR ///< MSC_CSW_STATUS_PHASE_ERROR +}msc_csw_status_t; + +/// Command Block Wrapper +typedef struct TU_ATTR_PACKED +{ + uint32_t signature; ///< Signature that helps identify this data packet as a CBW. The signature field shall contain the value 43425355h (little endian), indicating a CBW. + uint32_t tag; ///< Tag sent by the host. The device shall echo the contents of this field back to the host in the dCSWTagfield of the associated CSW. The dCSWTagpositively associates a CSW with the corresponding CBW. + uint32_t total_bytes; ///< The number of bytes of data that the host expects to transfer on the Bulk-In or Bulk-Out endpoint (as indicated by the Direction bit) during the execution of this command. If this field is zero, the device and the host shall transfer no data between the CBW and the associated CSW, and the device shall ignore the value of the Direction bit in bmCBWFlags. + uint8_t dir; ///< Bit 7 of this field define transfer direction \n - 0 : Data-Out from host to the device. \n - 1 : Data-In from the device to the host. + uint8_t lun; ///< The device Logical Unit Number (LUN) to which the command block is being sent. For devices that support multiple LUNs, the host shall place into this field the LUN to which this command block is addressed. Otherwise, the host shall set this field to zero. + uint8_t cmd_len; ///< The valid length of the CBWCBin bytes. This defines the valid length of the command block. The only legal values are 1 through 16 + uint8_t command[16]; ///< The command block to be executed by the device. The device shall interpret the first cmd_len bytes in this field as a command block +}msc_cbw_t; + +TU_VERIFY_STATIC(sizeof(msc_cbw_t) == 31, "size is not correct"); + +/// Command Status Wrapper +typedef struct TU_ATTR_PACKED +{ + uint32_t signature ; ///< Signature that helps identify this data packet as a CSW. The signature field shall contain the value 53425355h (little endian), indicating CSW. + uint32_t tag ; ///< The device shall set this field to the value received in the dCBWTag of the associated CBW. + uint32_t data_residue ; ///< For Data-Out the device shall report in the dCSWDataResiduethe difference between the amount of data expected as stated in the dCBWDataTransferLength, and the actual amount of data processed by the device. For Data-In the device shall report in the dCSWDataResiduethe difference between the amount of data expected as stated in the dCBWDataTransferLengthand the actual amount of relevant data sent by the device + uint8_t status ; ///< indicates the success or failure of the command. Values from \ref msc_csw_status_t +}msc_csw_t; + +TU_VERIFY_STATIC(sizeof(msc_csw_t) == 13, "size is not correct"); + +//--------------------------------------------------------------------+ +// SCSI Constant +//--------------------------------------------------------------------+ + +/// SCSI Command Operation Code +typedef enum +{ + SCSI_CMD_TEST_UNIT_READY = 0x00, ///< The SCSI Test Unit Ready command is used to determine if a device is ready to transfer data (read/write), i.e. if a disk has spun up, if a tape is loaded and ready etc. The device does not perform a self-test operation. + SCSI_CMD_INQUIRY = 0x12, ///< The SCSI Inquiry command is used to obtain basic information from a target device. + SCSI_CMD_MODE_SELECT_6 = 0x15, ///< provides a means for the application client to specify medium, logical unit, or peripheral device parameters to the device server. Device servers that implement the MODE SELECT(6) command shall also implement the MODE SENSE(6) command. Application clients should issue MODE SENSE(6) prior to each MODE SELECT(6) to determine supported mode pages, page lengths, and other parameters. + SCSI_CMD_MODE_SENSE_6 = 0x1A, ///< provides a means for a device server to report parameters to an application client. It is a complementary command to the MODE SELECT(6) command. Device servers that implement the MODE SENSE(6) command shall also implement the MODE SELECT(6) command. + SCSI_CMD_START_STOP_UNIT = 0x1B, + SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1E, + SCSI_CMD_READ_CAPACITY_10 = 0x25, ///< The SCSI Read Capacity command is used to obtain data capacity information from a target device. + SCSI_CMD_REQUEST_SENSE = 0x03, ///< The SCSI Request Sense command is part of the SCSI computer protocol standard. This command is used to obtain sense data -- status/error information -- from a target device. + SCSI_CMD_READ_FORMAT_CAPACITY = 0x23, ///< The command allows the Host to request a list of the possible format capacities for an installed writable media. This command also has the capability to report the writable capacity for a media when it is installed + SCSI_CMD_READ_10 = 0x28, ///< The READ (10) command requests that the device server read the specified logical block(s) and transfer them to the data-in buffer. + SCSI_CMD_WRITE_10 = 0x2A, ///< The WRITE (10) command requests thatthe device server transfer the specified logical block(s) from the data-out buffer and write them. +}scsi_cmd_type_t; + +/// SCSI Sense Key +typedef enum +{ + SCSI_SENSE_NONE = 0x00, ///< no specific Sense Key. This would be the case for a successful command + SCSI_SENSE_RECOVERED_ERROR = 0x01, ///< ndicates the last command completed successfully with some recovery action performed by the disc drive. + SCSI_SENSE_NOT_READY = 0x02, ///< Indicates the logical unit addressed cannot be accessed. + SCSI_SENSE_MEDIUM_ERROR = 0x03, ///< Indicates the command terminated with a non-recovered error condition. + SCSI_SENSE_HARDWARE_ERROR = 0x04, ///< Indicates the disc drive detected a nonrecoverable hardware failure while performing the command or during a self test. + SCSI_SENSE_ILLEGAL_REQUEST = 0x05, ///< Indicates an illegal parameter in the command descriptor block or in the additional parameters + SCSI_SENSE_UNIT_ATTENTION = 0x06, ///< Indicates the disc drive may have been reset. + SCSI_SENSE_DATA_PROTECT = 0x07, ///< Indicates that a command that reads or writes the medium was attempted on a block that is protected from this operation. The read or write operation is not performed. + SCSI_SENSE_FIRMWARE_ERROR = 0x08, ///< Vendor specific sense key. + SCSI_SENSE_ABORTED_COMMAND = 0x0b, ///< Indicates the disc drive aborted the command. + SCSI_SENSE_EQUAL = 0x0c, ///< Indicates a SEARCH DATA command has satisfied an equal comparison. + SCSI_SENSE_VOLUME_OVERFLOW = 0x0d, ///< Indicates a buffered peripheral device has reached the end of medium partition and data remains in the buffer that has not been written to the medium. + SCSI_SENSE_MISCOMPARE = 0x0e ///< ndicates that the source data did not match the data read from the medium. +}scsi_sense_key_type_t; + +//--------------------------------------------------------------------+ +// SCSI Primary Command (SPC-4) +//--------------------------------------------------------------------+ + +/// SCSI Test Unit Ready Command +typedef struct TU_ATTR_PACKED +{ + uint8_t cmd_code ; ///< SCSI OpCode for \ref SCSI_CMD_TEST_UNIT_READY + uint8_t lun ; ///< Logical Unit + uint8_t reserved[3] ; + uint8_t control ; +} scsi_test_unit_ready_t; + +TU_VERIFY_STATIC(sizeof(scsi_test_unit_ready_t) == 6, "size is not correct"); + +/// SCSI Inquiry Command +typedef struct TU_ATTR_PACKED +{ + uint8_t cmd_code ; ///< SCSI OpCode for \ref SCSI_CMD_INQUIRY + uint8_t reserved1 ; + uint8_t page_code ; + uint8_t reserved2 ; + uint8_t alloc_length ; ///< specifies the maximum number of bytes that USB host has allocated in the Data-In Buffer. An allocation length of zero specifies that no data shall be transferred. + uint8_t control ; +} scsi_inquiry_t, scsi_request_sense_t; + +TU_VERIFY_STATIC(sizeof(scsi_inquiry_t) == 6, "size is not correct"); + +/// SCSI Inquiry Response Data +typedef struct TU_ATTR_PACKED +{ + uint8_t peripheral_device_type : 5; + uint8_t peripheral_qualifier : 3; + + uint8_t : 7; + uint8_t is_removable : 1; + + uint8_t version; + + uint8_t response_data_format : 4; + uint8_t hierarchical_support : 1; + uint8_t normal_aca : 1; + uint8_t : 2; + + uint8_t additional_length; + + uint8_t protect : 1; + uint8_t : 2; + uint8_t third_party_copy : 1; + uint8_t target_port_group_support : 2; + uint8_t access_control_coordinator : 1; + uint8_t scc_support : 1; + + uint8_t addr16 : 1; + uint8_t : 3; + uint8_t multi_port : 1; + uint8_t : 1; // vendor specific + uint8_t enclosure_service : 1; + uint8_t : 1; + + uint8_t : 1; // vendor specific + uint8_t cmd_que : 1; + uint8_t : 2; + uint8_t sync : 1; + uint8_t wbus16 : 1; + uint8_t : 2; + + uint8_t vendor_id[8] ; ///< 8 bytes of ASCII data identifying the vendor of the product. + uint8_t product_id[16]; ///< 16 bytes of ASCII data defined by the vendor. + uint8_t product_rev[4]; ///< 4 bytes of ASCII data defined by the vendor. +} scsi_inquiry_resp_t; + +TU_VERIFY_STATIC(sizeof(scsi_inquiry_resp_t) == 36, "size is not correct"); + + +typedef struct TU_ATTR_PACKED +{ + uint8_t response_code : 7; ///< 70h - current errors, Fixed Format 71h - deferred errors, Fixed Format + uint8_t valid : 1; + + uint8_t reserved; + + uint8_t sense_key : 4; + uint8_t : 1; + uint8_t ili : 1; ///< Incorrect length indicator + uint8_t end_of_medium : 1; + uint8_t filemark : 1; + + uint32_t information; + uint8_t add_sense_len; + uint32_t command_specific_info; + uint8_t add_sense_code; + uint8_t add_sense_qualifier; + uint8_t field_replaceable_unit_code; + + uint8_t sense_key_specific[3]; ///< sense key specific valid bit is bit 7 of key[0], aka MSB in Big Endian layout + +} scsi_sense_fixed_resp_t; + +TU_VERIFY_STATIC(sizeof(scsi_sense_fixed_resp_t) == 18, "size is not correct"); + +typedef struct TU_ATTR_PACKED +{ + uint8_t cmd_code ; ///< SCSI OpCode for \ref SCSI_CMD_MODE_SENSE_6 + + uint8_t : 3; + uint8_t disable_block_descriptor : 1; + uint8_t : 4; + + uint8_t page_code : 6; + uint8_t page_control : 2; + + uint8_t subpage_code; + uint8_t alloc_length; + uint8_t control; +} scsi_mode_sense6_t; + +TU_VERIFY_STATIC( sizeof(scsi_mode_sense6_t) == 6, "size is not correct"); + +// This is only a Mode parameter header(6). +typedef struct TU_ATTR_PACKED +{ + uint8_t data_len; + uint8_t medium_type; + + uint8_t reserved : 7; + bool write_protected : 1; + + uint8_t block_descriptor_len; +} scsi_mode_sense6_resp_t; + +TU_VERIFY_STATIC( sizeof(scsi_mode_sense6_resp_t) == 4, "size is not correct"); + +typedef struct TU_ATTR_PACKED +{ + uint8_t cmd_code; ///< SCSI OpCode for \ref SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL + uint8_t reserved[3]; + uint8_t prohibit_removal; + uint8_t control; +} scsi_prevent_allow_medium_removal_t; + +TU_VERIFY_STATIC( sizeof(scsi_prevent_allow_medium_removal_t) == 6, "size is not correct"); + +typedef struct TU_ATTR_PACKED +{ + uint8_t cmd_code; + + uint8_t immded : 1; + uint8_t : 7; + + uint8_t TU_RESERVED; + + uint8_t power_condition_mod : 4; + uint8_t : 4; + + uint8_t start : 1; + uint8_t load_eject : 1; + uint8_t no_flush : 1; + uint8_t : 1; + uint8_t power_condition : 4; + + uint8_t control; +} scsi_start_stop_unit_t; + +TU_VERIFY_STATIC( sizeof(scsi_start_stop_unit_t) == 6, "size is not correct"); + +//--------------------------------------------------------------------+ +// SCSI MMC +//--------------------------------------------------------------------+ +/// SCSI Read Format Capacity: Write Capacity +typedef struct TU_ATTR_PACKED +{ + uint8_t cmd_code; + uint8_t reserved[6]; + uint16_t alloc_length; + uint8_t control; +} scsi_read_format_capacity_t; + +TU_VERIFY_STATIC( sizeof(scsi_read_format_capacity_t) == 10, "size is not correct"); + +typedef struct TU_ATTR_PACKED{ + uint8_t reserved[3]; + uint8_t list_length; /// must be 8*n, length in bytes of formattable capacity descriptor followed it. + + uint32_t block_num; /// Number of Logical Blocks + uint8_t descriptor_type; // 00: reserved, 01 unformatted media , 10 Formatted media, 11 No media present + + uint8_t reserved2; + uint16_t block_size_u16; + +} scsi_read_format_capacity_data_t; + +TU_VERIFY_STATIC( sizeof(scsi_read_format_capacity_data_t) == 12, "size is not correct"); + +//--------------------------------------------------------------------+ +// SCSI Block Command (SBC-3) +// NOTE: All data in SCSI command are in Big Endian +//--------------------------------------------------------------------+ + +/// SCSI Read Capacity 10 Command: Read Capacity +typedef struct TU_ATTR_PACKED +{ + uint8_t cmd_code ; ///< SCSI OpCode for \ref SCSI_CMD_READ_CAPACITY_10 + uint8_t reserved1 ; + uint32_t lba ; ///< The first Logical Block Address (LBA) accessed by this command + uint16_t reserved2 ; + uint8_t partial_medium_indicator ; + uint8_t control ; +} scsi_read_capacity10_t; + +TU_VERIFY_STATIC(sizeof(scsi_read_capacity10_t) == 10, "size is not correct"); + +/// SCSI Read Capacity 10 Response Data +typedef struct { + uint32_t last_lba ; ///< The last Logical Block Address of the device + uint32_t block_size ; ///< Block size in bytes +} scsi_read_capacity10_resp_t; + +TU_VERIFY_STATIC(sizeof(scsi_read_capacity10_resp_t) == 8, "size is not correct"); + +/// SCSI Read 10 Command +typedef struct TU_ATTR_PACKED +{ + uint8_t cmd_code ; ///< SCSI OpCode + uint8_t reserved ; // has LUN according to wiki + uint32_t lba ; ///< The first Logical Block Address (LBA) accessed by this command + uint8_t reserved2 ; + uint16_t block_count ; ///< Number of Blocks used by this command + uint8_t control ; +} scsi_read10_t, scsi_write10_t; + +TU_VERIFY_STATIC(sizeof(scsi_read10_t) == 10, "size is not correct"); +TU_VERIFY_STATIC(sizeof(scsi_write10_t) == 10, "size is not correct"); + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_MSC_H_ */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/msc/msc_device.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/msc/msc_device.c new file mode 100644 index 000000000..e09a2b11a --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/msc/msc_device.c @@ -0,0 +1,950 @@ +/* + * 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 (TUSB_OPT_DEVICE_ENABLED && CFG_TUD_MSC) + +#include "device/usbd.h" +#include "device/usbd_pvt.h" +#include "device/dcd.h" // for faking dcd_event_xfer_complete + +#include "msc_device.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + +// Can be selectively disabled to reduce logging when troubleshooting other driver +#define MSC_DEBUG 2 + +enum +{ + MSC_STAGE_CMD = 0, + MSC_STAGE_DATA, + MSC_STAGE_STATUS, + MSC_STAGE_STATUS_SENT, + MSC_STAGE_NEED_RESET, +}; + +typedef struct +{ + // TODO optimize alignment + CFG_TUSB_MEM_ALIGN msc_cbw_t cbw; + CFG_TUSB_MEM_ALIGN msc_csw_t csw; + + uint8_t itf_num; + uint8_t ep_in; + uint8_t ep_out; + + // Bulk Only Transfer (BOT) Protocol + uint8_t stage; + uint32_t total_len; // byte to be transferred, can be smaller than total_bytes in cbw + uint32_t xferred_len; // numbered of bytes transferred so far in the Data Stage + + // Sense Response Data + uint8_t sense_key; + uint8_t add_sense_code; + uint8_t add_sense_qualifier; +}mscd_interface_t; + +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static mscd_interface_t _mscd_itf; +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static uint8_t _mscd_buf[CFG_TUD_MSC_EP_BUFSIZE]; + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +static int32_t proc_builtin_scsi(uint8_t lun, uint8_t const scsi_cmd[16], uint8_t* buffer, uint32_t bufsize); +static void proc_read10_cmd(uint8_t rhport, mscd_interface_t* p_msc); + +static void proc_write10_cmd(uint8_t rhport, mscd_interface_t* p_msc); +static void proc_write10_new_data(uint8_t rhport, mscd_interface_t* p_msc, uint32_t xferred_bytes); + +TU_ATTR_ALWAYS_INLINE static inline bool is_data_in(uint8_t dir) +{ + return tu_bit_test(dir, 7); +} + +static inline bool send_csw(uint8_t rhport, mscd_interface_t* p_msc) +{ + // Data residue is always = host expect - actual transferred + p_msc->csw.data_residue = p_msc->cbw.total_bytes - p_msc->xferred_len; + + p_msc->stage = MSC_STAGE_STATUS_SENT; + return usbd_edpt_xfer(rhport, p_msc->ep_in , (uint8_t*) &p_msc->csw, sizeof(msc_csw_t)); +} + +static inline bool prepare_cbw(uint8_t rhport, mscd_interface_t* p_msc) +{ + p_msc->stage = MSC_STAGE_CMD; + return usbd_edpt_xfer(rhport, p_msc->ep_out, (uint8_t*) &p_msc->cbw, sizeof(msc_cbw_t)); +} + +static void fail_scsi_op(uint8_t rhport, mscd_interface_t* p_msc, uint8_t status) +{ + msc_cbw_t const * p_cbw = &p_msc->cbw; + msc_csw_t * p_csw = &p_msc->csw; + + p_csw->status = status; + p_csw->data_residue = p_msc->cbw.total_bytes - p_msc->xferred_len; + p_msc->stage = MSC_STAGE_STATUS; + + // failed but sense key is not set: default to Illegal Request + if ( p_msc->sense_key == 0 ) tud_msc_set_sense(p_cbw->lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); + + // If there is data stage and not yet complete, stall it + if ( p_cbw->total_bytes && p_csw->data_residue ) + { + if ( is_data_in(p_cbw->dir) ) + { + usbd_edpt_stall(rhport, p_msc->ep_in); + } + else + { + usbd_edpt_stall(rhport, p_msc->ep_out); + } + } +} + +static inline uint32_t rdwr10_get_lba(uint8_t const command[]) +{ + // use offsetof to avoid pointer to the odd/unaligned address + uint32_t const lba = tu_unaligned_read32(command + offsetof(scsi_write10_t, lba)); + + // lba is in Big Endian + return tu_ntohl(lba); +} + +static inline uint16_t rdwr10_get_blockcount(msc_cbw_t const* cbw) +{ + uint16_t const block_count = tu_unaligned_read16(cbw->command + offsetof(scsi_write10_t, block_count)); + return tu_ntohs(block_count); +} + +static inline uint16_t rdwr10_get_blocksize(msc_cbw_t const* cbw) +{ + // first extract block count in the command + uint16_t const block_count = rdwr10_get_blockcount(cbw); + + // invalid block count + if (block_count == 0) return 0; + + return (uint16_t) (cbw->total_bytes / block_count); +} + +uint8_t rdwr10_validate_cmd(msc_cbw_t const* cbw) +{ + uint8_t status = MSC_CSW_STATUS_PASSED; + uint16_t const block_count = rdwr10_get_blockcount(cbw); + + if ( cbw->total_bytes == 0 ) + { + if ( block_count ) + { + TU_LOG(MSC_DEBUG, " SCSI case 2 (Hn < Di) or case 3 (Hn < Do) \r\n"); + status = MSC_CSW_STATUS_PHASE_ERROR; + }else + { + // no data transfer, only exist in complaint test suite + } + }else + { + if ( SCSI_CMD_READ_10 == cbw->command[0] && !is_data_in(cbw->dir) ) + { + TU_LOG(MSC_DEBUG, " SCSI case 10 (Ho <> Di)\r\n"); + status = MSC_CSW_STATUS_PHASE_ERROR; + } + else if ( SCSI_CMD_WRITE_10 == cbw->command[0] && is_data_in(cbw->dir) ) + { + TU_LOG(MSC_DEBUG, " SCSI case 8 (Hi <> Do)\r\n"); + status = MSC_CSW_STATUS_PHASE_ERROR; + } + else if ( 0 == block_count ) + { + TU_LOG(MSC_DEBUG, " SCSI case 4 Hi > Dn (READ10) or case 9 Ho > Dn (WRITE10) \r\n"); + status = MSC_CSW_STATUS_FAILED; + } + else if ( cbw->total_bytes / block_count == 0 ) + { + TU_LOG(MSC_DEBUG, " Computed block size = 0. SCSI case 7 Hi < Di (READ10) or case 13 Ho < Do (WRIT10)\r\n"); + status = MSC_CSW_STATUS_PHASE_ERROR; + } + } + + return status; +} + +//--------------------------------------------------------------------+ +// Debug +//--------------------------------------------------------------------+ +#if CFG_TUSB_DEBUG >= 2 + +TU_ATTR_UNUSED static tu_lookup_entry_t const _msc_scsi_cmd_lookup[] = +{ + { .key = SCSI_CMD_TEST_UNIT_READY , .data = "Test Unit Ready" }, + { .key = SCSI_CMD_INQUIRY , .data = "Inquiry" }, + { .key = SCSI_CMD_MODE_SELECT_6 , .data = "Mode_Select 6" }, + { .key = SCSI_CMD_MODE_SENSE_6 , .data = "Mode_Sense 6" }, + { .key = SCSI_CMD_START_STOP_UNIT , .data = "Start Stop Unit" }, + { .key = SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL , .data = "Prevent/Allow Medium Removal" }, + { .key = SCSI_CMD_READ_CAPACITY_10 , .data = "Read Capacity10" }, + { .key = SCSI_CMD_REQUEST_SENSE , .data = "Request Sense" }, + { .key = SCSI_CMD_READ_FORMAT_CAPACITY , .data = "Read Format Capacity" }, + { .key = SCSI_CMD_READ_10 , .data = "Read10" }, + { .key = SCSI_CMD_WRITE_10 , .data = "Write10" } +}; + +TU_ATTR_UNUSED static tu_lookup_table_t const _msc_scsi_cmd_table = +{ + .count = TU_ARRAY_SIZE(_msc_scsi_cmd_lookup), + .items = _msc_scsi_cmd_lookup +}; + +#endif + +//--------------------------------------------------------------------+ +// APPLICATION API +//--------------------------------------------------------------------+ +bool tud_msc_set_sense(uint8_t lun, uint8_t sense_key, uint8_t add_sense_code, uint8_t add_sense_qualifier) +{ + (void) lun; + + _mscd_itf.sense_key = sense_key; + _mscd_itf.add_sense_code = add_sense_code; + _mscd_itf.add_sense_qualifier = add_sense_qualifier; + + return true; +} + +static inline void set_sense_medium_not_present(uint8_t lun) +{ + // default sense is NOT READY, MEDIUM NOT PRESENT + tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x3A, 0x00); +} + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void mscd_init(void) +{ + tu_memclr(&_mscd_itf, sizeof(mscd_interface_t)); +} + +void mscd_reset(uint8_t rhport) +{ + (void) rhport; + tu_memclr(&_mscd_itf, sizeof(mscd_interface_t)); +} + +uint16_t mscd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) +{ + // only support SCSI's BOT protocol + TU_VERIFY(TUSB_CLASS_MSC == itf_desc->bInterfaceClass && + MSC_SUBCLASS_SCSI == itf_desc->bInterfaceSubClass && + MSC_PROTOCOL_BOT == itf_desc->bInterfaceProtocol, 0); + + // msc driver length is fixed + uint16_t const drv_len = sizeof(tusb_desc_interface_t) + 2*sizeof(tusb_desc_endpoint_t); + + // Max length must be at least 1 interface + 2 endpoints + TU_ASSERT(max_len >= drv_len, 0); + + mscd_interface_t * p_msc = &_mscd_itf; + p_msc->itf_num = itf_desc->bInterfaceNumber; + + // Open endpoint pair + TU_ASSERT( usbd_open_edpt_pair(rhport, tu_desc_next(itf_desc), 2, TUSB_XFER_BULK, &p_msc->ep_out, &p_msc->ep_in), 0 ); + + // Prepare for Command Block Wrapper + TU_ASSERT( prepare_cbw(rhport, p_msc), drv_len); + + return drv_len; +} + +static void proc_bot_reset(mscd_interface_t* p_msc) +{ + p_msc->stage = MSC_STAGE_CMD; + p_msc->total_len = 0; + p_msc->xferred_len = 0; + + p_msc->sense_key = 0; + p_msc->add_sense_code = 0; + p_msc->add_sense_qualifier = 0; +} + +// 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 mscd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) +{ + // nothing to do with DATA & ACK stage + if (stage != CONTROL_STAGE_SETUP) return true; + + mscd_interface_t* p_msc = &_mscd_itf; + + // Clear Endpoint Feature (stall) for recovery + if ( TUSB_REQ_TYPE_STANDARD == request->bmRequestType_bit.type && + TUSB_REQ_RCPT_ENDPOINT == request->bmRequestType_bit.recipient && + TUSB_REQ_CLEAR_FEATURE == request->bRequest && + TUSB_REQ_FEATURE_EDPT_HALT == request->wValue ) + { + uint8_t const ep_addr = tu_u16_low(request->wIndex); + + if ( p_msc->stage == MSC_STAGE_NEED_RESET ) + { + // reset recovery is required to recover from this stage + // Clear Stall request cannot resolve this -> continue to stall endpoint + usbd_edpt_stall(rhport, ep_addr); + } + else + { + if ( ep_addr == p_msc->ep_in ) + { + if ( p_msc->stage == MSC_STAGE_STATUS ) + { + // resume sending SCSI status if we are in this stage previously before stalled + TU_ASSERT( send_csw(rhport, p_msc) ); + } + } + else if ( ep_addr == p_msc->ep_out ) + { + if ( p_msc->stage == MSC_STAGE_CMD ) + { + // part of reset recovery (probably due to invalid CBW) -> prepare for new command + // Note: skip if already queued previously + if ( usbd_edpt_ready(rhport, p_msc->ep_out) ) + { + TU_ASSERT( prepare_cbw(rhport, p_msc) ); + } + } + } + } + + return true; + } + + // From this point only handle class request only + TU_VERIFY(request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS); + + switch ( request->bRequest ) + { + case MSC_REQ_RESET: + TU_LOG(MSC_DEBUG, " MSC BOT Reset\r\n"); + TU_VERIFY(request->wValue == 0 && request->wLength == 0); + + // driver state reset + proc_bot_reset(p_msc); + + tud_control_status(rhport, request); + break; + + case MSC_REQ_GET_MAX_LUN: + { + TU_LOG(MSC_DEBUG, " MSC Get Max Lun\r\n"); + TU_VERIFY(request->wValue == 0 && request->wLength == 1); + + uint8_t maxlun = 1; + if (tud_msc_get_maxlun_cb) maxlun = tud_msc_get_maxlun_cb(); + TU_VERIFY(maxlun); + + // MAX LUN is minus 1 by specs + maxlun--; + + tud_control_xfer(rhport, request, &maxlun, 1); + } + break; + + default: return false; // stall unsupported request + } + + return true; +} + +bool mscd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes) +{ + (void) event; + + mscd_interface_t* p_msc = &_mscd_itf; + msc_cbw_t const * p_cbw = &p_msc->cbw; + msc_csw_t * p_csw = &p_msc->csw; + + switch (p_msc->stage) + { + case MSC_STAGE_CMD: + //------------- new CBW received -------------// + // Complete IN while waiting for CMD is usually Status of previous SCSI op, ignore it + if(ep_addr != p_msc->ep_out) return true; + + if ( !(xferred_bytes == sizeof(msc_cbw_t) && p_cbw->signature == MSC_CBW_SIGNATURE) ) + { + TU_LOG(MSC_DEBUG, " SCSI CBW is not valid\r\n"); + + // BOT 6.6.1 If CBW is not valid stall both endpoints until reset recovery + p_msc->stage = MSC_STAGE_NEED_RESET; + + // invalid CBW stall both endpoints + usbd_edpt_stall(rhport, p_msc->ep_in); + usbd_edpt_stall(rhport, p_msc->ep_out); + + return false; + } + + TU_LOG(MSC_DEBUG, " SCSI Command [Lun%u]: %s\r\n", p_cbw->lun, tu_lookup_find(&_msc_scsi_cmd_table, p_cbw->command[0])); + //TU_LOG_MEM(MSC_DEBUG, p_cbw, xferred_bytes, 2); + + p_csw->signature = MSC_CSW_SIGNATURE; + p_csw->tag = p_cbw->tag; + p_csw->data_residue = 0; + p_csw->status = MSC_CSW_STATUS_PASSED; + + /*------------- Parse command and prepare DATA -------------*/ + p_msc->stage = MSC_STAGE_DATA; + p_msc->total_len = p_cbw->total_bytes; + p_msc->xferred_len = 0; + + // Read10 or Write10 + if ( (SCSI_CMD_READ_10 == p_cbw->command[0]) || (SCSI_CMD_WRITE_10 == p_cbw->command[0]) ) + { + uint8_t const status = rdwr10_validate_cmd(p_cbw); + + if ( status != MSC_CSW_STATUS_PASSED) + { + fail_scsi_op(rhport, p_msc, status); + }else if ( p_cbw->total_bytes ) + { + if (SCSI_CMD_READ_10 == p_cbw->command[0]) + { + proc_read10_cmd(rhport, p_msc); + }else + { + proc_write10_cmd(rhport, p_msc); + } + }else + { + // no data transfer, only exist in complaint test suite + p_msc->stage = MSC_STAGE_STATUS; + } + } + else + { + // For other SCSI commands + // 1. OUT : queue transfer (invoke app callback after done) + // 2. IN & Zero: Process if is built-in, else Invoke app callback. Skip DATA if zero length + if ( (p_cbw->total_bytes > 0 ) && !is_data_in(p_cbw->dir) ) + { + if (p_cbw->total_bytes > sizeof(_mscd_buf)) + { + TU_LOG(MSC_DEBUG, " SCSI reject non READ10/WRITE10 with large data\r\n"); + fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED); + }else + { + // Didn't check for case 9 (Ho > Dn), which requires examining scsi command first + // but it is OK to just receive data then responded with failed status + TU_ASSERT( usbd_edpt_xfer(rhport, p_msc->ep_out, _mscd_buf, p_msc->total_len) ); + } + }else + { + // First process if it is a built-in commands + int32_t resplen = proc_builtin_scsi(p_cbw->lun, p_cbw->command, _mscd_buf, sizeof(_mscd_buf)); + + // Invoke user callback if not built-in + if ( (resplen < 0) && (p_msc->sense_key == 0) ) + { + resplen = tud_msc_scsi_cb(p_cbw->lun, p_cbw->command, _mscd_buf, p_msc->total_len); + } + + if ( resplen < 0 ) + { + // unsupported command + TU_LOG(MSC_DEBUG, " SCSI unsupported or failed command\r\n"); + fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED); + } + else if (resplen == 0) + { + if (p_cbw->total_bytes) + { + // 6.7 The 13 Cases: case 4 (Hi > Dn) + // TU_LOG(MSC_DEBUG, " SCSI case 4 (Hi > Dn): %lu\r\n", p_cbw->total_bytes); + fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED); + }else + { + // case 1 Hn = Dn: all good + p_msc->stage = MSC_STAGE_STATUS; + } + } + else + { + if ( p_cbw->total_bytes == 0 ) + { + // 6.7 The 13 Cases: case 2 (Hn < Di) + // TU_LOG(MSC_DEBUG, " SCSI case 2 (Hn < Di): %lu\r\n", p_cbw->total_bytes); + fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED); + }else + { + // cannot return more than host expect + p_msc->total_len = tu_min32((uint32_t) resplen, p_cbw->total_bytes); + TU_ASSERT( usbd_edpt_xfer(rhport, p_msc->ep_in, _mscd_buf, p_msc->total_len) ); + } + } + } + } + break; + + case MSC_STAGE_DATA: + TU_LOG(MSC_DEBUG, " SCSI Data [Lun%u]\r\n", p_cbw->lun); + //TU_LOG_MEM(MSC_DEBUG, _mscd_buf, xferred_bytes, 2); + + if (SCSI_CMD_READ_10 == p_cbw->command[0]) + { + p_msc->xferred_len += xferred_bytes; + + if ( p_msc->xferred_len >= p_msc->total_len ) + { + // Data Stage is complete + p_msc->stage = MSC_STAGE_STATUS; + }else + { + proc_read10_cmd(rhport, p_msc); + } + } + else if (SCSI_CMD_WRITE_10 == p_cbw->command[0]) + { + proc_write10_new_data(rhport, p_msc, xferred_bytes); + } + else + { + p_msc->xferred_len += xferred_bytes; + + // OUT transfer, invoke callback if needed + if ( !is_data_in(p_cbw->dir) ) + { + int32_t cb_result = tud_msc_scsi_cb(p_cbw->lun, p_cbw->command, _mscd_buf, p_msc->total_len); + + if ( cb_result < 0 ) + { + // unsupported command + TU_LOG(MSC_DEBUG, " SCSI unsupported command\r\n"); + fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED); + }else + { + // TODO haven't implement this scenario any further yet + } + } + + if ( p_msc->xferred_len >= p_msc->total_len ) + { + // Data Stage is complete + p_msc->stage = MSC_STAGE_STATUS; + } + else + { + // This scenario with command that take more than one transfer is already rejected at Command stage + TU_BREAKPOINT(); + } + } + break; + + case MSC_STAGE_STATUS: + // processed immediately after this switch, supposedly to be empty + break; + + case MSC_STAGE_STATUS_SENT: + // Wait for the Status phase to complete + if( (ep_addr == p_msc->ep_in) && (xferred_bytes == sizeof(msc_csw_t)) ) + { + TU_LOG(MSC_DEBUG, " SCSI Status [Lun%u] = %u\r\n", p_cbw->lun, p_csw->status); + // TU_LOG_MEM(MSC_DEBUG, p_csw, xferred_bytes, 2); + + // Invoke complete callback if defined + // Note: There is racing issue with samd51 + qspi flash testing with arduino + // if complete_cb() is invoked after queuing the status. + switch(p_cbw->command[0]) + { + case SCSI_CMD_READ_10: + if ( tud_msc_read10_complete_cb ) tud_msc_read10_complete_cb(p_cbw->lun); + break; + + case SCSI_CMD_WRITE_10: + if ( tud_msc_write10_complete_cb ) tud_msc_write10_complete_cb(p_cbw->lun); + break; + + default: + if ( tud_msc_scsi_complete_cb ) tud_msc_scsi_complete_cb(p_cbw->lun, p_cbw->command); + break; + } + + TU_ASSERT( prepare_cbw(rhport, p_msc) ); + }else + { + // Any xfer ended here is consider unknown error, ignore it + TU_LOG1(" Warning expect SCSI Status but received unknown data\r\n"); + } + break; + + default : break; + } + + if ( p_msc->stage == MSC_STAGE_STATUS ) + { + // skip status if epin is currently stalled, will do it when received Clear Stall request + if ( !usbd_edpt_stalled(rhport, p_msc->ep_in) ) + { + if ( (p_cbw->total_bytes > p_msc->xferred_len) && is_data_in(p_cbw->dir) ) + { + // 6.7 The 13 Cases: case 5 (Hi > Di): STALL before status + // TU_LOG(MSC_DEBUG, " SCSI case 5 (Hi > Di): %lu > %lu\r\n", p_cbw->total_bytes, p_msc->xferred_len); + usbd_edpt_stall(rhport, p_msc->ep_in); + }else + { + TU_ASSERT( send_csw(rhport, p_msc) ); + } + } + + #if TU_CHECK_MCU(OPT_MCU_CXD56) + // WORKAROUND: cxd56 has its own nuttx usb stack which does not forward Set/ClearFeature(Endpoint) to DCD. + // There is no way for us to know when EP is un-stall, therefore we will unconditionally un-stall here and + // hope everything will work + if ( usbd_edpt_stalled(rhport, p_msc->ep_in) ) + { + usbd_edpt_clear_stall(rhport, p_msc->ep_in); + send_csw(rhport, p_msc); + } + #endif + } + + return true; +} + +/*------------------------------------------------------------------*/ +/* SCSI Command Process + *------------------------------------------------------------------*/ + +// return response's length (copied to buffer). Negative if it is not an built-in command or indicate Failed status (CSW) +// In case of a failed status, sense key must be set for reason of failure +static int32_t proc_builtin_scsi(uint8_t lun, uint8_t const scsi_cmd[16], uint8_t* buffer, uint32_t bufsize) +{ + (void) bufsize; // TODO refractor later + int32_t resplen; + + mscd_interface_t* p_msc = &_mscd_itf; + + switch ( scsi_cmd[0] ) + { + case SCSI_CMD_TEST_UNIT_READY: + resplen = 0; + if ( !tud_msc_test_unit_ready_cb(lun) ) + { + // 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_START_STOP_UNIT: + resplen = 0; + + if (tud_msc_start_stop_cb) + { + scsi_start_stop_unit_t const * start_stop = (scsi_start_stop_unit_t const *) scsi_cmd; + if ( !tud_msc_start_stop_cb(lun, start_stop->power_condition, start_stop->start, start_stop->load_eject) ) + { + // 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; + uint32_t block_size; + uint16_t block_size_u16; + + tud_msc_capacity_cb(lun, &block_count, &block_size_u16); + block_size = (uint32_t) block_size_u16; + + // Invalid block size/count from callback, possibly unit is not ready + // stall this request, set sense key to NOT READY + if (block_count == 0 || block_size == 0) + { + resplen = -1; + + // set default sense if not set by callback + if ( p_msc->sense_key == 0 ) set_sense_medium_not_present(lun); + }else + { + scsi_read_capacity10_resp_t read_capa10; + + read_capa10.last_lba = tu_htonl(block_count-1); + read_capa10.block_size = tu_htonl(block_size); + + resplen = sizeof(read_capa10); + memcpy(buffer, &read_capa10, resplen); + } + } + break; + + case SCSI_CMD_READ_FORMAT_CAPACITY: + { + scsi_read_format_capacity_data_t read_fmt_capa = + { + .list_length = 8, + .block_num = 0, + .descriptor_type = 2, // formatted media + .block_size_u16 = 0 + }; + + uint32_t block_count; + uint16_t block_size; + + tud_msc_capacity_cb(lun, &block_count, &block_size); + + // Invalid block size/count from callback, possibly unit is not ready + // stall this request, set sense key to NOT READY + if (block_count == 0 || block_size == 0) + { + resplen = -1; + + // set default sense if not set by callback + if ( p_msc->sense_key == 0 ) set_sense_medium_not_present(lun); + }else + { + read_fmt_capa.block_num = tu_htonl(block_count); + read_fmt_capa.block_size_u16 = tu_htons(block_size); + + resplen = sizeof(read_fmt_capa); + memcpy(buffer, &read_fmt_capa, resplen); + } + } + break; + + case SCSI_CMD_INQUIRY: + { + scsi_inquiry_resp_t inquiry_rsp = + { + .is_removable = 1, + .version = 2, + .response_data_format = 2, + }; + + // vendor_id, product_id, product_rev is space padded string + memset(inquiry_rsp.vendor_id , ' ', sizeof(inquiry_rsp.vendor_id)); + memset(inquiry_rsp.product_id , ' ', sizeof(inquiry_rsp.product_id)); + memset(inquiry_rsp.product_rev, ' ', sizeof(inquiry_rsp.product_rev)); + + tud_msc_inquiry_cb(lun, inquiry_rsp.vendor_id, inquiry_rsp.product_id, inquiry_rsp.product_rev); + + resplen = sizeof(inquiry_rsp); + memcpy(buffer, &inquiry_rsp, resplen); + } + break; + + case SCSI_CMD_MODE_SENSE_6: + { + scsi_mode_sense6_resp_t mode_resp = + { + .data_len = 3, + .medium_type = 0, + .write_protected = false, + .reserved = 0, + .block_descriptor_len = 0 // no block descriptor are included + }; + + bool writable = true; + if ( tud_msc_is_writable_cb ) + { + writable = tud_msc_is_writable_cb(lun); + } + + mode_resp.write_protected = !writable; + + resplen = sizeof(mode_resp); + memcpy(buffer, &mode_resp, resplen); + } + break; + + case SCSI_CMD_REQUEST_SENSE: + { + scsi_sense_fixed_resp_t sense_rsp = + { + .response_code = 0x70, // current, fixed format + .valid = 1 + }; + + sense_rsp.add_sense_len = sizeof(scsi_sense_fixed_resp_t) - 8; + sense_rsp.sense_key = p_msc->sense_key; + sense_rsp.add_sense_code = p_msc->add_sense_code; + sense_rsp.add_sense_qualifier = p_msc->add_sense_qualifier; + + resplen = sizeof(sense_rsp); + memcpy(buffer, &sense_rsp, resplen); + + // request sense callback could overwrite the sense data + if (tud_msc_request_sense_cb) + { + resplen = tud_msc_request_sense_cb(lun, buffer, bufsize); + } + + // Clear sense data after copy + tud_msc_set_sense(lun, 0, 0, 0); + } + break; + + default: resplen = -1; break; + } + + return resplen; +} + +static void proc_read10_cmd(uint8_t rhport, mscd_interface_t* p_msc) +{ + msc_cbw_t const * p_cbw = &p_msc->cbw; + + // block size already verified not zero + uint16_t const block_sz = rdwr10_get_blocksize(p_cbw); + + // Adjust lba with transferred bytes + uint32_t const lba = rdwr10_get_lba(p_cbw->command) + (p_msc->xferred_len / block_sz); + + // remaining bytes capped at class buffer + int32_t nbytes = (int32_t) tu_min32(sizeof(_mscd_buf), p_cbw->total_bytes-p_msc->xferred_len); + + // Application can consume smaller bytes + uint32_t const offset = p_msc->xferred_len % block_sz; + nbytes = tud_msc_read10_cb(p_cbw->lun, lba, offset, _mscd_buf, (uint32_t) nbytes); + + if ( nbytes < 0 ) + { + // negative means error -> endpoint is stalled & status in CSW set to failed + TU_LOG(MSC_DEBUG, " tud_msc_read10_cb() return -1\r\n"); + + // set sense + set_sense_medium_not_present(p_cbw->lun); + + fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED); + } + else if ( nbytes == 0 ) + { + // zero means not ready -> simulate an transfer complete so that this driver callback will fired again + dcd_event_xfer_complete(rhport, p_msc->ep_in, 0, XFER_RESULT_SUCCESS, false); + } + else + { + TU_ASSERT( usbd_edpt_xfer(rhport, p_msc->ep_in, _mscd_buf, nbytes), ); + } +} + +static void proc_write10_cmd(uint8_t rhport, mscd_interface_t* p_msc) +{ + msc_cbw_t const * p_cbw = &p_msc->cbw; + bool writable = true; + + if ( tud_msc_is_writable_cb ) + { + writable = tud_msc_is_writable_cb(p_cbw->lun); + } + + if ( !writable ) + { + // Not writable, complete this SCSI op with error + // Sense = Write protected + tud_msc_set_sense(p_cbw->lun, SCSI_SENSE_DATA_PROTECT, 0x27, 0x00); + fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED); + return; + } + + // remaining bytes capped at class buffer + int32_t nbytes = (int32_t) tu_min32(sizeof(_mscd_buf), p_cbw->total_bytes-p_msc->xferred_len); + + // Write10 callback will be called later when usb transfer complete + TU_ASSERT( usbd_edpt_xfer(rhport, p_msc->ep_out, _mscd_buf, nbytes), ); +} + +// process new data arrived from WRITE10 +static void proc_write10_new_data(uint8_t rhport, mscd_interface_t* p_msc, uint32_t xferred_bytes) +{ + msc_cbw_t const * p_cbw = &p_msc->cbw; + + // block size already verified not zero + uint16_t const block_sz = rdwr10_get_blocksize(p_cbw); + + // Adjust lba with transferred bytes + uint32_t const lba = rdwr10_get_lba(p_cbw->command) + (p_msc->xferred_len / block_sz); + + // Invoke callback to consume new data + uint32_t const offset = p_msc->xferred_len % block_sz; + int32_t nbytes = tud_msc_write10_cb(p_cbw->lun, lba, offset, _mscd_buf, xferred_bytes); + + if ( nbytes < 0 ) + { + // negative means error -> failed this scsi op + TU_LOG(MSC_DEBUG, " tud_msc_write10_cb() return -1\r\n"); + + // update actual byte before failed + p_msc->xferred_len += xferred_bytes; + + // Set sense + set_sense_medium_not_present(p_cbw->lun); + + fail_scsi_op(rhport, p_msc, MSC_CSW_STATUS_FAILED); + }else + { + // Application consume less than what we got (including zero) + if ( (uint32_t) nbytes < xferred_bytes ) + { + if ( nbytes > 0 ) + { + p_msc->xferred_len += nbytes; + memmove(_mscd_buf, _mscd_buf+nbytes, xferred_bytes-nbytes); + } + + // simulate an transfer complete with adjusted parameters --> callback will be invoked with adjusted parameter + dcd_event_xfer_complete(rhport, p_msc->ep_out, xferred_bytes-nbytes, XFER_RESULT_SUCCESS, false); + } + else + { + // Application consume all bytes in our buffer + p_msc->xferred_len += xferred_bytes; + + if ( p_msc->xferred_len >= p_msc->total_len ) + { + // Data Stage is complete + p_msc->stage = MSC_STAGE_STATUS; + }else + { + // prepare to receive more data from host + proc_write10_cmd(rhport, p_msc); + } + } + } +} + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/msc/msc_device.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/msc/msc_device.h new file mode 100644 index 000000000..5839b168d --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/msc/msc_device.h @@ -0,0 +1,162 @@ +/* + * 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_DEVICE_H_ +#define _TUSB_MSC_DEVICE_H_ + +#include "common/tusb_common.h" +#include "msc.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Class Driver Configuration +//--------------------------------------------------------------------+ + +#if !defined(CFG_TUD_MSC_EP_BUFSIZE) & defined(CFG_TUD_MSC_BUFSIZE) + // TODO warn user to use new name later on + // #warning CFG_TUD_MSC_BUFSIZE is renamed to CFG_TUD_MSC_EP_BUFSIZE, please update to use the new name + #define CFG_TUD_MSC_EP_BUFSIZE CFG_TUD_MSC_BUFSIZE +#endif + +#ifndef CFG_TUD_MSC_EP_BUFSIZE + #error CFG_TUD_MSC_EP_BUFSIZE must be defined, value of a block size should work well, the more the better +#endif + +TU_VERIFY_STATIC(CFG_TUD_MSC_EP_BUFSIZE < UINT16_MAX, "Size is not correct"); + +//--------------------------------------------------------------------+ +// Application API +//--------------------------------------------------------------------+ + +// Set SCSI sense response +bool tud_msc_set_sense(uint8_t lun, uint8_t sense_key, uint8_t add_sense_code, uint8_t add_sense_qualifier); + +//--------------------------------------------------------------------+ +// Application Callbacks (WEAK is optional) +//--------------------------------------------------------------------+ + +// Invoked when received SCSI READ10 command +// - Address = lba * BLOCK_SIZE + offset +// - offset is only needed if CFG_TUD_MSC_EP_BUFSIZE is smaller than BLOCK_SIZE. +// +// - Application fill the buffer (up to bufsize) with address contents and return number of read byte. If +// - read < bufsize : These bytes are transferred first and callback invoked again for remaining data. +// +// - read == 0 : Indicate application is not ready yet e.g disk I/O busy. +// Callback invoked again with the same parameters later on. +// +// - read < 0 : Indicate application error e.g invalid address. This request will be STALLed +// and return failed status in command status wrapper phase. +int32_t tud_msc_read10_cb (uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize); + +// Invoked when received SCSI WRITE10 command +// - Address = lba * BLOCK_SIZE + offset +// - offset is only needed if CFG_TUD_MSC_EP_BUFSIZE is smaller than BLOCK_SIZE. +// +// - Application write data from buffer to address contents (up to bufsize) and return number of written byte. If +// - write < bufsize : callback invoked again with remaining data later on. +// +// - write == 0 : Indicate application is not ready yet e.g disk I/O busy. +// Callback invoked again with the same parameters later on. +// +// - write < 0 : Indicate application error e.g invalid address. This request will be STALLed +// and return failed status in command status wrapper phase. +// +// TODO change buffer to const uint8_t* +int32_t tud_msc_write10_cb (uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize); + +// Invoked when received SCSI_CMD_INQUIRY +// Application fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively +void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]); + +// Invoked when received Test Unit Ready command. +// return true allowing host to read/write this LUN e.g SD card inserted +bool tud_msc_test_unit_ready_cb(uint8_t lun); + +// Invoked when received SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size +// Application update block count and block size +void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size); + +/** + * Invoked when received an SCSI command not in built-in list below. + * - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, TEST_UNIT_READY, START_STOP_UNIT, MODE_SENSE6, REQUEST_SENSE + * - READ10 and WRITE10 has their own callbacks + * + * \param[in] lun Logical unit number + * \param[in] scsi_cmd SCSI command contents which application must examine to response accordingly + * \param[out] buffer Buffer for SCSI Data Stage. + * - For INPUT: application must fill this with response. + * - For OUTPUT it holds the Data from host + * \param[in] bufsize Buffer's length. + * + * \return Actual bytes processed, can be zero for no-data command. + * \retval negative Indicate error e.g unsupported command, tinyusb will \b STALL the corresponding + * endpoint and return failed status in command status wrapper phase. + */ +int32_t tud_msc_scsi_cb (uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, uint16_t bufsize); + +/*------------- Optional callbacks -------------*/ + +// Invoked when received GET_MAX_LUN request, required for multiple LUNs implementation +TU_ATTR_WEAK uint8_t tud_msc_get_maxlun_cb(void); + +// Invoked when received Start Stop Unit command +// - Start = 0 : stopped power mode, if load_eject = 1 : unload disk storage +// - Start = 1 : active mode, if load_eject = 1 : load disk storage +TU_ATTR_WEAK bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject); + +// Invoked when received REQUEST_SENSE +TU_ATTR_WEAK int32_t tud_msc_request_sense_cb(uint8_t lun, void* buffer, uint16_t bufsize); + +// Invoked when Read10 command is complete +TU_ATTR_WEAK void tud_msc_read10_complete_cb(uint8_t lun); + +// Invoke when Write10 command is complete, can be used to flush flash caching +TU_ATTR_WEAK void tud_msc_write10_complete_cb(uint8_t lun); + +// Invoked when command in tud_msc_scsi_cb is complete +TU_ATTR_WEAK void tud_msc_scsi_complete_cb(uint8_t lun, uint8_t const scsi_cmd[16]); + +// Invoked to check if device is writable as part of SCSI WRITE10 +TU_ATTR_WEAK bool tud_msc_is_writable_cb(uint8_t lun); + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void mscd_init (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); +bool mscd_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_MSC_DEVICE_H_ */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/msc/msc_host.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/msc/msc_host.c new file mode 100644 index 000000000..fa6519956 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/msc/msc_host.c @@ -0,0 +1,491 @@ +/* + * 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 TUSB_OPT_HOST_ENABLED & CFG_TUH_MSC + +#include "host/usbh.h" +#include "host/usbh_classdriver.h" + +#include "msc_host.h" + +//--------------------------------------------------------------------+ +// 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; + + msc_cbw_t cbw; + msc_csw_t csw; +}msch_interface_t; + +CFG_TUSB_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_TUSB_MEM_SECTION TU_ATTR_ALIGNED(4) +static uint8_t _msch_buffer[sizeof(scsi_inquiry_resp_t)]; + +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); +} + +//--------------------------------------------------------------------+ +// 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 dev_addr, msc_cbw_t const* cbw, void* data, tuh_msc_complete_cb_t complete_cb) +{ + msch_interface_t* p_msc = get_itf(dev_addr); + TU_VERIFY(p_msc->configured); + + // TODO claim endpoint + + p_msc->cbw = *cbw; + p_msc->stage = MSC_STAGE_CMD; + p_msc->buffer = data; + p_msc->complete_cb = complete_cb; + + TU_ASSERT(usbh_edpt_xfer(dev_addr, p_msc->ep_out, (uint8_t*) &p_msc->cbw, sizeof(msc_cbw_t))); + + 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) +{ + 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); +} + +bool tuh_msc_inquiry(uint8_t dev_addr, uint8_t lun, scsi_inquiry_resp_t* response, tuh_msc_complete_cb_t complete_cb) +{ + 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); +} + +bool tuh_msc_test_unit_ready(uint8_t dev_addr, uint8_t lun, tuh_msc_complete_cb_t complete_cb) +{ + 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); +} + +bool tuh_msc_request_sense(uint8_t dev_addr, uint8_t lun, void *resposne, tuh_msc_complete_cb_t complete_cb) +{ + 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, resposne, complete_cb); +} + +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) +{ + 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); +} + +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) +{ + 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); +} + +#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); + + // invoke Application Callback + if (p_msc->mounted && 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, 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, 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, sizeof(msc_csw_t))); + break; + + case MSC_STAGE_STATUS: + // SCSI op is complete + p_msc->stage = MSC_STAGE_IDLE; + + if (p_msc->complete_cb) p_msc->complete_cb(dev_addr, cbw, csw); + break; + + // unknown state + default: break; + } + + return true; +} + +//--------------------------------------------------------------------+ +// MSC Enumeration +//--------------------------------------------------------------------+ + +static bool config_get_maxlun_complete (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result); +static bool config_test_unit_ready_complete(uint8_t dev_addr, msc_cbw_t const* cbw, msc_csw_t const* csw); +static bool config_request_sense_complete(uint8_t dev_addr, msc_cbw_t const* cbw, msc_csw_t const* csw); +static bool config_read_capacity_complete(uint8_t dev_addr, msc_cbw_t const* cbw, msc_csw_t const* csw); + +bool msch_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *desc_itf, uint16_t max_len) +{ + TU_VERIFY (MSC_SUBCLASS_SCSI == desc_itf->bInterfaceSubClass && + MSC_PROTOCOL_BOT == desc_itf->bInterfaceProtocol); + + // msc driver length is fixed + uint16_t const drv_len = 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(usbh_edpt_open(rhport, dev_addr, ep_desc)); + + if ( tu_edpt_dir(ep_desc->bEndpointAddress) == TUSB_DIR_IN ) + { + 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_LOG2("MSC Get Max Lun\r\n"); + tusb_control_request_t 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 + }; + TU_ASSERT(tuh_control_xfer(dev_addr, &request, &p_msc->max_lun, config_get_maxlun_complete)); + + return true; +} + +static bool config_get_maxlun_complete (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result) +{ + (void) request; + + msch_interface_t* p_msc = get_itf(dev_addr); + + // STALL means zero + p_msc->max_lun = (XFER_RESULT_SUCCESS == result) ? _msch_buffer[0] : 0; + p_msc->max_lun++; // MAX LUN is minus 1 by specs + + // TODO multiple LUN support + TU_LOG2("SCSI Test Unit Ready\r\n"); + uint8_t const lun = 0; + tuh_msc_test_unit_ready(dev_addr, lun, config_test_unit_ready_complete); + + return true; +} + +static bool config_test_unit_ready_complete(uint8_t dev_addr, msc_cbw_t const* cbw, msc_csw_t const* csw) +{ + if (csw->status == 0) + { + // Unit is ready, read its capacity + TU_LOG2("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); + }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_LOG2("SCSI Request Sense\r\n"); + TU_ASSERT(tuh_msc_request_sense(dev_addr, cbw->lun, _msch_buffer, config_request_sense_complete)); + } + + return true; +} + +static bool config_request_sense_complete(uint8_t dev_addr, msc_cbw_t const* cbw, msc_csw_t const* csw) +{ + TU_ASSERT(csw->status == 0); + TU_ASSERT(tuh_msc_test_unit_ready(dev_addr, cbw->lun, config_test_unit_ready_complete)); + return true; +} + +static bool config_read_capacity_complete(uint8_t dev_addr, msc_cbw_t const* cbw, msc_csw_t const* 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/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/msc/msc_host.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/msc/msc_host.h new file mode 100644 index 000000000..7718ad4fe --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/msc/msc_host.h @@ -0,0 +1,119 @@ +/* + * 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 bool (*tuh_msc_complete_cb_t)(uint8_t dev_addr, msc_cbw_t const* cbw, msc_csw_t const* csw); + +//--------------------------------------------------------------------+ +// 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 dev_addr, msc_cbw_t const* cbw, void* data, tuh_msc_complete_cb_t complete_cb); + +// 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); + +// 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); + +// 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 *resposne, tuh_msc_complete_cb_t complete_cb); + +// 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); + +// 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); + +// 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); + +//------------- 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 /* _TUSB_MSC_HOST_H_ */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/net/ecm_rndis_device.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/net/ecm_rndis_device.c new file mode 100644 index 000000000..c6cd388e3 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/net/ecm_rndis_device.c @@ -0,0 +1,445 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Peter Lawrence + * 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 ( TUSB_OPT_DEVICE_ENABLED && CFG_TUD_ECM_RNDIS ) + +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "net_device.h" +#include "rndis_protocol.h" + +void rndis_class_set_handler(uint8_t *data, int size); /* found in ./misc/networking/rndis_reports.c */ + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +typedef struct +{ + uint8_t itf_num; // Index number of Management Interface, +1 for Data Interface + uint8_t itf_data_alt; // Alternate setting of Data Interface. 0 : inactive, 1 : active + + uint8_t ep_notif; + uint8_t ep_in; + uint8_t ep_out; + + bool ecm_mode; + + // Endpoint descriptor use to open/close when receving SetInterface + // TODO since configuration descriptor may not be long-lived memory, we should + // keep a copy of endpoint attribute instead + uint8_t const * ecm_desc_epdata; + +} netd_interface_t; + +#define CFG_TUD_NET_PACKET_PREFIX_LEN sizeof(rndis_data_packet_t) +#define CFG_TUD_NET_PACKET_SUFFIX_LEN 0 + +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static uint8_t received[CFG_TUD_NET_PACKET_PREFIX_LEN + CFG_TUD_NET_MTU + CFG_TUD_NET_PACKET_PREFIX_LEN]; +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static uint8_t transmitted[CFG_TUD_NET_PACKET_PREFIX_LEN + CFG_TUD_NET_MTU + CFG_TUD_NET_PACKET_PREFIX_LEN]; + +struct ecm_notify_struct +{ + tusb_control_request_t header; + uint32_t downlink, uplink; +}; + +static const struct ecm_notify_struct ecm_notify_nc = +{ + .header = { + .bmRequestType = 0xA1, + .bRequest = 0 /* NETWORK_CONNECTION aka NetworkConnection */, + .wValue = 1 /* Connected */, + .wLength = 0, + }, +}; + +static const struct ecm_notify_struct ecm_notify_csc = +{ + .header = { + .bmRequestType = 0xA1, + .bRequest = 0x2A /* CONNECTION_SPEED_CHANGE aka ConnectionSpeedChange */, + .wLength = 8, + }, + .downlink = 9728000, + .uplink = 9728000, +}; + +// TODO remove CFG_TUSB_MEM_SECTION, control internal buffer is already in this special section +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static union +{ + uint8_t rndis_buf[120]; + struct ecm_notify_struct ecm_buf; +} notify; + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +// TODO remove CFG_TUSB_MEM_SECTION +CFG_TUSB_MEM_SECTION static netd_interface_t _netd_itf; + +static bool can_xmit; + +void tud_network_recv_renew(void) +{ + usbd_edpt_xfer(TUD_OPT_RHPORT, _netd_itf.ep_out, received, sizeof(received)); +} + +static void do_in_xfer(uint8_t *buf, uint16_t len) +{ + can_xmit = false; + usbd_edpt_xfer(TUD_OPT_RHPORT, _netd_itf.ep_in, buf, len); +} + +void netd_report(uint8_t *buf, uint16_t len) +{ + // skip if previous report not yet acknowledged by host + if ( usbd_edpt_busy(TUD_OPT_RHPORT, _netd_itf.ep_notif) ) return; + usbd_edpt_xfer(TUD_OPT_RHPORT, _netd_itf.ep_notif, buf, len); +} + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void netd_init(void) +{ + tu_memclr(&_netd_itf, sizeof(_netd_itf)); +} + +void netd_reset(uint8_t rhport) +{ + (void) rhport; + + netd_init(); +} + +uint16_t netd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) +{ + bool const is_rndis = (TUD_RNDIS_ITF_CLASS == itf_desc->bInterfaceClass && + TUD_RNDIS_ITF_SUBCLASS == itf_desc->bInterfaceSubClass && + TUD_RNDIS_ITF_PROTOCOL == itf_desc->bInterfaceProtocol); + + bool const is_ecm = (TUSB_CLASS_CDC == itf_desc->bInterfaceClass && + CDC_COMM_SUBCLASS_ETHERNET_CONTROL_MODEL == itf_desc->bInterfaceSubClass && + 0x00 == itf_desc->bInterfaceProtocol); + + TU_VERIFY(is_rndis || is_ecm, 0); + + // confirm interface hasn't already been allocated + TU_ASSERT(0 == _netd_itf.ep_notif, 0); + + // sanity check the descriptor + _netd_itf.ecm_mode = is_ecm; + + //------------- Management Interface -------------// + _netd_itf.itf_num = itf_desc->bInterfaceNumber; + + uint16_t drv_len = sizeof(tusb_desc_interface_t); + uint8_t const * p_desc = tu_desc_next( itf_desc ); + + // Communication Functional Descriptors + while ( TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && drv_len <= max_len ) + { + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } + + // notification endpoint (if any) + if ( TUSB_DESC_ENDPOINT == tu_desc_type(p_desc) ) + { + TU_ASSERT( usbd_edpt_open(rhport, (tusb_desc_endpoint_t const *) p_desc), 0 ); + + _netd_itf.ep_notif = ((tusb_desc_endpoint_t const *) p_desc)->bEndpointAddress; + + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } + + //------------- Data Interface -------------// + // - RNDIS Data followed immediately by a pair of endpoints + // - CDC-ECM data interface has 2 alternate settings + // - 0 : zero endpoints for inactive (default) + // - 1 : IN & OUT endpoints for active networking + TU_ASSERT(TUSB_DESC_INTERFACE == tu_desc_type(p_desc), 0); + + do + { + tusb_desc_interface_t const * data_itf_desc = (tusb_desc_interface_t const *) p_desc; + TU_ASSERT(TUSB_CLASS_CDC_DATA == data_itf_desc->bInterfaceClass, 0); + + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + }while( _netd_itf.ecm_mode && (TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) && (drv_len <= max_len) ); + + // Pair of endpoints + TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(p_desc), 0); + + if ( _netd_itf.ecm_mode ) + { + // ECM by default is in-active, save the endpoint attribute + // to open later when received setInterface + _netd_itf.ecm_desc_epdata = p_desc; + }else + { + // Open endpoint pair for RNDIS + TU_ASSERT( usbd_open_edpt_pair(rhport, p_desc, 2, TUSB_XFER_BULK, &_netd_itf.ep_out, &_netd_itf.ep_in), 0 ); + + tud_network_init_cb(); + + // we are ready to transmit a packet + can_xmit = true; + + // prepare for incoming packets + tud_network_recv_renew(); + } + + drv_len += 2*sizeof(tusb_desc_endpoint_t); + + return drv_len; +} + +static void ecm_report(bool nc) +{ + notify.ecm_buf = (nc) ? ecm_notify_nc : ecm_notify_csc; + notify.ecm_buf.header.wIndex = _netd_itf.itf_num; + netd_report((uint8_t *)¬ify.ecm_buf, (nc) ? sizeof(notify.ecm_buf.header) : sizeof(notify.ecm_buf)); +} + +// 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 netd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) +{ + if ( stage == CONTROL_STAGE_SETUP ) + { + switch ( request->bmRequestType_bit.type ) + { + case TUSB_REQ_TYPE_STANDARD: + switch ( request->bRequest ) + { + case TUSB_REQ_GET_INTERFACE: + { + uint8_t const req_itfnum = (uint8_t) request->wIndex; + TU_VERIFY(_netd_itf.itf_num+1 == req_itfnum); + + tud_control_xfer(rhport, request, &_netd_itf.itf_data_alt, 1); + } + break; + + case TUSB_REQ_SET_INTERFACE: + { + uint8_t const req_itfnum = (uint8_t) request->wIndex; + uint8_t const req_alt = (uint8_t) request->wValue; + + // Only valid for Data Interface with Alternate is either 0 or 1 + TU_VERIFY(_netd_itf.itf_num+1 == req_itfnum && req_alt < 2); + + // ACM-ECM only: qequest to enable/disable network activities + TU_VERIFY(_netd_itf.ecm_mode); + + _netd_itf.itf_data_alt = req_alt; + + if ( _netd_itf.itf_data_alt ) + { + // TODO since we don't actually close endpoint + // hack here to not re-open it + if ( _netd_itf.ep_in == 0 && _netd_itf.ep_out == 0 ) + { + TU_ASSERT(_netd_itf.ecm_desc_epdata); + TU_ASSERT( usbd_open_edpt_pair(rhport, _netd_itf.ecm_desc_epdata, 2, TUSB_XFER_BULK, &_netd_itf.ep_out, &_netd_itf.ep_in) ); + + // TODO should be merge with RNDIS's after endpoint opened + // Also should have opposite callback for application to disable network !! + tud_network_init_cb(); + can_xmit = true; // we are ready to transmit a packet + tud_network_recv_renew(); // prepare for incoming packets + } + }else + { + // TODO close the endpoint pair + // For now pretend that we did, this should have no harm since host won't try to + // communicate with the endpoints again + // _netd_itf.ep_in = _netd_itf.ep_out = 0 + } + + tud_control_status(rhport, request); + } + break; + + // unsupported request + default: return false; + } + break; + + case TUSB_REQ_TYPE_CLASS: + TU_VERIFY (_netd_itf.itf_num == request->wIndex); + + if (_netd_itf.ecm_mode) + { + /* the only required CDC-ECM Management Element Request is SetEthernetPacketFilter */ + if (0x43 /* SET_ETHERNET_PACKET_FILTER */ == request->bRequest) + { + tud_control_xfer(rhport, request, NULL, 0); + ecm_report(true); + } + } + else + { + if (request->bmRequestType_bit.direction == TUSB_DIR_IN) + { + rndis_generic_msg_t *rndis_msg = (rndis_generic_msg_t *) ((void*) notify.rndis_buf); + uint32_t msglen = tu_le32toh(rndis_msg->MessageLength); + TU_ASSERT(msglen <= sizeof(notify.rndis_buf)); + tud_control_xfer(rhport, request, notify.rndis_buf, msglen); + } + else + { + tud_control_xfer(rhport, request, notify.rndis_buf, sizeof(notify.rndis_buf)); + } + } + break; + + // unsupported request + default: return false; + } + } + else if ( stage == CONTROL_STAGE_DATA ) + { + // Handle RNDIS class control OUT only + if (request->bmRequestType_bit.type == TUSB_REQ_TYPE_CLASS && + request->bmRequestType_bit.direction == TUSB_DIR_OUT && + _netd_itf.itf_num == request->wIndex) + { + if ( !_netd_itf.ecm_mode ) + { + rndis_class_set_handler(notify.rndis_buf, request->wLength); + } + } + } + + return true; +} + +static void handle_incoming_packet(uint32_t len) +{ + uint8_t *pnt = received; + uint32_t size = 0; + + if (_netd_itf.ecm_mode) + { + size = len; + } + else + { + rndis_data_packet_t *r = (rndis_data_packet_t *) ((void*) pnt); + if (len >= sizeof(rndis_data_packet_t)) + if ( (r->MessageType == REMOTE_NDIS_PACKET_MSG) && (r->MessageLength <= len)) + if ( (r->DataOffset + offsetof(rndis_data_packet_t, DataOffset) + r->DataLength) <= len) + { + pnt = &received[r->DataOffset + offsetof(rndis_data_packet_t, DataOffset)]; + size = r->DataLength; + } + } + + if (!tud_network_recv_cb(pnt, size)) + { + /* if a buffer was never handled by user code, we must renew on the user's behalf */ + tud_network_recv_renew(); + } +} + +bool netd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) +{ + (void) rhport; + (void) result; + + /* new packet received */ + if ( ep_addr == _netd_itf.ep_out ) + { + handle_incoming_packet(xferred_bytes); + } + + /* data transmission finished */ + if ( ep_addr == _netd_itf.ep_in ) + { + /* TinyUSB requires the class driver to implement ZLP (since ZLP usage is class-specific) */ + + if ( xferred_bytes && (0 == (xferred_bytes % CFG_TUD_NET_ENDPOINT_SIZE)) ) + { + do_in_xfer(NULL, 0); /* a ZLP is needed */ + } + else + { + /* we're finally finished */ + can_xmit = true; + } + } + + if ( _netd_itf.ecm_mode && (ep_addr == _netd_itf.ep_notif) ) + { + if (sizeof(notify.ecm_buf.header) == xferred_bytes) ecm_report(false); + } + + return true; +} + +bool tud_network_can_xmit(uint16_t size) +{ + (void)size; + + return can_xmit; +} + +void tud_network_xmit(void *ref, uint16_t arg) +{ + uint8_t *data; + uint16_t len; + + if (!can_xmit) + return; + + len = (_netd_itf.ecm_mode) ? 0 : CFG_TUD_NET_PACKET_PREFIX_LEN; + data = transmitted + len; + + len += tud_network_xmit_cb(data, ref, arg); + + if (!_netd_itf.ecm_mode) + { + rndis_data_packet_t *hdr = (rndis_data_packet_t *) ((void*) transmitted); + memset(hdr, 0, sizeof(rndis_data_packet_t)); + hdr->MessageType = REMOTE_NDIS_PACKET_MSG; + hdr->MessageLength = len; + hdr->DataOffset = sizeof(rndis_data_packet_t) - offsetof(rndis_data_packet_t, DataOffset); + hdr->DataLength = len - sizeof(rndis_data_packet_t); + } + + do_in_xfer(transmitted, len); +} + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/net/ncm.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/net/ncm.h new file mode 100644 index 000000000..96ba11fbc --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/net/ncm.h @@ -0,0 +1,69 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021, 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_NCM_H_ +#define _TUSB_NCM_H_ + +#include "common/tusb_common.h" + +#ifdef __cplusplus + extern "C" { +#endif + +// Table 4.3 Data Class Interface Protocol Codes +typedef enum +{ + NCM_DATA_PROTOCOL_NETWORK_TRANSFER_BLOCK = 0x01 +} ncm_data_interface_protocol_code_t; + + +// Table 6.2 Class-Specific Request Codes for Network Control Model subclass +typedef enum +{ + NCM_SET_ETHERNET_MULTICAST_FILTERS = 0x40, + NCM_SET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x41, + NCM_GET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER = 0x42, + NCM_SET_ETHERNET_PACKET_FILTER = 0x43, + NCM_GET_ETHERNET_STATISTIC = 0x44, + NCM_GET_NTB_PARAMETERS = 0x80, + NCM_GET_NET_ADDRESS = 0x81, + NCM_SET_NET_ADDRESS = 0x82, + NCM_GET_NTB_FORMAT = 0x83, + NCM_SET_NTB_FORMAT = 0x84, + NCM_GET_NTB_INPUT_SIZE = 0x85, + NCM_SET_NTB_INPUT_SIZE = 0x86, + NCM_GET_MAX_DATAGRAM_SIZE = 0x87, + NCM_SET_MAX_DATAGRAM_SIZE = 0x88, + NCM_GET_CRC_MODE = 0x89, + NCM_SET_CRC_MODE = 0x8A, +} ncm_request_code_t; + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/net/ncm_device.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/net/ncm_device.c new file mode 100644 index 000000000..3e131a85c --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/net/ncm_device.c @@ -0,0 +1,510 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Jacob Berg Potter + * Copyright (c) 2020 Peter Lawrence + * 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 ( TUSB_OPT_DEVICE_ENABLED && CFG_TUD_NCM ) + +#include "device/usbd.h" +#include "device/usbd_pvt.h" +#include "net_device.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + +#define NTH16_SIGNATURE 0x484D434E +#define NDP16_SIGNATURE_NCM0 0x304D434E +#define NDP16_SIGNATURE_NCM1 0x314D434E + +typedef struct TU_ATTR_PACKED +{ + uint16_t wLength; + uint16_t bmNtbFormatsSupported; + uint32_t dwNtbInMaxSize; + uint16_t wNdbInDivisor; + uint16_t wNdbInPayloadRemainder; + uint16_t wNdbInAlignment; + uint16_t wReserved; + uint32_t dwNtbOutMaxSize; + uint16_t wNdbOutDivisor; + uint16_t wNdbOutPayloadRemainder; + uint16_t wNdbOutAlignment; + uint16_t wNtbOutMaxDatagrams; +} ntb_parameters_t; + +typedef struct TU_ATTR_PACKED +{ + uint32_t dwSignature; + uint16_t wHeaderLength; + uint16_t wSequence; + uint16_t wBlockLength; + uint16_t wNdpIndex; +} nth16_t; + +typedef struct TU_ATTR_PACKED +{ + uint16_t wDatagramIndex; + uint16_t wDatagramLength; +} ndp16_datagram_t; + +typedef struct TU_ATTR_PACKED +{ + uint32_t dwSignature; + uint16_t wLength; + uint16_t wNextNdpIndex; + ndp16_datagram_t datagram[]; +} ndp16_t; + +typedef union TU_ATTR_PACKED { + struct { + nth16_t nth; + ndp16_t ndp; + }; + uint8_t data[CFG_TUD_NCM_IN_NTB_MAX_SIZE]; +} transmit_ntb_t; + +struct ecm_notify_struct +{ + tusb_control_request_t header; + uint32_t downlink, uplink; +}; + +typedef struct +{ + uint8_t itf_num; // Index number of Management Interface, +1 for Data Interface + uint8_t itf_data_alt; // Alternate setting of Data Interface. 0 : inactive, 1 : active + + uint8_t ep_notif; + uint8_t ep_in; + uint8_t ep_out; + + const ndp16_t *ndp; + uint8_t num_datagrams, current_datagram_index; + + enum { + REPORT_SPEED, + REPORT_CONNECTED, + REPORT_DONE + } report_state; + bool report_pending; + + uint8_t current_ntb; // Index in transmit_ntb[] that is currently being filled with datagrams + uint8_t datagram_count; // Number of datagrams in transmit_ntb[current_ntb] + uint16_t next_datagram_offset; // Offset in transmit_ntb[current_ntb].data to place the next datagram + uint16_t ntb_in_size; // Maximum size of transmitted (IN to host) NTBs; initially CFG_TUD_NCM_IN_NTB_MAX_SIZE + uint8_t max_datagrams_per_ntb; // Maximum number of datagrams per NTB; initially CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB + + uint16_t nth_sequence; // Sequence number counter for transmitted NTBs + + bool transferring; + +} ncm_interface_t; + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ + +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static const ntb_parameters_t ntb_parameters = { + .wLength = sizeof(ntb_parameters_t), + .bmNtbFormatsSupported = 0x01, + .dwNtbInMaxSize = CFG_TUD_NCM_IN_NTB_MAX_SIZE, + .wNdbInDivisor = 4, + .wNdbInPayloadRemainder = 0, + .wNdbInAlignment = CFG_TUD_NCM_ALIGNMENT, + .wReserved = 0, + .dwNtbOutMaxSize = CFG_TUD_NCM_OUT_NTB_MAX_SIZE, + .wNdbOutDivisor = 4, + .wNdbOutPayloadRemainder = 0, + .wNdbOutAlignment = CFG_TUD_NCM_ALIGNMENT, + .wNtbOutMaxDatagrams = 0 +}; + +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static transmit_ntb_t transmit_ntb[2]; + +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static uint8_t receive_ntb[CFG_TUD_NCM_OUT_NTB_MAX_SIZE]; + +static ncm_interface_t ncm_interface; + +/* + * Set up the NTB state in ncm_interface to be ready to add datagrams. + */ +static void ncm_prepare_for_tx(void) { + ncm_interface.datagram_count = 0; + // datagrams start after all the headers + ncm_interface.next_datagram_offset = sizeof(nth16_t) + sizeof(ndp16_t) + + ((CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB + 1) * sizeof(ndp16_datagram_t)); +} + +/* + * If not already transmitting, start sending the current NTB to the host and swap buffers + * to start filling the other one with datagrams. + */ +static void ncm_start_tx(void) { + if (ncm_interface.transferring) { + return; + } + + transmit_ntb_t *ntb = &transmit_ntb[ncm_interface.current_ntb]; + size_t ntb_length = ncm_interface.next_datagram_offset; + + // Fill in NTB header + ntb->nth.dwSignature = NTH16_SIGNATURE; + ntb->nth.wHeaderLength = sizeof(nth16_t); + ntb->nth.wSequence = ncm_interface.nth_sequence++; + ntb->nth.wBlockLength = ntb_length; + ntb->nth.wNdpIndex = sizeof(nth16_t); + + // Fill in NDP16 header and terminator + ntb->ndp.dwSignature = NDP16_SIGNATURE_NCM0; + ntb->ndp.wLength = sizeof(ndp16_t) + (ncm_interface.datagram_count + 1) * sizeof(ndp16_datagram_t); + ntb->ndp.wNextNdpIndex = 0; + ntb->ndp.datagram[ncm_interface.datagram_count].wDatagramIndex = 0; + ntb->ndp.datagram[ncm_interface.datagram_count].wDatagramLength = 0; + + // Kick off an endpoint transfer + usbd_edpt_xfer(TUD_OPT_RHPORT, ncm_interface.ep_in, ntb->data, ntb_length); + ncm_interface.transferring = true; + + // Swap to the other NTB and clear it out + ncm_interface.current_ntb = 1 - ncm_interface.current_ntb; + ncm_prepare_for_tx(); +} + +static struct ecm_notify_struct ncm_notify_connected = +{ + .header = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_IN + }, + .bRequest = CDC_NOTIF_NETWORK_CONNECTION, + .wValue = 1 /* Connected */, + .wLength = 0, + }, +}; + +static struct ecm_notify_struct ncm_notify_speed_change = +{ + .header = { + .bmRequestType_bit = { + .recipient = TUSB_REQ_RCPT_INTERFACE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_IN + }, + .bRequest = CDC_NOTIF_CONNECTION_SPEED_CHANGE, + .wLength = 8, + }, + .downlink = 10000000, + .uplink = 10000000, +}; + +void tud_network_recv_renew(void) +{ + if (!ncm_interface.num_datagrams) + { + usbd_edpt_xfer(TUD_OPT_RHPORT, ncm_interface.ep_out, receive_ntb, sizeof(receive_ntb)); + return; + } + + const ndp16_t *ndp = ncm_interface.ndp; + const int i = ncm_interface.current_datagram_index; + ncm_interface.current_datagram_index++; + ncm_interface.num_datagrams--; + + tud_network_recv_cb(receive_ntb + ndp->datagram[i].wDatagramIndex, ndp->datagram[i].wDatagramLength); +} + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ + +void netd_init(void) +{ + tu_memclr(&ncm_interface, sizeof(ncm_interface)); + ncm_interface.ntb_in_size = CFG_TUD_NCM_IN_NTB_MAX_SIZE; + ncm_interface.max_datagrams_per_ntb = CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB; + ncm_prepare_for_tx(); +} + +void netd_reset(uint8_t rhport) +{ + (void) rhport; + + netd_init(); +} + +uint16_t netd_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) +{ + // confirm interface hasn't already been allocated + TU_ASSERT(0 == ncm_interface.ep_notif, 0); + + //------------- Management Interface -------------// + ncm_interface.itf_num = itf_desc->bInterfaceNumber; + + uint16_t drv_len = sizeof(tusb_desc_interface_t); + uint8_t const * p_desc = tu_desc_next( itf_desc ); + + // Communication Functional Descriptors + while ( TUSB_DESC_CS_INTERFACE == tu_desc_type(p_desc) && drv_len <= max_len ) + { + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } + + // notification endpoint (if any) + if ( TUSB_DESC_ENDPOINT == tu_desc_type(p_desc) ) + { + TU_ASSERT( usbd_edpt_open(rhport, (tusb_desc_endpoint_t const *) p_desc), 0 ); + + ncm_interface.ep_notif = ((tusb_desc_endpoint_t const *) p_desc)->bEndpointAddress; + + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } + + //------------- Data Interface -------------// + // - CDC-NCM data interface has 2 alternate settings + // - 0 : zero endpoints for inactive (default) + // - 1 : IN & OUT endpoints for transfer of NTBs + TU_ASSERT(TUSB_DESC_INTERFACE == tu_desc_type(p_desc), 0); + + do + { + tusb_desc_interface_t const * data_itf_desc = (tusb_desc_interface_t const *) p_desc; + TU_ASSERT(TUSB_CLASS_CDC_DATA == data_itf_desc->bInterfaceClass, 0); + + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } while((TUSB_DESC_INTERFACE == tu_desc_type(p_desc)) && (drv_len <= max_len)); + + // Pair of endpoints + TU_ASSERT(TUSB_DESC_ENDPOINT == tu_desc_type(p_desc), 0); + + TU_ASSERT(usbd_open_edpt_pair(rhport, p_desc, 2, TUSB_XFER_BULK, &ncm_interface.ep_out, &ncm_interface.ep_in) ); + + drv_len += 2*sizeof(tusb_desc_endpoint_t); + + return drv_len; +} + +static void ncm_report(void) +{ + if (ncm_interface.report_state == REPORT_SPEED) { + ncm_notify_speed_change.header.wIndex = ncm_interface.itf_num; + usbd_edpt_xfer(TUD_OPT_RHPORT, ncm_interface.ep_notif, (uint8_t *) &ncm_notify_speed_change, sizeof(ncm_notify_speed_change)); + ncm_interface.report_state = REPORT_CONNECTED; + ncm_interface.report_pending = true; + } else if (ncm_interface.report_state == REPORT_CONNECTED) { + ncm_notify_connected.header.wIndex = ncm_interface.itf_num; + usbd_edpt_xfer(TUD_OPT_RHPORT, ncm_interface.ep_notif, (uint8_t *) &ncm_notify_connected, sizeof(ncm_notify_connected)); + ncm_interface.report_state = REPORT_DONE; + ncm_interface.report_pending = true; + } +} + +TU_ATTR_WEAK void tud_network_link_state_cb(bool state) +{ + (void)state; +} + +// Handle class control request +// return false to stall control endpoint (e.g unsupported request) +bool netd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) +{ + if ( stage != CONTROL_STAGE_SETUP ) return true; + + switch ( request->bmRequestType_bit.type ) + { + case TUSB_REQ_TYPE_STANDARD: + switch ( request->bRequest ) + { + case TUSB_REQ_GET_INTERFACE: + { + uint8_t const req_itfnum = (uint8_t) request->wIndex; + TU_VERIFY(ncm_interface.itf_num + 1 == req_itfnum); + + tud_control_xfer(rhport, request, &ncm_interface.itf_data_alt, 1); + } + break; + + case TUSB_REQ_SET_INTERFACE: + { + uint8_t const req_itfnum = (uint8_t) request->wIndex; + uint8_t const req_alt = (uint8_t) request->wValue; + + // Only valid for Data Interface with Alternate is either 0 or 1 + TU_VERIFY(ncm_interface.itf_num + 1 == req_itfnum && req_alt < 2); + + if (req_alt != ncm_interface.itf_data_alt) { + ncm_interface.itf_data_alt = req_alt; + + if (ncm_interface.itf_data_alt) { + if (!usbd_edpt_busy(rhport, ncm_interface.ep_out)) { + tud_network_recv_renew(); // prepare for incoming datagrams + } + if (!ncm_interface.report_pending) { + ncm_report(); + } + } + + tud_network_link_state_cb(ncm_interface.itf_data_alt); + } + + tud_control_status(rhport, request); + } + break; + + // unsupported request + default: return false; + } + break; + + case TUSB_REQ_TYPE_CLASS: + TU_VERIFY (ncm_interface.itf_num == request->wIndex); + + if (NCM_GET_NTB_PARAMETERS == request->bRequest) + { + tud_control_xfer(rhport, request, (void*)&ntb_parameters, sizeof(ntb_parameters)); + } + + break; + + // unsupported request + default: return false; + } + + return true; +} + +static void handle_incoming_datagram(uint32_t len) +{ + uint32_t size = len; + + if (len == 0) { + return; + } + + TU_ASSERT(size >= sizeof(nth16_t), ); + + const nth16_t *hdr = (const nth16_t *)receive_ntb; + TU_ASSERT(hdr->dwSignature == NTH16_SIGNATURE, ); + TU_ASSERT(hdr->wNdpIndex >= sizeof(nth16_t) && (hdr->wNdpIndex + sizeof(ndp16_t)) <= len, ); + + const ndp16_t *ndp = (const ndp16_t *)(receive_ntb + hdr->wNdpIndex); + TU_ASSERT(ndp->dwSignature == NDP16_SIGNATURE_NCM0 || ndp->dwSignature == NDP16_SIGNATURE_NCM1, ); + TU_ASSERT(hdr->wNdpIndex + ndp->wLength <= len, ); + + int num_datagrams = (ndp->wLength - 12) / 4; + ncm_interface.current_datagram_index = 0; + ncm_interface.num_datagrams = 0; + ncm_interface.ndp = ndp; + for (int i = 0; i < num_datagrams && ndp->datagram[i].wDatagramIndex && ndp->datagram[i].wDatagramLength; i++) + { + ncm_interface.num_datagrams++; + } + + tud_network_recv_renew(); +} + +bool netd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) +{ + (void) rhport; + (void) result; + + /* new datagram receive_ntb */ + if (ep_addr == ncm_interface.ep_out ) + { + handle_incoming_datagram(xferred_bytes); + } + + /* data transmission finished */ + if (ep_addr == ncm_interface.ep_in ) + { + if (ncm_interface.transferring) { + ncm_interface.transferring = false; + } + + // If there are datagrams queued up that we tried to send while this NTB was being emitted, send them now + if (ncm_interface.datagram_count && ncm_interface.itf_data_alt == 1) { + ncm_start_tx(); + } + } + + if (ep_addr == ncm_interface.ep_notif ) + { + ncm_interface.report_pending = false; + ncm_report(); + } + + return true; +} + +// poll network driver for its ability to accept another packet to transmit +bool tud_network_can_xmit(uint16_t size) +{ + TU_VERIFY(ncm_interface.itf_data_alt == 1); + + if (ncm_interface.datagram_count >= ncm_interface.max_datagrams_per_ntb) { + TU_LOG2("NTB full [by count]\r\n"); + return false; + } + + size_t next_datagram_offset = ncm_interface.next_datagram_offset; + if (next_datagram_offset + size > ncm_interface.ntb_in_size) { + TU_LOG2("ntb full [by size]\r\n"); + return false; + } + + return true; +} + +void tud_network_xmit(void *ref, uint16_t arg) +{ + transmit_ntb_t *ntb = &transmit_ntb[ncm_interface.current_ntb]; + size_t next_datagram_offset = ncm_interface.next_datagram_offset; + + uint16_t size = tud_network_xmit_cb(ntb->data + next_datagram_offset, ref, arg); + + ntb->ndp.datagram[ncm_interface.datagram_count].wDatagramIndex = ncm_interface.next_datagram_offset; + ntb->ndp.datagram[ncm_interface.datagram_count].wDatagramLength = size; + + ncm_interface.datagram_count++; + next_datagram_offset += size; + + // round up so the next datagram is aligned correctly + next_datagram_offset += (CFG_TUD_NCM_ALIGNMENT - 1); + next_datagram_offset -= (next_datagram_offset % CFG_TUD_NCM_ALIGNMENT); + + ncm_interface.next_datagram_offset = next_datagram_offset; + + ncm_start_tx(); +} + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/net/net_device.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/net/net_device.h new file mode 100644 index 000000000..6e294465b --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/net/net_device.h @@ -0,0 +1,118 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Peter Lawrence + * 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_NET_DEVICE_H_ +#define _TUSB_NET_DEVICE_H_ + +#include "class/cdc/cdc.h" + +#if CFG_TUD_ECM_RNDIS && CFG_TUD_NCM +#error "Cannot enable both ECM_RNDIS and NCM network drivers" +#endif + +#include "ncm.h" + +/* declared here, NOT in usb_descriptors.c, so that the driver can intelligently ZLP as needed */ +#define CFG_TUD_NET_ENDPOINT_SIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +/* Maximum Transmission Unit (in bytes) of the network, including Ethernet header */ +#ifndef CFG_TUD_NET_MTU +#define CFG_TUD_NET_MTU 1514 +#endif + +#ifndef CFG_TUD_NCM_IN_NTB_MAX_SIZE +#define CFG_TUD_NCM_IN_NTB_MAX_SIZE 3200 +#endif + +#ifndef CFG_TUD_NCM_OUT_NTB_MAX_SIZE +#define CFG_TUD_NCM_OUT_NTB_MAX_SIZE 3200 +#endif + +#ifndef CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB +#define CFG_TUD_NCM_MAX_DATAGRAMS_PER_NTB 8 +#endif + +#ifndef CFG_TUD_NCM_ALIGNMENT +#define CFG_TUD_NCM_ALIGNMENT 4 +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Application API +//--------------------------------------------------------------------+ + +// indicate to network driver that client has finished with the packet provided to network_recv_cb() +void tud_network_recv_renew(void); + +// poll network driver for its ability to accept another packet to transmit +bool tud_network_can_xmit(uint16_t size); + +// if network_can_xmit() returns true, network_xmit() can be called once +void tud_network_xmit(void *ref, uint16_t arg); + +//--------------------------------------------------------------------+ +// Application Callbacks (WEAK is optional) +//--------------------------------------------------------------------+ + +// client must provide this: return false if the packet buffer was not accepted +bool tud_network_recv_cb(const uint8_t *src, uint16_t size); + +// client must provide this: copy from network stack packet pointer to dst +uint16_t tud_network_xmit_cb(uint8_t *dst, void *ref, uint16_t arg); + +//------------- ECM/RNDIS -------------// + +// client must provide this: initialize any network state back to the beginning +void tud_network_init_cb(void); + +// client must provide this: 48-bit MAC address +// TODO removed later since it is not part of tinyusb stack +extern const uint8_t tud_network_mac_address[6]; + +//------------- NCM -------------// + +// callback to client providing optional indication of internal state of network driver +void tud_network_link_state_cb(bool state); + +//--------------------------------------------------------------------+ +// INTERNAL USBD-CLASS DRIVER API +//--------------------------------------------------------------------+ +void netd_init (void); +void netd_reset (uint8_t rhport); +uint16_t netd_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool netd_control_xfer_cb (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); +bool netd_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); +void netd_report (uint8_t *buf, uint16_t len); + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_NET_DEVICE_H_ */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/usbtmc/usbtmc.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/usbtmc/usbtmc.h new file mode 100644 index 000000000..7d7005c2e --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/usbtmc/usbtmc.h @@ -0,0 +1,316 @@ + +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 N Conrad + * + * 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_USBTMC_H__ +#define _TUSB_USBTMC_H__ + +#include "common/tusb_common.h" + + +/* Implements USBTMC Revision 1.0, April 14, 2003 + + String descriptors must have a "LANGID=0x409"/US English string. + Characters must be 0x20 (' ') to 0x7E ('~') ASCII, + But MUST not contain: "/:?\* + Also must not have leading or trailing space (' ') + Device descriptor must state USB version 0x0200 or greater + + If USB488DeviceCapabilites.D2 = 1 (SR1), then there must be a INT endpoint. +*/ + +#define USBTMC_VERSION 0x0100 +#define USBTMC_488_VERSION 0x0100 + +typedef enum { + USBTMC_MSGID_DEV_DEP_MSG_OUT = 1u, + USBTMC_MSGID_DEV_DEP_MSG_IN = 2u, + USBTMC_MSGID_VENDOR_SPECIFIC_MSG_OUT = 126u, + USBTMC_MSGID_VENDOR_SPECIFIC_IN = 127u, + USBTMC_MSGID_USB488_TRIGGER = 128u, +} usbtmc_msgid_enum; + +/// \brief Message header (For BULK OUT and BULK IN); 4 bytes +typedef struct TU_ATTR_PACKED +{ + uint8_t MsgID ; ///< Message type ID (usbtmc_msgid_enum) + uint8_t bTag ; ///< Transfer ID 1<=bTag<=255 + uint8_t bTagInverse ; ///< Complement of the tag + uint8_t _reserved ; ///< Must be 0x00 +} usbtmc_msg_header_t; + +typedef struct TU_ATTR_PACKED +{ + usbtmc_msg_header_t header; + uint8_t data[8]; +} usbtmc_msg_generic_t; + +/* Uses on the bulk-out endpoint: */ +// Next 8 bytes are message-specific +typedef struct TU_ATTR_PACKED { + usbtmc_msg_header_t header ; ///< Header + uint32_t TransferSize ; ///< Transfer size; LSB first + struct TU_ATTR_PACKED + { + unsigned int EOM : 1 ; ///< EOM set on last byte + } bmTransferAttributes; + uint8_t _reserved[3]; +} usbtmc_msg_request_dev_dep_out; + +TU_VERIFY_STATIC(sizeof(usbtmc_msg_request_dev_dep_out) == 12u, "struct wrong length"); + +// Next 8 bytes are message-specific +typedef struct TU_ATTR_PACKED +{ + usbtmc_msg_header_t header ; ///< Header + uint32_t TransferSize ; ///< Transfer size; LSB first + struct TU_ATTR_PACKED + { + unsigned int TermCharEnabled : 1 ; ///< "The Bulk-IN transfer must terminate on the specified TermChar."; CAPABILITIES must list TermChar + } bmTransferAttributes; + uint8_t TermChar; + uint8_t _reserved[2]; +} usbtmc_msg_request_dev_dep_in; + +TU_VERIFY_STATIC(sizeof(usbtmc_msg_request_dev_dep_in) == 12u, "struct wrong length"); + +/* Bulk-in headers */ + +typedef struct TU_ATTR_PACKED +{ + usbtmc_msg_header_t header; + uint32_t TransferSize; + struct TU_ATTR_PACKED + { + uint8_t EOM: 1; ///< Last byte of transfer is the end of the message + uint8_t UsingTermChar: 1; ///< Support TermChar && Request.TermCharEnabled && last char in transfer is TermChar + } bmTransferAttributes; + uint8_t _reserved[3]; +} usbtmc_msg_dev_dep_msg_in_header_t; + +TU_VERIFY_STATIC(sizeof(usbtmc_msg_dev_dep_msg_in_header_t) == 12u, "struct wrong length"); + +/* Unsupported vendor things.... Are these ever used?*/ + +typedef struct TU_ATTR_PACKED +{ + usbtmc_msg_header_t header ; ///< Header + uint32_t TransferSize ; ///< Transfer size; LSB first + uint8_t _reserved[4]; +} usbtmc_msg_request_vendor_specific_out; + + +TU_VERIFY_STATIC(sizeof(usbtmc_msg_request_vendor_specific_out) == 12u, "struct wrong length"); + +typedef struct TU_ATTR_PACKED +{ + usbtmc_msg_header_t header ; ///< Header + uint32_t TransferSize ; ///< Transfer size; LSB first + uint8_t _reserved[4]; +} usbtmc_msg_request_vendor_specific_in; + +TU_VERIFY_STATIC(sizeof(usbtmc_msg_request_vendor_specific_in) == 12u, "struct wrong length"); + +// Control request type should use tusb_control_request_t + +/* +typedef struct TU_ATTR_PACKED { + struct { + unsigned int Recipient : 5 ; ///< EOM set on last byte + unsigned int Type : 2 ; ///< EOM set on last byte + unsigned int DirectionToHost : 1 ; ///< 0 is OUT, 1 is IN + } bmRequestType; + uint8_t bRequest ; ///< If bmRequestType.Type = Class, see usmtmc_request_type_enum + uint16_t wValue ; + uint16_t wIndex ; + uint16_t wLength ; // Number of bytes in data stage +} usbtmc_class_specific_control_req; + +*/ +// bulk-in protocol errors +enum { + USBTMC_BULK_IN_ERR_INCOMPLETE_HEADER = 1u, + USBTMC_BULK_IN_ERR_UNSUPPORTED = 2u, + USBTMC_BULK_IN_ERR_BAD_PARAMETER = 3u, + USBTMC_BULK_IN_ERR_DATA_TOO_SHORT = 4u, + USBTMC_BULK_IN_ERR_DATA_TOO_LONG = 5u, +}; +// bult-in halt errors +enum { + USBTMC_BULK_IN_ERR = 1u, ///< receives a USBTMC command message that expects a response while a + /// Bulk-IN transfer is in progress +}; + +typedef enum { + USBTMC_bREQUEST_INITIATE_ABORT_BULK_OUT = 1u, + USBTMC_bREQUEST_CHECK_ABORT_BULK_OUT_STATUS = 2u, + USBTMC_bREQUEST_INITIATE_ABORT_BULK_IN = 3u, + USBTMC_bREQUEST_CHECK_ABORT_BULK_IN_STATUS = 4u, + USBTMC_bREQUEST_INITIATE_CLEAR = 5u, + USBTMC_bREQUEST_CHECK_CLEAR_STATUS = 6u, + USBTMC_bREQUEST_GET_CAPABILITIES = 7u, + + USBTMC_bREQUEST_INDICATOR_PULSE = 64u, // Optional + + /****** USBTMC 488 *************/ + USB488_bREQUEST_READ_STATUS_BYTE = 128u, + USB488_bREQUEST_REN_CONTROL = 160u, + USB488_bREQUEST_GO_TO_LOCAL = 161u, + USB488_bREQUEST_LOCAL_LOCKOUT = 162u, + +} usmtmc_request_type_enum; + +typedef enum { + USBTMC_STATUS_SUCCESS = 0x01, + USBTMC_STATUS_PENDING = 0x02, + USBTMC_STATUS_FAILED = 0x80, + USBTMC_STATUS_TRANSFER_NOT_IN_PROGRESS = 0x81, + USBTMC_STATUS_SPLIT_NOT_IN_PROGRESS = 0x82, + USBTMC_STATUS_SPLIT_IN_PROGRESS = 0x83 +} usbtmc_status_enum; + +/************************************************************ + * Control Responses + */ + +typedef struct TU_ATTR_PACKED { + uint8_t USBTMC_status; ///< usbtmc_status_enum + uint8_t _reserved; + uint16_t bcdUSBTMC; ///< USBTMC_VERSION + + struct TU_ATTR_PACKED + { + unsigned int listenOnly :1; + unsigned int talkOnly :1; + unsigned int supportsIndicatorPulse :1; + } bmIntfcCapabilities; + struct TU_ATTR_PACKED + { + unsigned int canEndBulkInOnTermChar :1; + } bmDevCapabilities; + uint8_t _reserved2[6]; + uint8_t _reserved3[12]; +} usbtmc_response_capabilities_t; + +TU_VERIFY_STATIC(sizeof(usbtmc_response_capabilities_t) == 0x18, "struct wrong length"); + +typedef struct TU_ATTR_PACKED +{ + uint8_t USBTMC_status; + struct TU_ATTR_PACKED + { + unsigned int BulkInFifoBytes :1; + } bmClear; +} usbtmc_get_clear_status_rsp_t; + +TU_VERIFY_STATIC(sizeof(usbtmc_get_clear_status_rsp_t) == 2u, "struct wrong length"); + +// Used for both abort bulk IN and bulk OUT +typedef struct TU_ATTR_PACKED +{ + uint8_t USBTMC_status; + uint8_t bTag; +} usbtmc_initiate_abort_rsp_t; + +TU_VERIFY_STATIC(sizeof(usbtmc_get_clear_status_rsp_t) == 2u, "struct wrong length"); + +// Used for both check_abort_bulk_in_status and check_abort_bulk_out_status +typedef struct TU_ATTR_PACKED +{ + uint8_t USBTMC_status; + struct TU_ATTR_PACKED + { + unsigned int BulkInFifoBytes : 1; ///< Has queued data or a short packet that is queued + } bmAbortBulkIn; + uint8_t _reserved[2]; ///< Must be zero + uint32_t NBYTES_RXD_TXD; +} usbtmc_check_abort_bulk_rsp_t; + +TU_VERIFY_STATIC(sizeof(usbtmc_check_abort_bulk_rsp_t) == 8u, "struct wrong length"); + +typedef struct TU_ATTR_PACKED +{ + uint8_t USBTMC_status; ///< usbtmc_status_enum + uint8_t _reserved; + uint16_t bcdUSBTMC; ///< USBTMC_VERSION + + struct TU_ATTR_PACKED + { + unsigned int listenOnly :1; + unsigned int talkOnly :1; + unsigned int supportsIndicatorPulse :1; + } bmIntfcCapabilities; + + struct TU_ATTR_PACKED + { + unsigned int canEndBulkInOnTermChar :1; + } bmDevCapabilities; + + uint8_t _reserved2[6]; + uint16_t bcdUSB488; + + struct TU_ATTR_PACKED + { + unsigned int is488_2 :1; + unsigned int supportsREN_GTL_LLO :1; + unsigned int supportsTrigger :1; + } bmIntfcCapabilities488; + + struct TU_ATTR_PACKED + { + unsigned int SCPI :1; + unsigned int SR1 :1; + unsigned int RL1 :1; + unsigned int DT1 :1; + } bmDevCapabilities488; + uint8_t _reserved3[8]; +} usbtmc_response_capabilities_488_t; + +TU_VERIFY_STATIC(sizeof(usbtmc_response_capabilities_488_t) == 0x18, "struct wrong length"); + +typedef struct TU_ATTR_PACKED +{ + uint8_t USBTMC_status; + uint8_t bTag; + uint8_t statusByte; +} usbtmc_read_stb_rsp_488_t; + +TU_VERIFY_STATIC(sizeof(usbtmc_read_stb_rsp_488_t) == 3u, "struct wrong length"); + +typedef struct TU_ATTR_PACKED +{ + struct TU_ATTR_PACKED + { + unsigned int bTag : 7; + unsigned int one : 1; + } bNotify1; + uint8_t StatusByte; +} usbtmc_read_stb_interrupt_488_t; + +TU_VERIFY_STATIC(sizeof(usbtmc_read_stb_interrupt_488_t) == 2u, "struct wrong length"); + +#endif + diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/usbtmc/usbtmc_device.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/usbtmc/usbtmc_device.c new file mode 100644 index 000000000..26be987cf --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/usbtmc/usbtmc_device.c @@ -0,0 +1,858 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Nathan Conrad + * + * 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. + */ + +/* + * This library is not fully reentrant, though it is reentrant from the view + * of either the application layer or the USB stack. Due to its locking, + * it is not safe to call its functions from interrupts. + * + * The one exception is that its functions may not be called from the application + * until the USB stack is initialized. This should not be a problem since the + * device shouldn't be sending messages until it receives a request from the + * host. + */ + + +/* + * In the case of single-CPU "no OS", this task is never preempted other than by + * interrupts, and the USBTMC code isn't called by interrupts, so all is OK. For "no OS", + * the mutex structure's main effect is to disable the USB interrupts. + * With an OS, this class driver uses the OSAL to perform locking. The code uses a single lock + * and does not call outside of this class with a lock held, so deadlocks won't happen. + */ + +//Limitations: +// "vendor-specific" commands are not handled. +// Dealing with "termchar" must be handled by the application layer, +// though additional error checking is does in this module. +// talkOnly and listenOnly are NOT supported. They're not permitted +// in USB488, anyway. + +/* Supported: + * + * Notification pulse + * Trigger + * Read status byte (both by interrupt endpoint and control message) + * + */ + + +// TODO: +// USBTMC 3.2.2 error conditions not strictly followed +// No local lock-out, REN, or GTL. +// Clear message available status byte at the correct time? (488 4.3.1.3) + + +#include "tusb_option.h" + +#if (TUSB_OPT_DEVICE_ENABLED && CFG_TUD_USBTMC) + +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "usbtmc_device.h" + +#ifdef xDEBUG +#include "uart_util.h" +static char logMsg[150]; +#endif + +/* + * The state machine does not allow simultaneous reading and writing. This is + * consistent with USBTMC. + */ + +typedef enum +{ + STATE_CLOSED, // Endpoints have not yet been opened since USB reset + STATE_NAK, // Bulk-out endpoint is in NAK state. + STATE_IDLE, // Bulk-out endpoint is waiting for CMD. + STATE_RCV, // Bulk-out is receiving DEV_DEP message + STATE_TX_REQUESTED, + STATE_TX_INITIATED, + STATE_TX_SHORTED, + STATE_CLEARING, + STATE_ABORTING_BULK_IN, + STATE_ABORTING_BULK_IN_SHORTED, // aborting, and short packet has been queued for transmission + STATE_ABORTING_BULK_IN_ABORTED, // aborting, and short packet has been transmitted + STATE_ABORTING_BULK_OUT, + STATE_NUM_STATES +} usbtmcd_state_enum; + +#if (CFG_TUD_USBTMC_ENABLE_488) + typedef usbtmc_response_capabilities_488_t usbtmc_capabilities_specific_t; +#else + typedef usbtmc_response_capabilities_t usbtmc_capabilities_specific_t; +#endif + + +typedef struct +{ + volatile usbtmcd_state_enum state; + + uint8_t itf_id; + uint8_t rhport; + uint8_t ep_bulk_in; + uint8_t ep_bulk_out; + uint8_t ep_int_in; + // IN buffer is only used for first packet, not the remainder + // in order to deal with prepending header + CFG_TUSB_MEM_ALIGN uint8_t ep_bulk_in_buf[USBTMCD_MAX_PACKET_SIZE]; + // OUT buffer receives one packet at a time + CFG_TUSB_MEM_ALIGN uint8_t ep_bulk_out_buf[USBTMCD_MAX_PACKET_SIZE]; + uint32_t transfer_size_remaining; // also used for requested length for bulk IN. + uint32_t transfer_size_sent; // To keep track of data bytes that have been queued in FIFO (not header bytes) + + uint8_t lastBulkOutTag; // used for aborts (mostly) + uint8_t lastBulkInTag; // used for aborts (mostly) + + uint8_t const * devInBuffer; // pointer to application-layer used for transmissions + + usbtmc_capabilities_specific_t const * capabilities; +} usbtmc_interface_state_t; + +CFG_TUSB_MEM_SECTION static usbtmc_interface_state_t usbtmc_state = +{ + .itf_id = 0xFF, +}; + +// We need all headers to fit in a single packet in this implementation. +TU_VERIFY_STATIC(USBTMCD_MAX_PACKET_SIZE >= 32u,"USBTMC dev EP packet size too small"); +TU_VERIFY_STATIC( + (sizeof(usbtmc_state.ep_bulk_in_buf) % USBTMCD_MAX_PACKET_SIZE) == 0, + "packet buffer must be a multiple of the packet size"); + +static bool handle_devMsgOutStart(uint8_t rhport, void *data, size_t len); +static bool handle_devMsgOut(uint8_t rhport, void *data, size_t len, size_t packetLen); + +static uint8_t termChar; +static uint8_t termCharRequested = false; + + +osal_mutex_def_t usbtmcLockBuffer; +static osal_mutex_t usbtmcLock; + +// Our own private lock, mostly for the state variable. +#define criticalEnter() do {osal_mutex_lock(usbtmcLock,OSAL_TIMEOUT_WAIT_FOREVER); } while (0) +#define criticalLeave() do {osal_mutex_unlock(usbtmcLock); } while (0) + +bool atomicChangeState(usbtmcd_state_enum expectedState, usbtmcd_state_enum newState) +{ + bool ret = true; + criticalEnter(); + usbtmcd_state_enum oldState = usbtmc_state.state; + if (oldState == expectedState) + { + usbtmc_state.state = newState; + } + else + { + ret = false; + } + criticalLeave(); + return ret; +} + +// called from app +// We keep a reference to the buffer, so it MUST not change until the app is +// notified that the transfer is complete. +// length of data is specified in the hdr. + +// We can't just send the whole thing at once because we need to concatanate the +// header with the data. +bool tud_usbtmc_transmit_dev_msg_data( + const void * data, size_t len, + bool endOfMessage, + bool usingTermChar) +{ + const unsigned int txBufLen = sizeof(usbtmc_state.ep_bulk_in_buf); + +#ifndef NDEBUG + TU_ASSERT(len > 0u); + TU_ASSERT(len <= usbtmc_state.transfer_size_remaining); + TU_ASSERT(usbtmc_state.transfer_size_sent == 0u); + if(usingTermChar) + { + TU_ASSERT(usbtmc_state.capabilities->bmDevCapabilities.canEndBulkInOnTermChar); + TU_ASSERT(termCharRequested); + TU_ASSERT(((uint8_t const*)data)[len-1u] == termChar); + } +#endif + + TU_VERIFY(usbtmc_state.state == STATE_TX_REQUESTED); + usbtmc_msg_dev_dep_msg_in_header_t *hdr = (usbtmc_msg_dev_dep_msg_in_header_t*)usbtmc_state.ep_bulk_in_buf; + tu_varclr(hdr); + hdr->header.MsgID = USBTMC_MSGID_DEV_DEP_MSG_IN; + hdr->header.bTag = usbtmc_state.lastBulkInTag; + hdr->header.bTagInverse = (uint8_t)~(usbtmc_state.lastBulkInTag); + hdr->TransferSize = len; + hdr->bmTransferAttributes.EOM = endOfMessage; + hdr->bmTransferAttributes.UsingTermChar = usingTermChar; + + // Copy in the header + const size_t headerLen = sizeof(*hdr); + const size_t dataLen = ((headerLen + hdr->TransferSize) <= txBufLen) ? + len : (txBufLen - headerLen); + const size_t packetLen = headerLen + dataLen; + + memcpy((uint8_t*)(usbtmc_state.ep_bulk_in_buf) + headerLen, data, dataLen); + usbtmc_state.transfer_size_remaining = len - dataLen; + usbtmc_state.transfer_size_sent = dataLen; + usbtmc_state.devInBuffer = (uint8_t const*) data + (dataLen); + + bool stateChanged = + atomicChangeState(STATE_TX_REQUESTED, (packetLen >= txBufLen) ? STATE_TX_INITIATED : STATE_TX_SHORTED); + TU_VERIFY(stateChanged); + TU_VERIFY(usbd_edpt_xfer(usbtmc_state.rhport, usbtmc_state.ep_bulk_in, usbtmc_state.ep_bulk_in_buf, (uint16_t)packetLen)); + return true; +} + +void usbtmcd_init_cb(void) +{ + usbtmc_state.capabilities = tud_usbtmc_get_capabilities_cb(); +#ifndef NDEBUG +# if CFG_TUD_USBTMC_ENABLE_488 + if (usbtmc_state.capabilities->bmIntfcCapabilities488.supportsTrigger) { + TU_ASSERT(&tud_usbtmc_msg_trigger_cb != NULL,); + } + // Per USB488 spec: table 8 + TU_ASSERT(!usbtmc_state.capabilities->bmIntfcCapabilities.listenOnly,); + TU_ASSERT(!usbtmc_state.capabilities->bmIntfcCapabilities.talkOnly,); +# endif + if (usbtmc_state.capabilities->bmIntfcCapabilities.supportsIndicatorPulse) { + TU_ASSERT(&tud_usbtmc_indicator_pulse_cb != NULL,); + } +#endif + + usbtmcLock = osal_mutex_create(&usbtmcLockBuffer); +} + +uint16_t usbtmcd_open_cb(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) +{ + (void)rhport; + + uint16_t drv_len; + uint8_t const * p_desc; + uint8_t found_endpoints = 0; + + TU_VERIFY(itf_desc->bInterfaceClass == TUD_USBTMC_APP_CLASS , 0); + TU_VERIFY(itf_desc->bInterfaceSubClass == TUD_USBTMC_APP_SUBCLASS, 0); + +#ifndef NDEBUG + // Only 2 or 3 endpoints are allowed for USBTMC. + TU_ASSERT((itf_desc->bNumEndpoints == 2) || (itf_desc->bNumEndpoints ==3), 0); +#endif + + TU_ASSERT(usbtmc_state.state == STATE_CLOSED, 0); + + // Interface + drv_len = 0u; + p_desc = (uint8_t const *) itf_desc; + + usbtmc_state.itf_id = itf_desc->bInterfaceNumber; + usbtmc_state.rhport = rhport; + + while (found_endpoints < itf_desc->bNumEndpoints && drv_len <= max_len) + { + if ( TUSB_DESC_ENDPOINT == p_desc[DESC_OFFSET_TYPE]) + { + tusb_desc_endpoint_t const *ep_desc = (tusb_desc_endpoint_t const *)p_desc; + switch(ep_desc->bmAttributes.xfer) { + case TUSB_XFER_BULK: + TU_ASSERT(tu_edpt_packet_size(ep_desc) == USBTMCD_MAX_PACKET_SIZE, 0); + if (tu_edpt_dir(ep_desc->bEndpointAddress) == TUSB_DIR_IN) + { + usbtmc_state.ep_bulk_in = ep_desc->bEndpointAddress; + } else { + usbtmc_state.ep_bulk_out = ep_desc->bEndpointAddress; + } + + break; + case TUSB_XFER_INTERRUPT: +#ifndef NDEBUG + TU_ASSERT(tu_edpt_dir(ep_desc->bEndpointAddress) == TUSB_DIR_IN, 0); + TU_ASSERT(usbtmc_state.ep_int_in == 0, 0); +#endif + usbtmc_state.ep_int_in = ep_desc->bEndpointAddress; + break; + default: + TU_ASSERT(false, 0); + } + TU_ASSERT( usbd_edpt_open(rhport, ep_desc), 0); + found_endpoints++; + } + + drv_len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } + + // bulk endpoints are required, but interrupt IN is optional +#ifndef NDEBUG + TU_ASSERT(usbtmc_state.ep_bulk_in != 0, 0); + TU_ASSERT(usbtmc_state.ep_bulk_out != 0, 0); + if (itf_desc->bNumEndpoints == 2) + { + TU_ASSERT(usbtmc_state.ep_int_in == 0, 0); + } + else if (itf_desc->bNumEndpoints == 3) + { + TU_ASSERT(usbtmc_state.ep_int_in != 0, 0); + } +#if (CFG_TUD_USBTMC_ENABLE_488) + if(usbtmc_state.capabilities->bmIntfcCapabilities488.is488_2 || + usbtmc_state.capabilities->bmDevCapabilities488.SR1) + { + TU_ASSERT(usbtmc_state.ep_int_in != 0, 0); + } +#endif +#endif + atomicChangeState(STATE_CLOSED, STATE_NAK); + tud_usbtmc_open_cb(itf_desc->iInterface); + + return drv_len; +} +// Tell USBTMC class to set its bulk-in EP to ACK so that it can +// receive USBTMC commands. +// Returns false if it was already in an ACK state or is busy +// processing a command (such as a clear). Returns true if it was +// in the NAK state and successfully transitioned to the ACK wait +// state. +bool tud_usbtmc_start_bus_read() +{ + usbtmcd_state_enum oldState = usbtmc_state.state; + switch(oldState) + { + // These may transition to IDLE + case STATE_NAK: + case STATE_ABORTING_BULK_IN_ABORTED: + TU_VERIFY(atomicChangeState(oldState, STATE_IDLE)); + break; + // When receiving, let it remain receiving + case STATE_RCV: + break; + default: + TU_VERIFY(false); + } + TU_VERIFY(usbd_edpt_xfer(usbtmc_state.rhport, usbtmc_state.ep_bulk_out, usbtmc_state.ep_bulk_out_buf, 64)); + return true; +} + +void usbtmcd_reset_cb(uint8_t rhport) +{ + (void)rhport; + usbtmc_capabilities_specific_t const * capabilities = tud_usbtmc_get_capabilities_cb(); + + criticalEnter(); + tu_varclr(&usbtmc_state); + usbtmc_state.capabilities = capabilities; + usbtmc_state.itf_id = 0xFFu; + criticalLeave(); +} + +static bool handle_devMsgOutStart(uint8_t rhport, void *data, size_t len) +{ + (void)rhport; + // return true upon failure, as we can assume error is being handled elsewhere. + TU_VERIFY(atomicChangeState(STATE_IDLE, STATE_RCV), true); + usbtmc_state.transfer_size_sent = 0u; + + // must be a header, should have been confirmed before calling here. + usbtmc_msg_request_dev_dep_out *msg = (usbtmc_msg_request_dev_dep_out*)data; + usbtmc_state.transfer_size_remaining = msg->TransferSize; + TU_VERIFY(tud_usbtmc_msgBulkOut_start_cb(msg)); + + TU_VERIFY(handle_devMsgOut(rhport, (uint8_t*)data + sizeof(*msg), len - sizeof(*msg), len)); + usbtmc_state.lastBulkOutTag = msg->header.bTag; + return true; +} + +static bool handle_devMsgOut(uint8_t rhport, void *data, size_t len, size_t packetLen) +{ + (void)rhport; + // return true upon failure, as we can assume error is being handled elsewhere. + TU_VERIFY(usbtmc_state.state == STATE_RCV,true); + + bool shortPacket = (packetLen < USBTMCD_MAX_PACKET_SIZE); + + // Packet is to be considered complete when we get enough data or at a short packet. + bool atEnd = false; + if(len >= usbtmc_state.transfer_size_remaining || shortPacket) + { + atEnd = true; + TU_VERIFY(atomicChangeState(STATE_RCV, STATE_NAK)); + } + + len = tu_min32(len, usbtmc_state.transfer_size_remaining); + + usbtmc_state.transfer_size_remaining -= len; + usbtmc_state.transfer_size_sent += len; + + // App may (should?) call the wait_for_bus() command at this point + if(!tud_usbtmc_msg_data_cb(data, len, atEnd)) + { + // TODO: Go to an error state upon failure other than just stalling the EP? + return false; + } + + + return true; +} + +static bool handle_devMsgIn(void *data, size_t len) +{ + TU_VERIFY(len == sizeof(usbtmc_msg_request_dev_dep_in)); + usbtmc_msg_request_dev_dep_in *msg = (usbtmc_msg_request_dev_dep_in*)data; + bool stateChanged = atomicChangeState(STATE_IDLE, STATE_TX_REQUESTED); + TU_VERIFY(stateChanged); + usbtmc_state.lastBulkInTag = msg->header.bTag; + usbtmc_state.transfer_size_remaining = msg->TransferSize; + usbtmc_state.transfer_size_sent = 0u; + + termCharRequested = msg->bmTransferAttributes.TermCharEnabled; + termChar = msg->TermChar; + + if(termCharRequested) + TU_VERIFY(usbtmc_state.capabilities->bmDevCapabilities.canEndBulkInOnTermChar); + + TU_VERIFY(tud_usbtmc_msgBulkIn_request_cb(msg)); + return true; +} + +bool usbtmcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) +{ + TU_VERIFY(result == XFER_RESULT_SUCCESS); + //uart_tx_str_sync("TMC XFER CB\r\n"); + if(usbtmc_state.state == STATE_CLEARING) { + return true; /* I think we can ignore everything here */ + } + + if(ep_addr == usbtmc_state.ep_bulk_out) + { + usbtmc_msg_generic_t *msg = NULL; + + switch(usbtmc_state.state) + { + case STATE_IDLE: + TU_VERIFY(xferred_bytes >= sizeof(usbtmc_msg_generic_t)); + msg = (usbtmc_msg_generic_t*)(usbtmc_state.ep_bulk_out_buf); + uint8_t invInvTag = (uint8_t)~(msg->header.bTagInverse); + TU_VERIFY(msg->header.bTag == invInvTag); + TU_VERIFY(msg->header.bTag != 0x00); + + switch(msg->header.MsgID) { + case USBTMC_MSGID_DEV_DEP_MSG_OUT: + if(!handle_devMsgOutStart(rhport, msg, xferred_bytes)) + { + usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); + TU_VERIFY(false); + } + break; + + case USBTMC_MSGID_DEV_DEP_MSG_IN: + TU_VERIFY(handle_devMsgIn(msg, xferred_bytes)); + break; + +#if (CFG_TUD_USBTMC_ENABLE_488) + case USBTMC_MSGID_USB488_TRIGGER: + // Spec says we halt the EP if we didn't declare we support it. + TU_VERIFY(usbtmc_state.capabilities->bmIntfcCapabilities488.supportsTrigger); + TU_VERIFY(tud_usbtmc_msg_trigger_cb(msg)); + + break; +#endif + case USBTMC_MSGID_VENDOR_SPECIFIC_MSG_OUT: + case USBTMC_MSGID_VENDOR_SPECIFIC_IN: + default: + usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); + TU_VERIFY(false); + return false; + } + return true; + + case STATE_RCV: + if(!handle_devMsgOut(rhport, usbtmc_state.ep_bulk_out_buf, xferred_bytes, xferred_bytes)) + { + usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); + TU_VERIFY(false); + } + return true; + + case STATE_ABORTING_BULK_OUT: + TU_VERIFY(false); + return false; // Should be stalled by now, shouldn't have received a packet. + + case STATE_TX_REQUESTED: + case STATE_TX_INITIATED: + case STATE_ABORTING_BULK_IN: + case STATE_ABORTING_BULK_IN_SHORTED: + case STATE_ABORTING_BULK_IN_ABORTED: + default: + TU_VERIFY(false); + } + } + else if(ep_addr == usbtmc_state.ep_bulk_in) + { + switch(usbtmc_state.state) { + case STATE_TX_SHORTED: + TU_VERIFY(atomicChangeState(STATE_TX_SHORTED, STATE_NAK)); + TU_VERIFY(tud_usbtmc_msgBulkIn_complete_cb()); + break; + + case STATE_TX_INITIATED: + if(usbtmc_state.transfer_size_remaining >=sizeof(usbtmc_state.ep_bulk_in_buf)) + { + // FIXME! This removes const below! + TU_VERIFY( usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, + (void*)(uintptr_t) usbtmc_state.devInBuffer, sizeof(usbtmc_state.ep_bulk_in_buf))); + usbtmc_state.devInBuffer += sizeof(usbtmc_state.ep_bulk_in_buf); + usbtmc_state.transfer_size_remaining -= sizeof(usbtmc_state.ep_bulk_in_buf); + usbtmc_state.transfer_size_sent += sizeof(usbtmc_state.ep_bulk_in_buf); + } + else // last packet + { + size_t packetLen = usbtmc_state.transfer_size_remaining; + memcpy(usbtmc_state.ep_bulk_in_buf, usbtmc_state.devInBuffer, usbtmc_state.transfer_size_remaining); + usbtmc_state.transfer_size_sent += sizeof(usbtmc_state.transfer_size_remaining); + usbtmc_state.transfer_size_remaining = 0; + usbtmc_state.devInBuffer = NULL; + TU_VERIFY( usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_state.ep_bulk_in_buf, (uint16_t)packetLen) ); + if(((packetLen % USBTMCD_MAX_PACKET_SIZE) != 0) || (packetLen == 0 )) + { + usbtmc_state.state = STATE_TX_SHORTED; + } + } + return true; + + case STATE_ABORTING_BULK_IN: + // need to send short packet (ZLP?) + TU_VERIFY( usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_state.ep_bulk_in_buf,(uint16_t)0u)); + usbtmc_state.state = STATE_ABORTING_BULK_IN_SHORTED; + return true; + + case STATE_ABORTING_BULK_IN_SHORTED: + /* Done. :)*/ + usbtmc_state.state = STATE_ABORTING_BULK_IN_ABORTED; + return true; + + default: + TU_ASSERT(false); + return false; + } + } + else if (ep_addr == usbtmc_state.ep_int_in) { + // Good? + return true; + } + return false; +} + +// 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 usbtmcd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) +{ + // nothing to do with DATA and ACK stage + if ( stage != CONTROL_STAGE_SETUP ) return true; + + uint8_t tmcStatusCode = USBTMC_STATUS_FAILED; +#if (CFG_TUD_USBTMC_ENABLE_488) + uint8_t bTag; +#endif + + if((request->bmRequestType_bit.type == TUSB_REQ_TYPE_STANDARD) && + (request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_ENDPOINT) && + (request->bRequest == TUSB_REQ_CLEAR_FEATURE) && + (request->wValue == TUSB_REQ_FEATURE_EDPT_HALT)) + { + uint32_t ep_addr = (request->wIndex); + + if(ep_addr == usbtmc_state.ep_bulk_out) + { + criticalEnter(); + usbtmc_state.state = STATE_NAK; // USBD core has placed EP in NAK state for us + criticalLeave(); + tud_usbtmc_bulkOut_clearFeature_cb(); + } + else if (ep_addr == usbtmc_state.ep_bulk_in) + { + tud_usbtmc_bulkIn_clearFeature_cb(); + } + else + { + return false; + } + return true; + } + + // Otherwise, we only handle class requests. + if(request->bmRequestType_bit.type != TUSB_REQ_TYPE_CLASS) + { + return false; + } + + // Verification that we own the interface is unneeded since it's been routed to us specifically. + + switch(request->bRequest) + { + // USBTMC required requests + case USBTMC_bREQUEST_INITIATE_ABORT_BULK_OUT: + { + usbtmc_initiate_abort_rsp_t rsp = { + .bTag = usbtmc_state.lastBulkOutTag, + }; + TU_VERIFY(request->bmRequestType == 0xA2); // in,class,interface + TU_VERIFY(request->wLength == sizeof(rsp)); + TU_VERIFY(request->wIndex == usbtmc_state.ep_bulk_out); + + // wValue is the requested bTag to abort + if(usbtmc_state.state != STATE_RCV) + { + rsp.USBTMC_status = USBTMC_STATUS_FAILED; + } + else if(usbtmc_state.lastBulkOutTag == (request->wValue & 0x7Fu)) + { + rsp.USBTMC_status = USBTMC_STATUS_TRANSFER_NOT_IN_PROGRESS; + } + else + { + rsp.USBTMC_status = USBTMC_STATUS_SUCCESS; + // Check if we've queued a short packet + criticalEnter(); + usbtmc_state.state = STATE_ABORTING_BULK_OUT; + criticalLeave(); + TU_VERIFY(tud_usbtmc_initiate_abort_bulk_out_cb(&(rsp.USBTMC_status))); + usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); + } + TU_VERIFY(tud_control_xfer(rhport, request, (void*)&rsp,sizeof(rsp))); + return true; + } + + case USBTMC_bREQUEST_CHECK_ABORT_BULK_OUT_STATUS: + { + usbtmc_check_abort_bulk_rsp_t rsp = { + .USBTMC_status = USBTMC_STATUS_SUCCESS, + .NBYTES_RXD_TXD = usbtmc_state.transfer_size_sent + }; + TU_VERIFY(request->bmRequestType == 0xA2); // in,class,EP + TU_VERIFY(request->wLength == sizeof(rsp)); + TU_VERIFY(request->wIndex == usbtmc_state.ep_bulk_out); + TU_VERIFY(tud_usbtmc_check_abort_bulk_out_cb(&rsp)); + TU_VERIFY(tud_control_xfer(rhport, request, (void*)&rsp,sizeof(rsp))); + return true; + } + + case USBTMC_bREQUEST_INITIATE_ABORT_BULK_IN: + { + usbtmc_initiate_abort_rsp_t rsp = { + .bTag = usbtmc_state.lastBulkInTag, + }; + TU_VERIFY(request->bmRequestType == 0xA2); // in,class,interface + TU_VERIFY(request->wLength == sizeof(rsp)); + TU_VERIFY(request->wIndex == usbtmc_state.ep_bulk_in); + // wValue is the requested bTag to abort + if((usbtmc_state.state == STATE_TX_REQUESTED || usbtmc_state.state == STATE_TX_INITIATED) && + usbtmc_state.lastBulkInTag == (request->wValue & 0x7Fu)) + { + rsp.USBTMC_status = USBTMC_STATUS_SUCCESS; + usbtmc_state.transfer_size_remaining = 0u; + // Check if we've queued a short packet + criticalEnter(); + usbtmc_state.state = ((usbtmc_state.transfer_size_sent % USBTMCD_MAX_PACKET_SIZE) == 0) ? + STATE_ABORTING_BULK_IN : STATE_ABORTING_BULK_IN_SHORTED; + criticalLeave(); + if(usbtmc_state.transfer_size_sent == 0) + { + // Send short packet, nothing is in the buffer yet + TU_VERIFY( usbd_edpt_xfer(rhport, usbtmc_state.ep_bulk_in, usbtmc_state.ep_bulk_in_buf,(uint16_t)0u)); + usbtmc_state.state = STATE_ABORTING_BULK_IN_SHORTED; + } + TU_VERIFY(tud_usbtmc_initiate_abort_bulk_in_cb(&(rsp.USBTMC_status))); + } + else if((usbtmc_state.state == STATE_TX_REQUESTED || usbtmc_state.state == STATE_TX_INITIATED)) + { // FIXME: Unsure how to check if the OUT endpoint fifo is non-empty.... + rsp.USBTMC_status = USBTMC_STATUS_TRANSFER_NOT_IN_PROGRESS; + } + else + { + rsp.USBTMC_status = USBTMC_STATUS_FAILED; + } + TU_VERIFY(tud_control_xfer(rhport, request, (void*)&rsp,sizeof(rsp))); + return true; + } + + case USBTMC_bREQUEST_CHECK_ABORT_BULK_IN_STATUS: + { + TU_VERIFY(request->bmRequestType == 0xA2); // in,class,EP + TU_VERIFY(request->wLength == 8u); + + usbtmc_check_abort_bulk_rsp_t rsp = + { + .USBTMC_status = USBTMC_STATUS_FAILED, + .bmAbortBulkIn = + { + .BulkInFifoBytes = (usbtmc_state.state != STATE_ABORTING_BULK_IN_ABORTED) + }, + .NBYTES_RXD_TXD = usbtmc_state.transfer_size_sent, + }; + TU_VERIFY(tud_usbtmc_check_abort_bulk_in_cb(&rsp)); + criticalEnter(); + switch(usbtmc_state.state) + { + case STATE_ABORTING_BULK_IN_ABORTED: + rsp.USBTMC_status = USBTMC_STATUS_SUCCESS; + usbtmc_state.state = STATE_IDLE; + break; + case STATE_ABORTING_BULK_IN: + case STATE_ABORTING_BULK_OUT: + rsp.USBTMC_status = USBTMC_STATUS_PENDING; + break; + default: + break; + } + criticalLeave(); + TU_VERIFY(tud_control_xfer(rhport, request, (void*)&rsp,sizeof(rsp))); + + return true; + } + + case USBTMC_bREQUEST_INITIATE_CLEAR: + { + TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface + TU_VERIFY(request->wLength == sizeof(tmcStatusCode)); + // After receiving an INITIATE_CLEAR request, the device must Halt the Bulk-OUT endpoint, queue the + // control endpoint response shown in Table 31, and clear all input buffers and output buffers. + usbd_edpt_stall(rhport, usbtmc_state.ep_bulk_out); + usbtmc_state.transfer_size_remaining = 0; + criticalEnter(); + usbtmc_state.state = STATE_CLEARING; + criticalLeave(); + TU_VERIFY(tud_usbtmc_initiate_clear_cb(&tmcStatusCode)); + TU_VERIFY(tud_control_xfer(rhport, request, (void*)&tmcStatusCode,sizeof(tmcStatusCode))); + return true; + } + + case USBTMC_bREQUEST_CHECK_CLEAR_STATUS: + { + TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface + usbtmc_get_clear_status_rsp_t clearStatusRsp = {0}; + TU_VERIFY(request->wLength == sizeof(clearStatusRsp)); + + if(usbd_edpt_busy(rhport, usbtmc_state.ep_bulk_in)) + { + // Stuff stuck in TX buffer? + clearStatusRsp.bmClear.BulkInFifoBytes = 1; + clearStatusRsp.USBTMC_status = USBTMC_STATUS_PENDING; + } + else + { + // Let app check if it's clear + TU_VERIFY(tud_usbtmc_check_clear_cb(&clearStatusRsp)); + } + if(clearStatusRsp.USBTMC_status == USBTMC_STATUS_SUCCESS) + { + criticalEnter(); + usbtmc_state.state = STATE_IDLE; + criticalLeave(); + } + TU_VERIFY(tud_control_xfer(rhport, request, (void*)&clearStatusRsp,sizeof(clearStatusRsp))); + return true; + } + + case USBTMC_bREQUEST_GET_CAPABILITIES: + { + TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface + TU_VERIFY(request->wLength == sizeof(*(usbtmc_state.capabilities))); + TU_VERIFY(tud_control_xfer(rhport, request, (void*)(uintptr_t) usbtmc_state.capabilities, sizeof(*usbtmc_state.capabilities))); + return true; + } + // USBTMC Optional Requests + + case USBTMC_bREQUEST_INDICATOR_PULSE: // Optional + { + TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface + TU_VERIFY(request->wLength == sizeof(tmcStatusCode)); + TU_VERIFY(usbtmc_state.capabilities->bmIntfcCapabilities.supportsIndicatorPulse); + TU_VERIFY(tud_usbtmc_indicator_pulse_cb(request, &tmcStatusCode)); + TU_VERIFY(tud_control_xfer(rhport, request, (void*)&tmcStatusCode, sizeof(tmcStatusCode))); + return true; + } +#if (CFG_TUD_USBTMC_ENABLE_488) + + // USB488 required requests + case USB488_bREQUEST_READ_STATUS_BYTE: + { + usbtmc_read_stb_rsp_488_t rsp; + TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface + TU_VERIFY(request->wLength == sizeof(rsp)); // in,class,interface + + bTag = request->wValue & 0x7F; + TU_VERIFY(request->bmRequestType == 0xA1); + TU_VERIFY((request->wValue & (~0x7F)) == 0u); // Other bits are required to be zero + TU_VERIFY(bTag >= 0x02 && bTag <= 127); + TU_VERIFY(request->wIndex == usbtmc_state.itf_id); + TU_VERIFY(request->wLength == 0x0003); + rsp.bTag = (uint8_t)bTag; + if(usbtmc_state.ep_int_in != 0) + { + rsp.USBTMC_status = USBTMC_STATUS_SUCCESS; + rsp.statusByte = 0x00; // Use interrupt endpoint, instead. + + usbtmc_read_stb_interrupt_488_t intMsg = + { + .bNotify1 = { + .one = 1, + .bTag = bTag & 0x7Fu, + }, + .StatusByte = tud_usbtmc_get_stb_cb(&(rsp.USBTMC_status)) + }; + usbd_edpt_xfer(rhport, usbtmc_state.ep_int_in, (void*)&intMsg, sizeof(intMsg)); + } + else + { + rsp.statusByte = tud_usbtmc_get_stb_cb(&(rsp.USBTMC_status)); + } + TU_VERIFY(tud_control_xfer(rhport, request, (void*)&rsp, sizeof(rsp))); + return true; + } + // USB488 optional requests + case USB488_bREQUEST_REN_CONTROL: + case USB488_bREQUEST_GO_TO_LOCAL: + case USB488_bREQUEST_LOCAL_LOCKOUT: + { + TU_VERIFY(request->bmRequestType == 0xA1); // in,class,interface + TU_VERIFY(false); + return false; + } +#endif + + default: + TU_VERIFY(false); + return false; + } + TU_VERIFY(false); +} + +#endif /* CFG_TUD_TSMC */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/usbtmc/usbtmc_device.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/usbtmc/usbtmc_device.h new file mode 100644 index 000000000..0549a1569 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/usbtmc/usbtmc_device.h @@ -0,0 +1,116 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 N Conrad + * + * 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 CLASS_USBTMC_USBTMC_DEVICE_H_ +#define CLASS_USBTMC_USBTMC_DEVICE_H_ + +#include "usbtmc.h" + +// Enable 488 mode by default +#if !defined(CFG_TUD_USBTMC_ENABLE_488) +#define CFG_TUD_USBTMC_ENABLE_488 (1) +#endif + +// USB spec says that full-speed must be 8,16,32, or 64. +// However, this driver implementation requires it to be >=32 +#define USBTMCD_MAX_PACKET_SIZE (64u) + +/*********************************************** + * Functions to be implemeted by the class implementation + */ + +// In order to proceed, app must call call tud_usbtmc_start_bus_read(rhport) during or soon after: +// * tud_usbtmc_open_cb +// * tud_usbtmc_msg_data_cb +// * tud_usbtmc_msgBulkIn_complete_cb +// * tud_usbtmc_msg_trigger_cb +// * (successful) tud_usbtmc_check_abort_bulk_out_cb +// * (successful) tud_usbtmc_check_abort_bulk_in_cb +// * (successful) tud_usmtmc_bulkOut_clearFeature_cb + +#if (CFG_TUD_USBTMC_ENABLE_488) +usbtmc_response_capabilities_488_t const * tud_usbtmc_get_capabilities_cb(void); +#else +usbtmc_response_capabilities_t const * tud_usbtmc_get_capabilities_cb(void); +#endif + +void tud_usbtmc_open_cb(uint8_t interface_id); + +bool tud_usbtmc_msgBulkOut_start_cb(usbtmc_msg_request_dev_dep_out const * msgHeader); +// transfer_complete does not imply that a message is complete. +bool tud_usbtmc_msg_data_cb( void *data, size_t len, bool transfer_complete); +void tud_usbtmc_bulkOut_clearFeature_cb(void); // Notice to clear and abort the pending BULK out transfer + +bool tud_usbtmc_msgBulkIn_request_cb(usbtmc_msg_request_dev_dep_in const * request); +bool tud_usbtmc_msgBulkIn_complete_cb(void); +void tud_usbtmc_bulkIn_clearFeature_cb(void); // Notice to clear and abort the pending BULK out transfer + +bool tud_usbtmc_initiate_abort_bulk_in_cb(uint8_t *tmcResult); +bool tud_usbtmc_initiate_abort_bulk_out_cb(uint8_t *tmcResult); +bool tud_usbtmc_initiate_clear_cb(uint8_t *tmcResult); + +bool tud_usbtmc_check_abort_bulk_in_cb(usbtmc_check_abort_bulk_rsp_t *rsp); +bool tud_usbtmc_check_abort_bulk_out_cb(usbtmc_check_abort_bulk_rsp_t *rsp); +bool tud_usbtmc_check_clear_cb(usbtmc_get_clear_status_rsp_t *rsp); + +// Indicator pulse should be 0.5 to 1.0 seconds long +TU_ATTR_WEAK bool tud_usbtmc_indicator_pulse_cb(tusb_control_request_t const * msg, uint8_t *tmcResult); + +#if (CFG_TUD_USBTMC_ENABLE_488) +uint8_t tud_usbtmc_get_stb_cb(uint8_t *tmcResult); +TU_ATTR_WEAK bool tud_usbtmc_msg_trigger_cb(usbtmc_msg_generic_t* msg); +//TU_ATTR_WEAK bool tud_usbtmc_app_go_to_local_cb(); +#endif + +/******************************************* + * Called from app + * + * We keep a reference to the buffer, so it MUST not change until the app is + * notified that the transfer is complete. + ******************************************/ + +bool tud_usbtmc_transmit_dev_msg_data( + const void * data, size_t len, + bool endOfMessage, bool usingTermChar); + +bool tud_usbtmc_start_bus_read(void); + + +/* "callbacks" from USB device core */ + +uint16_t usbtmcd_open_cb(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +void usbtmcd_reset_cb(uint8_t rhport); +bool usbtmcd_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); +bool usbtmcd_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); +void usbtmcd_init_cb(void); + +/************************************************************ + * USBTMC Descriptor Templates + *************************************************************/ + + +#endif /* CLASS_USBTMC_USBTMC_DEVICE_H_ */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/vendor/vendor_device.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/vendor/vendor_device.c new file mode 100644 index 000000000..8a4ca1d2e --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/vendor/vendor_device.c @@ -0,0 +1,257 @@ +/* + * 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 (TUSB_OPT_DEVICE_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_TUSB_MEM_SECTION 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) +{ + // skip if previous transfer not complete + if ( usbd_edpt_busy(TUD_OPT_RHPORT, p_itf->ep_out) ) return; + + // 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(TUD_OPT_RHPORT, p_itf->ep_out, p_itf->epout_buf, CFG_TUD_VENDOR_EPSIZE); + } +} + +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, 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 +//--------------------------------------------------------------------+ +static bool maybe_transmit(vendord_interface_t* p_itf) +{ + // skip if previous transfer not complete + TU_VERIFY( !usbd_edpt_busy(TUD_OPT_RHPORT, p_itf->ep_in) ); + + uint16_t count = tu_fifo_read_n(&p_itf->tx_ff, p_itf->epin_buf, CFG_TUD_VENDOR_EPSIZE); + if (count > 0) + { + TU_ASSERT( usbd_edpt_xfer(TUD_OPT_RHPORT, p_itf->ep_in, p_itf->epin_buf, count) ); + } + return true; +} + +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, bufsize); + maybe_transmit(p_itf); + return ret; +} + +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 ) + { + TU_ASSERT(usbd_edpt_xfer(rhport, p_vendor->ep_out, p_vendor->epout_buf, sizeof(p_vendor->epout_buf)), 0); + } + + if ( p_vendor->ep_in ) maybe_transmit(p_vendor); + } + + return (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, 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 ) + { + // Send complete, try to send more if possible + maybe_transmit(p_itf); + } + + return true; +} + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/vendor/vendor_device.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/vendor/vendor_device.h new file mode 100644 index 000000000..d71c2a3e9 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/vendor/vendor_device.h @@ -0,0 +1,136 @@ +/* + * 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_available (uint8_t itf); + +static inline +uint32_t tud_vendor_n_write_str (uint8_t itf, char const* str); + +//--------------------------------------------------------------------+ +// 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); + +//--------------------------------------------------------------------+ +// Application Callback API (weak is optional) +//--------------------------------------------------------------------+ + +// Invoked when received new data +TU_ATTR_WEAK void tud_vendor_rx_cb(uint8_t itf); + +//--------------------------------------------------------------------+ +// 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_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/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/vendor/vendor_host.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/vendor/vendor_host.c new file mode 100644 index 000000000..1e28e9af4 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/vendor/vendor_host.c @@ -0,0 +1,146 @@ +/* + * 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 (TUSB_OPT_HOST_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/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/vendor/vendor_host.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/vendor/vendor_host.h new file mode 100644 index 000000000..07fa56c61 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/vendor/vendor_host.h @@ -0,0 +1,67 @@ +/* + * 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; +} + +tusb_error_t tusbh_custom_read(uint8_t dev_addr, uint16_t vendor_id, uint16_t product_id, void * p_buffer, uint16_t length); +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); + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void cush_init(void); +tusb_error_t 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/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/video/video.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/video/video.h new file mode 100644 index 000000000..844746546 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/video/video.h @@ -0,0 +1,480 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 Koji KITAYAMA + * + * 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_VIDEO_H_ +#define TUSB_VIDEO_H_ + +#include "common/tusb_common.h" + +// Table 3-19 Color Matching Descriptor +typedef enum { + VIDEO_COLOR_PRIMARIES_UNDEFINED = 0x00, + VIDEO_COLOR_PRIMARIES_BT709, // sRGB (default) + VIDEO_COLOR_PRIMARIES_BT470_2M, + VIDEO_COLOR_PRIMARIES_BT470_2BG, + VIDEO_COLOR_PRIMARIES_SMPTE170M, + VIDEO_COLOR_PRIMARIES_SMPTE240M, +} video_color_primaries_t; + +// Table 3-19 Color Matching Descriptor +typedef enum { + VIDEO_COLOR_XFER_CH_UNDEFINED = 0x00, + VIDEO_COLOR_XFER_CH_BT709, // default + VIDEO_COLOR_XFER_CH_BT470_2M, + VIDEO_COLOR_XFER_CH_BT470_2BG, + VIDEO_COLOR_XFER_CH_SMPTE170M, + VIDEO_COLOR_XFER_CH_SMPTE240M, + VIDEO_COLOR_XFER_CH_LINEAR, + VIDEO_COLOR_XFER_CH_SRGB, +} video_color_transfer_characteristics_t; + +// Table 3-19 Color Matching Descriptor +typedef enum { + VIDEO_COLOR_COEF_UNDEFINED = 0x00, + VIDEO_COLOR_COEF_BT709, + VIDEO_COLOR_COEF_FCC, + VIDEO_COLOR_COEF_BT470_2BG, + VIDEO_COLOR_COEF_SMPTE170M, // BT.601 default + VIDEO_COLOR_COEF_SMPTE240M, +} video_color_matrix_coefficients_t; + +/* 4.2.1.2 Request Error Code Control */ +typedef enum { + VIDEO_ERROR_NONE = 0, /* The request succeeded. */ + VIDEO_ERROR_NOT_READY, + VIDEO_ERROR_WRONG_STATE, + VIDEO_ERROR_POWER, + VIDEO_ERROR_OUT_OF_RANGE, + VIDEO_ERROR_INVALID_UNIT, + VIDEO_ERROR_INVALID_CONTROL, + VIDEO_ERROR_INVALID_REQUEST, + VIDEO_ERROR_INVALID_VALUE_WITHIN_RANGE, + VIDEO_ERROR_UNKNOWN = 0xFF, +} video_error_code_t; + +/* A.2 Interface Subclass */ +typedef enum { + VIDEO_SUBCLASS_UNDEFINED = 0x00, + VIDEO_SUBCLASS_CONTROL, + VIDEO_SUBCLASS_STREAMING, + VIDEO_SUBCLASS_INTERFACE_COLLECTION, +} video_subclass_type_t; + +/* A.3 Interface Protocol */ +typedef enum { + VIDEO_ITF_PROTOCOL_UNDEFINED = 0x00, + VIDEO_ITF_PROTOCOL_15, +} video_interface_protocol_code_t; + +/* A.5 Class-Specific VideoControl Interface Descriptor Subtypes */ +typedef enum { + VIDEO_CS_ITF_VC_UNDEFINED = 0x00, + VIDEO_CS_ITF_VC_HEADER, + VIDEO_CS_ITF_VC_INPUT_TERMINAL, + VIDEO_CS_ITF_VC_OUTPUT_TERMINAL, + VIDEO_CS_ITF_VC_SELECTOR_UNIT, + VIDEO_CS_ITF_VC_PROCESSING_UNIT, + VIDEO_CS_ITF_VC_EXTENSION_UNIT, + VIDEO_CS_ITF_VC_ENCODING_UNIT, + VIDEO_CS_ITF_VC_MAX, +} video_cs_vc_interface_subtype_t; + +/* A.6 Class-Specific VideoStreaming Interface Descriptor Subtypes */ +typedef enum { + VIDEO_CS_ITF_VS_UNDEFINED = 0x00, + VIDEO_CS_ITF_VS_INPUT_HEADER = 0x01, + VIDEO_CS_ITF_VS_OUTPUT_HEADER = 0x02, + VIDEO_CS_ITF_VS_STILL_IMAGE_FRAME = 0x03, + VIDEO_CS_ITF_VS_FORMAT_UNCOMPRESSED = 0x04, + VIDEO_CS_ITF_VS_FRAME_UNCOMPRESSED = 0x05, + VIDEO_CS_ITF_VS_FORMAT_MJPEG = 0x06, + VIDEO_CS_ITF_VS_FRAME_MJPEG = 0x07, + VIDEO_CS_ITF_VS_FORMAT_MPEG2TS = 0x0A, + VIDEO_CS_ITF_VS_FORMAT_DV = 0x0C, + VIDEO_CS_ITF_VS_COLORFORMAT = 0x0D, + VIDEO_CS_ITF_VS_FORMAT_FRAME_BASED = 0x10, + VIDEO_CS_ITF_VS_FRAME_FRAME_BASED = 0x11, + VIDEO_CS_ITF_VS_FORMAT_STREAM_BASED = 0x12, + VIDEO_CS_ITF_VS_FORMAT_H264 = 0x13, + VIDEO_CS_ITF_VS_FRAME_H264 = 0x14, + VIDEO_CS_ITF_VS_FORMAT_H264_SIMULCAST = 0x15, + VIDEO_CS_ITF_VS_FORMAT_VP8 = 0x16, + VIDEO_CS_ITF_VS_FRAME_VP8 = 0x17, + VIDEO_CS_ITF_VS_FORMAT_VP8_SIMULCAST = 0x18, +} video_cs_vs_interface_subtype_t; + +/* A.7. Class-Specific Endpoint Descriptor Subtypes */ +typedef enum { + VIDEO_CS_EP_UNDEFINED = 0x00, + VIDEO_CS_EP_GENERAL, + VIDEO_CS_EP_ENDPOINT, + VIDEO_CS_EP_INTERRUPT +} video_cs_ep_subtype_t; + +/* A.8 Class-Specific Request Codes */ +typedef enum { + VIDEO_REQUEST_UNDEFINED = 0x00, + VIDEO_REQUEST_SET_CUR = 0x01, + VIDEO_REQUEST_SET_CUR_ALL = 0x11, + VIDEO_REQUEST_GET_CUR = 0x81, + VIDEO_REQUEST_GET_MIN = 0x82, + VIDEO_REQUEST_GET_MAX = 0x83, + VIDEO_REQUEST_GET_RES = 0x84, + VIDEO_REQUEST_GET_LEN = 0x85, + VIDEO_REQUEST_GET_INFO = 0x86, + VIDEO_REQUEST_GET_DEF = 0x87, + VIDEO_REQUEST_GET_CUR_ALL = 0x91, + VIDEO_REQUEST_GET_MIN_ALL = 0x92, + VIDEO_REQUEST_GET_MAX_ALL = 0x93, + VIDEO_REQUEST_GET_RES_ALL = 0x94, + VIDEO_REQUEST_GET_DEF_ALL = 0x97 +} video_control_request_t; + +/* A.9.1 VideoControl Interface Control Selectors */ +typedef enum { + VIDEO_VC_CTL_UNDEFINED = 0x00, + VIDEO_VC_CTL_VIDEO_POWER_MODE, + VIDEO_VC_CTL_REQUEST_ERROR_CODE, +} video_interface_control_selector_t; + +/* A.9.8 VideoStreaming Interface Control Selectors */ +typedef enum { + VIDEO_VS_CTL_UNDEFINED = 0x00, + VIDEO_VS_CTL_PROBE, + VIDEO_VS_CTL_COMMIT, + VIDEO_VS_CTL_STILL_PROBE, + VIDEO_VS_CTL_STILL_COMMIT, + VIDEO_VS_CTL_STILL_IMAGE_TRIGGER, + VIDEO_VS_CTL_STREAM_ERROR_CODE, + VIDEO_VS_CTL_GENERATE_KEY_FRAME, + VIDEO_VS_CTL_UPDATE_FRAME_SEGMENT, + VIDEO_VS_CTL_SYNCH_DELAY_CONTROL, +} video_interface_streaming_selector_t; + +/* B. Terminal Types */ +typedef enum { + // Terminal + VIDEO_TT_VENDOR_SPECIFIC = 0x0100, + VIDEO_TT_STREAMING = 0x0101, + + // Input + VIDEO_ITT_VENDOR_SPECIFIC = 0x0200, + VIDEO_ITT_CAMERA = 0x0201, + VIDEO_ITT_MEDIA_TRANSPORT_INPUT = 0x0202, + + // Output + VIDEO_OTT_VENDOR_SPECIFIC = 0x0300, + VIDEO_OTT_DISPLAY = 0x0301, + VIDEO_OTT_MEDIA_TRANSPORT_OUTPUT = 0x0302, + + // External + VIDEO_ETT_VENDOR_SPEIFIC = 0x0400, + VIDEO_ETT_COMPOSITE_CONNECTOR = 0x0401, + VIDEO_ETT_SVIDEO_CONNECTOR = 0x0402, + VIDEO_ETT_COMPONENT_CONNECTOR = 0x0403, +} video_terminal_type_t; + +//--------------------------------------------------------------------+ +// Descriptors +//--------------------------------------------------------------------+ + +/* 2.3.4.2 */ +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint16_t bcdUVC; + uint16_t wTotalLength; + uint32_t dwClockFrequency; + uint8_t bInCollection; + uint8_t baInterfaceNr[]; +} tusb_desc_cs_video_ctl_itf_hdr_t; + +/* 2.4.3.3 */ +typedef struct TU_ATTR_PACKED { + uint8_t bHeaderLength; + union { + uint8_t bmHeaderInfo; + struct { + uint8_t FrameID: 1; + uint8_t EndOfFrame: 1; + uint8_t PresentationTime: 1; + uint8_t SourceClockReference: 1; + uint8_t PayloadSpecific: 1; + uint8_t StillImage: 1; + uint8_t Error: 1; + uint8_t EndOfHeader: 1; + }; + }; +} tusb_video_payload_header_t; + +/* 3.9.2.1 */ +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bNumFormats; + uint16_t wTotalLength; + uint8_t bEndpointAddress; + uint8_t bmInfo; + uint8_t bTerminalLink; + uint8_t bStillCaptureMethod; + uint8_t bTriggerSupport; + uint8_t bTriggerUsage; + uint8_t bControlSize; + uint8_t bmaControls[]; +} tusb_desc_cs_video_stm_itf_in_hdr_t; + +/* 3.9.2.2 */ +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bNumFormats; + uint16_t wTotalLength; + uint8_t bEndpointAddress; + uint8_t bTerminalLink; + uint8_t bControlSize; + uint8_t bmaControls[]; +} tusb_desc_cs_video_stm_itf_out_hdr_t; + +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bNumFormats; + uint16_t wTotalLength; + uint8_t bEndpointAddress; + union { + struct { + uint8_t bmInfo; + uint8_t bTerminalLink; + uint8_t bStillCaptureMethod; + uint8_t bTriggerSupport; + uint8_t bTriggerUsage; + uint8_t bControlSize; + uint8_t bmaControls[]; + } input; + struct { + uint8_t bEndpointAddress; + uint8_t bTerminalLink; + uint8_t bControlSize; + uint8_t bmaControls[]; + } output; + }; +} tusb_desc_cs_video_stm_itf_hdr_t; + +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bFormatIndex; + uint8_t bNumFrameDescriptors; + uint8_t guidFormat[16]; + uint8_t bBitsPerPixel; + uint8_t bDefaultFrameIndex; + uint8_t bAspectRatioX; + uint8_t bAspectRatioY; + uint8_t bmInterlaceFlags; + uint8_t bCopyProtect; +} tusb_desc_cs_video_fmt_uncompressed_t; + +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubType; + uint8_t bFrameIndex; + uint8_t bmCapabilities; + uint16_t wWidth; + uint16_t wHeight; + uint32_t dwMinBitRate; + uint32_t dwMaxBitRate; + uint32_t dwMaxVideoFrameBufferSize; /* deprecated */ + uint32_t dwDefaultFrameInterval; + uint8_t bFrameIntervalType; + uint32_t dwFrameInterval[]; +} tusb_desc_cs_video_frm_uncompressed_t; + +//--------------------------------------------------------------------+ +// Requests +//--------------------------------------------------------------------+ + +/* 4.3.1.1 */ +typedef struct TU_ATTR_PACKED { + union { + uint8_t bmHint; + struct TU_ATTR_PACKED { + uint16_t dwFrameInterval: 1; + uint16_t wKeyFrameRatel : 1; + uint16_t wPFrameRate : 1; + uint16_t wCompQuality : 1; + uint16_t wCompWindowSize: 1; + uint16_t : 0; + } Hint; + }; + uint8_t bFormatIndex; + uint8_t bFrameIndex; + uint32_t dwFrameInterval; + uint16_t wKeyFrameRate; + uint16_t wPFrameRate; + uint16_t wCompQuality; + uint16_t wCompWindowSize; + uint16_t wDelay; + uint32_t dwMaxVideoFrameSize; + uint32_t dwMaxPayloadTransferSize; + uint32_t dwClockFrequency; + union { + uint8_t bmFramingInfo; + struct TU_ATTR_PACKED { + uint8_t FrameID : 1; + uint8_t EndOfFrame: 1; + uint8_t EndOfSlice: 1; + uint8_t : 0; + } FramingInfo; + }; + uint8_t bPreferedVersion; + uint8_t bMinVersion; + uint8_t bMaxVersion; + uint8_t bUsage; + uint8_t bBitDepthLuma; + uint8_t bmSettings; + uint8_t bMaxNumberOfRefFramesPlus1; + uint16_t bmRateControlModes; + uint64_t bmLayoutPerStream; +} video_probe_and_commit_control_t; + +TU_VERIFY_STATIC( sizeof(video_probe_and_commit_control_t) == 48, "size is not correct"); + +#define TUD_VIDEO_DESC_IAD_LEN 8 +#define TUD_VIDEO_DESC_STD_VC_LEN 9 +#define TUD_VIDEO_DESC_CS_VC_LEN 12 +#define TUD_VIDEO_DESC_INPUT_TERM_LEN 8 +#define TUD_VIDEO_DESC_OUTPUT_TERM_LEN 9 +#define TUD_VIDEO_DESC_CAMERA_TERM_LEN 18 +#define TUD_VIDEO_DESC_STD_VS_LEN 9 +#define TUD_VIDEO_DESC_CS_VS_IN_LEN 13 +#define TUD_VIDEO_DESC_CS_VS_OUT_LEN 9 +#define TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR_LEN 27 +#define TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_CONT_LEN 38 +#define TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_DISC_LEN 26 +#define TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN 6 + +/* 2.2 compression formats */ +#define TUD_VIDEO_GUID_YUY2 0x59,0x55,0x59,0x32,0x00,0x00,0x10,0x00,0x80,0x00,0x00,0xAA,0x00,0x38,0x9B,0x71 +#define TUD_VIDEO_GUID_NV12 0x4E,0x56,0x31,0x32,0x00,0x00,0x10,0x00,0x80,0x00,0x00,0xAA,0x00,0x38,0x9B,0x71 +#define TUD_VIDEO_GUID_M420 0x4D,0x34,0x32,0x30,0x00,0x00,0x10,0x00,0x80,0x00,0x00,0xAA,0x00,0x38,0x9B,0x71 +#define TUD_VIDEO_GUID_I420 0x49,0x34,0x32,0x30,0x00,0x00,0x10,0x00,0x80,0x00,0x00,0xAA,0x00,0x38,0x9B,0x71 + +#define TUD_VIDEO_DESC_IAD(_firstitfs, _nitfs, _stridx) \ + TUD_VIDEO_DESC_IAD_LEN, TUSB_DESC_INTERFACE_ASSOCIATION, \ + _firstitfs, _nitfs, TUSB_CLASS_VIDEO, VIDEO_SUBCLASS_INTERFACE_COLLECTION, \ + VIDEO_ITF_PROTOCOL_UNDEFINED, _stridx + +#define TUD_VIDEO_DESC_STD_VC(_itfnum, _nEPs, _stridx) \ + TUD_VIDEO_DESC_STD_VC_LEN, TUSB_DESC_INTERFACE, _itfnum, /* fixed to zero */ 0x00, \ + _nEPs, TUSB_CLASS_VIDEO, VIDEO_SUBCLASS_CONTROL, VIDEO_ITF_PROTOCOL_15, _stridx + +/* 3.7.2 */ +#define TUD_VIDEO_DESC_CS_VC(_bcdUVC, _totallen, _clkfreq, ...) \ + TUD_VIDEO_DESC_CS_VC_LEN + (TU_ARGS_NUM(__VA_ARGS__)), TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VC_HEADER, \ + U16_TO_U8S_LE(_bcdUVC), U16_TO_U8S_LE((_totallen) + TUD_VIDEO_DESC_CS_VC_LEN + (TU_ARGS_NUM(__VA_ARGS__))), \ + U32_TO_U8S_LE(_clkfreq), TU_ARGS_NUM(__VA_ARGS__), __VA_ARGS__ + +/* 3.7.2.1 */ +#define TUD_VIDEO_DESC_INPUT_TERM(_tid, _tt, _at, _stridx) \ + TUD_VIDEO_DESC_INPUT_TERM_LEN, TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VC_INPUT_TERMINAL, \ + _tid, U16_TO_U8S_LE(_tt), _at, _stridx + +/* 3.7.2.2 */ +#define TUD_VIDEO_DESC_OUTPUT_TERM(_tid, _tt, _at, _srcid, _stridx) \ + TUD_VIDEO_DESC_OUTPUT_TERM_LEN, TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VC_OUTPUT_TERMINAL, \ + _tid, U16_TO_U8S_LE(_tt), _at, _srcid, _stridx + +/* 3.7.2.3 */ +#define TUD_VIDEO_DESC_CAMERA_TERM(_tid, _at, _stridx, _focal_min, _focal_max, _focal, _ctls) \ + TUD_VIDEO_DESC_CAMERA_TERM_LEN, TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VC_INPUT_TERMINAL, \ + _tid, U16_TO_U8S_LE(VIDEO_ITT_CAMERA), _at, _stridx, \ + U16_TO_U8S_LE(_focal_min), U16_TO_U8S_LE(_focal_max), U16_TO_U8S_LE(_focal), 3, \ + TU_U32_BYTE0(_ctls), TU_U32_BYTE1(_ctls), TU_U32_BYTE2(_ctls) + +/* 3.9.1 */ +#define TUD_VIDEO_DESC_STD_VS(_itfnum, _alt, _epn, _stridx) \ + TUD_VIDEO_DESC_STD_VS_LEN, TUSB_DESC_INTERFACE, _itfnum, _alt, \ + _epn, TUSB_CLASS_VIDEO, VIDEO_SUBCLASS_STREAMING, VIDEO_ITF_PROTOCOL_15, _stridx + +/* 3.9.2.1 */ +#define TUD_VIDEO_DESC_CS_VS_INPUT(_numfmt, _totallen, _ep, _inf, _termlnk, _sticaptmeth, _trgspt, _trgusg, ...) \ + TUD_VIDEO_DESC_CS_VS_IN_LEN + (_numfmt) * (TU_ARGS_NUM(__VA_ARGS__)), TUSB_DESC_CS_INTERFACE, \ + VIDEO_CS_ITF_VS_INPUT_HEADER, _numfmt, \ + U16_TO_U8S_LE((_totallen) + TUD_VIDEO_DESC_CS_VS_IN_LEN + (_numfmt) * (TU_ARGS_NUM(__VA_ARGS__))), \ + _ep, _inf, _termlnk, _sticaptmeth, _trgspt, _trgusg, (TU_ARGS_NUM(__VA_ARGS__)), __VA_ARGS__ + +/* 3.9.2.2 */ +#define TUD_VIDEO_DESC_CS_VS_OUTPUT(_numfmt, _totallen, _ep, _inf, _termlnk, ...) \ + TUD_VIDEO_DESC_CS_VS_OUT_LEN + (_numfmt) * (TU_ARGS_NUM(__VA_ARGS__)), TUSB_DESC_CS_INTERFACE, \ + VIDEO_CS_ITF_VS_OUTPUT_HEADER, _numfmt, \ + U16_TO_U8S_LE((_totallen) + TUD_VIDEO_DESC_CS_VS_OUT_LEN + (_numfmt) * (TU_ARGS_NUM(__VA_ARGS__))), \ + _ep, _inf, _termlnk, (TU_ARGS_NUM(__VA_ARGS__)), __VA_ARGS__ + +/* Uncompressed 3.1.1 */ +#define TUD_VIDEO_GUID(_g0,_g1,_g2,_g3,_g4,_g5,_g6,_g7,_g8,_g9,_g10,_g11,_g12,_g13,_g14,_g15) _g0,_g1,_g2,_g3,_g4,_g5,_g6,_g7,_g8,_g9,_g10,_g11,_g12,_g13,_g14,_g15 + +#define TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR(_fmtidx, _numfrmdesc, \ + _guid, _bitsperpix, _frmidx, _asrx, _asry, _interlace, _cp) \ + TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR_LEN, TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_FORMAT_UNCOMPRESSED, \ + _fmtidx, _numfrmdesc, TUD_VIDEO_GUID(_guid), \ + _bitsperpix, _frmidx, _asrx, _asry, _interlace, _cp + +/* Uncompressed 3.1.2 Table 3-3 */ +#define TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_CONT(_frmidx, _cap, _width, _height, _minbr, _maxbr, _maxfrmbufsz, _frminterval, _minfrminterval, _maxfrminterval, _frmintervalstep) \ + TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_CONT_LEN, TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_FRAME_UNCOMPRESSED, \ + _frmidx, _cap, U16_TO_U8S_LE(_width), U16_TO_U8S_LE(_height), U32_TO_U8S_LE(_minbr), U32_TO_U8S_LE(_maxbr), \ + U32_TO_U8S_LE(_maxfrmbufsz), U32_TO_U8S_LE(_frminterval), 0, \ + U32_TO_U8S_LE(_minfrminterval), U32_TO_U8S_LE(_maxfrminterval), U32_TO_U8S_LE(_frmintervalstep) + +/* Uncompressed 3.1.2 Table 3-4 */ +#define TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_DISC(_frmidx, _cap, _width, _height, _minbr, _maxbr, _maxfrmbufsz, _frminterval, ...) \ + TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_DISC_LEN + (TU_ARGS_NUM(__VA_ARGS__)) * 4, \ + TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_FRAME_UNCOMPRESSED, \ + _frmidx, _cap, U16_TO_U8S_LE(_width), U16_TO_U8S_LE(_height), U32_TO_U8S_LE(_minbr), U32_TO_U8S_LE(_maxbr), \ + U32_TO_U8S_LE(_maxfrmbufsz), U32_TO_U8S_LE(_frminterval), (TU_ARGS_NUM(__VA_ARGS__)), __VA_ARGS__ + +/* 3.9.2.6 */ +#define TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING(_color, _trns, _mat) \ + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN, \ + TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_COLORFORMAT, \ + _color, _trns, _mat + +/* 3.10.1.1 */ +#define TUD_VIDEO_DESC_EP_ISO(_ep, _epsize, _ep_interval) \ + 7, TUSB_DESC_ENDPOINT, _ep, TUSB_XFER_ISOCHRONOUS | TUSB_ISO_EP_ATT_ASYNCHRONOUS,\ + U16_TO_U8S_LE(_epsize), _ep_interval + +/* 3.10.1.2 */ +#define TUD_VIDEO_DESC_EP_BULK(_ep, _epsize, _ep_interval) \ + 7, TUSB_DESC_ENDPOINT, _ep, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), _ep_interval + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/video/video_device.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/video/video_device.c new file mode 100644 index 000000000..eeb068197 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/video/video_device.c @@ -0,0 +1,1149 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 Koji KITAYAMA + * 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 (TUSB_OPT_DEVICE_ENABLED && CFG_TUD_VIDEO && CFG_TUD_VIDEO_STREAMING) + +#include "device/usbd.h" +#include "device/usbd_pvt.h" + +#include "video_device.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +typedef struct { + tusb_desc_interface_t std; + tusb_desc_cs_video_ctl_itf_hdr_t ctl; +} tusb_desc_vc_itf_t; + +typedef struct { + tusb_desc_interface_t std; + tusb_desc_cs_video_stm_itf_hdr_t stm; +} tusb_desc_vs_itf_t; + +typedef union { + tusb_desc_cs_video_ctl_itf_hdr_t ctl; + tusb_desc_cs_video_stm_itf_hdr_t stm; +} tusb_desc_video_itf_hdr_t; + +typedef struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bEntityId; +} tusb_desc_cs_video_entity_itf_t; + +/* video streaming interface */ +typedef struct TU_ATTR_PACKED { + uint8_t index_vc; /* index of bound video control interface */ + uint8_t index_vs; /* index from the video control interface */ + struct { + uint16_t beg; /* Offset of the begging of video streaming interface descriptor */ + uint16_t end; /* Offset of the end of video streaming interface descriptor */ + uint16_t cur; /* Offset of the current settings */ + uint16_t ep[2]; /* Offset of endpoint descriptors. 0: streaming, 1: still capture */ + } desc; + uint8_t *buffer; /* frame buffer. assume linear buffer. no support for stride access */ + uint32_t bufsize; /* frame buffer size */ + uint32_t offset; /* offset for the next payload transfer */ + uint32_t max_payload_transfer_size; + uint8_t error_code;/* error code */ + /*------------- From this point, data is not cleared by bus reset -------------*/ + CFG_TUSB_MEM_ALIGN uint8_t ep_buf[CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE]; /* EP transfer buffer for streaming */ +} videod_streaming_interface_t; + +/* video control interface */ +typedef struct TU_ATTR_PACKED { + void const *beg; /* The head of the first video control interface descriptor */ + uint16_t len; /* Byte length of the descriptors */ + uint16_t cur; /* offset for current video control interface */ + uint8_t stm[CFG_TUD_VIDEO_STREAMING]; /* Indices of streaming interface */ + uint8_t error_code; /* error code */ + uint8_t power_mode; + + /*------------- From this point, data is not cleared by bus reset -------------*/ + // CFG_TUSB_MEM_ALIGN uint8_t ctl_buf[64]; /* EP transfer buffer for interrupt transfer */ + +} videod_interface_t; + +#define ITF_STM_MEM_RESET_SIZE offsetof(videod_streaming_interface_t, ep_buf) + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ +CFG_TUSB_MEM_SECTION static videod_interface_t _videod_itf[CFG_TUD_VIDEO]; +CFG_TUSB_MEM_SECTION static videod_streaming_interface_t _videod_streaming_itf[CFG_TUD_VIDEO_STREAMING]; + +static uint8_t const _cap_get = 0x1u; /* support for GET */ +static uint8_t const _cap_get_set = 0x3u; /* support for GET and SET */ + +/** Get interface number from the interface descriptor + * + * @param[in] desc interface descriptor + * + * @return bInterfaceNumber */ +static inline uint8_t _desc_itfnum(void const *desc) +{ + return ((uint8_t const*)desc)[2]; +} + +/** Get endpoint address from the endpoint descriptor + * + * @param[in] desc endpoint descriptor + * + * @return bEndpointAddress */ +static inline uint8_t _desc_ep_addr(void const *desc) +{ + return ((uint8_t const*)desc)[2]; +} + +/** Get instance of streaming interface + * + * @param[in] ctl_idx instance number of video control + * @param[in] stm_idx index number of streaming interface + * + * @return instance */ +static videod_streaming_interface_t* _get_instance_streaming(uint_fast8_t ctl_idx, uint_fast8_t stm_idx) +{ + videod_interface_t *ctl = &_videod_itf[ctl_idx]; + if (!ctl->beg) return NULL; + videod_streaming_interface_t *stm = &_videod_streaming_itf[ctl->stm[stm_idx]]; + if (!stm->desc.beg) return NULL; + return stm; +} + +static tusb_desc_vc_itf_t const* _get_desc_vc(videod_interface_t const *self) +{ + return (tusb_desc_vc_itf_t const *)(self->beg + self->cur); +} + +static tusb_desc_vs_itf_t const* _get_desc_vs(videod_streaming_interface_t const *self) +{ + if (!self->desc.cur) return NULL; + void const *desc = _videod_itf[self->index_vc].beg; + return (tusb_desc_vs_itf_t const*)(desc + self->desc.cur); +} + +/** Find the first descriptor of a given type + * + * @param[in] beg The head of descriptor byte array. + * @param[in] end The tail of descriptor byte array. + * @param[in] desc_type The target descriptor type. + * + * @return The pointer for interface descriptor. + * @retval end did not found interface descriptor */ +static void const* _find_desc(void const *beg, void const *end, uint_fast8_t desc_type) +{ + void const *cur = beg; + while ((cur < end) && (desc_type != tu_desc_type(cur))) { + cur = tu_desc_next(cur); + } + return cur; +} + +/** Find the first descriptor specified by the arguments + * + * @param[in] beg The head of descriptor byte array. + * @param[in] end The tail of descriptor byte array. + * @param[in] desc_type The target descriptor type + * @param[in] element_0 The target element following the desc_type + * @param[in] element_1 The target element following the element_0 + * + * @return The pointer for interface descriptor. + * @retval end did not found interface descriptor */ +static void const* _find_desc_3(void const *beg, void const *end, + uint_fast8_t desc_type, + uint_fast8_t element_0, + uint_fast8_t element_1) +{ + for (void const *cur = beg; cur < end; cur = _find_desc(cur, end, desc_type)) { + uint8_t const *p = (uint8_t const *)cur; + if ((p[2] == element_0) && (p[3] == element_1)) { + return cur; + } + cur = tu_desc_next(cur); + } + return end; +} + +/** Return the next interface descriptor which has another interface number. + * + * @param[in] beg The head of descriptor byte array. + * @param[in] end The tail of descriptor byte array. + * + * @return The pointer for interface descriptor. + * @retval end did not found interface descriptor */ +static void const* _next_desc_itf(void const *beg, void const *end) +{ + void const *cur = beg; + uint_fast8_t itfnum = ((tusb_desc_interface_t const*)cur)->bInterfaceNumber; + while ((cur < end) && + (itfnum == ((tusb_desc_interface_t const*)cur)->bInterfaceNumber)) { + cur = _find_desc(tu_desc_next(cur), end, TUSB_DESC_INTERFACE); + } + return cur; +} + +/** Find the first interface descriptor with the specified interface number and alternate setting number. + * + * @param[in] beg The head of descriptor byte array. + * @param[in] end The tail of descriptor byte array. + * @param[in] itfnum The target interface number. + * @param[in] altnum The target alternate setting number. + * + * @return The pointer for interface descriptor. + * @retval end did not found interface descriptor */ +static inline void const* _find_desc_itf(void const *beg, void const *end, uint_fast8_t itfnum, uint_fast8_t altnum) +{ + return _find_desc_3(beg, end, TUSB_DESC_INTERFACE, itfnum, altnum); +} + +/** Find the first endpoint descriptor belonging to the current interface descriptor. + * + * The search range is from `beg` to `end` or the next interface descriptor. + * + * @param[in] beg The head of descriptor byte array. + * @param[in] end The tail of descriptor byte array. + * + * @return The pointer for endpoint descriptor. + * @retval end did not found endpoint descriptor */ +static void const* _find_desc_ep(void const *beg, void const *end) +{ + for (void const *cur = beg; cur < end; cur = tu_desc_next(cur)) { + uint_fast8_t desc_type = tu_desc_type(cur); + if (TUSB_DESC_ENDPOINT == desc_type) return cur; + if (TUSB_DESC_INTERFACE == desc_type) break; + } + return end; +} + +/** Find the first entity descriptor with the entity ID + * specified by the argument belonging to the current video control descriptor. + * + * @param[in] desc The video control interface descriptor. + * @param[in] entityid The target entity id. + * + * @return The pointer for interface descriptor. + * @retval end did not found interface descriptor */ +static void const* _find_desc_entity(void const *desc, uint_fast8_t entityid) +{ + tusb_desc_vc_itf_t const *vc = (tusb_desc_vc_itf_t const*)desc; + void const *beg = vc; + void const *end = beg + vc->std.bLength + vc->ctl.wTotalLength; + for (void const *cur = beg; cur < end; cur = _find_desc(cur, end, TUSB_DESC_CS_INTERFACE)) { + tusb_desc_cs_video_entity_itf_t const *itf = (tusb_desc_cs_video_entity_itf_t const *)cur; + if ((VIDEO_CS_ITF_VC_INPUT_TERMINAL <= itf->bDescriptorSubtype + && itf->bDescriptorSubtype < VIDEO_CS_ITF_VC_MAX) + && itf->bEntityId == entityid) { + return itf; + } + cur = tu_desc_next(cur); + } + return end; +} + +/** Return the end of the video streaming descriptor. */ +static inline void const* _end_of_streaming_descriptor(void const *desc) +{ + tusb_desc_vs_itf_t const *vs = (tusb_desc_vs_itf_t const *)desc; + return desc + vs->std.bLength + vs->stm.wTotalLength; +} + +/** Find the first format descriptor with the specified format number. */ +static inline tusb_desc_cs_video_fmt_uncompressed_t const *_find_desc_format(void const *beg, void const *end, uint_fast8_t fmtnum) +{ + return (tusb_desc_cs_video_fmt_uncompressed_t const*) + _find_desc_3(beg, end, TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_FORMAT_UNCOMPRESSED, fmtnum); +} + +/** Find the first frame descriptor with the specified format number. */ +static inline tusb_desc_cs_video_frm_uncompressed_t const *_find_desc_frame(void const *beg, void const *end, uint_fast8_t frmnum) +{ + return (tusb_desc_cs_video_frm_uncompressed_t const*) + _find_desc_3(beg, end, TUSB_DESC_CS_INTERFACE, VIDEO_CS_ITF_VS_FRAME_UNCOMPRESSED, frmnum); +} + +/** Set uniquely determined values to variables that have not been set + * + * @param[in,out] param Target */ +static bool _update_streaming_parameters(videod_streaming_interface_t const *stm, + video_probe_and_commit_control_t *param) +{ + tusb_desc_vs_itf_t const *vs = _get_desc_vs(stm); + uint_fast8_t fmtnum = param->bFormatIndex; + TU_ASSERT(fmtnum <= vs->stm.bNumFormats); + if (!fmtnum) { + if (1 < vs->stm.bNumFormats) return true; /* Need to negotiate all variables. */ + fmtnum = 1; + param->bFormatIndex = 1; + } + + /* Set the parameters determined by the format */ + param->wKeyFrameRate = 1; + param->wPFrameRate = 0; + param->wCompQuality = 1; /* 1 to 10000 */ + param->wCompWindowSize = 1; /* GOP size? */ + param->wDelay = 0; /* milliseconds */ + param->dwClockFrequency = 27000000; /* same as MPEG-2 system time clock */ + param->bmFramingInfo = 0x3; /* enables FrameID and EndOfFrame */ + param->bPreferedVersion = 1; + param->bMinVersion = 1; + param->bMaxVersion = 1; + param->bUsage = 0; + param->bBitDepthLuma = 8; + + void const *end = _end_of_streaming_descriptor(vs); + tusb_desc_cs_video_fmt_uncompressed_t const *fmt = _find_desc_format(tu_desc_next(vs), end, fmtnum); + TU_ASSERT(fmt != end); + uint_fast8_t frmnum = param->bFrameIndex; + TU_ASSERT(frmnum <= fmt->bNumFrameDescriptors); + if (!frmnum) { + if (1 < fmt->bNumFrameDescriptors) return true; + frmnum = 1; + param->bFrameIndex = 1; + } + tusb_desc_cs_video_frm_uncompressed_t const *frm = _find_desc_frame(tu_desc_next(fmt), end, frmnum); + TU_ASSERT(frm != end); + + /* Set the parameters determined by the frame */ + uint_fast32_t frame_size = param->dwMaxVideoFrameSize; + if (!frame_size) { + frame_size = (uint_fast32_t)frm->wWidth * frm->wHeight * fmt->bBitsPerPixel / 8; + param->dwMaxVideoFrameSize = frame_size; + } + + uint_fast32_t interval = param->dwFrameInterval; + if (!interval) { + if ((1 < frm->bFrameIntervalType) || + ((0 == frm->bFrameIntervalType) && (frm->dwFrameInterval[1] != frm->dwFrameInterval[0]))) { + return true; + } + interval = frm->dwFrameInterval[0]; + param->dwFrameInterval = interval; + } + uint_fast32_t interval_ms = interval / 10000; + TU_ASSERT(interval_ms); + uint_fast32_t payload_size = (frame_size + interval_ms - 1) / interval_ms + 2; + param->dwMaxPayloadTransferSize = payload_size; + return true; +} + +/** Set the minimum, maximum, default values or resolutions to variables which need to negotiate with the host + * + * @param[in] request GET_MAX, GET_MIN, GET_RES or GET_DEF + * @param[in,out] param Target + */ +static bool _negotiate_streaming_parameters(videod_streaming_interface_t const *stm, uint_fast8_t request, + video_probe_and_commit_control_t *param) +{ + uint_fast8_t const fmtnum = param->bFormatIndex; + if (!fmtnum) { + switch (request) { + case VIDEO_REQUEST_GET_MAX: + param->bFormatIndex = _get_desc_vs(stm)->stm.bNumFormats; + break; + case VIDEO_REQUEST_GET_MIN: + case VIDEO_REQUEST_GET_DEF: + param->bFormatIndex = 1; + break; + default: return false; + } + /* Set the parameters determined by the format */ + param->wKeyFrameRate = 1; + param->wPFrameRate = 0; + param->wCompQuality = 1; /* 1 to 10000 */ + param->wCompWindowSize = 1; /* GOP size? */ + param->wDelay = 0; /* milliseconds */ + param->dwClockFrequency = 27000000; /* same as MPEG-2 system time clock */ + param->bmFramingInfo = 0x3; /* enables FrameID and EndOfFrame */ + param->bPreferedVersion = 1; + param->bMinVersion = 1; + param->bMaxVersion = 1; + param->bUsage = 0; + param->bBitDepthLuma = 8; + return true; + } + + uint_fast8_t frmnum = param->bFrameIndex; + if (!frmnum) { + tusb_desc_vs_itf_t const *vs = _get_desc_vs(stm); + void const *end = _end_of_streaming_descriptor(vs); + tusb_desc_cs_video_fmt_uncompressed_t const *fmt = _find_desc_format(tu_desc_next(vs), end, fmtnum); + switch (request) { + case VIDEO_REQUEST_GET_MAX: + frmnum = fmt->bNumFrameDescriptors; + break; + case VIDEO_REQUEST_GET_MIN: + frmnum = 1; + break; + case VIDEO_REQUEST_GET_DEF: + frmnum = fmt->bDefaultFrameIndex; + break; + default: return false; + } + param->bFrameIndex = frmnum; + /* Set the parameters determined by the frame */ + tusb_desc_cs_video_frm_uncompressed_t const *frm = _find_desc_frame(tu_desc_next(fmt), end, frmnum); + param->dwMaxVideoFrameSize = frm->wWidth * frm->wHeight * fmt->bBitsPerPixel / 8; + return true; + } + + if (!param->dwFrameInterval) { + tusb_desc_vs_itf_t const *vs = _get_desc_vs(stm); + void const *end = _end_of_streaming_descriptor(vs); + tusb_desc_cs_video_fmt_uncompressed_t const *fmt = _find_desc_format(tu_desc_next(vs), end, fmtnum); + tusb_desc_cs_video_frm_uncompressed_t const *frm = _find_desc_frame(tu_desc_next(fmt), end, frmnum); + + uint_fast32_t interval, interval_ms; + switch (request) { + case VIDEO_REQUEST_GET_MAX: + { + uint_fast32_t min_interval, max_interval; + uint_fast8_t num_intervals = frm->bFrameIntervalType; + max_interval = num_intervals ? frm->dwFrameInterval[num_intervals - 1]: frm->dwFrameInterval[1]; + min_interval = frm->dwFrameInterval[0]; + interval = max_interval; + interval_ms = min_interval / 10000; + } + break; + case VIDEO_REQUEST_GET_MIN: + { + uint_fast32_t min_interval, max_interval; + uint_fast8_t num_intervals = frm->bFrameIntervalType; + max_interval = num_intervals ? frm->dwFrameInterval[num_intervals - 1]: frm->dwFrameInterval[1]; + min_interval = frm->dwFrameInterval[0]; + interval = min_interval; + interval_ms = max_interval / 10000; + } + break; + case VIDEO_REQUEST_GET_DEF: + interval = frm->dwDefaultFrameInterval; + interval_ms = interval / 10000; + break; + case VIDEO_REQUEST_GET_RES: + { + uint_fast8_t num_intervals = frm->bFrameIntervalType; + if (num_intervals) { + interval = 0; + } else { + interval = frm->dwFrameInterval[2]; + interval_ms = interval / 10000; + } + } + break; + default: return false; + } + param->dwFrameInterval = interval; + if (!interval) { + param->dwMaxPayloadTransferSize = 0; + } else { + uint_fast32_t frame_size = param->dwMaxVideoFrameSize; + if (!interval_ms) { + param->dwMaxPayloadTransferSize = frame_size + 2; + } else { + param->dwMaxPayloadTransferSize = (frame_size + interval_ms - 1) / interval_ms + 2; + } + } + return true; + } + return true; +} + +/** Close current video control interface. + * + * @param[in,out] self Video control interface context. + * @param[in] altnum The target alternate setting number. */ +static bool _close_vc_itf(uint8_t rhport, videod_interface_t *self) +{ + tusb_desc_vc_itf_t const *vc = _get_desc_vc(self); + /* The next descriptor after the class-specific VC interface header descriptor. */ + void const *cur = (void const*)vc + vc->std.bLength + vc->ctl.bLength; + /* The end of the video control interface descriptor. */ + void const *end = (void const*)vc + vc->std.bLength + vc->ctl.wTotalLength; + if (vc->std.bNumEndpoints) { + /* Find the notification endpoint descriptor. */ + cur = _find_desc(cur, end, TUSB_DESC_ENDPOINT); + TU_ASSERT(cur < end); + tusb_desc_endpoint_t const *notif = (tusb_desc_endpoint_t const *)cur; + usbd_edpt_close(rhport, notif->bEndpointAddress); + } + self->cur = 0; + return true; +} + +/** Set the alternate setting to own video control interface. + * + * @param[in,out] self Video control interface context. + * @param[in] altnum The target alternate setting number. */ +static bool _open_vc_itf(uint8_t rhport, videod_interface_t *self, uint_fast8_t altnum) +{ + TU_LOG2(" open VC %d\n", altnum); + void const *beg = self->beg; + void const *end = beg + self->len; + /* The first descriptor is a video control interface descriptor. */ + void const *cur = _find_desc_itf(beg, end, _desc_itfnum(beg), altnum); + TU_LOG2(" cur %d\n", cur - beg); + TU_VERIFY(cur < end); + + tusb_desc_vc_itf_t const *vc = (tusb_desc_vc_itf_t const *)cur; + TU_LOG2(" bInCollection %d\n", vc->ctl.bInCollection); + /* Support for up to 2 streaming interfaces only. */ + TU_ASSERT(vc->ctl.bInCollection <= CFG_TUD_VIDEO_STREAMING); + + /* Update to point the end of the video control interface descriptor. */ + end = cur + vc->std.bLength + vc->ctl.wTotalLength; + /* Advance to the next descriptor after the class-specific VC interface header descriptor. */ + cur += vc->std.bLength + vc->ctl.bLength; + TU_LOG2(" bNumEndpoints %d\n", vc->std.bNumEndpoints); + /* Open the notification endpoint if it exist. */ + if (vc->std.bNumEndpoints) { + /* Support for 1 endpoint only. */ + TU_VERIFY(1 == vc->std.bNumEndpoints); + /* Find the notification endpoint descriptor. */ + cur = _find_desc(cur, end, TUSB_DESC_ENDPOINT); + TU_VERIFY(cur < end); + tusb_desc_endpoint_t const *notif = (tusb_desc_endpoint_t const *)cur; + /* Open the notification endpoint */ + TU_ASSERT(usbd_edpt_open(rhport, notif)); + } + self->cur = (void const*)vc - beg; + return true; +} + +/** Set the alternate setting to own video streaming interface. + * + * @param[in,out] stm Streaming interface context. + * @param[in] altnum The target alternate setting number. */ +static bool _open_vs_itf(uint8_t rhport, videod_streaming_interface_t *stm, uint_fast8_t altnum) +{ + uint_fast8_t i; + TU_LOG2(" reopen VS %d\n", altnum); + void const *desc = _videod_itf[stm->index_vc].beg; + + /* Close endpoints of previous settings. */ + for (i = 0; i < TU_ARRAY_SIZE(stm->desc.ep); ++i) { + uint_fast16_t ofs_ep = stm->desc.ep[i]; + if (!ofs_ep) break; + uint_fast8_t ep_adr = _desc_ep_addr(desc + ofs_ep); + usbd_edpt_close(rhport, ep_adr); + stm->desc.ep[i] = 0; + TU_LOG2(" close EP%02x\n", ep_adr); + } + /* clear transfer management information */ + stm->buffer = NULL; + stm->bufsize = 0; + stm->offset = 0; + + /* Find a alternate interface */ + void const *beg = desc + stm->desc.beg; + void const *end = desc + stm->desc.end; + void const *cur = _find_desc_itf(beg, end, _desc_itfnum(beg), altnum); + TU_VERIFY(cur < end); + uint_fast8_t numeps = ((tusb_desc_interface_t const *)cur)->bNumEndpoints; + TU_ASSERT(numeps <= TU_ARRAY_SIZE(stm->desc.ep)); + stm->desc.cur = cur - desc; /* Save the offset of the new settings */ + if (!altnum) { + /* initialize streaming settings */ + stm->max_payload_transfer_size = 0; + video_probe_and_commit_control_t *param = + (video_probe_and_commit_control_t *)&stm->ep_buf; + tu_memclr(param, sizeof(*param)); + return _update_streaming_parameters(stm, param); + } + /* Open endpoints of the new settings. */ + for (i = 0, cur = tu_desc_next(cur); i < numeps; ++i, cur = tu_desc_next(cur)) { + cur = _find_desc_ep(cur, end); + TU_ASSERT(cur < end); + tusb_desc_endpoint_t const *ep = (tusb_desc_endpoint_t const*)cur; + if (!stm->max_payload_transfer_size) { + video_probe_and_commit_control_t const *param = (video_probe_and_commit_control_t const*)&stm->ep_buf; + uint_fast32_t max_size = param->dwMaxPayloadTransferSize; + if ((TUSB_XFER_ISOCHRONOUS == ep->bmAttributes.xfer) && + (tu_edpt_packet_size(ep) < max_size)) + { + /* FS must be less than or equal to max packet size */ + return false; + } + /* Set the negotiated value */ + stm->max_payload_transfer_size = max_size; + } + TU_ASSERT(usbd_edpt_open(rhport, ep)); + stm->desc.ep[i] = cur - desc; + TU_LOG2(" open EP%02x\n", _desc_ep_addr(cur)); + } + /* initialize payload header */ + tusb_video_payload_header_t *hdr = (tusb_video_payload_header_t*)stm->ep_buf; + hdr->bHeaderLength = sizeof(*hdr); + hdr->bmHeaderInfo = 0; + + return true; +} + +/** Prepare the next packet payload. */ +static uint_fast16_t _prepare_in_payload(videod_streaming_interface_t *stm) +{ + uint_fast16_t remaining = stm->bufsize - stm->offset; + uint_fast16_t hdr_len = stm->ep_buf[0]; + uint_fast16_t pkt_len = stm->max_payload_transfer_size; + if (hdr_len + remaining < pkt_len) { + pkt_len = hdr_len + remaining; + } + uint_fast16_t data_len = pkt_len - hdr_len; + memcpy(&stm->ep_buf[hdr_len], stm->buffer + stm->offset, data_len); + stm->offset += data_len; + remaining -= data_len; + if (!remaining) { + tusb_video_payload_header_t *hdr = (tusb_video_payload_header_t*)stm->ep_buf; + hdr->EndOfFrame = 1; + } + return hdr_len + data_len; +} + +/** Handle a standard request to the video control interface. */ +static int handle_video_ctl_std_req(uint8_t rhport, uint8_t stage, + tusb_control_request_t const *request, + uint_fast8_t ctl_idx) +{ + switch (request->bRequest) { + case TUSB_REQ_GET_INTERFACE: + if (stage == CONTROL_STAGE_SETUP) + { + TU_VERIFY(1 == request->wLength, VIDEO_ERROR_UNKNOWN); + tusb_desc_vc_itf_t const *vc = _get_desc_vc(&_videod_itf[ctl_idx]); + TU_VERIFY(vc, VIDEO_ERROR_UNKNOWN); + + uint8_t alt_num = vc->std.bAlternateSetting; + + TU_VERIFY(tud_control_xfer(rhport, request, &alt_num, sizeof(alt_num)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case TUSB_REQ_SET_INTERFACE: + if (stage == CONTROL_STAGE_SETUP) + { + TU_VERIFY(0 == request->wLength, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(_close_vc_itf(rhport, &_videod_itf[ctl_idx]), VIDEO_ERROR_UNKNOWN); + TU_VERIFY(_open_vc_itf(rhport, &_videod_itf[ctl_idx], request->wValue), VIDEO_ERROR_UNKNOWN); + tud_control_status(rhport, request); + } + return VIDEO_ERROR_NONE; + + default: /* Unknown/Unsupported request */ + TU_BREAKPOINT(); + return VIDEO_ERROR_INVALID_REQUEST; + } +} + +static int handle_video_ctl_cs_req(uint8_t rhport, uint8_t stage, + tusb_control_request_t const *request, + uint_fast8_t ctl_idx) +{ + videod_interface_t *self = &_videod_itf[ctl_idx]; + + /* 4.2.1 Interface Control Request */ + switch (TU_U16_HIGH(request->wValue)) { + case VIDEO_VC_CTL_VIDEO_POWER_MODE: + switch (request->bRequest) { + case VIDEO_REQUEST_SET_CUR: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(1 == request->wLength, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(tud_control_xfer(rhport, request, &self->power_mode, sizeof(self->power_mode)), VIDEO_ERROR_UNKNOWN); + } else if (stage == CONTROL_STAGE_ACK) { + if (tud_video_power_mode_cb) return tud_video_power_mode_cb(ctl_idx, self->power_mode); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_CUR: + if (stage == CONTROL_STAGE_SETUP) + { + TU_VERIFY(1 == request->wLength, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(tud_control_xfer(rhport, request, &self->power_mode, sizeof(self->power_mode)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_INFO: + if (stage == CONTROL_STAGE_SETUP) + { + TU_VERIFY(1 == request->wLength, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(tud_control_xfer(rhport, request, (uint8_t*)(uintptr_t) &_cap_get_set, sizeof(_cap_get_set)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + default: break; + } + break; + + case VIDEO_VC_CTL_REQUEST_ERROR_CODE: + switch (request->bRequest) { + case VIDEO_REQUEST_GET_CUR: + if (stage == CONTROL_STAGE_SETUP) + { + TU_VERIFY(tud_control_xfer(rhport, request, &self->error_code, sizeof(uint8_t)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_INFO: + if (stage == CONTROL_STAGE_SETUP) + { + TU_VERIFY(tud_control_xfer(rhport, request, (uint8_t*)(uintptr_t) &_cap_get, sizeof(_cap_get)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + default: break; + } + break; + + default: break; + } + + /* Unknown/Unsupported request */ + TU_BREAKPOINT(); + return VIDEO_ERROR_INVALID_REQUEST; +} + +static int handle_video_ctl_req(uint8_t rhport, uint8_t stage, + tusb_control_request_t const *request, + uint_fast8_t ctl_idx) +{ + uint_fast8_t entity_id; + switch (request->bmRequestType_bit.type) { + case TUSB_REQ_TYPE_STANDARD: + return handle_video_ctl_std_req(rhport, stage, request, ctl_idx); + + case TUSB_REQ_TYPE_CLASS: + entity_id = TU_U16_HIGH(request->wIndex); + if (!entity_id) { + return handle_video_ctl_cs_req(rhport, stage, request, ctl_idx); + } else { + TU_VERIFY(_find_desc_entity(_get_desc_vc(&_videod_itf[ctl_idx]), entity_id), VIDEO_ERROR_INVALID_REQUEST); + return VIDEO_ERROR_NONE; + } + + default: + return VIDEO_ERROR_INVALID_REQUEST; + } +} + +static int handle_video_stm_std_req(uint8_t rhport, uint8_t stage, + tusb_control_request_t const *request, + uint_fast8_t stm_idx) +{ + videod_streaming_interface_t *self = &_videod_streaming_itf[stm_idx]; + switch (request->bRequest) { + case TUSB_REQ_GET_INTERFACE: + if (stage == CONTROL_STAGE_SETUP) + { + TU_VERIFY(1 == request->wLength, VIDEO_ERROR_UNKNOWN); + tusb_desc_vs_itf_t const *vs = _get_desc_vs(self); + TU_VERIFY(vs, VIDEO_ERROR_UNKNOWN); + uint8_t alt_num = vs->std.bAlternateSetting; + + TU_VERIFY(tud_control_xfer(rhport, request, &alt_num, sizeof(alt_num)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case TUSB_REQ_SET_INTERFACE: + if (stage == CONTROL_STAGE_SETUP) + { + TU_VERIFY(_open_vs_itf(rhport, self, request->wValue), VIDEO_ERROR_UNKNOWN); + tud_control_status(rhport, request); + } + return VIDEO_ERROR_NONE; + + default: /* Unknown/Unsupported request */ + TU_BREAKPOINT(); + return VIDEO_ERROR_INVALID_REQUEST; + } +} + +static int handle_video_stm_cs_req(uint8_t rhport, uint8_t stage, + tusb_control_request_t const *request, + uint_fast8_t stm_idx) +{ + (void)rhport; + videod_streaming_interface_t *self = &_videod_streaming_itf[stm_idx]; + + /* 4.2.1 Interface Control Request */ + switch (TU_U16_HIGH(request->wValue)) { + case VIDEO_VS_CTL_STREAM_ERROR_CODE: + switch (request->bRequest) { + case VIDEO_REQUEST_GET_CUR: + if (stage == CONTROL_STAGE_SETUP) + { + /* TODO */ + TU_VERIFY(tud_control_xfer(rhport, request, &self->error_code, sizeof(uint8_t)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_INFO: + if (stage == CONTROL_STAGE_SETUP) + { + TU_VERIFY(tud_control_xfer(rhport, request, (uint8_t*)(uintptr_t) &_cap_get, sizeof(_cap_get)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + default: break; + } + break; + + case VIDEO_VS_CTL_PROBE: + switch (request->bRequest) { + case VIDEO_REQUEST_SET_CUR: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(sizeof(video_probe_and_commit_control_t) == request->wLength, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(tud_control_xfer(rhport, request, self->ep_buf, sizeof(video_probe_and_commit_control_t)), + VIDEO_ERROR_UNKNOWN); + } else if (stage == CONTROL_STAGE_ACK) { + TU_VERIFY(_update_streaming_parameters(self, (video_probe_and_commit_control_t*)self->ep_buf), + VIDEO_ERROR_INVALID_VALUE_WITHIN_RANGE); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_CUR: + if (stage == CONTROL_STAGE_SETUP) + { + TU_VERIFY(request->wLength, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(tud_control_xfer(rhport, request, self->ep_buf, sizeof(video_probe_and_commit_control_t)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_MIN: + case VIDEO_REQUEST_GET_MAX: + case VIDEO_REQUEST_GET_RES: + case VIDEO_REQUEST_GET_DEF: + if (stage == CONTROL_STAGE_SETUP) + { + TU_VERIFY(request->wLength, VIDEO_ERROR_UNKNOWN); + video_probe_and_commit_control_t tmp; + tmp = *(video_probe_and_commit_control_t*)&self->ep_buf; + TU_VERIFY(_negotiate_streaming_parameters(self, request->bRequest, &tmp), VIDEO_ERROR_INVALID_VALUE_WITHIN_RANGE); + TU_VERIFY(tud_control_xfer(rhport, request, self->ep_buf, sizeof(video_probe_and_commit_control_t)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_LEN: + if (stage == CONTROL_STAGE_SETUP) + { + TU_VERIFY(2 == request->wLength, VIDEO_ERROR_UNKNOWN); + uint16_t len = sizeof(video_probe_and_commit_control_t); + TU_VERIFY(tud_control_xfer(rhport, request, (uint8_t*)&len, sizeof(len)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_INFO: + if (stage == CONTROL_STAGE_SETUP) + { + TU_VERIFY(1 == request->wLength, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(tud_control_xfer(rhport, request, (uint8_t*)(uintptr_t) &_cap_get_set, sizeof(_cap_get_set)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + default: break; + } + break; + + case VIDEO_VS_CTL_COMMIT: + switch (request->bRequest) { + case VIDEO_REQUEST_SET_CUR: + if (stage == CONTROL_STAGE_SETUP) { + TU_VERIFY(sizeof(video_probe_and_commit_control_t) == request->wLength, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(tud_control_xfer(rhport, request, self->ep_buf, sizeof(video_probe_and_commit_control_t)), VIDEO_ERROR_UNKNOWN); + } else if (stage == CONTROL_STAGE_ACK) { + TU_VERIFY(_update_streaming_parameters(self, (video_probe_and_commit_control_t*)self->ep_buf), VIDEO_ERROR_INVALID_VALUE_WITHIN_RANGE); + if (tud_video_commit_cb) { + return tud_video_commit_cb(self->index_vc, self->index_vs, (video_probe_and_commit_control_t*)self->ep_buf); + } + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_CUR: + if (stage == CONTROL_STAGE_SETUP) + { + TU_VERIFY(request->wLength, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(tud_control_xfer(rhport, request, self->ep_buf, sizeof(video_probe_and_commit_control_t)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_LEN: + if (stage == CONTROL_STAGE_SETUP) + { + TU_VERIFY(2 == request->wLength, VIDEO_ERROR_UNKNOWN); + uint16_t len = sizeof(video_probe_and_commit_control_t); + TU_VERIFY(tud_control_xfer(rhport, request, (uint8_t*)&len, sizeof(len)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + case VIDEO_REQUEST_GET_INFO: + if (stage == CONTROL_STAGE_SETUP) + { + TU_VERIFY(1 == request->wLength, VIDEO_ERROR_UNKNOWN); + TU_VERIFY(tud_control_xfer(rhport, request, (uint8_t*)(uintptr_t) &_cap_get_set, sizeof(_cap_get_set)), VIDEO_ERROR_UNKNOWN); + } + return VIDEO_ERROR_NONE; + + default: break; + } + break; + + case VIDEO_VS_CTL_STILL_PROBE: + case VIDEO_VS_CTL_STILL_COMMIT: + case VIDEO_VS_CTL_STILL_IMAGE_TRIGGER: + case VIDEO_VS_CTL_GENERATE_KEY_FRAME: + case VIDEO_VS_CTL_UPDATE_FRAME_SEGMENT: + case VIDEO_VS_CTL_SYNCH_DELAY_CONTROL: + /* TODO */ + break; + + default: break; + } + + /* Unknown/Unsupported request */ + TU_BREAKPOINT(); + return VIDEO_ERROR_INVALID_REQUEST; +} + +static int handle_video_stm_req(uint8_t rhport, uint8_t stage, + tusb_control_request_t const *request, + uint_fast8_t stm_idx) +{ + switch (request->bmRequestType_bit.type) { + case TUSB_REQ_TYPE_STANDARD: + return handle_video_stm_std_req(rhport, stage, request, stm_idx); + + case TUSB_REQ_TYPE_CLASS: + if (TU_U16_HIGH(request->wIndex)) return VIDEO_ERROR_INVALID_REQUEST; + return handle_video_stm_cs_req(rhport, stage, request, stm_idx); + + default: return VIDEO_ERROR_INVALID_REQUEST; + } + return VIDEO_ERROR_UNKNOWN; +} + +//--------------------------------------------------------------------+ +// APPLICATION API +//--------------------------------------------------------------------+ + +bool tud_video_n_connected(uint_fast8_t ctl_idx) +{ + TU_ASSERT(ctl_idx < CFG_TUD_VIDEO); + videod_streaming_interface_t *stm = _get_instance_streaming(ctl_idx, 0); + if (stm) return true; + return false; +} + +bool tud_video_n_streaming(uint_fast8_t ctl_idx, uint_fast8_t stm_idx) +{ + TU_ASSERT(ctl_idx < CFG_TUD_VIDEO); + TU_ASSERT(stm_idx < CFG_TUD_VIDEO_STREAMING); + videod_streaming_interface_t *stm = _get_instance_streaming(ctl_idx, stm_idx); + if (!stm || !stm->desc.ep[0]) return false; + return true; +} + +bool tud_video_n_frame_xfer(uint_fast8_t ctl_idx, uint_fast8_t stm_idx, void *buffer, size_t bufsize) +{ + TU_ASSERT(ctl_idx < CFG_TUD_VIDEO); + TU_ASSERT(stm_idx < CFG_TUD_VIDEO_STREAMING); + if (!buffer || !bufsize) return false; + videod_streaming_interface_t *stm = _get_instance_streaming(ctl_idx, stm_idx); + if (!stm || !stm->desc.ep[0] || stm->buffer) return false; + + /* Find EP address */ + void const *desc = _videod_itf[stm->index_vc].beg; + uint_fast8_t ep_addr = 0; + for (uint_fast8_t i = 0; i < CFG_TUD_VIDEO_STREAMING; ++i) { + uint_fast16_t ofs_ep = stm->desc.ep[i]; + if (!ofs_ep) continue; + ep_addr = _desc_ep_addr(desc + ofs_ep); + break; + } + if (!ep_addr) return false; + + TU_VERIFY( usbd_edpt_claim(0, ep_addr)); + /* update the packet header */ + tusb_video_payload_header_t *hdr = (tusb_video_payload_header_t*)stm->ep_buf; + hdr->FrameID ^= 1; + hdr->EndOfFrame = 0; + /* update the packet data */ + stm->buffer = (uint8_t*)buffer; + stm->bufsize = bufsize; + uint_fast16_t pkt_len = _prepare_in_payload(stm); + TU_ASSERT( usbd_edpt_xfer(0, ep_addr, stm->ep_buf, pkt_len), 0); + return true; +} + +//--------------------------------------------------------------------+ +// USBD Driver API +//--------------------------------------------------------------------+ +void videod_init(void) +{ + for (uint_fast8_t i = 0; i < CFG_TUD_VIDEO; ++i) { + videod_interface_t* ctl = &_videod_itf[i]; + tu_memclr(ctl, sizeof(*ctl)); + } + for (uint_fast8_t i = 0; i < CFG_TUD_VIDEO_STREAMING; ++i) { + videod_streaming_interface_t *stm = &_videod_streaming_itf[i]; + tu_memclr(stm, ITF_STM_MEM_RESET_SIZE); + } +} + +void videod_reset(uint8_t rhport) +{ + (void) rhport; + for (uint_fast8_t i = 0; i < CFG_TUD_VIDEO; ++i) { + videod_interface_t* ctl = &_videod_itf[i]; + tu_memclr(ctl, sizeof(*ctl)); + } + for (uint_fast8_t i = 0; i < CFG_TUD_VIDEO_STREAMING; ++i) { + videod_streaming_interface_t *stm = &_videod_streaming_itf[i]; + tu_memclr(stm, ITF_STM_MEM_RESET_SIZE); + } +} + +uint16_t videod_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) +{ + TU_VERIFY((TUSB_CLASS_VIDEO == itf_desc->bInterfaceClass) && + (VIDEO_SUBCLASS_CONTROL == itf_desc->bInterfaceSubClass) && + (VIDEO_ITF_PROTOCOL_15 == itf_desc->bInterfaceProtocol), 0); + + /* Find available interface */ + videod_interface_t *self = NULL; + uint_fast8_t ctl_idx; + for (ctl_idx = 0; ctl_idx < CFG_TUD_VIDEO; ++ctl_idx) { + if (_videod_itf[ctl_idx].beg) continue; + self = &_videod_itf[ctl_idx]; + break; + } + TU_ASSERT(ctl_idx < CFG_TUD_VIDEO, 0); + + void const *end = (void const*)itf_desc + max_len; + self->beg = itf_desc; + self->len = max_len; + /*------------- Video Control Interface -------------*/ + TU_VERIFY(_open_vc_itf(rhport, self, 0), 0); + tusb_desc_vc_itf_t const *vc = _get_desc_vc(self); + uint_fast8_t bInCollection = vc->ctl.bInCollection; + /* Find the end of the video interface descriptor */ + void const *cur = _next_desc_itf(itf_desc, end); + for (uint_fast8_t stm_idx = 0; stm_idx < bInCollection; ++stm_idx) { + videod_streaming_interface_t *stm = NULL; + /* find free streaming interface handle */ + for (uint_fast8_t i = 0; i < CFG_TUD_VIDEO_STREAMING; ++i) { + if (_videod_streaming_itf[i].desc.beg) continue; + stm = &_videod_streaming_itf[i]; + self->stm[stm_idx] = i; + break; + } + TU_ASSERT(stm, 0); + stm->index_vc = ctl_idx; + stm->index_vs = stm_idx; + stm->desc.beg = (uintptr_t)cur - (uintptr_t)itf_desc; + cur = _next_desc_itf(cur, end); + stm->desc.end = (uintptr_t)cur - (uintptr_t)itf_desc; + } + self->len = (uintptr_t)cur - (uintptr_t)itf_desc; + return (uintptr_t)cur - (uintptr_t)itf_desc; +} + +// 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 videod_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) +{ + int err; + TU_VERIFY(request->bmRequestType_bit.recipient == TUSB_REQ_RCPT_INTERFACE); + uint_fast8_t itfnum = tu_u16_low(request->wIndex); + + /* Identify which control interface to use */ + uint_fast8_t itf; + for (itf = 0; itf < CFG_TUD_VIDEO; ++itf) { + void const *desc = _videod_itf[itf].beg; + if (!desc) continue; + if (itfnum == _desc_itfnum(desc)) break; + } + + if (itf < CFG_TUD_VIDEO) { + err = handle_video_ctl_req(rhport, stage, request, itf); + _videod_itf[itf].error_code = (uint8_t)err; + if (err) return false; + return true; + } + + /* Identify which streaming interface to use */ + for (itf = 0; itf < CFG_TUD_VIDEO_STREAMING; ++itf) { + videod_streaming_interface_t *stm = &_videod_streaming_itf[itf]; + if (!stm->desc.beg) continue; + void const *desc = _videod_itf[stm->index_vc].beg; + if (itfnum == _desc_itfnum(desc + stm->desc.beg)) break; + } + + if (itf < CFG_TUD_VIDEO_STREAMING) { + err = handle_video_stm_req(rhport, stage, request, itf); + _videod_streaming_itf[itf].error_code = (uint8_t)err; + if (err) return false; + return true; + } + return false; +} + +bool videod_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) +{ + (void)result; (void)xferred_bytes; + + /* find streaming handle */ + uint_fast8_t itf; + videod_interface_t *ctl; + videod_streaming_interface_t *stm; + for (itf = 0; itf < CFG_TUD_VIDEO_STREAMING; ++itf) { + stm = &_videod_streaming_itf[itf]; + uint_fast16_t const ep_ofs = stm->desc.ep[0]; + if (!ep_ofs) continue; + ctl = &_videod_itf[stm->index_vc]; + void const *desc = ctl->beg; + if (ep_addr == _desc_ep_addr(desc + ep_ofs)) break; + } + + TU_ASSERT(itf < CFG_TUD_VIDEO_STREAMING); + if (stm->offset < stm->bufsize) { + /* Claim the endpoint */ + TU_VERIFY( usbd_edpt_claim(rhport, ep_addr), 0); + uint_fast16_t pkt_len = _prepare_in_payload(stm); + TU_ASSERT( usbd_edpt_xfer(rhport, ep_addr, stm->ep_buf, pkt_len), 0); + } else { + stm->buffer = NULL; + stm->bufsize = 0; + stm->offset = 0; + if (tud_video_frame_xfer_complete_cb) { + tud_video_frame_xfer_complete_cb(stm->index_vc, stm->index_vs); + } + } + return true; +} + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/video/video_device.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/video/video_device.h new file mode 100644 index 000000000..ee2fcb9d5 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/class/video/video_device.h @@ -0,0 +1,97 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * Copyright (c) 2021 Koji KITAYAMA + * + * 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_VIDEO_DEVICE_H_ +#define TUSB_VIDEO_DEVICE_H_ + +#include "common/tusb_common.h" +#include "video.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Application API (Multiple Ports) +// CFG_TUD_VIDEO > 1 +//--------------------------------------------------------------------+ + +/** Return true if streaming + * + * @param[in] ctl_idx Destination control interface index + * @param[in] stm_idx Destination streaming interface index */ +bool tud_video_n_streaming(uint_fast8_t ctl_idx, uint_fast8_t stm_idx); + +/** Transfer a frame + * + * @param[in] ctl_idx Destination control interface index + * @param[in] stm_idx Destination streaming interface index + * @param[in] buffer Frame buffer. The caller must not use this buffer until the operation is completed. + * @param[in] bufsize Byte size of the frame buffer */ +bool tud_video_n_frame_xfer(uint_fast8_t ctl_idx, uint_fast8_t stm_idx, void *buffer, size_t bufsize); + +/*------------- Optional callbacks -------------*/ +/** Invoked when compeletion of a frame transfer + * + * @param[in] ctl_idx Destination control interface index + * @param[in] stm_idx Destination streaming interface index */ +TU_ATTR_WEAK void tud_video_frame_xfer_complete_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx); + +//--------------------------------------------------------------------+ +// Application Callback API (weak is optional) +//--------------------------------------------------------------------+ + +/** Invoked when SET_POWER_MODE request received + * + * @param[in] ctl_idx Destination control interface index + * @param[in] stm_idx Destination streaming interface index + * @return video_error_code_t */ +TU_ATTR_WEAK int tud_video_power_mode_cb(uint_fast8_t ctl_idx, uint8_t power_mod); + +/** Invoked when VS_COMMIT_CONTROL(SET_CUR) request received + * + * @param[in] ctl_idx Destination control interface index + * @param[in] stm_idx Destination streaming interface index + * @param[in] parameters Video streaming parameters + * @return video_error_code_t */ +TU_ATTR_WEAK int tud_video_commit_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx, + video_probe_and_commit_control_t const *parameters); + +//--------------------------------------------------------------------+ +// INTERNAL USBD-CLASS DRIVER API +//--------------------------------------------------------------------+ +void videod_init (void); +void videod_reset (uint8_t rhport); +uint16_t videod_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len); +bool videod_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); +bool videod_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_common.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_common.h new file mode 100644 index 000000000..9b9e2b007 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_common.h @@ -0,0 +1,406 @@ +/* + * 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_COMMON_H_ +#define _TUSB_COMMON_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Macros Helper +//--------------------------------------------------------------------+ +#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_U16_HIGH(_u16) ((uint8_t) (((_u16) >> 8) & 0x00ff)) +#define TU_U16_LOW(_u16) ((uint8_t) ((_u16) & 0x00ff)) +#define U16_TO_U8S_BE(_u16) TU_U16_HIGH(_u16), TU_U16_LOW(_u16) +#define U16_TO_U8S_LE(_u16) TU_U16_LOW(_u16), TU_U16_HIGH(_u16) + +#define TU_U32_BYTE3(_u32) ((uint8_t) ((((uint32_t) _u32) >> 24) & 0x000000ff)) // MSB +#define TU_U32_BYTE2(_u32) ((uint8_t) ((((uint32_t) _u32) >> 16) & 0x000000ff)) +#define TU_U32_BYTE1(_u32) ((uint8_t) ((((uint32_t) _u32) >> 8) & 0x000000ff)) +#define TU_U32_BYTE0(_u32) ((uint8_t) (((uint32_t) _u32) & 0x000000ff)) // LSB + +#define U32_TO_U8S_BE(_u32) TU_U32_BYTE3(_u32), TU_U32_BYTE2(_u32), TU_U32_BYTE1(_u32), TU_U32_BYTE0(_u32) +#define U32_TO_U8S_LE(_u32) TU_U32_BYTE0(_u32), TU_U32_BYTE1(_u32), TU_U32_BYTE2(_u32), TU_U32_BYTE3(_u32) + +#define TU_BIT(n) (1UL << (n)) +#define TU_GENMASK(h, l) ( (UINT32_MAX << (l)) & (UINT32_MAX >> (31 - (h))) ) + +//--------------------------------------------------------------------+ +// Includes +//--------------------------------------------------------------------+ + +// Standard Headers +#include +#include +#include +#include +#include + +// Tinyusb Common Headers +#include "tusb_option.h" +#include "tusb_compiler.h" +#include "tusb_verify.h" +#include "tusb_types.h" + +#include "tusb_error.h" // TODO remove +#include "tusb_timeout.h" // TODO remove + +//--------------------------------------------------------------------+ +// Internal Helper used by Host and Device Stack +//--------------------------------------------------------------------+ + +// Check if endpoint descriptor is valid per USB specs +bool tu_edpt_validate(tusb_desc_endpoint_t const * desc_ep, tusb_speed_t speed); + +// Bind all endpoint of a interface descriptor to class driver +void tu_edpt_bind_driver(uint8_t ep2drv[][2], tusb_desc_interface_t const* p_desc, uint16_t desc_len, uint8_t driver_id); + +// Calculate total length of n interfaces (depending on IAD) +uint16_t tu_desc_get_interface_total_len(tusb_desc_interface_t const* desc_itf, uint8_t itf_count, uint16_t max_len); + +//--------------------------------------------------------------------+ +// Internal Inline Functions +//--------------------------------------------------------------------+ + +//------------- Mem -------------// +#define tu_memclr(buffer, size) memset((buffer), 0, (size)) +#define tu_varclr(_var) tu_memclr(_var, sizeof(*(_var))) + +//------------- Bytes -------------// +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_u32(uint8_t b3, uint8_t b2, uint8_t b1, uint8_t b0) +{ + return ( ((uint32_t) b3) << 24) | ( ((uint32_t) b2) << 16) | ( ((uint32_t) b1) << 8) | b0; +} + +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_u16(uint8_t high, uint8_t low) +{ + return (uint16_t) ((((uint16_t) high) << 8) | low); +} + +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_u32_byte3(uint32_t ui32) { return TU_U32_BYTE3(ui32); } +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_u32_byte2(uint32_t ui32) { return TU_U32_BYTE2(ui32); } +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_u32_byte1(uint32_t ui32) { return TU_U32_BYTE1(ui32); } +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_u32_byte0(uint32_t ui32) { return TU_U32_BYTE0(ui32); } + +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_u32_high16(uint32_t ui32) { return (uint16_t) (ui32 >> 16); } +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_u32_low16 (uint32_t ui32) { return (uint16_t) (ui32 & 0x0000ffffu); } + +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_u16_high(uint16_t ui16) { return TU_U16_HIGH(ui16); } +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_u16_low (uint16_t ui16) { return TU_U16_LOW(ui16); } + +//------------- Bits -------------// +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_bit_set (uint32_t value, uint8_t pos) { return value | TU_BIT(pos); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_bit_clear(uint32_t value, uint8_t pos) { return value & (~TU_BIT(pos)); } +TU_ATTR_ALWAYS_INLINE static inline bool tu_bit_test (uint32_t value, uint8_t pos) { return (value & TU_BIT(pos)) ? true : false; } + +//------------- Min -------------// +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_min8 (uint8_t x, uint8_t y ) { return (x < y) ? x : y; } +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_min16 (uint16_t x, uint16_t y) { return (x < y) ? x : y; } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_min32 (uint32_t x, uint32_t y) { return (x < y) ? x : y; } + +//------------- Max -------------// +TU_ATTR_ALWAYS_INLINE static inline uint8_t tu_max8 (uint8_t x, uint8_t y ) { return (x > y) ? x : y; } +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_max16 (uint16_t x, uint16_t y) { return (x > y) ? x : y; } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_max32 (uint32_t x, uint32_t y) { return (x > y) ? x : y; } + +//------------- Align -------------// +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_align(uint32_t value, uint32_t alignment) +{ + return value & ((uint32_t) ~(alignment-1)); +} + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_align16 (uint32_t value) { return (value & 0xFFFFFFF0UL); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_align32 (uint32_t value) { return (value & 0xFFFFFFE0UL); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_align4k (uint32_t value) { return (value & 0xFFFFF000UL); } +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_offset4k(uint32_t value) { return (value & 0xFFFUL); } + +//------------- Mathematics -------------// +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_div_ceil(uint32_t v, uint32_t d) { return (v + d -1)/d; } + +/// inclusive range checking TODO remove +TU_ATTR_ALWAYS_INLINE static inline bool tu_within(uint32_t lower, uint32_t value, uint32_t upper) +{ + return (lower <= value) && (value <= upper); +} + +// log2 of a value is its MSB's position +// TODO use clz TODO remove +static inline uint8_t tu_log2(uint32_t value) +{ + uint8_t result = 0; + while (value >>= 1) { result++; } + return result; +} + +//------------- Unaligned Access -------------// +#if TUP_ARCH_STRICT_ALIGN + +// Rely on compiler to generate correct code for unaligned access +typedef struct { uint16_t val; } TU_ATTR_PACKED tu_unaligned_uint16_t; +typedef struct { uint32_t val; } TU_ATTR_PACKED tu_unaligned_uint32_t; + +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_unaligned_read32(const void* mem) +{ + tu_unaligned_uint32_t const* ua32 = (tu_unaligned_uint32_t const*) mem; + return ua32->val; +} + +TU_ATTR_ALWAYS_INLINE static inline void tu_unaligned_write32(void* mem, uint32_t value) +{ + tu_unaligned_uint32_t* ua32 = (tu_unaligned_uint32_t*) mem; + ua32->val = value; +} + +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_unaligned_read16(const void* mem) +{ + tu_unaligned_uint16_t const* ua16 = (tu_unaligned_uint16_t const*) mem; + return ua16->val; +} + +TU_ATTR_ALWAYS_INLINE static inline void tu_unaligned_write16(void* mem, uint16_t value) +{ + tu_unaligned_uint16_t* ua16 = (tu_unaligned_uint16_t*) mem; + ua16->val = value; +} + +#elif TUP_MCU_STRICT_ALIGN + +// MCU such as LPC_IP3511 Highspeed cannot access unaligned memory on USB_RAM although it is ARM M4. +// We have to manually pick up bytes since tu_unaligned_uint32_t will still generate unaligned code +// NOTE: volatile cast to memory to prevent compiler to optimize and generate unaligned code +// TODO Big Endian may need minor changes +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_unaligned_read32(const void* mem) +{ + volatile uint8_t const* buf8 = (uint8_t const*) mem; + return tu_u32(buf8[3], buf8[2], buf8[1], buf8[0]); +} + +TU_ATTR_ALWAYS_INLINE static inline void tu_unaligned_write32(void* mem, uint32_t value) +{ + volatile uint8_t* buf8 = (uint8_t*) mem; + buf8[0] = tu_u32_byte0(value); + buf8[1] = tu_u32_byte1(value); + buf8[2] = tu_u32_byte2(value); + buf8[3] = tu_u32_byte3(value); +} + +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_unaligned_read16(const void* mem) +{ + volatile uint8_t const* buf8 = (uint8_t const*) mem; + return tu_u16(buf8[1], buf8[0]); +} + +TU_ATTR_ALWAYS_INLINE static inline void tu_unaligned_write16(void* mem, uint16_t value) +{ + volatile uint8_t* buf8 = (uint8_t*) mem; + buf8[0] = tu_u16_low(value); + buf8[1] = tu_u16_high(value); +} + + +#else + +// MCU that could access unaligned memory natively +TU_ATTR_ALWAYS_INLINE static inline uint32_t tu_unaligned_read32 (const void* mem) { return *((uint32_t const *) mem); } +TU_ATTR_ALWAYS_INLINE static inline uint16_t tu_unaligned_read16 (const void* mem) { return *((uint16_t const *) mem); } + +TU_ATTR_ALWAYS_INLINE static inline void tu_unaligned_write32 (void* mem, uint32_t value ) { *((uint32_t*) mem) = value; } +TU_ATTR_ALWAYS_INLINE static inline void tu_unaligned_write16 (void* mem, uint16_t value ) { *((uint16_t*) mem) = value; } + +#endif + +// To be removed +//------------- Binary constant -------------// +#if defined(__GNUC__) && !defined(__CC_ARM) + +#define TU_BIN8(x) ((uint8_t) (0b##x)) +#define TU_BIN16(b1, b2) ((uint16_t) (0b##b1##b2)) +#define TU_BIN32(b1, b2, b3, b4) ((uint32_t) (0b##b1##b2##b3##b4)) + +#else + +// internal macro of B8, B16, B32 +#define _B8__(x) (((x&0x0000000FUL)?1:0) \ + +((x&0x000000F0UL)?2:0) \ + +((x&0x00000F00UL)?4:0) \ + +((x&0x0000F000UL)?8:0) \ + +((x&0x000F0000UL)?16:0) \ + +((x&0x00F00000UL)?32:0) \ + +((x&0x0F000000UL)?64:0) \ + +((x&0xF0000000UL)?128:0)) + +#define TU_BIN8(d) ((uint8_t) _B8__(0x##d##UL)) +#define TU_BIN16(dmsb,dlsb) (((uint16_t)TU_BIN8(dmsb)<<8) + TU_BIN8(dlsb)) +#define TU_BIN32(dmsb,db2,db3,dlsb) \ + (((uint32_t)TU_BIN8(dmsb)<<24) \ + + ((uint32_t)TU_BIN8(db2)<<16) \ + + ((uint32_t)TU_BIN8(db3)<<8) \ + + TU_BIN8(dlsb)) +#endif + +//--------------------------------------------------------------------+ +// Debug Function +//--------------------------------------------------------------------+ + +// CFG_TUSB_DEBUG for debugging +// 0 : no debug +// 1 : print error +// 2 : print warning +// 3 : print info +#if CFG_TUSB_DEBUG + +void tu_print_mem(void const *buf, uint32_t count, uint8_t indent); + +#ifdef CFG_TUSB_DEBUG_PRINTF + extern int CFG_TUSB_DEBUG_PRINTF(const char *format, ...); + #define tu_printf CFG_TUSB_DEBUG_PRINTF +#else + #define tu_printf printf +#endif + +static inline +void tu_print_var(uint8_t const* buf, uint32_t bufsize) +{ + for(uint32_t i=0; i= 2 + #define TU_LOG2 TU_LOG1 + #define TU_LOG2_MEM TU_LOG1_MEM + #define TU_LOG2_VAR TU_LOG1_VAR + #define TU_LOG2_INT TU_LOG1_INT + #define TU_LOG2_HEX TU_LOG1_HEX +#endif + +// Log Level 3: Info +#if CFG_TUSB_DEBUG >= 3 + #define TU_LOG3 TU_LOG1 + #define TU_LOG3_MEM TU_LOG1_MEM + #define TU_LOG3_VAR TU_LOG1_VAR + #define TU_LOG3_INT TU_LOG1_INT + #define TU_LOG3_HEX TU_LOG1_HEX +#endif + +typedef struct +{ + uint32_t key; + const char* data; +} tu_lookup_entry_t; + +typedef struct +{ + uint16_t count; + tu_lookup_entry_t const* items; +} tu_lookup_table_t; + +static inline const char* tu_lookup_find(tu_lookup_table_t const* p_table, uint32_t key) +{ + static char not_found[11]; + + for(uint16_t i=0; icount; i++) + { + if (p_table->items[i].key == key) return p_table->items[i].data; + } + + // not found return the key value in hex + sprintf(not_found, "0x%08lX", (unsigned long) key); + + return not_found; +} + +#endif // CFG_TUSB_DEBUG + +#ifndef TU_LOG +#define TU_LOG(n, ...) +#define TU_LOG_MEM(n, ...) +#define TU_LOG_VAR(n, ...) +#define TU_LOG_INT(n, ...) +#define TU_LOG_HEX(n, ...) +#define TU_LOG_LOCATION() +#define TU_LOG_FAILED() +#endif + +// TODO replace all TU_LOGn with TU_LOG(n) + +#define TU_LOG0(...) +#define TU_LOG0_MEM(...) +#define TU_LOG0_VAR(...) +#define TU_LOG0_INT(...) +#define TU_LOG0_HEX(...) + + +#ifndef TU_LOG1 + #define TU_LOG1(...) + #define TU_LOG1_MEM(...) + #define TU_LOG1_VAR(...) + #define TU_LOG1_INT(...) + #define TU_LOG1_HEX(...) +#endif + +#ifndef TU_LOG2 + #define TU_LOG2(...) + #define TU_LOG2_MEM(...) + #define TU_LOG2_VAR(...) + #define TU_LOG2_INT(...) + #define TU_LOG2_HEX(...) +#endif + +#ifndef TU_LOG3 + #define TU_LOG3(...) + #define TU_LOG3_MEM(...) + #define TU_LOG3_VAR(...) + #define TU_LOG3_INT(...) + #define TU_LOG3_HEX(...) +#endif + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_COMMON_H_ */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_compiler.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_compiler.h new file mode 100644 index 000000000..38e6a16d0 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_compiler.h @@ -0,0 +1,260 @@ +/* + * 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 Group_Common + * \defgroup Group_Compiler Compiler + * \brief Group_Compiler brief + * @{ */ + +#ifndef _TUSB_COMPILER_H_ +#define _TUSB_COMPILER_H_ + +#define TU_TOKEN(x) x +#define TU_STRING(x) #x ///< stringify without expand +#define TU_XSTRING(x) TU_STRING(x) ///< expand then stringify + +#define TU_STRCAT(a, b) a##b ///< concat without expand +#define TU_STRCAT3(a, b, c) a##b##c ///< concat without expand + +#define TU_XSTRCAT(a, b) TU_STRCAT(a, b) ///< expand then concat +#define TU_XSTRCAT3(a, b, c) TU_STRCAT3(a, b, c) ///< expand then concat 3 tokens + +#define TU_INCLUDE_PATH(_dir,_file) TU_XSTRING( TU_TOKEN(_dir)TU_TOKEN(_file) ) + +#if defined __COUNTER__ && __COUNTER__ != __COUNTER__ + #define _TU_COUNTER_ __COUNTER__ +#else + #define _TU_COUNTER_ __LINE__ +#endif + +// Compile-time Assert +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + #define TU_VERIFY_STATIC _Static_assert +#elif defined (__cplusplus) && __cplusplus >= 201103L + #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]; +#else + #define TU_VERIFY_STATIC(const_expr, _mess) enum { TU_XSTRCAT(_verify_static_, _TU_COUNTER_) = 1/(!!(const_expr)) } +#endif + +// for declaration of reserved field, make use of _TU_COUNTER_ +#define TU_RESERVED TU_XSTRCAT(reserved, _TU_COUNTER_) + +#define TU_LITTLE_ENDIAN (0x12u) +#define TU_BIG_ENDIAN (0x21u) + +/*------------------------------------------------------------------*/ +/* Count number of arguments of __VA_ARGS__ + * - reference https://stackoverflow.com/questions/2124339/c-preprocessor-va-args-number-of-arguments + * - _GET_NTH_ARG() takes args >= N (64) but only expand to Nth one (64th) + * - _RSEQ_N() is reverse sequential to N to add padding to have + * Nth position is the same as the number of arguments + * - ##__VA_ARGS__ is used to deal with 0 paramerter (swallows comma) + *------------------------------------------------------------------*/ +#define TU_ARGS_NUM(...) _TU_NARG(_0, ##__VA_ARGS__,_RSEQ_N()) + +#define _TU_NARG(...) _GET_NTH_ARG(__VA_ARGS__) +#define _GET_NTH_ARG( \ + _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \ + _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \ + _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \ + _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \ + _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \ + _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \ + _61,_62,_63,N,...) N +#define _RSEQ_N() \ + 62,61,60, \ + 59,58,57,56,55,54,53,52,51,50, \ + 49,48,47,46,45,44,43,42,41,40, \ + 39,38,37,36,35,34,33,32,31,30, \ + 29,28,27,26,25,24,23,22,21,20, \ + 19,18,17,16,15,14,13,12,11,10, \ + 9,8,7,6,5,4,3,2,1,0 + +// Apply an macro X to each of the arguments with an separated of choice +#define TU_ARGS_APPLY(_X, _s, ...) TU_XSTRCAT(_TU_ARGS_APPLY_, TU_ARGS_NUM(__VA_ARGS__))(_X, _s, __VA_ARGS__) + +#define _TU_ARGS_APPLY_1(_X, _s, _a1) _X(_a1) +#define _TU_ARGS_APPLY_2(_X, _s, _a1, _a2) _X(_a1) _s _X(_a2) +#define _TU_ARGS_APPLY_3(_X, _s, _a1, _a2, _a3) _X(_a1) _s _TU_ARGS_APPLY_2(_X, _s, _a2, _a3) +#define _TU_ARGS_APPLY_4(_X, _s, _a1, _a2, _a3, _a4) _X(_a1) _s _TU_ARGS_APPLY_3(_X, _s, _a2, _a3, _a4) +#define _TU_ARGS_APPLY_5(_X, _s, _a1, _a2, _a3, _a4, _a5) _X(_a1) _s _TU_ARGS_APPLY_4(_X, _s, _a2, _a3, _a4, _a5) +#define _TU_ARGS_APPLY_6(_X, _s, _a1, _a2, _a3, _a4, _a5, _a6) _X(_a1) _s _TU_ARGS_APPLY_5(_X, _s, _a2, _a3, _a4, _a5, _a6) +#define _TU_ARGS_APPLY_7(_X, _s, _a1, _a2, _a3, _a4, _a5, _a6, _a7) _X(_a1) _s _TU_ARGS_APPLY_6(_X, _s, _a2, _a3, _a4, _a5, _a6, _a7) +#define _TU_ARGS_APPLY_8(_X, _s, _a1, _a2, _a3, _a4, _a5, _a6, _a7, _a8) _X(_a1) _s _TU_ARGS_APPLY_7(_X, _s, _a2, _a3, _a4, _a5, _a6, _a7, _a8) + +//--------------------------------------------------------------------+ +// Compiler porting with Attribute and Endian +//--------------------------------------------------------------------+ + +// TODO refactor since __attribute__ is supported across many compiler +#if defined(__GNUC__) + #define TU_ATTR_ALIGNED(Bytes) __attribute__ ((aligned(Bytes))) + #define TU_ATTR_SECTION(sec_name) __attribute__ ((section(#sec_name))) + #define TU_ATTR_PACKED __attribute__ ((packed)) + #define TU_ATTR_WEAK __attribute__ ((weak)) + #define TU_ATTR_ALWAYS_INLINE __attribute__ ((always_inline)) + #define TU_ATTR_DEPRECATED(mess) __attribute__ ((deprecated(mess))) // warn if function with this attribute is used + #define TU_ATTR_UNUSED __attribute__ ((unused)) // Function/Variable is meant to be possibly unused + #define TU_ATTR_USED __attribute__ ((used)) // Function/Variable is meant to be used + + #define TU_ATTR_PACKED_BEGIN + #define TU_ATTR_PACKED_END + #define TU_ATTR_BIT_FIELD_ORDER_BEGIN + #define TU_ATTR_BIT_FIELD_ORDER_END + + // Endian conversion use well-known host to network (big endian) naming + #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + #define TU_BYTE_ORDER TU_LITTLE_ENDIAN + #else + #define TU_BYTE_ORDER TU_BIG_ENDIAN + #endif + + #define TU_BSWAP16(u16) (__builtin_bswap16(u16)) + #define TU_BSWAP32(u32) (__builtin_bswap32(u32)) + + #ifndef __ARMCC_VERSION + // List of obsolete callback function that is renamed and should not be defined. + // Put it here since only gcc support this pragma + #pragma GCC poison tud_vendor_control_request_cb + #endif + +#elif defined(__TI_COMPILER_VERSION__) + #define TU_ATTR_ALIGNED(Bytes) __attribute__ ((aligned(Bytes))) + #define TU_ATTR_SECTION(sec_name) __attribute__ ((section(#sec_name))) + #define TU_ATTR_PACKED __attribute__ ((packed)) + #define TU_ATTR_WEAK __attribute__ ((weak)) + #define TU_ATTR_ALWAYS_INLINE __attribute__ ((always_inline)) + #define TU_ATTR_DEPRECATED(mess) __attribute__ ((deprecated(mess))) // warn if function with this attribute is used + #define TU_ATTR_UNUSED __attribute__ ((unused)) // Function/Variable is meant to be possibly unused + #define TU_ATTR_USED __attribute__ ((used)) + + #define TU_ATTR_PACKED_BEGIN + #define TU_ATTR_PACKED_END + #define TU_ATTR_BIT_FIELD_ORDER_BEGIN + #define TU_ATTR_BIT_FIELD_ORDER_END + + // __BYTE_ORDER is defined in the TI ARM compiler, but not MSP430 (which is little endian) + #if ((__BYTE_ORDER__) == (__ORDER_LITTLE_ENDIAN__)) || defined(__MSP430__) + #define TU_BYTE_ORDER TU_LITTLE_ENDIAN + #else + #define TU_BYTE_ORDER TU_BIG_ENDIAN + #endif + + #define TU_BSWAP16(u16) (__builtin_bswap16(u16)) + #define TU_BSWAP32(u32) (__builtin_bswap32(u32)) + +#elif defined(__ICCARM__) + #include + #define TU_ATTR_ALIGNED(Bytes) __attribute__ ((aligned(Bytes))) + #define TU_ATTR_SECTION(sec_name) __attribute__ ((section(#sec_name))) + #define TU_ATTR_PACKED __attribute__ ((packed)) + #define TU_ATTR_WEAK __attribute__ ((weak)) + #define TU_ATTR_ALWAYS_INLINE __attribute__ ((always_inline)) + #define TU_ATTR_DEPRECATED(mess) __attribute__ ((deprecated(mess))) // warn if function with this attribute is used + #define TU_ATTR_UNUSED __attribute__ ((unused)) // Function/Variable is meant to be possibly unused + #define TU_ATTR_USED __attribute__ ((used)) // Function/Variable is meant to be used + + #define TU_ATTR_PACKED_BEGIN + #define TU_ATTR_PACKED_END + #define TU_ATTR_BIT_FIELD_ORDER_BEGIN + #define TU_ATTR_BIT_FIELD_ORDER_END + + // Endian conversion use well-known host to network (big endian) naming + #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + #define TU_BYTE_ORDER TU_LITTLE_ENDIAN + #else + #define TU_BYTE_ORDER TU_BIG_ENDIAN + #endif + + #define TU_BSWAP16(u16) (__iar_builtin_REV16(u16)) + #define TU_BSWAP32(u32) (__iar_builtin_REV(u32)) + +#elif defined(__CCRX__) + #define TU_ATTR_ALIGNED(Bytes) + #define TU_ATTR_SECTION(sec_name) + #define TU_ATTR_PACKED + #define TU_ATTR_WEAK + #define TU_ATTR_ALWAYS_INLINE + #define TU_ATTR_DEPRECATED(mess) + #define TU_ATTR_UNUSED + #define TU_ATTR_USED + + #define TU_ATTR_PACKED_BEGIN _Pragma("pack") + #define TU_ATTR_PACKED_END _Pragma("packoption") + #define TU_ATTR_BIT_FIELD_ORDER_BEGIN _Pragma("bit_order right") + #define TU_ATTR_BIT_FIELD_ORDER_END _Pragma("bit_order") + + // Endian conversion use well-known host to network (big endian) naming + #if defined(__LIT) + #define TU_BYTE_ORDER TU_LITTLE_ENDIAN + #else + #define TU_BYTE_ORDER TU_BIG_ENDIAN + #endif + + #define TU_BSWAP16(u16) ((unsigned short)_builtin_revw((unsigned long)u16)) + #define TU_BSWAP32(u32) (_builtin_revl(u32)) + +#else + #error "Compiler attribute porting is required" +#endif + +#if (TU_BYTE_ORDER == TU_LITTLE_ENDIAN) + + #define tu_htons(u16) (TU_BSWAP16(u16)) + #define tu_ntohs(u16) (TU_BSWAP16(u16)) + + #define tu_htonl(u32) (TU_BSWAP32(u32)) + #define tu_ntohl(u32) (TU_BSWAP32(u32)) + + #define tu_htole16(u16) (u16) + #define tu_le16toh(u16) (u16) + + #define tu_htole32(u32) (u32) + #define tu_le32toh(u32) (u32) + +#elif (TU_BYTE_ORDER == TU_BIG_ENDIAN) + + #define tu_htons(u16) (u16) + #define tu_ntohs(u16) (u16) + + #define tu_htonl(u32) (u32) + #define tu_ntohl(u32) (u32) + + #define tu_htole16(u16) (TU_BSWAP16(u16)) + #define tu_le16toh(u16) (TU_BSWAP16(u16)) + + #define tu_htole32(u32) (TU_BSWAP32(u32)) + #define tu_le32toh(u32) (TU_BSWAP32(u32)) + +#else + #error Byte order is undefined +#endif + +#endif /* _TUSB_COMPILER_H_ */ + +/// @} diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_error.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_error.h new file mode 100644 index 000000000..d7ad8c318 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_error.h @@ -0,0 +1,75 @@ +/* + * 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 Group_Common + * \defgroup Group_Error Error Codes + * @{ */ + +#ifndef _TUSB_ERRORS_H_ +#define _TUSB_ERRORS_H_ + +#include "tusb_option.h" + +#ifdef __cplusplus + extern "C" { +#endif + +#define ERROR_ENUM(x) x, +#define ERROR_STRING(x) #x, + +#define ERROR_TABLE(ENTRY) \ + ENTRY(TUSB_ERROR_NONE )\ + ENTRY(TUSB_ERROR_INVALID_PARA )\ + ENTRY(TUSB_ERROR_DEVICE_NOT_READY )\ + ENTRY(TUSB_ERROR_INTERFACE_IS_BUSY )\ + ENTRY(TUSB_ERROR_HCD_OPEN_PIPE_FAILED )\ + ENTRY(TUSB_ERROR_OSAL_TIMEOUT )\ + ENTRY(TUSB_ERROR_CDCH_DEVICE_NOT_MOUNTED )\ + ENTRY(TUSB_ERROR_MSCH_DEVICE_NOT_MOUNTED )\ + ENTRY(TUSB_ERROR_NOT_SUPPORTED )\ + ENTRY(TUSB_ERROR_NOT_ENOUGH_MEMORY )\ + ENTRY(TUSB_ERROR_FAILED )\ + +/// \brief Error Code returned +/// TODO obsolete and to be remove +typedef enum +{ + ERROR_TABLE(ERROR_ENUM) + TUSB_ERROR_COUNT +}tusb_error_t; + +#if CFG_TUSB_DEBUG +/// Enum to String for debugging purposes. Only available if \ref CFG_TUSB_DEBUG > 0 +extern char const* const tusb_strerr[TUSB_ERROR_COUNT]; +#endif + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_ERRORS_H_ */ + +/** @} */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_fifo.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_fifo.c new file mode 100644 index 000000000..183c9c6fc --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_fifo.c @@ -0,0 +1,1007 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * Copyright (c) 2020 Reinhard Panhuber - rework to unmasked pointers + * + * 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 "osal/osal.h" +#include "tusb_fifo.h" + +// Supress IAR warning +// Warning[Pa082]: undefined behavior: the order of volatile accesses is undefined in this statement +#if defined(__ICCARM__) +#pragma diag_suppress = Pa082 +#endif + +// implement mutex lock and unlock +#if CFG_FIFO_MUTEX + +static inline void _ff_lock(tu_fifo_mutex_t mutex) +{ + if (mutex) osal_mutex_lock(mutex, OSAL_TIMEOUT_WAIT_FOREVER); +} + +static inline void _ff_unlock(tu_fifo_mutex_t mutex) +{ + if (mutex) osal_mutex_unlock(mutex); +} + +#else + +#define _ff_lock(_mutex) +#define _ff_unlock(_mutex) + +#endif + +/** \enum tu_fifo_copy_mode_t + * \brief Write modes intended to allow special read and write functions to be able to + * copy data to and from USB hardware FIFOs as needed for e.g. STM32s and others + */ +typedef enum +{ + TU_FIFO_COPY_INC, ///< Copy from/to an increasing source/destination address - default mode + 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 +} tu_fifo_copy_mode_t; + +bool tu_fifo_config(tu_fifo_t *f, void* buffer, uint16_t depth, uint16_t item_size, bool overwritable) +{ + if (depth > 0x8000) return false; // Maximum depth is 2^15 items + + _ff_lock(f->mutex_wr); + _ff_lock(f->mutex_rd); + + f->buffer = (uint8_t*) buffer; + f->depth = depth; + f->item_size = item_size; + f->overwritable = overwritable; + + // Limit index space to 2*depth - this allows for a fast "modulo" calculation + // but limits the maximum depth to 2^16/2 = 2^15 and buffer overflows are detectable + // only if overflow happens once (important for unsupervised DMA applications) + f->max_pointer_idx = 2*depth - 1; + f->non_used_index_space = UINT16_MAX - f->max_pointer_idx; + + f->rd_idx = f->wr_idx = 0; + + _ff_unlock(f->mutex_wr); + _ff_unlock(f->mutex_rd); + + return true; +} + +// Static functions are intended to work on local variables +static inline uint16_t _ff_mod(uint16_t idx, uint16_t depth) +{ + while ( idx >= depth) idx -= depth; + return idx; +} + +// 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_synopsis.c +// TODO generalize with configurable 1 byte or 4 byte each read +static void _ff_push_const_addr(uint8_t * ff_buf, const void * app_buf, uint16_t len) +{ + volatile const uint32_t * rx_fifo = (volatile const uint32_t *) app_buf; + + // Reading full available 32 bit words from const app address + uint16_t full_words = len >> 2; + while(full_words--) + { + tu_unaligned_write32(ff_buf, *rx_fifo); + ff_buf += 4; + } + + // Read the remaining 1-3 bytes from const app address + uint8_t const bytes_rem = len & 0x03; + if ( bytes_rem ) + { + uint32_t tmp32 = *rx_fifo; + memcpy(ff_buf, &tmp32, bytes_rem); + } +} + +// Intended to be used to write to hardware USB FIFO in e.g. STM32 +// where all data is written to a constant address in full word copies +static void _ff_pull_const_addr(void * app_buf, const uint8_t * ff_buf, uint16_t len) +{ + volatile uint32_t * tx_fifo = (volatile uint32_t *) app_buf; + + // Pushing full available 32 bit words to const app address + uint16_t full_words = len >> 2; + while(full_words--) + { + *tx_fifo = tu_unaligned_read32(ff_buf); + ff_buf += 4; + } + + // Write the remaining 1-3 bytes into const app address + uint8_t const bytes_rem = len & 0x03; + if ( bytes_rem ) + { + uint32_t tmp32 = 0; + memcpy(&tmp32, ff_buf, bytes_rem); + + *tx_fifo = tmp32; + } +} + +// 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) +{ + memcpy(f->buffer + (rel * f->item_size), app_buf, f->item_size); +} + +// send n items to FIFO WITHOUT updating write pointer +static void _ff_push_n(tu_fifo_t* f, void const * app_buf, uint16_t n, uint16_t rel, tu_fifo_copy_mode_t copy_mode) +{ + uint16_t const nLin = f->depth - rel; + uint16_t const nWrap = n - nLin; + + uint16_t nLin_bytes = nLin * f->item_size; + uint16_t nWrap_bytes = nWrap * f->item_size; + + // current buffer of fifo + uint8_t* ff_buf = f->buffer + (rel * f->item_size); + + switch (copy_mode) + { + case TU_FIFO_COPY_INC: + if(n <= nLin) + { + // Linear only + memcpy(ff_buf, app_buf, n*f->item_size); + } + else + { + // Wrap around + + // Write data to linear part of buffer + memcpy(ff_buf, app_buf, nLin_bytes); + + // Write data wrapped around + memcpy(f->buffer, ((uint8_t const*) app_buf) + nLin_bytes, nWrap_bytes); + } + break; + + case TU_FIFO_COPY_CST_FULL_WORDS: + // Intended for hardware buffers from which it can be read word by word only + if(n <= nLin) + { + // Linear only + _ff_push_const_addr(ff_buf, app_buf, n*f->item_size); + } + else + { + // Wrap around case + + // Write full words to linear part of buffer + uint16_t nLin_4n_bytes = nLin_bytes & 0xFFFC; + _ff_push_const_addr(ff_buf, app_buf, nLin_4n_bytes); + ff_buf += nLin_4n_bytes; + + // There could be odd 1-3 bytes before the wrap-around boundary + volatile const uint32_t * rx_fifo = (volatile const uint32_t *) app_buf; + uint8_t rem = nLin_bytes & 0x03; + if (rem > 0) + { + uint8_t remrem = tu_min16(nWrap_bytes, 4-rem); + nWrap_bytes -= remrem; + + uint32_t tmp32 = *rx_fifo; + uint8_t * src_u8 = ((uint8_t *) &tmp32); + + // Write 1-3 bytes before wrapped boundary + while(rem--) *ff_buf++ = *src_u8++; + + // Read more bytes to beginning to complete a word + ff_buf = f->buffer; + while(remrem--) *ff_buf++ = *src_u8++; + } + else + { + ff_buf = f->buffer; // wrap around to beginning + } + + // Write data wrapped part + if (nWrap_bytes > 0) _ff_push_const_addr(ff_buf, app_buf, nWrap_bytes); + } + break; + } +} + +// get one item from FIFO WITHOUT updating read pointer +static inline void _ff_pull(tu_fifo_t* f, void * app_buf, uint16_t rel) +{ + memcpy(app_buf, f->buffer + (rel * f->item_size), f->item_size); +} + +// get n items from FIFO WITHOUT updating read pointer +static void _ff_pull_n(tu_fifo_t* f, void* app_buf, uint16_t n, uint16_t rel, tu_fifo_copy_mode_t copy_mode) +{ + uint16_t const nLin = f->depth - rel; + uint16_t const nWrap = n - nLin; // only used if wrapped + + uint16_t nLin_bytes = nLin * f->item_size; + uint16_t nWrap_bytes = nWrap * f->item_size; + + // current buffer of fifo + uint8_t* ff_buf = f->buffer + (rel * f->item_size); + + switch (copy_mode) + { + case TU_FIFO_COPY_INC: + if ( n <= nLin ) + { + // Linear only + memcpy(app_buf, ff_buf, n*f->item_size); + } + else + { + // Wrap around + + // Read data from linear part of buffer + memcpy(app_buf, ff_buf, nLin_bytes); + + // Read data wrapped part + memcpy((uint8_t*) app_buf + nLin_bytes, f->buffer, nWrap_bytes); + } + break; + + case TU_FIFO_COPY_CST_FULL_WORDS: + if ( n <= nLin ) + { + // Linear only + _ff_pull_const_addr(app_buf, ff_buf, n*f->item_size); + } + else + { + // Wrap around case + + // Read full words from linear part of buffer + uint16_t nLin_4n_bytes = nLin_bytes & 0xFFFC; + _ff_pull_const_addr(app_buf, ff_buf, nLin_4n_bytes); + ff_buf += nLin_4n_bytes; + + // There could be odd 1-3 bytes before the wrap-around boundary + volatile uint32_t * tx_fifo = (volatile uint32_t *) app_buf; + uint8_t rem = nLin_bytes & 0x03; + if (rem > 0) + { + uint8_t remrem = tu_min16(nWrap_bytes, 4-rem); + nWrap_bytes -= remrem; + + uint32_t tmp32=0; + uint8_t * dst_u8 = (uint8_t *)&tmp32; + + // Read 1-3 bytes before wrapped boundary + while(rem--) *dst_u8++ = *ff_buf++; + + // Read more bytes from beginning to complete a word + ff_buf = f->buffer; + while(remrem--) *dst_u8++ = *ff_buf++; + + *tx_fifo = tmp32; + } + else + { + ff_buf = f->buffer; // wrap around to beginning + } + + // Read data wrapped part + if (nWrap_bytes > 0) _ff_pull_const_addr(app_buf, ff_buf, nWrap_bytes); + } + break; + + default: break; + } +} + +// Advance an absolute pointer +static uint16_t advance_pointer(tu_fifo_t* f, uint16_t p, uint16_t offset) +{ + // We limit the index space of p such that a correct wrap around happens + // Check for a wrap around or if we are in unused index space - This has to be checked first!! + // We are exploiting the wrap around to the correct index + if ((p > (uint16_t)(p + offset)) || ((uint16_t)(p + offset) > f->max_pointer_idx)) + { + p = (p + offset) + f->non_used_index_space; + } + else + { + p += offset; + } + return p; +} + +// Backward an absolute pointer +static uint16_t backward_pointer(tu_fifo_t* f, uint16_t p, uint16_t offset) +{ + // We limit the index space of p such that a correct wrap around happens + // Check for a wrap around or if we are in unused index space - This has to be checked first!! + // We are exploiting the wrap around to the correct index + if ((p < (uint16_t)(p - offset)) || ((uint16_t)(p - offset) > f->max_pointer_idx)) + { + p = (p - offset) - f->non_used_index_space; + } + else + { + p -= offset; + } + return p; +} + +// get relative from absolute pointer +static uint16_t get_relative_pointer(tu_fifo_t* f, uint16_t p) +{ + return _ff_mod(p, f->depth); +} + +// Works on local copies of w and r - return only the difference and as such can be used to determine an overflow +static inline uint16_t _tu_fifo_count(tu_fifo_t* f, uint16_t wAbs, uint16_t rAbs) +{ + uint16_t cnt = wAbs-rAbs; + + // In case we have non-power of two depth we need a further modification + if (rAbs > wAbs) cnt -= f->non_used_index_space; + + return cnt; +} + +// Works on local copies of w and r +static inline bool _tu_fifo_empty(uint16_t wAbs, uint16_t rAbs) +{ + return wAbs == rAbs; +} + +// Works on local copies of w and r +static inline bool _tu_fifo_full(tu_fifo_t* f, uint16_t wAbs, uint16_t rAbs) +{ + return (_tu_fifo_count(f, wAbs, rAbs) == f->depth); +} + +// Works on local copies of w and r +// BE AWARE - THIS FUNCTION MIGHT NOT GIVE A CORRECT ANSWERE IN CASE WRITE POINTER "OVERFLOWS" +// Only one overflow is allowed for this function to work e.g. if depth = 100, you must not +// write more than 2*depth-1 items in one rush without updating write pointer. Otherwise +// write pointer wraps and you pointer states are messed up. This can only happen if you +// use DMAs, write functions do not allow such an error. +static inline bool _tu_fifo_overflowed(tu_fifo_t* f, uint16_t wAbs, uint16_t rAbs) +{ + return (_tu_fifo_count(f, wAbs, rAbs) > f->depth); +} + +// Works on local copies of w +// For more details see _tu_fifo_overflow()! +static inline void _tu_fifo_correct_read_pointer(tu_fifo_t* f, uint16_t wAbs) +{ + f->rd_idx = backward_pointer(f, wAbs, f->depth); +} + +// Works on local copies of w and r +// Must be protected by mutexes since in case of an overflow read pointer gets modified +static bool _tu_fifo_peek(tu_fifo_t* f, void * p_buffer, uint16_t wAbs, uint16_t rAbs) +{ + uint16_t cnt = _tu_fifo_count(f, wAbs, rAbs); + + // Check overflow and correct if required + if (cnt > f->depth) + { + _tu_fifo_correct_read_pointer(f, wAbs); + cnt = f->depth; + } + + // Skip beginning of buffer + if (cnt == 0) return false; + + uint16_t rRel = get_relative_pointer(f, rAbs); + + // Peek data + _ff_pull(f, p_buffer, rRel); + + return true; +} + +// Works on local copies of w and r +// Must be protected by mutexes since in case of an overflow read pointer gets modified +static uint16_t _tu_fifo_peek_n(tu_fifo_t* f, void * p_buffer, uint16_t n, uint16_t wAbs, uint16_t rAbs, tu_fifo_copy_mode_t copy_mode) +{ + uint16_t cnt = _tu_fifo_count(f, wAbs, rAbs); + + // Check overflow and correct if required + if (cnt > f->depth) + { + _tu_fifo_correct_read_pointer(f, wAbs); + rAbs = f->rd_idx; + cnt = f->depth; + } + + // Skip beginning of buffer + if (cnt == 0) return 0; + + // Check if we can read something at and after offset - if too less is available we read what remains + if (cnt < n) n = cnt; + + uint16_t rRel = get_relative_pointer(f, rAbs); + + // Peek data + _ff_pull_n(f, p_buffer, n, rRel, copy_mode); + + return n; +} + +// Works on local copies of w and r +static inline uint16_t _tu_fifo_remaining(tu_fifo_t* f, uint16_t wAbs, uint16_t rAbs) +{ + return f->depth - _tu_fifo_count(f, wAbs, rAbs); +} + +static uint16_t _tu_fifo_write_n(tu_fifo_t* f, const void * data, uint16_t n, tu_fifo_copy_mode_t copy_mode) +{ + if ( n == 0 ) return 0; + + _ff_lock(f->mutex_wr); + + uint16_t w = f->wr_idx, r = f->rd_idx; + uint8_t const* buf8 = (uint8_t const*) data; + + if (!f->overwritable) + { + // Not overwritable limit up to full + n = tu_min16(n, _tu_fifo_remaining(f, w, r)); + } + else if (n >= f->depth) + { + // Only copy last part + buf8 = buf8 + (n - f->depth) * f->item_size; + n = f->depth; + + // We start writing at the read pointer's position since we fill the complete + // buffer and we do not want to modify the read pointer within a write function! + // This would end up in a race condition with read functions! + w = r; + } + + uint16_t wRel = get_relative_pointer(f, w); + + // Write data + _ff_push_n(f, buf8, n, wRel, copy_mode); + + // Advance pointer + f->wr_idx = advance_pointer(f, w, n); + + _ff_unlock(f->mutex_wr); + + return n; +} + +static uint16_t _tu_fifo_read_n(tu_fifo_t* f, void * buffer, uint16_t n, tu_fifo_copy_mode_t copy_mode) +{ + _ff_lock(f->mutex_rd); + + // Peek the data + // f->rd_idx might get modified in case of an overflow so we can not use a local variable + n = _tu_fifo_peek_n(f, buffer, n, f->wr_idx, f->rd_idx, copy_mode); + + // Advance read pointer + f->rd_idx = advance_pointer(f, f->rd_idx, n); + + _ff_unlock(f->mutex_rd); + return n; +} + +/******************************************************************************/ +/*! + @brief Get number of items in FIFO. + + As this function only reads the read and write pointers once, this function is + reentrant and thus thread and ISR save without any mutexes. In case an + overflow occurred, this function return f.depth at maximum. Overflows are + checked and corrected for in the read functions! + + @param[in] f + Pointer to the FIFO buffer to manipulate + + @returns Number of items in FIFO + */ +/******************************************************************************/ +uint16_t tu_fifo_count(tu_fifo_t* f) +{ + return tu_min16(_tu_fifo_count(f, f->wr_idx, f->rd_idx), f->depth); +} + +/******************************************************************************/ +/*! + @brief Check if FIFO is empty. + + As this function only reads the read and write pointers once, this function is + reentrant and thus thread and ISR save without any mutexes. + + @param[in] f + Pointer to the FIFO buffer to manipulate + + @returns Number of items in FIFO + */ +/******************************************************************************/ +bool tu_fifo_empty(tu_fifo_t* f) +{ + return _tu_fifo_empty(f->wr_idx, f->rd_idx); +} + +/******************************************************************************/ +/*! + @brief Check if FIFO is full. + + As this function only reads the read and write pointers once, this function is + reentrant and thus thread and ISR save without any mutexes. + + @param[in] f + Pointer to the FIFO buffer to manipulate + + @returns Number of items in FIFO + */ +/******************************************************************************/ +bool tu_fifo_full(tu_fifo_t* f) +{ + return _tu_fifo_full(f, f->wr_idx, f->rd_idx); +} + +/******************************************************************************/ +/*! + @brief Get remaining space in FIFO. + + As this function only reads the read and write pointers once, this function is + reentrant and thus thread and ISR save without any mutexes. + + @param[in] f + Pointer to the FIFO buffer to manipulate + + @returns Number of items in FIFO + */ +/******************************************************************************/ +uint16_t tu_fifo_remaining(tu_fifo_t* f) +{ + return _tu_fifo_remaining(f, f->wr_idx, f->rd_idx); +} + +/******************************************************************************/ +/*! + @brief Check if overflow happened. + + BE AWARE - THIS FUNCTION MIGHT NOT GIVE A CORRECT ANSWERE IN CASE WRITE POINTER "OVERFLOWS" + Only one overflow is allowed for this function to work e.g. if depth = 100, you must not + write more than 2*depth-1 items in one rush without updating write pointer. Otherwise + write pointer wraps and your pointer states are messed up. This can only happen if you + use DMAs, write functions do not allow such an error. Avoid such nasty things! + + All reading functions (read, peek) check for overflows and correct read pointer on their own such + that latest items are read. + If required (e.g. for DMA use) you can also correct the read pointer by + tu_fifo_correct_read_pointer(). + + @param[in] f + Pointer to the FIFO buffer to manipulate + + @returns True if overflow happened + */ +/******************************************************************************/ +bool tu_fifo_overflowed(tu_fifo_t* f) +{ + return _tu_fifo_overflowed(f, f->wr_idx, f->rd_idx); +} + +// Only use in case tu_fifo_overflow() returned true! +void tu_fifo_correct_read_pointer(tu_fifo_t* f) +{ + _ff_lock(f->mutex_rd); + _tu_fifo_correct_read_pointer(f, f->wr_idx); + _ff_unlock(f->mutex_rd); +} + +/******************************************************************************/ +/*! + @brief Read one element out of the buffer. + + This function will return the element located at the array index of the + read pointer, and then increment the read pointer index. + This function checks for an overflow and corrects read pointer if required. + + @param[in] f + Pointer to the FIFO buffer to manipulate + @param[in] buffer + Pointer to the place holder for data read from the buffer + + @returns TRUE if the queue is not empty + */ +/******************************************************************************/ +bool tu_fifo_read(tu_fifo_t* f, void * buffer) +{ + _ff_lock(f->mutex_rd); + + // Peek the data + // f->rd_idx might get modified in case of an overflow so we can not use a local variable + bool ret = _tu_fifo_peek(f, buffer, f->wr_idx, f->rd_idx); + + // Advance pointer + f->rd_idx = advance_pointer(f, f->rd_idx, ret); + + _ff_unlock(f->mutex_rd); + return ret; +} + +/******************************************************************************/ +/*! + @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. + + @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(tu_fifo_t* f, void * buffer, uint16_t n) +{ + return _tu_fifo_read_n(f, buffer, n, TU_FIFO_COPY_INC); +} + +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); +} + +/******************************************************************************/ +/*! + @brief Read one item without removing it from the FIFO. + This function checks for an overflow and corrects read pointer if required. + + @param[in] f + Pointer to the FIFO buffer to manipulate + @param[in] offset + Position to read from in the FIFO buffer with respect to read pointer + @param[in] p_buffer + Pointer to the place holder for data read from the buffer + + @returns TRUE if the queue is not empty + */ +/******************************************************************************/ +bool tu_fifo_peek(tu_fifo_t* f, void * p_buffer) +{ + _ff_lock(f->mutex_rd); + bool ret = _tu_fifo_peek(f, p_buffer, f->wr_idx, f->rd_idx); + _ff_unlock(f->mutex_rd); + return ret; +} + +/******************************************************************************/ +/*! + @brief Read n items without removing it from the FIFO + This function checks for an overflow and corrects read pointer if required. + + @param[in] f + Pointer to the FIFO buffer to manipulate + @param[in] p_buffer + Pointer to the place holder for data read from the buffer + @param[in] n + Number of items to peek + + @returns Number of bytes written to p_buffer + */ +/******************************************************************************/ +uint16_t tu_fifo_peek_n(tu_fifo_t* f, void * p_buffer, uint16_t n) +{ + _ff_lock(f->mutex_rd); + uint16_t ret = _tu_fifo_peek_n(f, p_buffer, n, f->wr_idx, f->rd_idx, TU_FIFO_COPY_INC); + _ff_unlock(f->mutex_rd); + return ret; +} + +/******************************************************************************/ +/*! + @brief Write one element into the buffer. + + This function will write one element into the array index specified by + the write pointer and increment the write index. + + @param[in] f + Pointer to the FIFO buffer to manipulate + @param[in] data + The byte to add to the FIFO + + @returns TRUE if the data was written to the FIFO (overwrittable + FIFO will always return TRUE) + */ +/******************************************************************************/ +bool tu_fifo_write(tu_fifo_t* f, const void * data) +{ + _ff_lock(f->mutex_wr); + + bool ret; + uint16_t const w = f->wr_idx; + + if ( _tu_fifo_full(f, w, f->rd_idx) && !f->overwritable ) + { + ret = false; + }else + { + uint16_t wRel = get_relative_pointer(f, w); + + // Write data + _ff_push(f, data, wRel); + + // Advance pointer + f->wr_idx = advance_pointer(f, w, 1); + + ret = true; + } + + _ff_unlock(f->mutex_wr); + + return ret; +} + +/******************************************************************************/ +/*! + @brief This function will write n elements into the array index specified by + the write pointer and increment the write index. + + @param[in] f + Pointer to the FIFO buffer to manipulate + @param[in] data + The pointer to data to add to the FIFO + @param[in] count + Number of element + @return Number of written elements + */ +/******************************************************************************/ +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); +} + +/******************************************************************************/ +/*! + @brief This function will write n elements into the array index specified by + the write pointer and increment the write index. The source address will + not be incremented which is useful for reading from registers. + + @param[in] f + Pointer to the FIFO buffer to manipulate + @param[in] data + The pointer to data to add to the FIFO + @param[in] count + Number of element + @return Number of written elements + */ +/******************************************************************************/ +uint16_t tu_fifo_write_n_const_addr_full_words(tu_fifo_t* f, const void * data, uint16_t n) +{ + return _tu_fifo_write_n(f, data, n, TU_FIFO_COPY_CST_FULL_WORDS); +} + +/******************************************************************************/ +/*! + @brief Clear the fifo read and write pointers + + @param[in] f + Pointer to the FIFO buffer to manipulate + */ +/******************************************************************************/ +bool tu_fifo_clear(tu_fifo_t *f) +{ + _ff_lock(f->mutex_wr); + _ff_lock(f->mutex_rd); + + f->rd_idx = f->wr_idx = 0; + f->max_pointer_idx = 2*f->depth-1; + f->non_used_index_space = UINT16_MAX - f->max_pointer_idx; + + _ff_unlock(f->mutex_wr); + _ff_unlock(f->mutex_rd); + return true; +} + +/******************************************************************************/ +/*! + @brief Change the fifo mode to overwritable or not overwritable + + @param[in] f + Pointer to the FIFO buffer to manipulate + @param[in] overwritable + Overwritable mode the fifo is set to + */ +/******************************************************************************/ +bool tu_fifo_set_overwritable(tu_fifo_t *f, bool overwritable) +{ + _ff_lock(f->mutex_wr); + _ff_lock(f->mutex_rd); + + f->overwritable = overwritable; + + _ff_unlock(f->mutex_wr); + _ff_unlock(f->mutex_rd); + + return true; +} + +/******************************************************************************/ +/*! + @brief Advance write pointer - intended to be used in combination with DMA. + It is possible to fill the FIFO by use of a DMA in circular mode. Within + DMA ISRs you may update the write pointer to be able to read from the FIFO. + As long as the DMA is the only process writing into the FIFO this is safe + to use. + + USE WITH CARE - WE DO NOT CONDUCT SAFTY CHECKS HERE! + + @param[in] f + Pointer to the FIFO buffer to manipulate + @param[in] n + Number of items the write pointer moves forward + */ +/******************************************************************************/ +void tu_fifo_advance_write_pointer(tu_fifo_t *f, uint16_t n) +{ + f->wr_idx = advance_pointer(f, f->wr_idx, n); +} + +/******************************************************************************/ +/*! + @brief Advance read pointer - intended to be used in combination with DMA. + It is possible to read from the FIFO by use of a DMA in linear mode. Within + DMA ISRs you may update the read pointer to be able to again write into the + FIFO. As long as the DMA is the only process reading from the FIFO this is + safe to use. + + USE WITH CARE - WE DO NOT CONDUCT SAFTY CHECKS HERE! + + @param[in] f + Pointer to the FIFO buffer to manipulate + @param[in] n + Number of items the read pointer moves forward + */ +/******************************************************************************/ +void tu_fifo_advance_read_pointer(tu_fifo_t *f, uint16_t n) +{ + f->rd_idx = advance_pointer(f, f->rd_idx, n); +} + +/******************************************************************************/ +/*! + @brief Get read info + + Returns the length and pointer from which bytes can be read in a linear manner. + This is of major interest for DMA transmissions. If returned length is zero the + corresponding pointer is invalid. + The read pointer does NOT get advanced, use tu_fifo_advance_read_pointer() to + do so! + @param[in] f + Pointer to FIFO + @param[out] *info + Pointer to struct which holds the desired infos + */ +/******************************************************************************/ +void tu_fifo_get_read_info(tu_fifo_t *f, tu_fifo_buffer_info_t *info) +{ + // Operate on temporary values in case they change in between + uint16_t w = f->wr_idx, r = f->rd_idx; + + uint16_t cnt = _tu_fifo_count(f, w, r); + + // Check overflow and correct if required - may happen in case a DMA wrote too fast + if (cnt > f->depth) + { + _ff_lock(f->mutex_rd); + _tu_fifo_correct_read_pointer(f, w); + _ff_unlock(f->mutex_rd); + r = f->rd_idx; + cnt = f->depth; + } + + // Check if fifo is empty + if (cnt == 0) + { + info->len_lin = 0; + info->len_wrap = 0; + info->ptr_lin = NULL; + info->ptr_wrap = NULL; + return; + } + + // Get relative pointers + w = get_relative_pointer(f, w); + r = get_relative_pointer(f, r); + + // Copy pointer to buffer to start reading from + info->ptr_lin = &f->buffer[r]; + + // Check if there is a wrap around necessary + if (w > r) { + // Non wrapping case + info->len_lin = cnt; + info->len_wrap = 0; + info->ptr_wrap = NULL; + } + else + { + info->len_lin = f->depth - r; // Also the case if FIFO was full + info->len_wrap = cnt - info->len_lin; + info->ptr_wrap = f->buffer; + } +} + +/******************************************************************************/ +/*! + @brief Get linear write info + + Returns the length and pointer to which bytes can be written into FIFO in a linear manner. + This is of major interest for DMA transmissions not using circular mode. If a returned length is zero the + corresponding pointer is invalid. The returned lengths summed up are the currently free space in the FIFO. + The write pointer does NOT get advanced, use tu_fifo_advance_write_pointer() to do so! + TAKE CARE TO NOT OVERFLOW THE BUFFER MORE THAN TWO TIMES THE FIFO DEPTH - IT CAN NOT RECOVERE OTHERWISE! + @param[in] f + Pointer to FIFO + @param[out] *info + Pointer to struct which holds the desired infos + */ +/******************************************************************************/ +void tu_fifo_get_write_info(tu_fifo_t *f, tu_fifo_buffer_info_t *info) +{ + uint16_t w = f->wr_idx, r = f->rd_idx; + uint16_t free = _tu_fifo_remaining(f, w, r); + + if (free == 0) + { + info->len_lin = 0; + info->len_wrap = 0; + info->ptr_lin = NULL; + info->ptr_wrap = NULL; + return; + } + + // Get relative pointers + w = get_relative_pointer(f, w); + r = get_relative_pointer(f, r); + + // Copy pointer to buffer to start writing to + info->ptr_lin = &f->buffer[w]; + + if (w < r) + { + // Non wrapping case + info->len_lin = r-w; + info->len_wrap = 0; + info->ptr_wrap = NULL; + } + else + { + info->len_lin = f->depth - w; + info->len_wrap = free - info->len_lin; // Remaining length - n already was limited to free or FIFO depth + info->ptr_wrap = f->buffer; // Always start of buffer + } +} diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_fifo.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_fifo.h new file mode 100644 index 000000000..18db289a1 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_fifo.h @@ -0,0 +1,151 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * Copyright (c) 2020 Reinhard Panhuber - rework to unmasked pointers + * + * 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_FIFO_H_ +#define _TUSB_FIFO_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +// Due to the use of unmasked pointers, this FIFO does not suffer from loosing +// one item slice. Furthermore, write and read operations are completely +// decoupled as write and read functions do not modify a common state. Henceforth, +// writing or reading from the FIFO within an ISR is safe as long as no other +// process (thread or ISR) interferes. +// Also, this FIFO is ready to be used in combination with a DMA as the write and +// read pointers can be updated from within a DMA ISR. Overflows are detectable +// within a certain number (see tu_fifo_overflow()). + +#include "common/tusb_common.h" + +// mutex is only needed for RTOS +// for OS None, we don't get preempted +#define CFG_FIFO_MUTEX (CFG_TUSB_OS != OPT_OS_NONE) + +#if CFG_FIFO_MUTEX +#include "osal/osal.h" +#define tu_fifo_mutex_t osal_mutex_t +#endif + +typedef struct +{ + uint8_t* buffer ; ///< buffer pointer + uint16_t depth ; ///< max items + uint16_t item_size ; ///< size of each item + bool overwritable ; + + uint16_t non_used_index_space ; ///< required for non-power-of-two buffer length + uint16_t max_pointer_idx ; ///< maximum absolute pointer index + + volatile uint16_t wr_idx ; ///< write pointer + volatile uint16_t rd_idx ; ///< read pointer + +#if CFG_FIFO_MUTEX + tu_fifo_mutex_t mutex_wr; + tu_fifo_mutex_t mutex_rd; +#endif + +} tu_fifo_t; + +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) \ +{ \ + .buffer = _buffer, \ + .depth = _depth, \ + .item_size = sizeof(_type), \ + .overwritable = _overwritable, \ + .non_used_index_space = UINT16_MAX - (2*(_depth)-1), \ + .max_pointer_idx = 2*(_depth)-1, \ +} + +#define TU_FIFO_DEF(_name, _depth, _type, _overwritable) \ + 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 CFG_FIFO_MUTEX +TU_ATTR_ALWAYS_INLINE static inline +void tu_fifo_config_mutex(tu_fifo_t *f, tu_fifo_mutex_t write_mutex_hdl, tu_fifo_mutex_t read_mutex_hdl) +{ + f->mutex_wr = write_mutex_hdl; + f->mutex_rd = read_mutex_hdl; +} +#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); +uint16_t tu_fifo_write_n_const_addr_full_words (tu_fifo_t* f, const void * data, uint16_t n); + +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); +uint16_t tu_fifo_read_n_const_addr_full_words (tu_fifo_t* f, void * buffer, uint16_t n); + +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); + +uint16_t tu_fifo_count (tu_fifo_t* f); +uint16_t tu_fifo_remaining (tu_fifo_t* f); +bool tu_fifo_empty (tu_fifo_t* f); +bool tu_fifo_full (tu_fifo_t* f); +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) +{ + return f->depth; +} + +// Pointer modifications intended to be used in combinations with DMAs. +// USE WITH CARE - NO SAFTY CHECKS CONDUCTED HERE! NOT MUTEX PROTECTED! +void tu_fifo_advance_write_pointer(tu_fifo_t *f, uint16_t n); +void tu_fifo_advance_read_pointer (tu_fifo_t *f, uint16_t n); + +// If you want to read/write from/to the FIFO by use of a DMA, you may need to conduct two copies +// to handle a possible wrapping part. These functions deliver a pointer to start +// reading/writing from/to and a valid linear length along which no wrap occurs. +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 + +#endif /* _TUSB_FIFO_H_ */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_timeout.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_timeout.h new file mode 100644 index 000000000..ce53955f0 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_timeout.h @@ -0,0 +1,80 @@ +/* + * 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 Group_Common Common Files + * \defgroup Group_TimeoutTimer timeout timer + * @{ */ + +#ifndef _TUSB_TIMEOUT_H_ +#define _TUSB_TIMEOUT_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + uint32_t start; + uint32_t interval; +}tu_timeout_t; + +#if 0 + +extern uint32_t tusb_hal_millis(void); + +static inline void tu_timeout_set(tu_timeout_t* tt, uint32_t msec) +{ + tt->interval = msec; + tt->start = tusb_hal_millis(); +} + +static inline bool tu_timeout_expired(tu_timeout_t* tt) +{ + return ( tusb_hal_millis() - tt->start ) >= tt->interval; +} + +// For used with periodic event to prevent drift +static inline void tu_timeout_reset(tu_timeout_t* tt) +{ + tt->start += tt->interval; +} + +static inline void tu_timeout_restart(tu_timeout_t* tt) +{ + tt->start = tusb_hal_millis(); +} + +#endif + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_TIMEOUT_H_ */ + +/** @} */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_types.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_types.h new file mode 100644 index 000000000..5b26f5aec --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_types.h @@ -0,0 +1,546 @@ +/* + * 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 group_usb_definitions + * \defgroup USBDef_Type USB Types + * @{ */ + +#ifndef _TUSB_TYPES_H_ +#define _TUSB_TYPES_H_ + +#include +#include +#include "tusb_compiler.h" + +#ifdef __cplusplus + extern "C" { +#endif + +/*------------------------------------------------------------------*/ +/* CONSTANTS + *------------------------------------------------------------------*/ + +/// defined base on EHCI specs value for Endpoint Speed +typedef enum +{ + TUSB_SPEED_FULL = 0, + TUSB_SPEED_LOW = 1, + TUSB_SPEED_HIGH = 2, + TUSB_SPEED_INVALID = 0xff, +}tusb_speed_t; + +/// defined base on USB Specs Endpoint's bmAttributes +typedef enum +{ + TUSB_XFER_CONTROL = 0 , + TUSB_XFER_ISOCHRONOUS , + TUSB_XFER_BULK , + TUSB_XFER_INTERRUPT +}tusb_xfer_type_t; + +typedef enum +{ + TUSB_DIR_OUT = 0, + TUSB_DIR_IN = 1, + + TUSB_DIR_IN_MASK = 0x80 +}tusb_dir_t; + +/// Isochronous End Point Attributes +typedef enum +{ + TUSB_ISO_EP_ATT_NO_SYNC = 0x00, + TUSB_ISO_EP_ATT_ASYNCHRONOUS = 0x04, + TUSB_ISO_EP_ATT_ADAPTIVE = 0x08, + TUSB_ISO_EP_ATT_SYNCHRONOUS = 0x0C, + 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; + +/// USB Descriptor Types +typedef enum +{ + TUSB_DESC_DEVICE = 0x01, + TUSB_DESC_CONFIGURATION = 0x02, + TUSB_DESC_STRING = 0x03, + TUSB_DESC_INTERFACE = 0x04, + TUSB_DESC_ENDPOINT = 0x05, + TUSB_DESC_DEVICE_QUALIFIER = 0x06, + TUSB_DESC_OTHER_SPEED_CONFIG = 0x07, + TUSB_DESC_INTERFACE_POWER = 0x08, + TUSB_DESC_OTG = 0x09, + TUSB_DESC_DEBUG = 0x0A, + TUSB_DESC_INTERFACE_ASSOCIATION = 0x0B, + + TUSB_DESC_BOS = 0x0F, + TUSB_DESC_DEVICE_CAPABILITY = 0x10, + + TUSB_DESC_FUNCTIONAL = 0x21, + + // Class Specific Descriptor + TUSB_DESC_CS_DEVICE = 0x21, + TUSB_DESC_CS_CONFIGURATION = 0x22, + TUSB_DESC_CS_STRING = 0x23, + TUSB_DESC_CS_INTERFACE = 0x24, + TUSB_DESC_CS_ENDPOINT = 0x25, + + TUSB_DESC_SUPERSPEED_ENDPOINT_COMPANION = 0x30, + TUSB_DESC_SUPERSPEED_ISO_ENDPOINT_COMPANION = 0x31 +}tusb_desc_type_t; + +typedef enum +{ + TUSB_REQ_GET_STATUS = 0 , + TUSB_REQ_CLEAR_FEATURE = 1 , + TUSB_REQ_RESERVED = 2 , + TUSB_REQ_SET_FEATURE = 3 , + TUSB_REQ_RESERVED2 = 4 , + TUSB_REQ_SET_ADDRESS = 5 , + TUSB_REQ_GET_DESCRIPTOR = 6 , + TUSB_REQ_SET_DESCRIPTOR = 7 , + TUSB_REQ_GET_CONFIGURATION = 8 , + TUSB_REQ_SET_CONFIGURATION = 9 , + TUSB_REQ_GET_INTERFACE = 10 , + TUSB_REQ_SET_INTERFACE = 11 , + TUSB_REQ_SYNCH_FRAME = 12 +}tusb_request_code_t; + +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; + +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 +{ + TUSB_REQ_RCPT_DEVICE =0, + TUSB_REQ_RCPT_INTERFACE, + TUSB_REQ_RCPT_ENDPOINT, + TUSB_REQ_RCPT_OTHER +} tusb_request_recipient_t; + +// https://www.usb.org/defined-class-codes +typedef enum +{ + TUSB_CLASS_UNSPECIFIED = 0 , + TUSB_CLASS_AUDIO = 1 , + TUSB_CLASS_CDC = 2 , + TUSB_CLASS_HID = 3 , + TUSB_CLASS_RESERVED_4 = 4 , + TUSB_CLASS_PHYSICAL = 5 , + TUSB_CLASS_IMAGE = 6 , + TUSB_CLASS_PRINTER = 7 , + TUSB_CLASS_MSC = 8 , + TUSB_CLASS_HUB = 9 , + TUSB_CLASS_CDC_DATA = 10 , + TUSB_CLASS_SMART_CARD = 11 , + TUSB_CLASS_RESERVED_12 = 12 , + TUSB_CLASS_CONTENT_SECURITY = 13 , + TUSB_CLASS_VIDEO = 14 , + TUSB_CLASS_PERSONAL_HEALTHCARE = 15 , + TUSB_CLASS_AUDIO_VIDEO = 16 , + + TUSB_CLASS_DIAGNOSTIC = 0xDC , + TUSB_CLASS_WIRELESS_CONTROLLER = 0xE0 , + TUSB_CLASS_MISC = 0xEF , + TUSB_CLASS_APPLICATION_SPECIFIC = 0xFE , + TUSB_CLASS_VENDOR_SPECIFIC = 0xFF +}tusb_class_code_t; + +typedef enum +{ + MISC_SUBCLASS_COMMON = 2 +}misc_subclass_type_t; + +typedef enum +{ + MISC_PROTOCOL_IAD = 1 +}misc_protocol_type_t; + +typedef enum +{ + APP_SUBCLASS_USBTMC = 0x03, + APP_SUBCLASS_DFU_RUNTIME = 0x01 +} app_subclass_type_t; + +typedef enum +{ + DEVICE_CAPABILITY_WIRELESS_USB = 0x01, + DEVICE_CAPABILITY_USB20_EXTENSION = 0x02, + DEVICE_CAPABILITY_SUPERSPEED_USB = 0x03, + DEVICE_CAPABILITY_CONTAINER_id = 0x04, + DEVICE_CAPABILITY_PLATFORM = 0x05, + DEVICE_CAPABILITY_POWER_DELIVERY = 0x06, + DEVICE_CAPABILITY_BATTERY_INFO = 0x07, + DEVICE_CAPABILITY_PD_CONSUMER_PORT = 0x08, + DEVICE_CAPABILITY_PD_PROVIDER_PORT = 0x09, + DEVICE_CAPABILITY_SUPERSPEED_PLUS = 0x0A, + DEVICE_CAPABILITY_PRECESION_TIME_MEASUREMENT = 0x0B, + DEVICE_CAPABILITY_WIRELESS_USB_EXT = 0x0C, + DEVICE_CAPABILITY_BILLBOARD = 0x0D, + DEVICE_CAPABILITY_AUTHENTICATION = 0x0E, + DEVICE_CAPABILITY_BILLBOARD_EX = 0x0F, + DEVICE_CAPABILITY_CONFIGURATION_SUMMARY = 0x10 +}device_capability_type_t; + +enum { + TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP = TU_BIT(5), + TUSB_DESC_CONFIG_ATT_SELF_POWERED = TU_BIT(6), +}; + +#define TUSB_DESC_CONFIG_POWER_MA(x) ((x)/2) + +/// Device State TODO remove +typedef enum +{ + TUSB_DEVICE_STATE_UNPLUG = 0 , + TUSB_DEVICE_STATE_CONFIGURED , + TUSB_DEVICE_STATE_SUSPENDED , +}tusb_device_state_t; + +typedef enum +{ + XFER_RESULT_SUCCESS, + XFER_RESULT_FAILED, + XFER_RESULT_STALLED, +}xfer_result_t; + +enum // TODO remove +{ + DESC_OFFSET_LEN = 0, + DESC_OFFSET_TYPE = 1 +}; + +enum +{ + INTERFACE_INVALID_NUMBER = 0xff +}; + + +typedef enum +{ + MS_OS_20_SET_HEADER_DESCRIPTOR = 0x00, + MS_OS_20_SUBSET_HEADER_CONFIGURATION = 0x01, + MS_OS_20_SUBSET_HEADER_FUNCTION = 0x02, + MS_OS_20_FEATURE_COMPATBLE_ID = 0x03, + MS_OS_20_FEATURE_REG_PROPERTY = 0x04, + MS_OS_20_FEATURE_MIN_RESUME_TIME = 0x05, + MS_OS_20_FEATURE_MODEL_ID = 0x06, + MS_OS_20_FEATURE_CCGP_DEVICE = 0x07, + MS_OS_20_FEATURE_VENDOR_REVISION = 0x08 +} microsoft_os_20_type_t; + +enum +{ + CONTROL_STAGE_SETUP, + CONTROL_STAGE_DATA, + CONTROL_STAGE_ACK +}; + +//--------------------------------------------------------------------+ +// USB Descriptors +//--------------------------------------------------------------------+ + +// Start of all packed definitions for compiler without per-type packed +TU_ATTR_PACKED_BEGIN +TU_ATTR_BIT_FIELD_ORDER_BEGIN + +/// USB Device Descriptor +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. + + 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 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). + uint16_t idProduct ; ///< Product ID (assigned by the manufacturer). + uint16_t bcdDevice ; ///< Device release number in binary-coded decimal. + uint8_t iManufacturer ; ///< Index of string descriptor describing manufacturer. + uint8_t iProduct ; ///< Index of string descriptor describing product. + uint8_t iSerialNumber ; ///< Index of string descriptor describing the device's serial number. + + uint8_t bNumConfigurations ; ///< Number of possible configurations. +} tusb_desc_device_t; + +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 +{ + 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 + uint8_t bNumDeviceCaps ; ///< Number of device capability descriptors in the BOS +} tusb_desc_bos_t; + +TU_VERIFY_STATIC( sizeof(tusb_desc_bos_t) == 5, "size is not correct"); + +/// USB Configuration Descriptor +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. + + uint8_t bNumInterfaces ; ///< Number of interfaces supported by this configuration + uint8_t bConfigurationValue ; ///< Value to use as an argument to the SetConfiguration() request to select this configuration. + uint8_t iConfiguration ; ///< Index of string descriptor describing this configuration + uint8_t bmAttributes ; ///< Configuration characteristics \n D7: Reserved (set to one)\n D6: Self-powered \n D5: Remote Wakeup \n D4...0: Reserved (reset to zero) \n D7 is reserved and must be set to one for historical reasons. \n A device configuration that uses power from the bus and a local source reports a non-zero value in bMaxPower to indicate the amount of bus power required and sets D6. The actual power source at runtime may be determined using the GetStatus(DEVICE) request (see USB 2.0 spec Section 9.4.5). \n If a device configuration supports remote wakeup, D5 is set to one. + uint8_t bMaxPower ; ///< Maximum power consumption of the USB device from the bus in this specific configuration when the device is fully operational. Expressed in 2 mA units (i.e., 50 = 100 mA). +} tusb_desc_configuration_t; + +TU_VERIFY_STATIC( sizeof(tusb_desc_configuration_t) == 9, "size is not correct"); + +/// USB Interface Descriptor +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; ///< Size of this descriptor in bytes + uint8_t bDescriptorType ; ///< INTERFACE Descriptor Type + + uint8_t bInterfaceNumber ; ///< Number of this interface. Zero-based value identifying the index in the array of concurrent interfaces supported by this configuration. + uint8_t bAlternateSetting ; ///< Value used to select this alternate setting for the interface identified in the prior field + uint8_t bNumEndpoints ; ///< Number of endpoints used by this interface (excluding endpoint zero). If this value is zero, this interface only uses the Default Control Pipe. + uint8_t bInterfaceClass ; ///< Class code (assigned by the USB-IF). \li A value of zero is reserved for future standardization. \li If this field is set to FFH, the interface class is vendor-specific. \li All other values are reserved for assignment by the USB-IF. + uint8_t bInterfaceSubClass ; ///< Subclass code (assigned by the USB-IF). \n These codes are qualified by the value of the bInterfaceClass field. \li If the bInterfaceClass field is reset to zero, this field must also be reset to zero. \li If the bInterfaceClass field is not set to FFH, all values are reserved for assignment by the USB-IF. + uint8_t bInterfaceProtocol ; ///< Protocol code (assigned by the USB). \n These codes are qualified by the value of the bInterfaceClass and the bInterfaceSubClass fields. If an interface supports class-specific requests, 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 a class-specific protocol on this interface. \li If this field is set to FFH, the device uses a vendor-specific protocol for this interface. + uint8_t iInterface ; ///< Index of string descriptor describing this interface +} tusb_desc_interface_t; + +TU_VERIFY_STATIC( sizeof(tusb_desc_interface_t) == 9, "size is not correct"); + +/// USB Endpoint Descriptor +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength ; // Size of this descriptor in bytes + uint8_t bDescriptorType ; // ENDPOINT Descriptor Type + + uint8_t bEndpointAddress ; // The address of the endpoint + + struct TU_ATTR_PACKED { + uint8_t xfer : 2; // Control, ISO, Bulk, Interrupt + uint8_t sync : 2; // None, Asynchronous, Adaptive, Synchronous + uint8_t usage : 2; // Data, Feedback, Implicit feedback + uint8_t : 2; + } bmAttributes; + + uint16_t wMaxPacketSize ; // Bit 10..0 : max packet size, bit 12..11 additional transaction per highspeed micro-frame + uint8_t bInterval ; // Polling interval, in frames or microframes depending on the operating speed +} tusb_desc_endpoint_t; + +TU_VERIFY_STATIC( sizeof(tusb_desc_endpoint_t) == 7, "size is not correct"); + +/// USB Other Speed Configuration Descriptor +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 + + uint8_t bNumInterfaces ; ///< Number of interfaces supported by this speed configuration + uint8_t bConfigurationValue ; ///< Value to use to select configuration + uint8_t iConfiguration ; ///< Index of string descriptor + uint8_t bmAttributes ; ///< Same as Configuration descriptor + uint8_t bMaxPower ; ///< Same as Configuration descriptor +} tusb_desc_other_speed_t; + +/// USB Device Qualifier Descriptor +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) + + uint8_t bDeviceClass ; ///< Class Code + uint8_t bDeviceSubClass ; ///< SubClass Code + uint8_t bDeviceProtocol ; ///< Protocol Code + + uint8_t bMaxPacketSize0 ; ///< Maximum packet size for other speed + uint8_t bNumConfigurations ; ///< Number of Other-speed Configurations + uint8_t bReserved ; ///< Reserved for future use, must be zero +} tusb_desc_device_qualifier_t; + +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 +{ + uint8_t bLength ; ///< Size of descriptor + uint8_t bDescriptorType ; ///< Other_speed_Configuration Type + + uint8_t bFirstInterface ; ///< Index of the first associated interface. + uint8_t bInterfaceCount ; ///< Total number of associated interfaces. + + uint8_t bFunctionClass ; ///< Interface class ID. + uint8_t bFunctionSubClass ; ///< Interface subclass ID. + uint8_t bFunctionProtocol ; ///< Interface protocol ID. + + uint8_t iFunction ; ///< Index of the string descriptor describing the interface association. +} tusb_desc_interface_assoc_t; + +// USB String Descriptor +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 +{ + uint8_t bLength; + uint8_t bDescriptorType ; + uint8_t bDevCapabilityType; + uint8_t bReserved; + uint8_t PlatformCapabilityUUID[16]; + uint8_t CapabilityData[]; +} tusb_desc_bos_platform_t; + +// USB WebuSB URL Descriptor +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bScheme; + char url[]; +} tusb_desc_webusb_url_t; + +// DFU Functional Descriptor +typedef struct TU_ATTR_PACKED +{ + uint8_t bLength; + uint8_t bDescriptorType; + + union { + struct TU_ATTR_PACKED { + uint8_t bitCanDnload : 1; + uint8_t bitCanUpload : 1; + uint8_t bitManifestationTolerant : 1; + uint8_t bitWillDetach : 1; + uint8_t reserved : 4; + } bmAttributes; + + uint8_t bAttributes; + }; + + uint16_t wDetachTimeOut; + uint16_t wTransferSize; + uint16_t bcdDFUVersion; +} tusb_desc_dfu_functional_t; + +/*------------------------------------------------------------------*/ +/* Types + *------------------------------------------------------------------*/ +typedef struct TU_ATTR_PACKED{ + union { + struct TU_ATTR_PACKED { + uint8_t recipient : 5; ///< Recipient type tusb_request_recipient_t. + uint8_t type : 2; ///< Request type tusb_request_type_t. + uint8_t direction : 1; ///< Direction type. tusb_dir_t + } bmRequestType_bit; + + uint8_t bmRequestType; + }; + + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; +} tusb_control_request_t; + +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 + +//--------------------------------------------------------------------+ +// Endpoint helper +//--------------------------------------------------------------------+ + +// Get direction from Endpoint address +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 +static inline uint8_t tu_edpt_number(uint8_t addr) +{ + return (uint8_t)(addr & (~TUSB_DIR_IN_MASK)); +} + +static inline uint8_t tu_edpt_addr(uint8_t num, uint8_t dir) +{ + return (uint8_t)(num | (dir ? TUSB_DIR_IN_MASK : 0)); +} + +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); +} + +//--------------------------------------------------------------------+ +// Descriptor helper +//--------------------------------------------------------------------+ +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]; +} + +static inline uint8_t tu_desc_type(void const* desc) +{ + return ((uint8_t const*) desc)[DESC_OFFSET_TYPE]; +} + +static inline uint8_t tu_desc_len(void const* desc) +{ + return ((uint8_t const*) desc)[DESC_OFFSET_LEN]; +} + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_TYPES_H_ */ + +/** @} */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_verify.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_verify.h new file mode 100644 index 000000000..8fef11dc7 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/common/tusb_verify.h @@ -0,0 +1,181 @@ +/* + * 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_VERIFY_H_ +#define TUSB_VERIFY_H_ + +#include +#include +#include "tusb_option.h" +#include "tusb_compiler.h" + +/*------------------------------------------------------------------*/ +/* This file use an advanced macro technique to mimic the default parameter + * as C++ for the sake of code simplicity. Beware of a headache macro + * manipulation that you are told to stay away. + * + * This contains macros for both VERIFY and ASSERT: + * + * VERIFY: Used when there is an error condition which is not the + * fault of the MCU. For example, bounds checking on data + * sent to the micro over USB should use this function. + * Another example is checking for buffer overflows, where + * returning from the active function causes a NAK. + * + * ASSERT: Used for error conditions that are caused by MCU firmware + * bugs. This is used to discover bugs in the code more + * quickly. One example would be adding assertions in library + * function calls to confirm a function's (untainted) + * parameters are valid. + * + * The difference in behavior is that ASSERT triggers a breakpoint while + * verify does not. + * + * #define TU_VERIFY(cond) if(cond) return false; + * #define TU_VERIFY(cond,ret) if(cond) return ret; + * + * #define TU_VERIFY_HDLR(cond,handler) if(cond) {handler; return false;} + * #define TU_VERIFY_HDLR(cond,ret,handler) if(cond) {handler; return ret;} + * + * #define TU_ASSERT(cond) if(cond) {_MESS_FAILED(); TU_BREAKPOINT(), return false;} + * #define TU_ASSERT(cond,ret) if(cond) {_MESS_FAILED(); TU_BREAKPOINT(), return ret;} + * + *------------------------------------------------------------------*/ + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// TU_VERIFY Helper +//--------------------------------------------------------------------+ + +#if CFG_TUSB_DEBUG + #include + #define _MESS_ERR(_err) tu_printf("%s %d: failed, error = %s\r\n", __func__, __LINE__, tusb_strerr[_err]) + #define _MESS_FAILED() tu_printf("%s %d: ASSERT FAILED\r\n", __func__, __LINE__) +#else + #define _MESS_ERR(_err) do {} while (0) + #define _MESS_FAILED() do {} while (0) +#endif + +// Halt CPU (breakpoint) when hitting error, only apply for Cortex M3, M4, M7, M33 +#if defined(__ARM_ARCH_7M__) || defined (__ARM_ARCH_7EM__) || defined(__ARM_ARCH_8M_MAIN__) + #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) + #define TU_BREAKPOINT() do { __asm("ebreak\n"); } while(0) + +#else + #define TU_BREAKPOINT() do {} while (0) +#endif + +/*------------------------------------------------------------------*/ +/* Macro Generator + *------------------------------------------------------------------*/ + +// Helper to implement optional parameter for TU_VERIFY Macro family +#define GET_3RD_ARG(arg1, arg2, arg3, ...) arg3 +#define GET_4TH_ARG(arg1, arg2, arg3, arg4, ...) arg4 + +/*------------- Generator for TU_VERIFY and TU_VERIFY_HDLR -------------*/ +#define TU_VERIFY_DEFINE(_cond, _handler, _ret) do \ +{ \ + if ( !(_cond) ) { _handler; return _ret; } \ +} while(0) + +/*------------------------------------------------------------------*/ +/* TU_VERIFY + * - TU_VERIFY_1ARGS : return false if failed + * - TU_VERIFY_2ARGS : return provided value if failed + *------------------------------------------------------------------*/ +#define TU_VERIFY_1ARGS(_cond) TU_VERIFY_DEFINE(_cond, , false) +#define TU_VERIFY_2ARGS(_cond, _ret) TU_VERIFY_DEFINE(_cond, , _ret) + +#define TU_VERIFY(...) GET_3RD_ARG(__VA_ARGS__, TU_VERIFY_2ARGS, TU_VERIFY_1ARGS, UNUSED)(__VA_ARGS__) + + +/*------------------------------------------------------------------*/ +/* TU_VERIFY WITH HANDLER + * - TU_VERIFY_HDLR_2ARGS : execute handler, return false if failed + * - TU_VERIFY_HDLR_3ARGS : execute handler, return provided error if failed + *------------------------------------------------------------------*/ +#define TU_VERIFY_HDLR_2ARGS(_cond, _handler) TU_VERIFY_DEFINE(_cond, _handler, false) +#define TU_VERIFY_HDLR_3ARGS(_cond, _handler, _ret) TU_VERIFY_DEFINE(_cond, _handler, _ret) + +#define TU_VERIFY_HDLR(...) GET_4TH_ARG(__VA_ARGS__, TU_VERIFY_HDLR_3ARGS, TU_VERIFY_HDLR_2ARGS,UNUSED)(__VA_ARGS__) + +/*------------------------------------------------------------------*/ +/* ASSERT + * basically TU_VERIFY with TU_BREAKPOINT() as handler + * - 1 arg : return false if failed + * - 2 arg : return error if failed + *------------------------------------------------------------------*/ +#define ASSERT_1ARGS(_cond) TU_VERIFY_DEFINE(_cond, _MESS_FAILED(); TU_BREAKPOINT(), false) +#define ASSERT_2ARGS(_cond, _ret) TU_VERIFY_DEFINE(_cond, _MESS_FAILED(); TU_BREAKPOINT(), _ret) + +#ifndef TU_ASSERT +#define TU_ASSERT(...) GET_3RD_ARG(__VA_ARGS__, ASSERT_2ARGS, ASSERT_1ARGS,UNUSED)(__VA_ARGS__) +#endif + +// TODO remove TU_ASSERT_ERR() later + +/*------------- Generator for TU_VERIFY_ERR and TU_VERIFY_ERR_HDLR -------------*/ +#define TU_VERIFY_ERR_DEF2(_error, _handler) do \ +{ \ + uint32_t _err = (uint32_t)(_error); \ + if ( 0 != _err ) { _MESS_ERR(_err); _handler; return _err; } \ +} while(0) + +#define TU_VERIFY_ERR_DEF3(_error, _handler, _ret) do \ +{ \ + uint32_t _err = (uint32_t)(_error); \ + if ( 0 != _err ) { _MESS_ERR(_err); _handler; return _ret; } \ +} while(0) + +/*------------------------------------------------------------------*/ +/* ASSERT Error + * basically TU_VERIFY Error with TU_BREAKPOINT() as handler + *------------------------------------------------------------------*/ +#define ASSERT_ERR_1ARGS(_error) TU_VERIFY_ERR_DEF2(_error, TU_BREAKPOINT()) +#define ASSERT_ERR_2ARGS(_error, _ret) TU_VERIFY_ERR_DEF3(_error, TU_BREAKPOINT(), _ret) + +#ifndef TU_ASSERT_ERR +#define TU_ASSERT_ERR(...) GET_3RD_ARG(__VA_ARGS__, ASSERT_ERR_2ARGS, ASSERT_ERR_1ARGS,UNUSED)(__VA_ARGS__) +#endif + +/*------------------------------------------------------------------*/ +/* ASSERT HDLR + *------------------------------------------------------------------*/ + +#ifdef __cplusplus + } +#endif + +#endif /* TUSB_VERIFY_H_ */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/device/dcd.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/device/dcd.h new file mode 100644 index 000000000..fbcef40d4 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/device/dcd.h @@ -0,0 +1,193 @@ +/* + * 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_DCD_H_ +#define _TUSB_DCD_H_ + +#include "common/tusb_common.h" +#include "osal/osal.h" +#include "common/tusb_fifo.h" +#include "dcd_attr.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Configuration +//--------------------------------------------------------------------+ + +#ifndef CFG_TUD_ENDPPOINT_MAX + #define CFG_TUD_ENDPPOINT_MAX DCD_ATTR_ENDPOINT_MAX +#endif + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF PROTYPES +//--------------------------------------------------------------------+ + +typedef enum +{ + DCD_EVENT_INVALID = 0, + DCD_EVENT_BUS_RESET, + DCD_EVENT_UNPLUGGED, + DCD_EVENT_SOF, + DCD_EVENT_SUSPEND, // TODO LPM Sleep L1 support + DCD_EVENT_RESUME, + + DCD_EVENT_SETUP_RECEIVED, + DCD_EVENT_XFER_COMPLETE, + + // Not an DCD event, just a convenient way to defer ISR function + USBD_EVENT_FUNC_CALL, + + DCD_EVENT_COUNT +} dcd_eventid_t; + +typedef struct TU_ATTR_ALIGNED(4) +{ + uint8_t rhport; + uint8_t event_id; + + union + { + // BUS RESET + struct { + tusb_speed_t speed; + } bus_reset; + + // SETUP_RECEIVED + tusb_control_request_t setup_received; + + // XFER_COMPLETE + struct { + uint8_t ep_addr; + uint8_t result; + uint32_t len; + }xfer_complete; + + // FUNC_CALL + struct { + void (*func) (void*); + void* param; + }func_call; + }; +} dcd_event_t; + +//TU_VERIFY_STATIC(sizeof(dcd_event_t) <= 12, "size is not correct"); + +//--------------------------------------------------------------------+ +// Controller API +//--------------------------------------------------------------------+ + +// Initialize controller to device mode +void dcd_init (uint8_t rhport); + +// Interrupt Handler +#if __GNUC__ && !defined(__ARMCC_VERSION) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wredundant-decls" +#endif +void dcd_int_handler(uint8_t rhport); +#if __GNUC__ && !defined(__ARMCC_VERSION) +#pragma GCC diagnostic pop +#endif + +// Enable device interrupt +void dcd_int_enable (uint8_t rhport); + +// Disable device interrupt +void dcd_int_disable(uint8_t rhport); + +// Receive Set Address request, mcu port must also include status IN response +void dcd_set_address(uint8_t rhport, uint8_t dev_addr); + +// Wake up host +void dcd_remote_wakeup(uint8_t rhport); + +// Connect by enabling internal pull-up resistor on D+/D- +void dcd_connect(uint8_t rhport) TU_ATTR_WEAK; + +// Disconnect by disabling internal pull-up resistor on D+/D- +void dcd_disconnect(uint8_t rhport) TU_ATTR_WEAK; + +//--------------------------------------------------------------------+ +// Endpoint API +//--------------------------------------------------------------------+ + +// Invoked when a control transfer's status stage is complete. +// May help DCD to prepare for next control transfer, this API is optional. +void dcd_edpt0_status_complete(uint8_t rhport, tusb_control_request_t const * request) TU_ATTR_WEAK; + +// Configure endpoint's registers according to descriptor +bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * desc_ep); + +// Close all non-control endpoints, cancel all pending transfers if any. +// Invoked when switching from a non-zero Configuration by SET_CONFIGURE therefore +// required for multiple configuration support. +void dcd_edpt_close_all (uint8_t rhport); + +// Close an endpoint. +// Since it is weak, caller must TU_ASSERT this function's existence before calling it. +void dcd_edpt_close (uint8_t rhport, uint8_t ep_addr) TU_ATTR_WEAK; + +// Submit a transfer, When complete dcd_event_xfer_complete() is invoked to notify the stack +bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes); + +// Submit an transfer using fifo, When complete dcd_event_xfer_complete() is invoked to notify the stack +// This API is optional, may be useful for register-based for transferring data. +bool dcd_edpt_xfer_fifo (uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes) TU_ATTR_WEAK; + +// Stall endpoint, any queuing transfer should be removed from endpoint +void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr); + +// clear stall, data toggle is also reset to DATA0 +// This API never calls with control endpoints, since it is auto cleared when receiving setup packet +void dcd_edpt_clear_stall (uint8_t rhport, uint8_t ep_addr); + +//--------------------------------------------------------------------+ +// Event API (implemented by stack) +//--------------------------------------------------------------------+ + +// Called by DCD to notify device stack +extern void dcd_event_handler(dcd_event_t const * event, bool in_isr); + +// helper to send bus signal event +extern void dcd_event_bus_signal (uint8_t rhport, dcd_eventid_t eid, bool in_isr); + +// helper to send bus reset event +extern void dcd_event_bus_reset (uint8_t rhport, tusb_speed_t speed, bool in_isr); + +// helper to send setup received +extern void dcd_event_setup_received(uint8_t rhport, uint8_t const * setup, bool in_isr); + +// helper to send transfer complete event +extern void dcd_event_xfer_complete (uint8_t rhport, uint8_t ep_addr, uint32_t xferred_bytes, uint8_t result, bool in_isr); + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_DCD_H_ */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/device/dcd_attr.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/device/dcd_attr.h new file mode 100644 index 000000000..e16a5ccca --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/device/dcd_attr.h @@ -0,0 +1,225 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021, 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_DCD_ATTR_H_ +#define TUSB_DCD_ATTR_H_ + +#include "tusb_option.h" + +// Attribute includes +// - ENDPOINT_MAX: max (logical) number of endpoint +// - ENDPOINT_EXCLUSIVE_NUMBER: endpoint number with different direction IN and OUT aren't allowed, +// e.g EP1 OUT & EP1 IN cannot exist together +// - PORT_HIGHSPEED: mask to indicate which port support highspeed mode, bit0 for port0 and so on. + +//------------- NXP -------------// +#if TU_CHECK_MCU(OPT_MCU_LPC11UXX, OPT_MCU_LPC13XX, OPT_MCU_LPC15XX) + #define DCD_ATTR_ENDPOINT_MAX 5 + +#elif TU_CHECK_MCU(OPT_MCU_LPC175X_6X, OPT_MCU_LPC177X_8X, OPT_MCU_LPC40XX) + #define DCD_ATTR_ENDPOINT_MAX 16 + +#elif TU_CHECK_MCU(OPT_MCU_LPC18XX, OPT_MCU_LPC43XX) + // TODO USB0 has 6, USB1 has 4 + #define DCD_ATTR_CONTROLLER_CHIPIDEA_HS + #define DCD_ATTR_ENDPOINT_MAX 6 + +#elif TU_CHECK_MCU(OPT_MCU_LPC51UXX) + #define DCD_ATTR_ENDPOINT_MAX 5 + +#elif TU_CHECK_MCU(OPT_MCU_LPC54XXX) + // TODO USB0 has 5, USB1 has 6 + #define DCD_ATTR_ENDPOINT_MAX 6 + +#elif TU_CHECK_MCU(OPT_MCU_LPC55XX) + // TODO USB0 has 5, USB1 has 6 + #define DCD_ATTR_ENDPOINT_MAX 6 + +#elif TU_CHECK_MCU(OPT_MCU_MIMXRT10XX) + #define DCD_ATTR_CONTROLLER_CHIPIDEA_HS + #define DCD_ATTR_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_MKL25ZXX, OPT_MCU_K32L2BXX) + #define DCD_ATTR_ENDPOINT_MAX 16 + +#elif TU_CHECK_MCU(OPT_MCU_MM32F327X) + #define DCD_ATTR_ENDPOINT_MAX 16 + +//------------- Nordic -------------// +#elif TU_CHECK_MCU(OPT_MCU_NRF5X) + // 8 CBI + 1 ISO + #define DCD_ATTR_ENDPOINT_MAX 9 + +//------------- Microchip -------------// +#elif TU_CHECK_MCU(OPT_MCU_SAMD21, OPT_MCU_SAMD51, OPT_MCU_SAME5X) || \ + TU_CHECK_MCU(OPT_MCU_SAMD11, OPT_MCU_SAML21, OPT_MCU_SAML22) + #define DCD_ATTR_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_SAMG) + #define DCD_ATTR_ENDPOINT_MAX 6 + #define DCD_ATTR_ENDPOINT_EXCLUSIVE_NUMBER + +#elif TU_CHECK_MCU(OPT_MCU_SAMX7X) + #define DCD_ATTR_ENDPOINT_MAX 10 + #define DCD_ATTR_ENDPOINT_EXCLUSIVE_NUMBER + +#elif TU_CHECK_MCU(OPT_MCU_PIC32MZ) + #define DCD_ATTR_ENDPOINT_MAX 8 + #define DCD_ATTR_ENDPOINT_EXCLUSIVE_NUMBER + +//------------- ST -------------// +#elif TU_CHECK_MCU(OPT_MCU_STM32F0) + #define DCD_ATTR_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_STM32F1) + #if defined (STM32F105x8) || defined (STM32F105xB) || defined (STM32F105xC) || \ + defined (STM32F107xB) || defined (STM32F107xC) + #define DCD_ATTR_ENDPOINT_MAX 4 + #define DCD_ATTR_DWC2_STM32 + #else + #define DCD_ATTR_ENDPOINT_MAX 8 + #endif + +#elif TU_CHECK_MCU(OPT_MCU_STM32F2) + // FS has 4 ep, HS has 5 ep + #define DCD_ATTR_ENDPOINT_MAX 6 + #define DCD_ATTR_DWC2_STM32 + +#elif TU_CHECK_MCU(OPT_MCU_STM32F3) + #define DCD_ATTR_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_STM32F4) + // For most mcu, FS has 4, HS has 6. TODO 446/469/479 HS has 9 + #define DCD_ATTR_ENDPOINT_MAX 6 + #define DCD_ATTR_DWC2_STM32 + +#elif TU_CHECK_MCU(OPT_MCU_STM32F7) + // FS has 6, HS has 9 + #define DCD_ATTR_ENDPOINT_MAX 9 + #define DCD_ATTR_DWC2_STM32 + +#elif TU_CHECK_MCU(OPT_MCU_STM32H7) + #define DCD_ATTR_ENDPOINT_MAX 9 + #define DCD_ATTR_DWC2_STM32 + +#elif TU_CHECK_MCU(OPT_MCU_STM32G4) + #define DCD_ATTR_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_STM32L0, OPT_MCU_STM32L1) + #define DCD_ATTR_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_STM32L4) + #if defined (STM32L475xx) || defined (STM32L476xx) || \ + defined (STM32L485xx) || defined (STM32L486xx) || defined (STM32L496xx) || \ + defined (STM32L4A6xx) || defined (STM32L4P5xx) || defined (STM32L4Q5xx) || \ + defined (STM32L4R5xx) || defined (STM32L4R7xx) || defined (STM32L4R9xx) || \ + defined (STM32L4S5xx) || defined (STM32L4S7xx) || defined (STM32L4S9xx) + #define DCD_ATTR_ENDPOINT_MAX 6 + #define DCD_ATTR_DWC2_STM32 + #else + #define DCD_ATTR_ENDPOINT_MAX 8 + #endif + +//------------- Sony -------------// +#elif TU_CHECK_MCU(OPT_MCU_CXD56) + #define DCD_ATTR_ENDPOINT_MAX 7 + #define DCD_ATTR_ENDPOINT_EXCLUSIVE_NUMBER + +//------------- TI -------------// +#elif TU_CHECK_MCU(OPT_MCU_MSP430x5xx) + #define DCD_ATTR_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_MSP432E4, OPT_MCU_TM4C123, OPT_MCU_TM4C129) + #define DCD_ATTR_ENDPOINT_MAX 8 + +//------------- ValentyUSB -------------// +#elif TU_CHECK_MCU(OPT_MCU_VALENTYUSB_EPTRI) + #define DCD_ATTR_ENDPOINT_MAX 16 + +//------------- Nuvoton -------------// +#elif TU_CHECK_MCU(OPT_MCU_NUC121, OPT_MCU_NUC126) + #define DCD_ATTR_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_NUC120) + #define DCD_ATTR_ENDPOINT_MAX 6 + +#elif TU_CHECK_MCU(OPT_MCU_NUC505) + #define DCD_ATTR_ENDPOINT_MAX 12 + +//------------- Espressif -------------// +#elif TU_CHECK_MCU(OPT_MCU_ESP32S2, OPT_MCU_ESP32S3) + #define DCD_ATTR_ENDPOINT_MAX 6 + +//------------- Dialog -------------// +#elif TU_CHECK_MCU(OPT_MCU_DA1469X) + #define DCD_ATTR_ENDPOINT_MAX 4 + +//------------- Raspberry Pi -------------// +#elif TU_CHECK_MCU(OPT_MCU_RP2040) + #define DCD_ATTR_ENDPOINT_MAX 16 + +//------------- Silabs -------------// +#elif TU_CHECK_MCU(OPT_MCU_EFM32GG) + #define DCD_ATTR_ENDPOINT_MAX 7 + +//------------- Renesas -------------// +#elif TU_CHECK_MCU(OPT_MCU_RX63X, OPT_MCU_RX65X, OPT_MCU_RX72N) + #define DCD_ATTR_ENDPOINT_MAX 10 + +//------------- GigaDevice -------------// +#elif TU_CHECK_MCU(OPT_MCU_GD32VF103) + #define DCD_ATTR_ENDPOINT_MAX 4 + +//------------- Broadcom -------------// +#elif TU_CHECK_MCU(OPT_MCU_BCM2711, OPT_MCU_BCM2835, OPT_MCU_BCM2837) + #define DCD_ATTR_ENDPOINT_MAX 8 + +//------------- Broadcom -------------// +#elif TU_CHECK_MCU(OPT_MCU_XMC4000) + #define DCD_ATTR_ENDPOINT_MAX 8 + +//------------- BridgeTek -------------// +#elif TU_CHECK_MCU(OPT_MCU_FT90X) + #define DCD_ATTR_ENDPOINT_MAX 8 + +#elif TU_CHECK_MCU(OPT_MCU_FT93X) + #define DCD_ATTR_ENDPOINT_MAX 16 + +//------------ Allwinner -------------// +#elif TU_CHECK_MCU(OPT_MCU_F1C100S) + #define DCD_ATTR_ENDPOINT_MAX 4 + +#else + #warning "DCD_ATTR_ENDPOINT_MAX is not defined for this MCU, default to 8" + #define DCD_ATTR_ENDPOINT_MAX 8 +#endif + +// Default to fullspeed if not defined +//#ifndef PORT_HIGHSPEED +// #define DCD_ATTR_PORT_HIGHSPEED 0x00 +//#endif + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/device/usbd.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/device/usbd.c new file mode 100644 index 000000000..448114303 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/device/usbd.c @@ -0,0 +1,1419 @@ +/* + * 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 TUSB_OPT_DEVICE_ENABLED + +#include "tusb.h" +#include "device/usbd.h" +#include "device/usbd_pvt.h" +#include "device/dcd.h" + +//--------------------------------------------------------------------+ +// USBD Configuration +//--------------------------------------------------------------------+ + +// Debug level of USBD +#define USBD_DBG 2 + +#ifndef CFG_TUD_TASK_QUEUE_SZ + #define CFG_TUD_TASK_QUEUE_SZ 16 +#endif + +//--------------------------------------------------------------------+ +// Device Data +//--------------------------------------------------------------------+ + +// Invalid driver ID in itf2drv[] ep2drv[][] mapping +enum { DRVID_INVALID = 0xFFu }; + +typedef struct +{ + struct TU_ATTR_PACKED + { + volatile uint8_t connected : 1; + volatile uint8_t addressed : 1; + volatile uint8_t suspended : 1; + + uint8_t remote_wakeup_en : 1; // enable/disable by host + 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; + + uint8_t itf2drv[16]; // map interface number to driver (0xff is invalid) + uint8_t ep2drv[CFG_TUD_ENDPPOINT_MAX][2]; // map endpoint to driver ( 0xff is invalid ) + + struct TU_ATTR_PACKED + { + volatile bool busy : 1; + volatile bool stalled : 1; + volatile bool claimed : 1; + + // TODO merge ep2drv here, 4-bit should be sufficient + }ep_status[CFG_TUD_ENDPPOINT_MAX][2]; + +}usbd_device_t; + +static usbd_device_t _usbd_dev; + +//--------------------------------------------------------------------+ +// Class Driver +//--------------------------------------------------------------------+ +#if CFG_TUSB_DEBUG >= 2 + #define DRIVER_NAME(_name) .name = _name, +#else + #define DRIVER_NAME(_name) +#endif + +// Built-in class drivers +static usbd_class_driver_t const _usbd_driver[] = +{ + #if CFG_TUD_CDC + { + DRIVER_NAME("CDC") + .init = cdcd_init, + .reset = cdcd_reset, + .open = cdcd_open, + .control_xfer_cb = cdcd_control_xfer_cb, + .xfer_cb = cdcd_xfer_cb, + .sof = NULL + }, + #endif + + #if CFG_TUD_MSC + { + DRIVER_NAME("MSC") + .init = mscd_init, + .reset = mscd_reset, + .open = mscd_open, + .control_xfer_cb = mscd_control_xfer_cb, + .xfer_cb = mscd_xfer_cb, + .sof = NULL + }, + #endif + + #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 + }, + #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 = NULL + }, + #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 + }, + #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 + }, + #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 + }, + #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 + }, + #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 + }, + #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 + }, + #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, + }, + #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 + }, + #endif +}; + +enum { BUILTIN_DRIVER_COUNT = TU_ARRAY_SIZE(_usbd_driver) }; + +// Additional class drivers implemented by application +static usbd_class_driver_t const * _app_driver = NULL; +static uint8_t _app_driver_count = 0; + +// virtually joins built-in and application drivers together. +// Application is positioned first to allow overwriting built-in ones. +static inline usbd_class_driver_t const * get_driver(uint8_t drvid) +{ + // Application drivers + if ( usbd_app_driver_get_cb ) + { + if ( drvid < _app_driver_count ) return &_app_driver[drvid]; + drvid -= _app_driver_count; + } + + // Built-in drivers + if (drvid < BUILTIN_DRIVER_COUNT) return &_usbd_driver[drvid]; + + return NULL; +} + +#define TOTAL_DRIVER_COUNT (_app_driver_count + BUILTIN_DRIVER_COUNT) + +//--------------------------------------------------------------------+ +// DCD Event +//--------------------------------------------------------------------+ + +static bool _usbd_initialized = false; + +// Event queue +// OPT_MODE_DEVICE is used by OS NONE for mutex (disable usb isr) +OSAL_QUEUE_DEF(OPT_MODE_DEVICE, _usbd_qdef, CFG_TUD_TASK_QUEUE_SZ, dcd_event_t); +static osal_queue_t _usbd_q; + +// Mutex for claiming endpoint, only needed when using with preempted RTOS +#if CFG_TUSB_OS != OPT_OS_NONE +static osal_mutex_def_t _ubsd_mutexdef; +static osal_mutex_t _usbd_mutex; +#endif + + +//--------------------------------------------------------------------+ +// Prototypes +//--------------------------------------------------------------------+ +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); + +// from usbd_control.c +void usbd_control_reset(void); +void usbd_control_set_request(tusb_control_request_t const *request); +void usbd_control_set_complete_callback( usbd_control_xfer_cb_t fp ); +bool usbd_control_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); + + +//--------------------------------------------------------------------+ +// Debug +//--------------------------------------------------------------------+ +#if CFG_TUSB_DEBUG >= 2 +static char const* const _usbd_event_str[DCD_EVENT_COUNT] = +{ + "Invalid" , + "Bus Reset" , + "Unplugged" , + "SOF" , + "Suspend" , + "Resume" , + "Setup Received" , + "Xfer Complete" , + "Func Call" +}; + +static char const* const _tusb_std_request_str[] = +{ + "Get Status" , + "Clear Feature" , + "Reserved" , + "Set Feature" , + "Reserved" , + "Set Address" , + "Get Descriptor" , + "Set Descriptor" , + "Get Configuration" , + "Set Configuration" , + "Get Interface" , + "Set Interface" , + "Synch Frame" +}; + +static char const* const _tusb_speed_str[] = { "Full", "Low", "High" }; + +// for usbd_control to print the name of control complete driver +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->control_xfer_cb == callback ) + { + TU_LOG2(" %s control complete\r\n", driver->name); + return; + } + } +} + +#endif + +//--------------------------------------------------------------------+ +// Application API +//--------------------------------------------------------------------+ +tusb_speed_t tud_speed_get(void) +{ + return (tusb_speed_t) _usbd_dev.speed; +} + +bool tud_connected(void) +{ + return _usbd_dev.connected; +} + +bool tud_mounted(void) +{ + return _usbd_dev.cfg_num ? true : false; +} + +bool tud_suspended(void) +{ + return _usbd_dev.suspended; +} + +bool tud_remote_wakeup(void) +{ + // only wake up host if this feature is supported and enabled and we are suspended + TU_VERIFY (_usbd_dev.suspended && _usbd_dev.remote_wakeup_support && _usbd_dev.remote_wakeup_en ); + dcd_remote_wakeup(TUD_OPT_RHPORT); + return true; +} + +bool tud_disconnect(void) +{ + TU_VERIFY(dcd_disconnect); + dcd_disconnect(TUD_OPT_RHPORT); + return true; +} + +bool tud_connect(void) +{ + TU_VERIFY(dcd_connect); + dcd_connect(TUD_OPT_RHPORT); + return true; +} + +//--------------------------------------------------------------------+ +// USBD Task +//--------------------------------------------------------------------+ +bool tud_inited(void) +{ + return _usbd_initialized; +} + +bool tud_init (uint8_t rhport) +{ + // skip if already initialized + if (_usbd_initialized) return _usbd_initialized; + + TU_LOG2("USBD init\r\n"); + + tu_varclr(&_usbd_dev); + +#if CFG_TUSB_OS != OPT_OS_NONE + // Init device mutex + _usbd_mutex = osal_mutex_create(&_ubsd_mutexdef); + TU_ASSERT(_usbd_mutex); +#endif + + // Init device queue & task + _usbd_q = osal_queue_create(&_usbd_qdef); + TU_ASSERT(_usbd_q); + + // Get application driver if available + 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_LOG2("%s init\r\n", driver->name); + driver->init(); + } + + // Init device controller driver + dcd_init(rhport); + dcd_int_enable(rhport); + + _usbd_initialized = true; + + return true; +} + +static void configuration_reset(uint8_t rhport) +{ + for ( uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++ ) + { + get_driver(i)->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 +} + +static void usbd_reset(uint8_t rhport) +{ + configuration_reset(rhport); + usbd_control_reset(); +} + +bool tud_task_event_ready(void) +{ + // Skip if stack is not initialized + if ( !tusb_inited() ) return false; + + return !osal_queue_empty(_usbd_q); +} + +/* USB Device Driver task + * 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) + { + application_init(); + tusb_init(); + + while(1) // the mainloop + { + application_code(); + tud_task(); // tinyusb device task + } + } + @endcode + */ +void tud_task (void) +{ + // Skip if stack is not initialized + if ( !tusb_inited() ) return; + + // Loop until there is no more events in the queue + while (1) + { + dcd_event_t event; + + if ( !osal_queue_receive(_usbd_q, &event) ) return; + +#if CFG_TUSB_DEBUG >= 2 + if (event.event_id == DCD_EVENT_SETUP_RECEIVED) TU_LOG2("\r\n"); // extra line for setup + TU_LOG2("USBD %s ", event.event_id < DCD_EVENT_COUNT ? _usbd_event_str[event.event_id] : "CORRUPTED"); +#endif + + switch ( event.event_id ) + { + case DCD_EVENT_BUS_RESET: + TU_LOG2(": %s Speed\r\n", _tusb_speed_str[event.bus_reset.speed]); + usbd_reset(event.rhport); + _usbd_dev.speed = event.bus_reset.speed; + break; + + case DCD_EVENT_UNPLUGGED: + TU_LOG2("\r\n"); + usbd_reset(event.rhport); + + // invoke callback + if (tud_umount_cb) tud_umount_cb(); + break; + + case DCD_EVENT_SETUP_RECEIVED: + TU_LOG2_VAR(&event.setup_received); + TU_LOG2("\r\n"); + + // 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 + _usbd_dev.connected = 1; + + // mark both in & out control as free + _usbd_dev.ep_status[0][TUSB_DIR_OUT].busy = false; + _usbd_dev.ep_status[0][TUSB_DIR_OUT].claimed = 0; + _usbd_dev.ep_status[0][TUSB_DIR_IN ].busy = false; + _usbd_dev.ep_status[0][TUSB_DIR_IN ].claimed = 0; + + // Process control request + if ( !process_control_request(event.rhport, &event.setup_received) ) + { + TU_LOG2(" 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; + + 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); + + TU_LOG2("on EP %02X with %u bytes\r\n", ep_addr, (unsigned int) event.xfer_complete.len); + + _usbd_dev.ep_status[epnum][ep_dir].busy = false; + _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, ); + + TU_LOG2(" %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; + + 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 ) + { + TU_LOG2(": Remote Wakeup = %u\r\n", _usbd_dev.remote_wakeup_en); + if (tud_suspend_cb) tud_suspend_cb(_usbd_dev.remote_wakeup_en); + }else + { + TU_LOG2(" Skipped\r\n"); + } + break; + + case DCD_EVENT_RESUME: + if ( _usbd_dev.connected ) + { + TU_LOG2("\r\n"); + if (tud_resume_cb) tud_resume_cb(); + }else + { + TU_LOG2(" Skipped\r\n"); + } + break; + + case DCD_EVENT_SOF: + TU_LOG2("\r\n"); + for ( uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++ ) + { + usbd_class_driver_t const * driver = get_driver(i); + if ( driver->sof ) driver->sof(event.rhport); + } + break; + + case USBD_EVENT_FUNC_CALL: + TU_LOG2("\r\n"); + if ( event.func_call.func ) event.func_call.func(event.func_call.param); + break; + + default: + TU_BREAKPOINT(); + break; + } + } +} + +//--------------------------------------------------------------------+ +// Control Request Parser & Handling +//--------------------------------------------------------------------+ + +// 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) +{ + usbd_control_set_complete_callback(driver->control_xfer_cb); + TU_LOG2(" %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) +{ + 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 ) + { + TU_VERIFY(tud_vendor_control_xfer_cb); + + usbd_control_set_complete_callback(tud_vendor_control_xfer_cb); + return tud_vendor_control_xfer_cb(rhport, CONTROL_STAGE_SETUP, p_request); + } + +#if CFG_TUSB_DEBUG >= 2 + if (TUSB_REQ_TYPE_STANDARD == p_request->bmRequestType_bit.type && p_request->bRequest <= TUSB_REQ_SYNCH_FRAME) + { + TU_LOG2(" %s", _tusb_std_request_str[p_request->bRequest]); + if (TUSB_REQ_GET_DESCRIPTOR != p_request->bRequest) TU_LOG2("\r\n"); + } +#endif + + 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 ) + { + uint8_t const itf = tu_u16_low(p_request->wIndex); + TU_VERIFY(itf < TU_ARRAY_SIZE(_usbd_dev.itf2drv)); + + usbd_class_driver_t const * driver = get_driver(_usbd_dev.itf2drv[itf]); + TU_VERIFY(driver); + + // forward to class driver: "non-STD request to Interface" + return invoke_class_control(rhport, driver, p_request); + } + + if ( TUSB_REQ_TYPE_STANDARD != p_request->bmRequestType_bit.type ) + { + // Non standard request is not supported + TU_BREAKPOINT(); + return false; + } + + 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 + // Therefore DCD must take full responsibility to response and include zlp status packet if needed. + usbd_control_set_request(p_request); // set request since DCD has no access to tud_control_status() API + dcd_set_address(rhport, (uint8_t) p_request->wValue); + // skip tud_control_status() + _usbd_dev.addressed = 1; + break; + + 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: + { + 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 ) + { + // already configured: need to clear all endpoints and driver first + TU_LOG(USBD_DBG, " Clear current Configuration (%u) before switching\r\n", _usbd_dev.cfg_num); + + // close all non-control endpoints, cancel all pending transfers if any + dcd_edpt_close_all(rhport); + + // close all drivers and current configured state except bus speed + uint8_t const speed = _usbd_dev.speed; + configuration_reset(rhport); + + _usbd_dev.speed = speed; // restore speed + } + + // switch to new configuration if not zero + if ( cfg_num ) TU_ASSERT( process_set_config(rhport, cfg_num) ); + } + + _usbd_dev.cfg_num = cfg_num; + tud_control_status(rhport, p_request); + } + break; + + case TUSB_REQ_GET_DESCRIPTOR: + TU_VERIFY( process_get_descriptor(rhport, p_request) ); + break; + + case TUSB_REQ_SET_FEATURE: + // Only support remote wakeup for device feature + TU_VERIFY(TUSB_REQ_FEATURE_REMOTE_WAKEUP == p_request->wValue); + + TU_LOG(USBD_DBG, " 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; + + case TUSB_REQ_CLEAR_FEATURE: + // Only support remote wakeup for device feature + TU_VERIFY(TUSB_REQ_FEATURE_REMOTE_WAKEUP == p_request->wValue); + + TU_LOG(USBD_DBG, " Disable Remote Wakeup\r\n"); + + // Host may disable remote wake up after resuming + _usbd_dev.remote_wakeup_en = false; + tud_control_status(rhport, p_request); + break; + + case TUSB_REQ_GET_STATUS: + { + // Device status bit mask + // - Bit 0: Self Powered + // - Bit 1: Remote Wakeup enabled + uint16_t status = (_usbd_dev.self_powered ? 1 : 0) | (_usbd_dev.remote_wakeup_en ? 2 : 0); + tud_control_xfer(rhport, p_request, &status, 2); + } + break; + + // Unknown/Unsupported request + default: TU_BREAKPOINT(); return false; + } + break; + + //------------- Class/Interface Specific Request -------------// + case TUSB_REQ_RCPT_INTERFACE: + { + uint8_t const itf = tu_u16_low(p_request->wIndex); + TU_VERIFY(itf < TU_ARRAY_SIZE(_usbd_dev.itf2drv)); + + usbd_class_driver_t const * driver = get_driver(_usbd_dev.itf2drv[itf]); + TU_VERIFY(driver); + + // 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) ) + { + // 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) + { + 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) + { + uint8_t alternate = 0; + tud_control_xfer(rhport, p_request, &alternate, 1); + }else + { + tud_control_status(rhport, p_request); + } + break; + + default: return false; + } + } + } + break; + + //------------- Endpoint Request -------------// + 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 ) + { + // Forward class request to its driver + TU_VERIFY(driver); + return invoke_class_control(rhport, driver, p_request); + } + else + { + // Handle STD request to endpoint + 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 ) + { + usbd_edpt_clear_stall(rhport, ep_addr); + }else + { + usbd_edpt_stall(rhport, ep_addr); + } + } + + 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 + + // STD request must always be ACKed regardless of driver returned value + // Also clear complete callback if driver set since it can also stall the request. + (void) invoke_class_control(rhport, driver, p_request); + usbd_control_set_complete_callback(NULL); + + // skip ZLP status if driver already did that + if ( !_usbd_dev.ep_status[0][TUSB_DIR_IN].busy ) tud_control_status(rhport, p_request); + } + } + break; + + // Unknown/Unsupported request + default: TU_BREAKPOINT(); return false; + } + } + } + break; + + // Unknown recipient + default: TU_BREAKPOINT(); return false; + } + + return true; +} + +// Process Set Configure Request +// This function parse configuration descriptor & open drivers accordingly +static bool process_set_config(uint8_t rhport, uint8_t cfg_num) +{ + // index is cfg_num-1 + tusb_desc_configuration_t const * desc_cfg = (tusb_desc_configuration_t const *) tud_descriptor_configuration_cb(cfg_num-1); + TU_ASSERT(desc_cfg != NULL && desc_cfg->bDescriptorType == TUSB_DESC_CONFIGURATION); + + // Parse configuration descriptor + _usbd_dev.remote_wakeup_support = (desc_cfg->bmAttributes & TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP) ? 1 : 0; + _usbd_dev.self_powered = (desc_cfg->bmAttributes & TUSB_DESC_CONFIG_ATT_SELF_POWERED ) ? 1 : 0; + + // Parse interface descriptor + uint8_t const * p_desc = ((uint8_t const*) desc_cfg) + sizeof(tusb_desc_configuration_t); + uint8_t const * desc_end = ((uint8_t const*) desc_cfg) + tu_le16toh(desc_cfg->wTotalLength); + + 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) ) + { + tusb_desc_interface_assoc_t const * desc_iad = (tusb_desc_interface_assoc_t const *) p_desc; + assoc_itf_count = desc_iad->bInterfaceCount; + + p_desc = tu_desc_next(p_desc); // next to Interface + + // IAD's first interface number and class should match with opened interface + //TU_ASSERT(desc_iad->bFirstInterface == desc_itf->bInterfaceNumber && + // desc_iad->bFunctionClass == desc_itf->bInterfaceClass); + } + + TU_ASSERT( TUSB_DESC_INTERFACE == tu_desc_type(p_desc) ); + tusb_desc_interface_t const * desc_itf = (tusb_desc_interface_t const*) p_desc; + + // Find driver for this interface + uint16_t const remaining_len = desc_end-p_desc; + uint8_t drv_id; + for (drv_id = 0; drv_id < TOTAL_DRIVER_COUNT; drv_id++) + { + usbd_class_driver_t const *driver = get_driver(drv_id); + uint16_t const drv_len = driver->open(rhport, desc_itf, remaining_len); + + if ( (sizeof(tusb_desc_interface_t) <= drv_len) && (drv_len <= remaining_len) ) + { + // Open successfully + TU_LOG2(" %s opened\r\n", driver->name); + + // Some drivers use 2 or more interfaces but may not have IAD e.g MIDI (always) or + // BTH (even CDC) with class in device descriptor (single interface) + if ( assoc_itf_count == 1) + { + #if CFG_TUD_CDC + if ( driver->open == cdcd_open ) assoc_itf_count = 2; + #endif + + #if CFG_TUD_MIDI + if ( driver->open == midid_open ) assoc_itf_count = 2; + #endif + + #if CFG_TUD_BTH && CFG_TUD_BTH_ISO_ALT_COUNT + if ( driver->open == btd_open ) assoc_itf_count = 2; + #endif + } + + // bind (associated) interfaces to found driver + for(uint8_t i=0; ibInterfaceNumber+i; + + // Interface number must not be used already + TU_ASSERT(DRVID_INVALID == _usbd_dev.itf2drv[itf_num]); + _usbd_dev.itf2drv[itf_num] = drv_id; + } + + // bind all endpoints to found driver + tu_edpt_bind_driver(_usbd_dev.ep2drv, desc_itf, drv_len, drv_id); + + // next Interface + p_desc += drv_len; + + break; // exit driver find loop + } + } + + // Failed if there is no supported drivers + TU_ASSERT(drv_id < TOTAL_DRIVER_COUNT); + } + + // invoke callback + if (tud_mount_cb) tud_mount_cb(); + + return true; +} + +// return descriptor's buffer and update desc_len +static bool process_get_descriptor(uint8_t rhport, tusb_control_request_t const * p_request) +{ + tusb_desc_type_t const desc_type = (tusb_desc_type_t) tu_u16_high(p_request->wValue); + uint8_t const desc_index = tu_u16_low( p_request->wValue ); + + switch(desc_type) + { + case TUSB_DESC_DEVICE: + { + TU_LOG2(" Device\r\n"); + + void* desc_device = (void*) (uintptr_t) tud_descriptor_device_cb(); + + // Only response with exactly 1 Packet if: not addressed and host requested more data than device descriptor has. + // This only happens with the very first get device descriptor and EP0 size = 8 or 16. + if ((CFG_TUD_ENDPOINT0_SIZE < sizeof(tusb_desc_device_t)) && !_usbd_dev.addressed && + ((tusb_control_request_t const*) p_request)->wLength > sizeof(tusb_desc_device_t)) + { + // Hack here: we modify the request length to prevent usbd_control response with zlp + // since we are responding with 1 packet & less data than wLength. + tusb_control_request_t mod_request = *p_request; + mod_request.wLength = CFG_TUD_ENDPOINT0_SIZE; + + return tud_control_xfer(rhport, &mod_request, desc_device, CFG_TUD_ENDPOINT0_SIZE); + }else + { + return tud_control_xfer(rhport, p_request, desc_device, sizeof(tusb_desc_device_t)); + } + } + // break; // unreachable + + case TUSB_DESC_BOS: + { + TU_LOG2(" BOS\r\n"); + + // requested by host if USB > 2.0 ( i.e 2.1 or 3.x ) + if (!tud_descriptor_bos_cb) return false; + + uintptr_t desc_bos = (uintptr_t) tud_descriptor_bos_cb(); + TU_ASSERT(desc_bos); + + // Use offsetof to avoid pointer to the odd/misaligned address + uint16_t const total_len = tu_le16toh( tu_unaligned_read16((const void*) (desc_bos + offsetof(tusb_desc_bos_t, wTotalLength))) ); + + return tud_control_xfer(rhport, p_request, (void*) desc_bos, total_len); + } + // break; // unreachable + + case TUSB_DESC_CONFIGURATION: + case TUSB_DESC_OTHER_SPEED_CONFIG: + { + uintptr_t desc_config; + + if ( desc_type == TUSB_DESC_CONFIGURATION ) + { + TU_LOG2(" Configuration[%u]\r\n", desc_index); + desc_config = (uintptr_t) tud_descriptor_configuration_cb(desc_index); + }else + { + // Host only request this after getting Device Qualifier descriptor + TU_LOG2(" Other Speed Configuration\r\n"); + TU_VERIFY( tud_descriptor_other_speed_configuration_cb ); + desc_config = (uintptr_t) tud_descriptor_other_speed_configuration_cb(desc_index); + } + + TU_ASSERT(desc_config); + + // Use offsetof to avoid pointer to the odd/misaligned address + uint16_t const total_len = tu_le16toh( tu_unaligned_read16((const void*) (desc_config + offsetof(tusb_desc_configuration_t, wTotalLength))) ); + + return tud_control_xfer(rhport, p_request, (void*) desc_config, total_len); + } + // break; // unreachable + + case TUSB_DESC_STRING: + { + TU_LOG2(" String[%u]\r\n", desc_index); + + // String Descriptor always uses the desc set from user + uint8_t const* desc_str = (uint8_t const*) tud_descriptor_string_cb(desc_index, tu_le16toh(p_request->wIndex)); + TU_VERIFY(desc_str); + + // first byte of descriptor is its size + return tud_control_xfer(rhport, p_request, (void*) (uintptr_t) desc_str, tu_desc_len(desc_str)); + } + // break; // unreachable + + case TUSB_DESC_DEVICE_QUALIFIER: + { + TU_LOG2(" Device Qualifier\r\n"); + + TU_VERIFY( tud_descriptor_device_qualifier_cb ); + + uint8_t const* desc_qualifier = tud_descriptor_device_qualifier_cb(); + TU_VERIFY(desc_qualifier); + + // first byte of descriptor is its size + return tud_control_xfer(rhport, p_request, (void*) (uintptr_t) desc_qualifier, tu_desc_len(desc_qualifier)); + } + // break; // unreachable + + default: return false; + } +} + +//--------------------------------------------------------------------+ +// DCD Event Handler +//--------------------------------------------------------------------+ +void dcd_event_handler(dcd_event_t const * event, bool in_isr) +{ + switch (event->event_id) + { + case DCD_EVENT_UNPLUGGED: + _usbd_dev.connected = 0; + _usbd_dev.addressed = 0; + _usbd_dev.cfg_num = 0; + _usbd_dev.suspended = 0; + osal_queue_send(_usbd_q, event, in_isr); + 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 ). + // In addition, some MCUs such as SAMD or boards that haven no VBUS detection cannot distinguish + // suspended vs disconnected. We will skip handling SUSPEND/RESUME event if not currently connected + if ( _usbd_dev.connected ) + { + _usbd_dev.suspended = 1; + osal_queue_send(_usbd_q, event, in_isr); + } + break; + + case DCD_EVENT_RESUME: + // skip event if not connected (especially required for SAMD) + if ( _usbd_dev.connected ) + { + _usbd_dev.suspended = 0; + osal_queue_send(_usbd_q, event, in_isr); + } + break; + + case DCD_EVENT_SOF: + // 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 ) + { + _usbd_dev.suspended = 0; + dcd_event_t const event_resume = { .rhport = event->rhport, .event_id = DCD_EVENT_RESUME }; + osal_queue_send(_usbd_q, &event_resume, in_isr); + } + break; + + default: + osal_queue_send(_usbd_q, event, in_isr); + break; + } +} + +void dcd_event_bus_signal (uint8_t rhport, dcd_eventid_t eid, bool in_isr) +{ + dcd_event_t event = { .rhport = rhport, .event_id = eid }; + dcd_event_handler(&event, in_isr); +} + +void dcd_event_bus_reset (uint8_t rhport, tusb_speed_t speed, bool in_isr) +{ + dcd_event_t event = { .rhport = rhport, .event_id = DCD_EVENT_BUS_RESET }; + event.bus_reset.speed = speed; + dcd_event_handler(&event, in_isr); +} + +void dcd_event_setup_received(uint8_t rhport, uint8_t const * setup, bool in_isr) +{ + dcd_event_t event = { .rhport = rhport, .event_id = DCD_EVENT_SETUP_RECEIVED }; + memcpy(&event.setup_received, setup, 8); + + dcd_event_handler(&event, in_isr); +} + +void dcd_event_xfer_complete (uint8_t rhport, uint8_t ep_addr, uint32_t xferred_bytes, uint8_t result, bool in_isr) +{ + dcd_event_t event = { .rhport = rhport, .event_id = DCD_EVENT_XFER_COMPLETE }; + + event.xfer_complete.ep_addr = ep_addr; + event.xfer_complete.len = xferred_bytes; + event.xfer_complete.result = result; + + dcd_event_handler(&event, in_isr); +} + +//--------------------------------------------------------------------+ +// USBD API For Class Driver +//--------------------------------------------------------------------+ + +// Parse consecutive endpoint descriptors (IN & OUT) +bool usbd_open_edpt_pair(uint8_t rhport, uint8_t const* p_desc, uint8_t ep_count, uint8_t xfer_type, uint8_t* ep_out, uint8_t* ep_in) +{ + for(int i=0; ibDescriptorType && xfer_type == desc_ep->bmAttributes.xfer); + TU_ASSERT(usbd_edpt_open(rhport, desc_ep)); + + if ( tu_edpt_dir(desc_ep->bEndpointAddress) == TUSB_DIR_IN ) + { + (*ep_in) = desc_ep->bEndpointAddress; + }else + { + (*ep_out) = desc_ep->bEndpointAddress; + } + + p_desc = tu_desc_next(p_desc); + } + + return true; +} + +// Helper to defer an isr function +void usbd_defer_func(osal_task_func_t func, void* param, bool in_isr) +{ + dcd_event_t event = + { + .rhport = 0, + .event_id = USBD_EVENT_FUNC_CALL, + }; + + event.func_call.func = func; + event.func_call.param = param; + + dcd_event_handler(&event, in_isr); +} + +//--------------------------------------------------------------------+ +// USBD Endpoint API +//--------------------------------------------------------------------+ + +bool usbd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * desc_ep) +{ + TU_ASSERT(tu_edpt_number(desc_ep->bEndpointAddress) < CFG_TUD_ENDPPOINT_MAX); + TU_ASSERT(tu_edpt_validate(desc_ep, (tusb_speed_t) _usbd_dev.speed)); + + return dcd_edpt_open(rhport, desc_ep); +} + +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); + +#if CFG_TUSB_OS != OPT_OS_NONE + // pre-check to help reducing mutex lock + TU_VERIFY((_usbd_dev.ep_status[epnum][dir].busy == 0) && (_usbd_dev.ep_status[epnum][dir].claimed == 0)); + osal_mutex_lock(_usbd_mutex, OSAL_TIMEOUT_WAIT_FOREVER); +#endif + + // can only claim the endpoint if it is not busy and not claimed yet. + bool const ret = (_usbd_dev.ep_status[epnum][dir].busy == 0) && (_usbd_dev.ep_status[epnum][dir].claimed == 0); + if (ret) + { + _usbd_dev.ep_status[epnum][dir].claimed = 1; + } + +#if CFG_TUSB_OS != OPT_OS_NONE + osal_mutex_unlock(_usbd_mutex); +#endif + + return ret; +} + +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); + +#if CFG_TUSB_OS != OPT_OS_NONE + osal_mutex_lock(_usbd_mutex, OSAL_TIMEOUT_WAIT_FOREVER); +#endif + + // can only release the endpoint if it is claimed and not busy + bool const ret = (_usbd_dev.ep_status[epnum][dir].busy == 0) && (_usbd_dev.ep_status[epnum][dir].claimed == 1); + if (ret) + { + _usbd_dev.ep_status[epnum][dir].claimed = 0; + } + +#if CFG_TUSB_OS != OPT_OS_NONE + osal_mutex_unlock(_usbd_mutex); +#endif + + return ret; +} + +bool usbd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes) +{ + uint8_t const epnum = tu_edpt_number(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_LOG2(" 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(_usbd_dev.ep_status[epnum][dir].busy == 0); + + // Set busy first since the actual transfer can be complete before dcd_edpt_xfer() + // could return and USBD task can preempt and clear the busy + _usbd_dev.ep_status[epnum][dir].busy = true; + + if ( dcd_edpt_xfer(rhport, ep_addr, buffer, total_bytes) ) + { + return true; + }else + { + // DCD error, mark endpoint as ready to allow next transfer + _usbd_dev.ep_status[epnum][dir].busy = false; + _usbd_dev.ep_status[epnum][dir].claimed = 0; + TU_LOG2("FAILED\r\n"); + TU_BREAKPOINT(); + return false; + } +} + +// The number of bytes has to be given explicitly to allow more flexible control of how many +// 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) +{ + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + TU_LOG2(" Queue ISO EP %02X with %u bytes ... ", ep_addr, total_bytes); + + // Attempt to transfer on a busy endpoint, sound like an race condition ! + TU_ASSERT(_usbd_dev.ep_status[epnum][dir].busy == 0); + + // Set busy first since the actual transfer can be complete before dcd_edpt_xfer() could return + // and usbd task can preempt and clear the busy + _usbd_dev.ep_status[epnum][dir].busy = true; + + if (dcd_edpt_xfer_fifo(rhport, ep_addr, ff, total_bytes)) + { + TU_LOG2("OK\r\n"); + return true; + }else + { + // DCD error, mark endpoint as ready to allow next transfer + _usbd_dev.ep_status[epnum][dir].busy = false; + _usbd_dev.ep_status[epnum][dir].claimed = 0; + TU_LOG2("failed\r\n"); + TU_BREAKPOINT(); + return false; + } +} + +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); + + return _usbd_dev.ep_status[epnum][dir].busy; +} + +void usbd_edpt_stall(uint8_t rhport, uint8_t ep_addr) +{ + + uint8_t const epnum = tu_edpt_number(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_DBG, " Stall EP %02X\r\n", ep_addr); + dcd_edpt_stall(rhport, ep_addr); + _usbd_dev.ep_status[epnum][dir].stalled = true; + _usbd_dev.ep_status[epnum][dir].busy = true; + } +} + +void usbd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) +{ + uint8_t const epnum = tu_edpt_number(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_DBG, " Clear Stall EP %02X\r\n", ep_addr); + dcd_edpt_clear_stall(rhport, ep_addr); + _usbd_dev.ep_status[epnum][dir].stalled = false; + _usbd_dev.ep_status[epnum][dir].busy = false; + } +} + +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); + + 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) +{ + TU_ASSERT(dcd_edpt_close, /**/); + TU_LOG2(" 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); + + dcd_edpt_close(rhport, ep_addr); + _usbd_dev.ep_status[epnum][dir].stalled = false; + _usbd_dev.ep_status[epnum][dir].busy = false; + _usbd_dev.ep_status[epnum][dir].claimed = false; + + return; +} + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/device/usbd.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/device/usbd.h new file mode 100644 index 000000000..ec34817fa --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/device/usbd.h @@ -0,0 +1,853 @@ +/* + * 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_USBD_H_ +#define _TUSB_USBD_H_ + +#include "common/tusb_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Application API +//--------------------------------------------------------------------+ + +// Init device stack +bool tud_init (uint8_t rhport); + +// Check if device stack is already initialized +bool tud_inited(void); + +// Task function should be called in main/rtos loop +void tud_task (void); + +// Check if there is pending events need proccessing by tud_task() +bool tud_task_event_ready(void); + +// Interrupt handler, name alias to DCD +extern void dcd_int_handler(uint8_t rhport); +#define tud_int_handler dcd_int_handler + +// Get current bus speed +tusb_speed_t tud_speed_get(void); + +// Check if device is connected (may not mounted/configured yet) +// True if just got out of Bus Reset and received the very first data from host +bool tud_connected(void); + +// Check if device is connected and configured +bool tud_mounted(void); + +// Check if device is suspended +bool tud_suspended(void); + +// Check if device is ready to transfer +TU_ATTR_ALWAYS_INLINE static inline +bool tud_ready(void) +{ + return tud_mounted() && !tud_suspended(); +} + +// Remote wake up host, only if suspended and enabled by host +bool tud_remote_wakeup(void); + +// Enable pull-up resistor on D+ D- +// Return false on unsupported MCUs +bool tud_disconnect(void); + +// Disable pull-up resistor on D+ D- +// Return false on unsupported MCUs +bool tud_connect(void); + +// 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 +bool tud_control_xfer(uint8_t rhport, tusb_control_request_t const * request, void* buffer, uint16_t len); + +// Send STATUS (zero length) packet +bool tud_control_status(uint8_t rhport, tusb_control_request_t const * request); + +//--------------------------------------------------------------------+ +// Application Callbacks (WEAK is optional) +//--------------------------------------------------------------------+ + +// Invoked when received GET DEVICE DESCRIPTOR request +// Application return pointer to descriptor +uint8_t const * tud_descriptor_device_cb(void); + +// Invoked when received GET CONFIGURATION DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +uint8_t const * tud_descriptor_configuration_cb(uint8_t index); + +// Invoked when received GET STRING DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid); + +// Invoked when received GET BOS DESCRIPTOR request +// Application return pointer to descriptor +TU_ATTR_WEAK uint8_t const * tud_descriptor_bos_cb(void); + +// Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete. +// device_qualifier descriptor describes information about a high-speed capable device that would +// change if the device were operating at the other speed. If not highspeed capable stall this request. +TU_ATTR_WEAK uint8_t const* tud_descriptor_device_qualifier_cb(void); + +// Invoked when received GET OTHER SEED CONFIGURATION DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +// Configuration descriptor in the other speed e.g if high speed then this is for full speed and vice versa +TU_ATTR_WEAK uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index); + +// Invoked when device is mounted (configured) +TU_ATTR_WEAK void tud_mount_cb(void); + +// Invoked when device is unmounted +TU_ATTR_WEAK void tud_umount_cb(void); + +// Invoked when usb bus is suspended +// Within 7ms, device must draw an average of current less than 2.5 mA from bus +TU_ATTR_WEAK void tud_suspend_cb(bool remote_wakeup_en); + +// Invoked when usb bus is resumed +TU_ATTR_WEAK void tud_resume_cb(void); + +// 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); + +//--------------------------------------------------------------------+ +// Binary Device Object Store (BOS) Descriptor Templates +//--------------------------------------------------------------------+ + +#define TUD_BOS_DESC_LEN 5 + +// total length, number of device caps +#define TUD_BOS_DESCRIPTOR(_total_len, _caps_num) \ + 5, TUSB_DESC_BOS, U16_TO_U8S_LE(_total_len), _caps_num + +// Device Capability Platform 128-bit UUID + Data +#define TUD_BOS_PLATFORM_DESCRIPTOR(...) \ + 4+TU_ARGS_NUM(__VA_ARGS__), TUSB_DESC_DEVICE_CAPABILITY, DEVICE_CAPABILITY_PLATFORM, 0x00, __VA_ARGS__ + +//------------- WebUSB BOS Platform -------------// + +// Descriptor Length +#define TUD_BOS_WEBUSB_DESC_LEN 24 + +// Vendor Code, iLandingPage +#define TUD_BOS_WEBUSB_DESCRIPTOR(_vendor_code, _ipage) \ + TUD_BOS_PLATFORM_DESCRIPTOR(TUD_BOS_WEBUSB_UUID, U16_TO_U8S_LE(0x0100), _vendor_code, _ipage) + +#define TUD_BOS_WEBUSB_UUID \ + 0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47, \ + 0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65 + +//------------- Microsoft OS 2.0 Platform -------------// +#define TUD_BOS_MICROSOFT_OS_DESC_LEN 28 + +// Total Length of descriptor set, vendor code +#define TUD_BOS_MS_OS_20_DESCRIPTOR(_desc_set_len, _vendor_code) \ + TUD_BOS_PLATFORM_DESCRIPTOR(TUD_BOS_MS_OS_20_UUID, U32_TO_U8S_LE(0x06030000), U16_TO_U8S_LE(_desc_set_len), _vendor_code, 0) + +#define TUD_BOS_MS_OS_20_UUID \ + 0xDF, 0x60, 0xDD, 0xD8, 0x89, 0x45, 0xC7, 0x4C, \ + 0x9C, 0xD2, 0x65, 0x9D, 0x9E, 0x64, 0x8A, 0x9F + +//--------------------------------------------------------------------+ +// Configuration Descriptor Templates +//--------------------------------------------------------------------+ + +#define TUD_CONFIG_DESC_LEN (9) + +// Config number, interface count, string index, total length, attribute, power in mA +#define TUD_CONFIG_DESCRIPTOR(config_num, _itfcount, _stridx, _total_len, _attribute, _power_ma) \ + 9, TUSB_DESC_CONFIGURATION, U16_TO_U8S_LE(_total_len), _itfcount, config_num, _stridx, TU_BIT(7) | _attribute, (_power_ma)/2 + +//--------------------------------------------------------------------+ +// CDC Descriptor Templates +//--------------------------------------------------------------------+ + +// Length of template descriptor: 66 bytes +#define TUD_CDC_DESC_LEN (8+9+5+5+4+5+7+9+7+7) + +// CDC Descriptor Template +// Interface number, string index, EP notification address and size, EP data address (out, in) and size. +#define TUD_CDC_DESCRIPTOR(_itfnum, _stridx, _ep_notif, _ep_notif_size, _epout, _epin, _epsize) \ + /* Interface Associate */\ + 8, TUSB_DESC_INTERFACE_ASSOCIATION, _itfnum, 2, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL, CDC_COMM_PROTOCOL_NONE, 0,\ + /* CDC Control Interface */\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 1, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL, CDC_COMM_PROTOCOL_NONE, _stridx,\ + /* CDC Header */\ + 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 Union */\ + 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_UNION, _itfnum, (uint8_t)((_itfnum) + 1),\ + /* Endpoint Notification */\ + 7, TUSB_DESC_ENDPOINT, _ep_notif, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_notif_size), 16,\ + /* CDC Data Interface */\ + 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 0, 2, TUSB_CLASS_CDC_DATA, 0, 0, 0,\ + /* Endpoint Out */\ + 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0,\ + /* Endpoint In */\ + 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 + +//--------------------------------------------------------------------+ +// MSC Descriptor Templates +//--------------------------------------------------------------------+ + +// Length of template descriptor: 23 bytes +#define TUD_MSC_DESC_LEN (9 + 7 + 7) + +// Interface number, string index, EP Out & EP In address, EP size +#define TUD_MSC_DESCRIPTOR(_itfnum, _stridx, _epout, _epin, _epsize) \ + /* Interface */\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 2, TUSB_CLASS_MSC, MSC_SUBCLASS_SCSI, MSC_PROTOCOL_BOT, _stridx,\ + /* Endpoint Out */\ + 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0,\ + /* Endpoint In */\ + 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 + + +//--------------------------------------------------------------------+ +// HID Descriptor Templates +//--------------------------------------------------------------------+ + +// Length of template descriptor: 25 bytes +#define TUD_HID_DESC_LEN (9 + 9 + 7) + +// HID Input only descriptor +// Interface number, string index, protocol, report descriptor len, EP In address, size & polling interval +#define TUD_HID_DESCRIPTOR(_itfnum, _stridx, _boot_protocol, _report_desc_len, _epin, _epsize, _ep_interval) \ + /* Interface */\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 1, TUSB_CLASS_HID, (uint8_t)((_boot_protocol) ? (uint8_t)HID_SUBCLASS_BOOT : 0), _boot_protocol, _stridx,\ + /* HID descriptor */\ + 9, HID_DESC_TYPE_HID, U16_TO_U8S_LE(0x0111), 0, 1, HID_DESC_TYPE_REPORT, U16_TO_U8S_LE(_report_desc_len),\ + /* Endpoint In */\ + 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_epsize), _ep_interval + +// Length of template descriptor: 32 bytes +#define TUD_HID_INOUT_DESC_LEN (9 + 9 + 7 + 7) + +// HID Input & Output descriptor +// Interface number, string index, protocol, report descriptor len, EP OUT & IN address, size & polling interval +#define TUD_HID_INOUT_DESCRIPTOR(_itfnum, _stridx, _boot_protocol, _report_desc_len, _epout, _epin, _epsize, _ep_interval) \ + /* Interface */\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 2, TUSB_CLASS_HID, (uint8_t)((_boot_protocol) ? (uint8_t)HID_SUBCLASS_BOOT : 0), _boot_protocol, _stridx,\ + /* HID descriptor */\ + 9, HID_DESC_TYPE_HID, U16_TO_U8S_LE(0x0111), 0, 1, HID_DESC_TYPE_REPORT, U16_TO_U8S_LE(_report_desc_len),\ + /* Endpoint Out */\ + 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_epsize), _ep_interval, \ + /* Endpoint In */\ + 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_epsize), _ep_interval + +//--------------------------------------------------------------------+ +// MIDI Descriptor Templates +// Note: MIDI v1.0 is based on Audio v1.0 +//--------------------------------------------------------------------+ + +#define TUD_MIDI_DESC_HEAD_LEN (9 + 9 + 9 + 7) +#define TUD_MIDI_DESC_HEAD(_itfnum, _stridx, _numcables) \ + /* Audio Control (AC) Interface */\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 0, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_CONTROL, AUDIO_FUNC_PROTOCOL_CODE_UNDEF, _stridx,\ + /* AC Header */\ + 9, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_HEADER, U16_TO_U8S_LE(0x0100), U16_TO_U8S_LE(0x0009), 1, (uint8_t)((_itfnum) + 1),\ + /* MIDI Streaming (MS) Interface */\ + 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum) + 1), 0, 2, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_MIDI_STREAMING, AUDIO_FUNC_PROTOCOL_CODE_UNDEF, 0,\ + /* MS Header */\ + 7, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_HEADER, U16_TO_U8S_LE(0x0100), U16_TO_U8S_LE(7 + (_numcables) * TUD_MIDI_DESC_JACK_LEN) + +#define TUD_MIDI_JACKID_IN_EMB(_cablenum) \ + (uint8_t)(((_cablenum) - 1) * 4 + 1) + +#define TUD_MIDI_JACKID_IN_EXT(_cablenum) \ + (uint8_t)(((_cablenum) - 1) * 4 + 2) + +#define TUD_MIDI_JACKID_OUT_EMB(_cablenum) \ + (uint8_t)(((_cablenum) - 1) * 4 + 3) + +#define TUD_MIDI_JACKID_OUT_EXT(_cablenum) \ + (uint8_t)(((_cablenum) - 1) * 4 + 4) + +#define TUD_MIDI_DESC_JACK_LEN (6 + 6 + 9 + 9) +#define TUD_MIDI_DESC_JACK(_cablenum) \ + /* MS In Jack (Embedded) */\ + 6, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_IN_JACK, MIDI_JACK_EMBEDDED, TUD_MIDI_JACKID_IN_EMB(_cablenum), 0,\ + /* MS In Jack (External) */\ + 6, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_IN_JACK, MIDI_JACK_EXTERNAL, TUD_MIDI_JACKID_IN_EXT(_cablenum), 0,\ + /* MS Out Jack (Embedded), connected to In Jack External */\ + 9, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_OUT_JACK, MIDI_JACK_EMBEDDED, TUD_MIDI_JACKID_OUT_EMB(_cablenum), 1, TUD_MIDI_JACKID_IN_EXT(_cablenum), 1, 0,\ + /* MS Out Jack (External), connected to In Jack Embedded */\ + 9, TUSB_DESC_CS_INTERFACE, MIDI_CS_INTERFACE_OUT_JACK, MIDI_JACK_EXTERNAL, TUD_MIDI_JACKID_OUT_EXT(_cablenum), 1, TUD_MIDI_JACKID_IN_EMB(_cablenum), 1, 0 + +#define TUD_MIDI_DESC_EP_LEN(_numcables) (9 + 4 + (_numcables)) +#define TUD_MIDI_DESC_EP(_epout, _epsize, _numcables) \ + /* Endpoint: Note Audio v1.0's endpoint has 9 bytes instead of 7 */\ + 9, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0, 0, 0, \ + /* MS Endpoint (connected to embedded jack) */\ + (uint8_t)(4 + (_numcables)), TUSB_DESC_CS_ENDPOINT, MIDI_CS_ENDPOINT_GENERAL, _numcables + +// Length of template descriptor (88 bytes) +#define TUD_MIDI_DESC_LEN (TUD_MIDI_DESC_HEAD_LEN + TUD_MIDI_DESC_JACK_LEN + TUD_MIDI_DESC_EP_LEN(1) * 2) + +// MIDI simple descriptor +// - 1 Embedded Jack In connected to 1 External Jack Out +// - 1 Embedded Jack out connected to 1 External Jack In +#define TUD_MIDI_DESCRIPTOR(_itfnum, _stridx, _epout, _epin, _epsize) \ + TUD_MIDI_DESC_HEAD(_itfnum, _stridx, 1),\ + TUD_MIDI_DESC_JACK(1),\ + TUD_MIDI_DESC_EP(_epout, _epsize, 1),\ + TUD_MIDI_JACKID_IN_EMB(1),\ + TUD_MIDI_DESC_EP(_epin, _epsize, 1),\ + TUD_MIDI_JACKID_OUT_EMB(1) + +//--------------------------------------------------------------------+ +// Audio v2.0 Descriptor Templates +//--------------------------------------------------------------------+ + +/* Standard Interface Association Descriptor (IAD) */ +#define TUD_AUDIO_DESC_IAD_LEN 8 +#define TUD_AUDIO_DESC_IAD(_firstitfs, _nitfs, _stridx) \ + TUD_AUDIO_DESC_IAD_LEN, TUSB_DESC_INTERFACE_ASSOCIATION, _firstitfs, _nitfs, TUSB_CLASS_AUDIO, AUDIO_FUNCTION_SUBCLASS_UNDEFINED, AUDIO_FUNC_PROTOCOL_CODE_V2, _stridx + +/* Standard AC Interface Descriptor(4.7.1) */ +#define TUD_AUDIO_DESC_STD_AC_LEN 9 +#define TUD_AUDIO_DESC_STD_AC(_itfnum, _nEPs, _stridx) /* _nEPs is 0 or 1 */\ + TUD_AUDIO_DESC_STD_AC_LEN, TUSB_DESC_INTERFACE, _itfnum, /* fixed to zero */ 0x00, _nEPs, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_CONTROL, AUDIO_INT_PROTOCOL_CODE_V2, _stridx + +/* Class-Specific AC Interface Header Descriptor(4.7.2) */ +#define TUD_AUDIO_DESC_CS_AC_LEN 9 +#define TUD_AUDIO_DESC_CS_AC(_bcdADC, _category, _totallen, _ctrl) /* _bcdADC : Audio Device Class Specification Release Number in Binary-Coded Decimal, _category : see audio_function_t, _totallen : Total number of bytes returned for the class-specific AudioControl interface i.e. Clock Source, Unit and Terminal descriptors - Do not include TUD_AUDIO_DESC_CS_AC_LEN, we already do this here*/ \ + TUD_AUDIO_DESC_CS_AC_LEN, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_HEADER, U16_TO_U8S_LE(_bcdADC), _category, U16_TO_U8S_LE(_totallen + TUD_AUDIO_DESC_CS_AC_LEN), _ctrl + +/* Clock Source Descriptor(4.7.2.1) */ +#define TUD_AUDIO_DESC_CLK_SRC_LEN 8 +#define TUD_AUDIO_DESC_CLK_SRC(_clkid, _attr, _ctrl, _assocTerm, _stridx) \ + TUD_AUDIO_DESC_CLK_SRC_LEN, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_CLOCK_SOURCE, _clkid, _attr, _ctrl, _assocTerm, _stridx + +/* Input Terminal Descriptor(4.7.2.4) */ +#define TUD_AUDIO_DESC_INPUT_TERM_LEN 17 +#define TUD_AUDIO_DESC_INPUT_TERM(_termid, _termtype, _assocTerm, _clkid, _nchannelslogical, _channelcfg, _idxchannelnames, _ctrl, _stridx) \ + TUD_AUDIO_DESC_INPUT_TERM_LEN, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_INPUT_TERMINAL, _termid, U16_TO_U8S_LE(_termtype), _assocTerm, _clkid, _nchannelslogical, U32_TO_U8S_LE(_channelcfg), _idxchannelnames, U16_TO_U8S_LE(_ctrl), _stridx + +/* Output Terminal Descriptor(4.7.2.5) */ +#define TUD_AUDIO_DESC_OUTPUT_TERM_LEN 12 +#define TUD_AUDIO_DESC_OUTPUT_TERM(_termid, _termtype, _assocTerm, _srcid, _clkid, _ctrl, _stridx) \ + TUD_AUDIO_DESC_OUTPUT_TERM_LEN, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_OUTPUT_TERMINAL, _termid, U16_TO_U8S_LE(_termtype), _assocTerm, _srcid, _clkid, U16_TO_U8S_LE(_ctrl), _stridx + +/* Feature Unit Descriptor(4.7.2.8) */ +// 1 - Channel +#define TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL_LEN 6+(1+1)*4 +#define TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL(_unitid, _srcid, _ctrlch0master, _ctrlch1, _stridx) \ + TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL_LEN, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_FEATURE_UNIT, _unitid, _srcid, U32_TO_U8S_LE(_ctrlch0master), U32_TO_U8S_LE(_ctrlch1), _stridx + +// 2 - Channels +#define TUD_AUDIO_DESC_FEATURE_UNIT_TWO_CHANNEL_LEN (6+(2+1)*4) +#define TUD_AUDIO_DESC_FEATURE_UNIT_TWO_CHANNEL(_unitid, _srcid, _ctrlch0master, _ctrlch1, _ctrlch2, _stridx) \ + TUD_AUDIO_DESC_FEATURE_UNIT_TWO_CHANNEL_LEN, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_FEATURE_UNIT, _unitid, _srcid, U32_TO_U8S_LE(_ctrlch0master), U32_TO_U8S_LE(_ctrlch1), U32_TO_U8S_LE(_ctrlch2), _stridx +// 4 - Channels +#define TUD_AUDIO_DESC_FEATURE_UNIT_FOUR_CHANNEL_LEN (6+(4+1)*4) +#define TUD_AUDIO_DESC_FEATURE_UNIT_FOUR_CHANNEL(_unitid, _srcid, _ctrlch0master, _ctrlch1, _ctrlch2, _ctrlch3, _ctrlch4, _stridx) \ + TUD_AUDIO_DESC_FEATURE_UNIT_FOUR_CHANNEL_LEN, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AC_INTERFACE_FEATURE_UNIT, _unitid, _srcid, U32_TO_U8S_LE(_ctrlch0master), U32_TO_U8S_LE(_ctrlch1), U32_TO_U8S_LE(_ctrlch2), U32_TO_U8S_LE(_ctrlch3), U32_TO_U8S_LE(_ctrlch4), _stridx + +// For more channels, add definitions here + +/* 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) \ + TUD_AUDIO_DESC_STD_AS_INT_LEN, TUSB_DESC_INTERFACE, _itfnum, _altset, _nEPs, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_STREAMING, AUDIO_INT_PROTOCOL_CODE_V2, _stridx + +/* Class-Specific AS Interface Descriptor(4.9.2) */ +#define TUD_AUDIO_DESC_CS_AS_INT_LEN 16 +#define TUD_AUDIO_DESC_CS_AS_INT(_termid, _ctrl, _formattype, _formats, _nchannelsphysical, _channelcfg, _stridx) \ + TUD_AUDIO_DESC_CS_AS_INT_LEN, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AS_INTERFACE_AS_GENERAL, _termid, _ctrl, _formattype, U32_TO_U8S_LE(_formats), _nchannelsphysical, U32_TO_U8S_LE(_channelcfg), _stridx + +/* Type I Format Type Descriptor(2.3.1.6 - Audio Formats) */ +#define TUD_AUDIO_DESC_TYPE_I_FORMAT_LEN 6 +#define TUD_AUDIO_DESC_TYPE_I_FORMAT(_subslotsize, _bitresolution) /* _subslotsize is number of bytes per sample (i.e. subslot) and can be 1,2,3, or 4 */\ + TUD_AUDIO_DESC_TYPE_I_FORMAT_LEN, TUSB_DESC_CS_INTERFACE, AUDIO_CS_AS_INTERFACE_FORMAT_TYPE, AUDIO_FORMAT_TYPE_I, _subslotsize, _bitresolution + +/* Standard AS Isochronous Audio Data Endpoint Descriptor(4.10.1.1) */ +#define TUD_AUDIO_DESC_STD_AS_ISO_EP_LEN 7 +#define TUD_AUDIO_DESC_STD_AS_ISO_EP(_ep, _attr, _maxEPsize, _interval) \ + TUD_AUDIO_DESC_STD_AS_ISO_EP_LEN, TUSB_DESC_ENDPOINT, _ep, _attr, U16_TO_U8S_LE(_maxEPsize), _interval + +/* Class-Specific AS Isochronous Audio Data Endpoint Descriptor(4.10.1.2) */ +#define TUD_AUDIO_DESC_CS_AS_ISO_EP_LEN 8 +#define TUD_AUDIO_DESC_CS_AS_ISO_EP(_attr, _ctrl, _lockdelayunit, _lockdelay) \ + TUD_AUDIO_DESC_CS_AS_ISO_EP_LEN, TUSB_DESC_CS_ENDPOINT, AUDIO_CS_EP_SUBTYPE_GENERAL, _attr, _ctrl, _lockdelayunit, U16_TO_U8S_LE(_lockdelay) + +/* 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, (TUSB_XFER_ISOCHRONOUS | TUSB_ISO_EP_ATT_NO_SYNC | 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 + +#define TUD_AUDIO_MIC_ONE_CH_DESC_LEN (TUD_AUDIO_DESC_IAD_LEN\ + + TUD_AUDIO_DESC_STD_AC_LEN\ + + TUD_AUDIO_DESC_CS_AC_LEN\ + + TUD_AUDIO_DESC_CLK_SRC_LEN\ + + TUD_AUDIO_DESC_INPUT_TERM_LEN\ + + TUD_AUDIO_DESC_OUTPUT_TERM_LEN\ + + TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL_LEN\ + + TUD_AUDIO_DESC_STD_AS_INT_LEN\ + + TUD_AUDIO_DESC_STD_AS_INT_LEN\ + + TUD_AUDIO_DESC_CS_AS_INT_LEN\ + + TUD_AUDIO_DESC_TYPE_I_FORMAT_LEN\ + + TUD_AUDIO_DESC_STD_AS_ISO_EP_LEN\ + + TUD_AUDIO_DESC_CS_AS_ISO_EP_LEN) + +#define TUD_AUDIO_MIC_ONE_CH_DESC_N_AS_INT 1 // Number of AS interfaces + +#define TUD_AUDIO_MIC_ONE_CH_DESCRIPTOR(_itfnum, _stridx, _nBytesPerSample, _nBitsUsedPerSample, _epin, _epsize) \ + /* Standard Interface Association Descriptor (IAD) */\ + TUD_AUDIO_DESC_IAD(/*_firstitfs*/ _itfnum, /*_nitfs*/ 0x02, /*_stridx*/ 0x00),\ + /* Standard AC Interface Descriptor(4.7.1) */\ + TUD_AUDIO_DESC_STD_AC(/*_itfnum*/ _itfnum, /*_nEPs*/ 0x00, /*_stridx*/ _stridx),\ + /* Class-Specific AC Interface Header Descriptor(4.7.2) */\ + TUD_AUDIO_DESC_CS_AC(/*_bcdADC*/ 0x0200, /*_category*/ AUDIO_FUNC_MICROPHONE, /*_totallen*/ TUD_AUDIO_DESC_CLK_SRC_LEN+TUD_AUDIO_DESC_INPUT_TERM_LEN+TUD_AUDIO_DESC_OUTPUT_TERM_LEN+TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL_LEN, /*_ctrl*/ AUDIO_CS_AS_INTERFACE_CTRL_LATENCY_POS),\ + /* Clock Source Descriptor(4.7.2.1) */\ + TUD_AUDIO_DESC_CLK_SRC(/*_clkid*/ 0x04, /*_attr*/ AUDIO_CLOCK_SOURCE_ATT_INT_FIX_CLK, /*_ctrl*/ (AUDIO_CTRL_R << AUDIO_CLOCK_SOURCE_CTRL_CLK_FRQ_POS), /*_assocTerm*/ 0x01, /*_stridx*/ 0x00),\ + /* Input Terminal Descriptor(4.7.2.4) */\ + TUD_AUDIO_DESC_INPUT_TERM(/*_termid*/ 0x01, /*_termtype*/ AUDIO_TERM_TYPE_IN_GENERIC_MIC, /*_assocTerm*/ 0x03, /*_clkid*/ 0x04, /*_nchannelslogical*/ 0x01, /*_channelcfg*/ AUDIO_CHANNEL_CONFIG_NON_PREDEFINED, /*_idxchannelnames*/ 0x00, /*_ctrl*/ AUDIO_CTRL_R << AUDIO_IN_TERM_CTRL_CONNECTOR_POS, /*_stridx*/ 0x00),\ + /* Output Terminal Descriptor(4.7.2.5) */\ + TUD_AUDIO_DESC_OUTPUT_TERM(/*_termid*/ 0x03, /*_termtype*/ AUDIO_TERM_TYPE_USB_STREAMING, /*_assocTerm*/ 0x01, /*_srcid*/ 0x02, /*_clkid*/ 0x04, /*_ctrl*/ 0x0000, /*_stridx*/ 0x00),\ + /* Feature Unit Descriptor(4.7.2.8) */\ + TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL(/*_unitid*/ 0x02, /*_srcid*/ 0x01, /*_ctrlch0master*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch1*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS, /*_stridx*/ 0x00),\ + /* Standard AS Interface Descriptor(4.9.1) */\ + /* Interface 1, Alternate 0 - default alternate setting with 0 bandwidth */\ + TUD_AUDIO_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum)+1), /*_altset*/ 0x00, /*_nEPs*/ 0x00, /*_stridx*/ 0x00),\ + /* Standard AS Interface Descriptor(4.9.1) */\ + /* Interface 1, Alternate 1 - alternate interface for data streaming */\ + TUD_AUDIO_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum)+1), /*_altset*/ 0x01, /*_nEPs*/ 0x01, /*_stridx*/ 0x00),\ + /* Class-Specific AS Interface Descriptor(4.9.2) */\ + TUD_AUDIO_DESC_CS_AS_INT(/*_termid*/ 0x03, /*_ctrl*/ AUDIO_CTRL_NONE, /*_formattype*/ AUDIO_FORMAT_TYPE_I, /*_formats*/ AUDIO_DATA_FORMAT_TYPE_I_PCM, /*_nchannelsphysical*/ 0x01, /*_channelcfg*/ AUDIO_CHANNEL_CONFIG_NON_PREDEFINED, /*_stridx*/ 0x00),\ + /* 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*/ (TUSB_XFER_ISOCHRONOUS | TUSB_ISO_EP_ATT_ASYNCHRONOUS | TUSB_ISO_EP_ATT_DATA), /*_maxEPsize*/ _epsize, /*_interval*/ (CFG_TUSB_RHPORT0_MODE & OPT_MODE_HIGH_SPEED) ? 0x04 : 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) + +// AUDIO simple descriptor (UAC2) for 4 microphone input +// - 1 Input Terminal, 1 Feature Unit (Mute and Volume Control), 1 Output Terminal, 1 Clock Source + +#define TUD_AUDIO_MIC_FOUR_CH_DESC_LEN (TUD_AUDIO_DESC_IAD_LEN\ + + TUD_AUDIO_DESC_STD_AC_LEN\ + + TUD_AUDIO_DESC_CS_AC_LEN\ + + TUD_AUDIO_DESC_CLK_SRC_LEN\ + + TUD_AUDIO_DESC_INPUT_TERM_LEN\ + + TUD_AUDIO_DESC_OUTPUT_TERM_LEN\ + + TUD_AUDIO_DESC_FEATURE_UNIT_FOUR_CHANNEL_LEN\ + + TUD_AUDIO_DESC_STD_AS_INT_LEN\ + + TUD_AUDIO_DESC_STD_AS_INT_LEN\ + + TUD_AUDIO_DESC_CS_AS_INT_LEN\ + + TUD_AUDIO_DESC_TYPE_I_FORMAT_LEN\ + + TUD_AUDIO_DESC_STD_AS_ISO_EP_LEN\ + + TUD_AUDIO_DESC_CS_AS_ISO_EP_LEN) + +#define TUD_AUDIO_MIC_FOUR_CH_DESC_N_AS_INT 1 // Number of AS interfaces + +#define TUD_AUDIO_MIC_FOUR_CH_DESCRIPTOR(_itfnum, _stridx, _nBytesPerSample, _nBitsUsedPerSample, _epin, _epsize) \ + /* Standard Interface Association Descriptor (IAD) */\ + TUD_AUDIO_DESC_IAD(/*_firstitfs*/ _itfnum, /*_nitfs*/ 0x02, /*_stridx*/ 0x00),\ + /* Standard AC Interface Descriptor(4.7.1) */\ + TUD_AUDIO_DESC_STD_AC(/*_itfnum*/ _itfnum, /*_nEPs*/ 0x00, /*_stridx*/ _stridx),\ + /* Class-Specific AC Interface Header Descriptor(4.7.2) */\ + TUD_AUDIO_DESC_CS_AC(/*_bcdADC*/ 0x0200, /*_category*/ AUDIO_FUNC_MICROPHONE, /*_totallen*/ TUD_AUDIO_DESC_CLK_SRC_LEN+TUD_AUDIO_DESC_INPUT_TERM_LEN+TUD_AUDIO_DESC_OUTPUT_TERM_LEN+TUD_AUDIO_DESC_FEATURE_UNIT_FOUR_CHANNEL_LEN, /*_ctrl*/ AUDIO_CS_AS_INTERFACE_CTRL_LATENCY_POS),\ + /* Clock Source Descriptor(4.7.2.1) */\ + TUD_AUDIO_DESC_CLK_SRC(/*_clkid*/ 0x04, /*_attr*/ AUDIO_CLOCK_SOURCE_ATT_INT_FIX_CLK, /*_ctrl*/ (AUDIO_CTRL_R << AUDIO_CLOCK_SOURCE_CTRL_CLK_FRQ_POS), /*_assocTerm*/ 0x01, /*_stridx*/ 0x00),\ + /* Input Terminal Descriptor(4.7.2.4) */\ + TUD_AUDIO_DESC_INPUT_TERM(/*_termid*/ 0x01, /*_termtype*/ AUDIO_TERM_TYPE_IN_GENERIC_MIC, /*_assocTerm*/ 0x03, /*_clkid*/ 0x04, /*_nchannelslogical*/ 0x04, /*_channelcfg*/ AUDIO_CHANNEL_CONFIG_NON_PREDEFINED, /*_idxchannelnames*/ 0x00, /*_ctrl*/ AUDIO_CTRL_R << AUDIO_IN_TERM_CTRL_CONNECTOR_POS, /*_stridx*/ 0x00),\ + /* Output Terminal Descriptor(4.7.2.5) */\ + TUD_AUDIO_DESC_OUTPUT_TERM(/*_termid*/ 0x03, /*_termtype*/ AUDIO_TERM_TYPE_USB_STREAMING, /*_assocTerm*/ 0x01, /*_srcid*/ 0x02, /*_clkid*/ 0x04, /*_ctrl*/ 0x0000, /*_stridx*/ 0x00),\ + /* Feature Unit Descriptor(4.7.2.8) */\ + TUD_AUDIO_DESC_FEATURE_UNIT_FOUR_CHANNEL(/*_unitid*/ 0x02, /*_srcid*/ 0x01, /*_ctrlch0master*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch1*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch2*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch3*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS, /*_ctrlch4*/ AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS, /*_stridx*/ 0x00),\ + /* Standard AS Interface Descriptor(4.9.1) */\ + /* Interface 1, Alternate 0 - default alternate setting with 0 bandwidth */\ + TUD_AUDIO_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum)+1), /*_altset*/ 0x00, /*_nEPs*/ 0x00, /*_stridx*/ 0x00),\ + /* Standard AS Interface Descriptor(4.9.1) */\ + /* Interface 1, Alternate 1 - alternate interface for data streaming */\ + TUD_AUDIO_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum)+1), /*_altset*/ 0x01, /*_nEPs*/ 0x01, /*_stridx*/ 0x00),\ + /* Class-Specific AS Interface Descriptor(4.9.2) */\ + TUD_AUDIO_DESC_CS_AS_INT(/*_termid*/ 0x03, /*_ctrl*/ AUDIO_CTRL_NONE, /*_formattype*/ AUDIO_FORMAT_TYPE_I, /*_formats*/ AUDIO_DATA_FORMAT_TYPE_I_PCM, /*_nchannelsphysical*/ 0x04, /*_channelcfg*/ AUDIO_CHANNEL_CONFIG_NON_PREDEFINED, /*_stridx*/ 0x00),\ + /* 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*/ (TUSB_XFER_ISOCHRONOUS | TUSB_ISO_EP_ATT_ASYNCHRONOUS | TUSB_ISO_EP_ATT_DATA), /*_maxEPsize*/ _epsize, /*_interval*/ (CFG_TUSB_RHPORT0_MODE & OPT_MODE_HIGH_SPEED) ? 0x04 : 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) + +// AUDIO simple descriptor (UAC2) for mono speaker +// - 1 Input Terminal, 2 Feature Unit (Mute and Volume Control), 3 Output Terminal, 4 Clock Source + +#define TUD_AUDIO_SPEAKER_MONO_FB_DESC_LEN (TUD_AUDIO_DESC_IAD_LEN\ + + TUD_AUDIO_DESC_STD_AC_LEN\ + + TUD_AUDIO_DESC_CS_AC_LEN\ + + TUD_AUDIO_DESC_CLK_SRC_LEN\ + + TUD_AUDIO_DESC_INPUT_TERM_LEN\ + + TUD_AUDIO_DESC_OUTPUT_TERM_LEN\ + + TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL_LEN\ + + TUD_AUDIO_DESC_STD_AS_INT_LEN\ + + TUD_AUDIO_DESC_STD_AS_INT_LEN\ + + TUD_AUDIO_DESC_CS_AS_INT_LEN\ + + TUD_AUDIO_DESC_TYPE_I_FORMAT_LEN\ + + TUD_AUDIO_DESC_STD_AS_ISO_EP_LEN\ + + TUD_AUDIO_DESC_CS_AS_ISO_EP_LEN\ + + TUD_AUDIO_DESC_STD_AS_ISO_FB_EP_LEN) + +#define TUD_AUDIO_SPEAKER_MONO_FB_DESCRIPTOR(_itfnum, _stridx, _nBytesPerSample, _nBitsUsedPerSample, _epout, _epsize, _epfb) \ + /* Standard Interface Association Descriptor (IAD) */\ + TUD_AUDIO_DESC_IAD(/*_firstitfs*/ _itfnum, /*_nitfs*/ 0x02, /*_stridx*/ 0x00),\ + /* Standard AC Interface Descriptor(4.7.1) */\ + TUD_AUDIO_DESC_STD_AC(/*_itfnum*/ _itfnum, /*_nEPs*/ 0x00, /*_stridx*/ _stridx),\ + /* Class-Specific AC Interface Header Descriptor(4.7.2) */\ + TUD_AUDIO_DESC_CS_AC(/*_bcdADC*/ 0x0200, /*_category*/ AUDIO_FUNC_DESKTOP_SPEAKER, /*_totallen*/ TUD_AUDIO_DESC_CLK_SRC_LEN+TUD_AUDIO_DESC_INPUT_TERM_LEN+TUD_AUDIO_DESC_OUTPUT_TERM_LEN+TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL_LEN, /*_ctrl*/ AUDIO_CS_AS_INTERFACE_CTRL_LATENCY_POS),\ + /* Clock Source Descriptor(4.7.2.1) */\ + TUD_AUDIO_DESC_CLK_SRC(/*_clkid*/ 0x04, /*_attr*/ AUDIO_CLOCK_SOURCE_ATT_INT_FIX_CLK, /*_ctrl*/ (AUDIO_CTRL_R << AUDIO_CLOCK_SOURCE_CTRL_CLK_FRQ_POS), /*_assocTerm*/ 0x01, /*_stridx*/ 0x00),\ + /* Input Terminal Descriptor(4.7.2.4) */\ + TUD_AUDIO_DESC_INPUT_TERM(/*_termid*/ 0x01, /*_termtype*/ AUDIO_TERM_TYPE_USB_STREAMING, /*_assocTerm*/ 0x00, /*_clkid*/ 0x04, /*_nchannelslogical*/ 0x01, /*_channelcfg*/ AUDIO_CHANNEL_CONFIG_NON_PREDEFINED, /*_idxchannelnames*/ 0x00, /*_ctrl*/ 0 * (AUDIO_CTRL_R << AUDIO_IN_TERM_CTRL_CONNECTOR_POS), /*_stridx*/ 0x00),\ + /* Output Terminal Descriptor(4.7.2.5) */\ + TUD_AUDIO_DESC_OUTPUT_TERM(/*_termid*/ 0x03, /*_termtype*/ AUDIO_TERM_TYPE_OUT_DESKTOP_SPEAKER, /*_assocTerm*/ 0x01, /*_srcid*/ 0x02, /*_clkid*/ 0x04, /*_ctrl*/ 0x0000, /*_stridx*/ 0x00),\ + /* Feature Unit Descriptor(4.7.2.8) */\ + TUD_AUDIO_DESC_FEATURE_UNIT_ONE_CHANNEL(/*_unitid*/ 0x02, /*_srcid*/ 0x01, /*_ctrlch0master*/ 0 * (AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS), /*_ctrlch1*/ 0 * (AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_MUTE_POS | AUDIO_CTRL_RW << AUDIO_FEATURE_UNIT_CTRL_VOLUME_POS), /*_stridx*/ 0x00),\ + /* Standard AS Interface Descriptor(4.9.1) */\ + /* Interface 1, Alternate 0 - default alternate setting with 0 bandwidth */\ + TUD_AUDIO_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum) + 1), /*_altset*/ 0x00, /*_nEPs*/ 0x00, /*_stridx*/ 0x00),\ + /* Standard AS Interface Descriptor(4.9.1) */\ + /* Interface 1, Alternate 1 - alternate interface for data streaming */\ + TUD_AUDIO_DESC_STD_AS_INT(/*_itfnum*/ (uint8_t)((_itfnum) + 1), /*_altset*/ 0x01, /*_nEPs*/ 0x02, /*_stridx*/ 0x00),\ + /* Class-Specific AS Interface Descriptor(4.9.2) */\ + TUD_AUDIO_DESC_CS_AS_INT(/*_termid*/ 0x01, /*_ctrl*/ AUDIO_CTRL_NONE, /*_formattype*/ AUDIO_FORMAT_TYPE_I, /*_formats*/ AUDIO_DATA_FORMAT_TYPE_I_PCM, /*_nchannelsphysical*/ 0x01, /*_channelcfg*/ AUDIO_CHANNEL_CONFIG_NON_PREDEFINED, /*_stridx*/ 0x00),\ + /* 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*/ (TUSB_XFER_ISOCHRONOUS | TUSB_ISO_EP_ATT_ASYNCHRONOUS | TUSB_ISO_EP_ATT_DATA), /*_maxEPsize*/ _epsize, /*_interval*/ (CFG_TUSB_RHPORT0_MODE & OPT_MODE_HIGH_SPEED) ? 0x04 : 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) */\ + TUD_AUDIO_DESC_STD_AS_ISO_FB_EP(/*_ep*/ _epfb, /*_interval*/ 1)\ + +// Calculate wMaxPacketSize of Endpoints +#define TUD_AUDIO_EP_SIZE(_maxFrequency, _nBytesPerSample, _nChannels) \ + ((((_maxFrequency + ((CFG_TUSB_RHPORT0_MODE & OPT_MODE_HIGH_SPEED) ? 7999 : 999)) / ((CFG_TUSB_RHPORT0_MODE & OPT_MODE_HIGH_SPEED) ? 8000 : 1000)) + 1) * _nBytesPerSample * _nChannels) + + +//--------------------------------------------------------------------+ +// USBTMC/USB488 Descriptor Templates +//--------------------------------------------------------------------+ + +#define TUD_USBTMC_APP_CLASS (TUSB_CLASS_APPLICATION_SPECIFIC) +#define TUD_USBTMC_APP_SUBCLASS 0x03u + +#define TUD_USBTMC_PROTOCOL_STD 0x00u +#define TUD_USBTMC_PROTOCOL_USB488 0x01u + +// Interface number, number of endpoints, EP string index, USB_TMC_PROTOCOL*, bulk-out endpoint ID, +// bulk-in endpoint ID +#define TUD_USBTMC_IF_DESCRIPTOR(_itfnum, _bNumEndpoints, _stridx, _itfProtocol) \ + /* Interface */ \ + 0x09, TUSB_DESC_INTERFACE, _itfnum, 0x00, _bNumEndpoints, TUD_USBTMC_APP_CLASS, TUD_USBTMC_APP_SUBCLASS, _itfProtocol, _stridx + +#define TUD_USBTMC_IF_DESCRIPTOR_LEN 9u + +#define TUD_USBTMC_BULK_DESCRIPTORS(_epout, _epin, _bulk_epsize) \ + /* Endpoint Out */ \ + 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_bulk_epsize), 0u, \ + /* Endpoint In */ \ + 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_bulk_epsize), 0u + +#define TUD_USBTMC_BULK_DESCRIPTORS_LEN (7u+7u) + +/* optional interrupt endpoint */ \ +// _int_pollingInterval : for LS/FS, expressed in frames (1ms each). 16 may be a good number? +#define TUD_USBTMC_INT_DESCRIPTOR(_ep_interrupt, _ep_interrupt_size, _int_pollingInterval ) \ + 7, TUSB_DESC_ENDPOINT, _ep_interrupt, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_interrupt_size), 0x16 + +#define TUD_USBTMC_INT_DESCRIPTOR_LEN (7u) + +//--------------------------------------------------------------------+ +// Vendor Descriptor Templates +//--------------------------------------------------------------------+ + +#define TUD_VENDOR_DESC_LEN (9+7+7) + +// Interface number, string index, EP Out & IN address, EP size +#define TUD_VENDOR_DESCRIPTOR(_itfnum, _stridx, _epout, _epin, _epsize) \ + /* Interface */\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 2, TUSB_CLASS_VENDOR_SPECIFIC, 0x00, 0x00, _stridx,\ + /* Endpoint Out */\ + 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0,\ + /* Endpoint In */\ + 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 + +//--------------------------------------------------------------------+ +// DFU Runtime Descriptor Templates +//--------------------------------------------------------------------+ + +#define TUD_DFU_APP_CLASS (TUSB_CLASS_APPLICATION_SPECIFIC) +#define TUD_DFU_APP_SUBCLASS (APP_SUBCLASS_DFU_RUNTIME) + +// Length of template descriptr: 18 bytes +#define TUD_DFU_RT_DESC_LEN (9 + 9) + +// DFU runtime descriptor +// Interface number, string index, attributes, detach timeout, transfer size +#define TUD_DFU_RT_DESCRIPTOR(_itfnum, _stridx, _attr, _timeout, _xfer_size) \ + /* Interface */ \ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 0, TUD_DFU_APP_CLASS, TUD_DFU_APP_SUBCLASS, DFU_PROTOCOL_RT, _stridx, \ + /* Function */ \ + 9, DFU_DESC_FUNCTIONAL, _attr, U16_TO_U8S_LE(_timeout), U16_TO_U8S_LE(_xfer_size), U16_TO_U8S_LE(0x0101) + +//--------------------------------------------------------------------+ +// DFU Descriptor Templates +//--------------------------------------------------------------------+ + +// Length of template descriptor: 9 bytes + number of alternatives * 9 +#define TUD_DFU_DESC_LEN(_alt_count) (9 + (_alt_count) * 9) + +// Interface number, Alternate count, starting string index, attributes, detach timeout, transfer size +// Note: Alternate count must be numberic or macro, string index is increased by one for each Alt interface +#define TUD_DFU_DESCRIPTOR(_itfnum, _alt_count, _stridx, _attr, _timeout, _xfer_size) \ + TU_XSTRCAT(_TUD_DFU_ALT_,_alt_count)(_itfnum, 0, _stridx), \ + /* Function */ \ + 9, DFU_DESC_FUNCTIONAL, _attr, U16_TO_U8S_LE(_timeout), U16_TO_U8S_LE(_xfer_size), U16_TO_U8S_LE(0x0101) + +#define _TUD_DFU_ALT(_itfnum, _alt, _stridx) \ + /* Interface */ \ + 9, TUSB_DESC_INTERFACE, _itfnum, _alt, 0, TUD_DFU_APP_CLASS, TUD_DFU_APP_SUBCLASS, DFU_PROTOCOL_DFU, _stridx + +#define _TUD_DFU_ALT_1(_itfnum, _alt_count, _stridx) \ + _TUD_DFU_ALT(_itfnum, _alt_count, _stridx) + +#define _TUD_DFU_ALT_2(_itfnum, _alt_count, _stridx) \ + _TUD_DFU_ALT(_itfnum, _alt_count, _stridx), \ + _TUD_DFU_ALT_1(_itfnum, _alt_count+1, _stridx+1) + +#define _TUD_DFU_ALT_3(_itfnum, _alt_count, _stridx) \ + _TUD_DFU_ALT(_itfnum, _alt_count, _stridx), \ + _TUD_DFU_ALT_2(_itfnum, _alt_count+1, _stridx+1) + +#define _TUD_DFU_ALT_4(_itfnum, _alt_count, _stridx) \ + _TUD_DFU_ALT(_itfnum, _alt_count, _stridx), \ + _TUD_DFU_ALT_3(_itfnum, _alt_count+1, _stridx+1) + +#define _TUD_DFU_ALT_5(_itfnum, _alt_count, _stridx) \ + _TUD_DFU_ALT(_itfnum, _alt_count, _stridx), \ + _TUD_DFU_ALT_4(_itfnum, _alt_count+1, _stridx+1) + +#define _TUD_DFU_ALT_6(_itfnum, _alt_count, _stridx) \ + _TUD_DFU_ALT(_itfnum, _alt_count, _stridx), \ + _TUD_DFU_ALT_5(_itfnum, _alt_count+1, _stridx+1) + +#define _TUD_DFU_ALT_7(_itfnum, _alt_count, _stridx) \ + _TUD_DFU_ALT(_itfnum, _alt_count, _stridx), \ + _TUD_DFU_ALT_6(_itfnum, _alt_count+1, _stridx+1) + +#define _TUD_DFU_ALT_8(_itfnum, _alt_count, _stridx) \ + _TUD_DFU_ALT(_itfnum, _alt_count, _stridx), \ + _TUD_DFU_ALT_7(_itfnum, _alt_count+1, _stridx+1) + +//--------------------------------------------------------------------+ +// CDC-ECM Descriptor Templates +//--------------------------------------------------------------------+ + +// Length of template descriptor: 71 bytes +#define TUD_CDC_ECM_DESC_LEN (8+9+5+5+13+7+9+9+7+7) + +// CDC-ECM Descriptor Template +// Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size. +#define TUD_CDC_ECM_DESCRIPTOR(_itfnum, _desc_stridx, _mac_stridx, _ep_notif, _ep_notif_size, _epout, _epin, _epsize, _maxsegmentsize) \ + /* Interface Association */\ + 8, TUSB_DESC_INTERFACE_ASSOCIATION, _itfnum, 2, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_ETHERNET_CONTROL_MODEL, 0, 0,\ + /* CDC Control Interface */\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 1, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_ETHERNET_CONTROL_MODEL, 0, _desc_stridx,\ + /* CDC-ECM Header */\ + 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_HEADER, U16_TO_U8S_LE(0x0120),\ + /* CDC-ECM Union */\ + 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_UNION, _itfnum, (uint8_t)((_itfnum) + 1),\ + /* CDC-ECM Functional Descriptor */\ + 13, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_ETHERNET_NETWORKING, _mac_stridx, 0, 0, 0, 0, U16_TO_U8S_LE(_maxsegmentsize), U16_TO_U8S_LE(0), 0,\ + /* Endpoint Notification */\ + 7, TUSB_DESC_ENDPOINT, _ep_notif, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_notif_size), 1,\ + /* CDC Data Interface (default inactive) */\ + 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 0, 0, TUSB_CLASS_CDC_DATA, 0, 0, 0,\ + /* CDC Data Interface (alternative active) */\ + 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 1, 2, TUSB_CLASS_CDC_DATA, 0, 0, 0,\ + /* Endpoint In */\ + 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0,\ + /* Endpoint Out */\ + 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 + +//--------------------------------------------------------------------+ +// RNDIS Descriptor Templates +//--------------------------------------------------------------------+ + +#if 0 +/* Windows XP */ +#define TUD_RNDIS_ITF_CLASS TUSB_CLASS_CDC +#define TUD_RNDIS_ITF_SUBCLASS CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL +#define TUD_RNDIS_ITF_PROTOCOL 0xFF /* CDC_COMM_PROTOCOL_MICROSOFT_RNDIS */ +#else +/* Windows 7+ */ +#define TUD_RNDIS_ITF_CLASS TUSB_CLASS_WIRELESS_CONTROLLER +#define TUD_RNDIS_ITF_SUBCLASS 0x01 +#define TUD_RNDIS_ITF_PROTOCOL 0x03 +#endif + +// Length of template descriptor: 66 bytes +#define TUD_RNDIS_DESC_LEN (8+9+5+5+4+5+7+9+7+7) + +// RNDIS Descriptor Template +// Interface number, string index, EP notification address and size, EP data address (out, in) and size. +#define TUD_RNDIS_DESCRIPTOR(_itfnum, _stridx, _ep_notif, _ep_notif_size, _epout, _epin, _epsize) \ + /* Interface Association */\ + 8, TUSB_DESC_INTERFACE_ASSOCIATION, _itfnum, 2, TUD_RNDIS_ITF_CLASS, TUD_RNDIS_ITF_SUBCLASS, TUD_RNDIS_ITF_PROTOCOL, 0,\ + /* CDC Control Interface */\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 1, TUD_RNDIS_ITF_CLASS, TUD_RNDIS_ITF_SUBCLASS, TUD_RNDIS_ITF_PROTOCOL, _stridx,\ + /* CDC-ACM Header */\ + 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_HEADER, U16_TO_U8S_LE(0x0110),\ + /* CDC Call Management */\ + 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_CALL_MANAGEMENT, 0, (uint8_t)((_itfnum) + 1),\ + /* ACM */\ + 4, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_ABSTRACT_CONTROL_MANAGEMENT, 0,\ + /* CDC Union */\ + 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_UNION, _itfnum, (uint8_t)((_itfnum) + 1),\ + /* Endpoint Notification */\ + 7, TUSB_DESC_ENDPOINT, _ep_notif, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_notif_size), 1,\ + /* CDC Data Interface */\ + 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 0, 2, TUSB_CLASS_CDC_DATA, 0, 0, 0,\ + /* Endpoint In */\ + 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0,\ + /* Endpoint Out */\ + 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 + +//--------------------------------------------------------------------+ +// Bluetooth Radio Descriptor Templates +//--------------------------------------------------------------------+ + +#define TUD_BT_APP_CLASS (TUSB_CLASS_WIRELESS_CONTROLLER) +#define TUD_BT_APP_SUBCLASS 0x01 +#define TUD_BT_PROTOCOL_PRIMARY_CONTROLLER 0x01 +#define TUD_BT_PROTOCOL_AMP_CONTROLLER 0x02 + +#ifndef CFG_TUD_BTH_ISO_ALT_COUNT +#define CFG_TUD_BTH_ISO_ALT_COUNT 0 +#endif + +// Length of template descriptor: 38 bytes + number of ISO alternatives * 23 +#define TUD_BTH_DESC_LEN (8 + 9 + 7 + 7 + 7 + (CFG_TUD_BTH_ISO_ALT_COUNT) * (9 + 7 + 7)) + +/* Primary Interface */ +#define TUD_BTH_PRI_ITF(_itfnum, _stridx, _ep_evt, _ep_evt_size, _ep_evt_interval, _ep_in, _ep_out, _ep_size) \ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 3, TUD_BT_APP_CLASS, TUD_BT_APP_SUBCLASS, TUD_BT_PROTOCOL_PRIMARY_CONTROLLER, _stridx, \ + /* Endpoint In for events */ \ + 7, TUSB_DESC_ENDPOINT, _ep_evt, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_evt_size), _ep_evt_interval, \ + /* Endpoint In for ACL data */ \ + 7, TUSB_DESC_ENDPOINT, _ep_in, TUSB_XFER_BULK, U16_TO_U8S_LE(_ep_size), 1, \ + /* Endpoint Out for ACL data */ \ + 7, TUSB_DESC_ENDPOINT, _ep_out, TUSB_XFER_BULK, U16_TO_U8S_LE(_ep_size), 1 + +#define TUD_BTH_ISO_ITF(_itfnum, _alt, _ep_in, _ep_out, _n) ,\ + /* Interface with 2 endpoints */ \ + 9, TUSB_DESC_INTERFACE, _itfnum, _alt, 2, TUD_BT_APP_CLASS, TUD_BT_APP_SUBCLASS, TUD_BT_PROTOCOL_PRIMARY_CONTROLLER, 0, \ + /* Isochronous endpoints */ \ + 7, TUSB_DESC_ENDPOINT, _ep_in, TUSB_XFER_ISOCHRONOUS, U16_TO_U8S_LE(_n), 1, \ + 7, TUSB_DESC_ENDPOINT, _ep_out, TUSB_XFER_ISOCHRONOUS, U16_TO_U8S_LE(_n), 1 + +#define _FIRST(a, ...) a +#define _REST(a, ...) __VA_ARGS__ + +#define TUD_BTH_ISO_ITF_0(_itfnum, ...) +#define TUD_BTH_ISO_ITF_1(_itfnum, _ep_in, _ep_out, ...) TUD_BTH_ISO_ITF(_itfnum, (CFG_TUD_BTH_ISO_ALT_COUNT) - 1, _ep_in, _ep_out, _FIRST(__VA_ARGS__)) +#define TUD_BTH_ISO_ITF_2(_itfnum, _ep_in, _ep_out, ...) TUD_BTH_ISO_ITF(_itfnum, (CFG_TUD_BTH_ISO_ALT_COUNT) - 2, _ep_in, _ep_out, _FIRST(__VA_ARGS__)) \ + TUD_BTH_ISO_ITF_1(_itfnum, _ep_in, _ep_out, _REST(__VA_ARGS__)) +#define TUD_BTH_ISO_ITF_3(_itfnum, _ep_in, _ep_out, ...) TUD_BTH_ISO_ITF(_itfnum, (CFG_TUD_BTH_ISO_ALT_COUNT) - 3, _ep_in, _ep_out, _FIRST(__VA_ARGS__)) \ + TUD_BTH_ISO_ITF_2(_itfnum, _ep_in, _ep_out, _REST(__VA_ARGS__)) +#define TUD_BTH_ISO_ITF_4(_itfnum, _ep_in, _ep_out, ...) TUD_BTH_ISO_ITF(_itfnum, (CFG_TUD_BTH_ISO_ALT_COUNT) - 4, _ep_in, _ep_out, _FIRST(__VA_ARGS__)) \ + TUD_BTH_ISO_ITF_3(_itfnum, _ep_in, _ep_out, _REST(__VA_ARGS__)) +#define TUD_BTH_ISO_ITF_5(_itfnum, _ep_in, _ep_out, ...) TUD_BTH_ISO_ITF(_itfnum, (CFG_TUD_BTH_ISO_ALT_COUNT) - 5, _ep_in, _ep_out, _FIRST(__VA_ARGS__)) \ + TUD_BTH_ISO_ITF_4(_itfnum, _ep_in, _ep_out, _REST(__VA_ARGS__)) +#define TUD_BTH_ISO_ITF_6(_itfnum, _ep_in, _ep_out, ...) TUD_BTH_ISO_ITF(_itfnum, (CFG_TUD_BTH_ISO_ALT_COUNT) - 6, _ep_in, _ep_out, _FIRST(__VA_ARGS__)) \ + TUD_BTH_ISO_ITF_5(_itfnum, _ep_in, _ep_out, _REST(__VA_ARGS__)) + +#define TUD_BTH_ISO_ITFS(_itfnum, _ep_in, _ep_out, ...) \ + TU_XSTRCAT(TUD_BTH_ISO_ITF_, CFG_TUD_BTH_ISO_ALT_COUNT)(_itfnum, _ep_in, _ep_out, __VA_ARGS__) + +// BT Primary controller descriptor +// Interface number, string index, attributes, event endpoint, event endpoint size, interval, data in, data out, data endpoint size, iso endpoint sizes +// TODO BTH should also use IAD like CDC for composite device +#define TUD_BTH_DESCRIPTOR(_itfnum, _stridx, _ep_evt, _ep_evt_size, _ep_evt_interval, _ep_in, _ep_out, _ep_size,...) \ + /* Interface Associate */\ + 8, TUSB_DESC_INTERFACE_ASSOCIATION, _itfnum, 2, TUD_BT_APP_CLASS, TUD_BT_APP_SUBCLASS, TUD_BT_PROTOCOL_PRIMARY_CONTROLLER, 0,\ + TUD_BTH_PRI_ITF(_itfnum, _stridx, _ep_evt, _ep_evt_size, _ep_evt_interval, _ep_in, _ep_out, _ep_size) \ + TUD_BTH_ISO_ITFS(_itfnum + 1, _ep_in + 1, _ep_out + 1, __VA_ARGS__) + +//--------------------------------------------------------------------+ +// CDC-NCM Descriptor Templates +//--------------------------------------------------------------------+ + +// Length of template descriptor +#define TUD_CDC_NCM_DESC_LEN (8+9+5+5+13+6+7+9+9+7+7) + +// CDC-ECM Descriptor Template +// Interface number, description string index, MAC address string index, EP notification address and size, EP data address (out, in), and size, max segment size. +#define TUD_CDC_NCM_DESCRIPTOR(_itfnum, _desc_stridx, _mac_stridx, _ep_notif, _ep_notif_size, _epout, _epin, _epsize, _maxsegmentsize) \ + /* Interface Association */\ + 8, TUSB_DESC_INTERFACE_ASSOCIATION, _itfnum, 2, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_NETWORK_CONTROL_MODEL, 0, 0,\ + /* CDC Control Interface */\ + 9, TUSB_DESC_INTERFACE, _itfnum, 0, 1, TUSB_CLASS_CDC, CDC_COMM_SUBCLASS_NETWORK_CONTROL_MODEL, 0, _desc_stridx,\ + /* CDC-NCM Header */\ + 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_HEADER, U16_TO_U8S_LE(0x0110),\ + /* CDC-NCM Union */\ + 5, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_UNION, _itfnum, (uint8_t)((_itfnum) + 1),\ + /* CDC-NCM Functional Descriptor */\ + 13, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_ETHERNET_NETWORKING, _mac_stridx, 0, 0, 0, 0, U16_TO_U8S_LE(_maxsegmentsize), U16_TO_U8S_LE(0), 0, \ + /* CDC-NCM Functional Descriptor */\ + 6, TUSB_DESC_CS_INTERFACE, CDC_FUNC_DESC_NCM, U16_TO_U8S_LE(0x0100), 0, \ + /* Endpoint Notification */\ + 7, TUSB_DESC_ENDPOINT, _ep_notif, TUSB_XFER_INTERRUPT, U16_TO_U8S_LE(_ep_notif_size), 50,\ + /* CDC Data Interface (default inactive) */\ + 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 0, 0, TUSB_CLASS_CDC_DATA, 0, NCM_DATA_PROTOCOL_NETWORK_TRANSFER_BLOCK, 0,\ + /* CDC Data Interface (alternative active) */\ + 9, TUSB_DESC_INTERFACE, (uint8_t)((_itfnum)+1), 1, 2, TUSB_CLASS_CDC_DATA, 0, NCM_DATA_PROTOCOL_NETWORK_TRANSFER_BLOCK, 0,\ + /* Endpoint In */\ + 7, TUSB_DESC_ENDPOINT, _epin, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0,\ + /* Endpoint Out */\ + 7, TUSB_DESC_ENDPOINT, _epout, TUSB_XFER_BULK, U16_TO_U8S_LE(_epsize), 0 + +#ifdef __cplusplus +} +#endif + +#endif /* _TUSB_USBD_H_ */ + +/** @} */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/device/usbd_control.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/device/usbd_control.c new file mode 100644 index 000000000..7a8244699 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/device/usbd_control.c @@ -0,0 +1,233 @@ +/* + * 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 TUSB_OPT_DEVICE_ENABLED + +#include "tusb.h" +#include "device/usbd_pvt.h" +#include "dcd.h" + +#if CFG_TUSB_DEBUG >= 2 +extern void usbd_driver_print_control_complete_name(usbd_control_xfer_cb_t callback); +#endif + +enum +{ + EDPT_CTRL_OUT = 0x00, + EDPT_CTRL_IN = 0x80 +}; + +typedef struct +{ + tusb_control_request_t request; + + uint8_t* buffer; + uint16_t data_len; + uint16_t total_xferred; + + usbd_control_xfer_cb_t complete_cb; +} usbd_control_xfer_t; + +static usbd_control_xfer_t _ctrl_xfer; + +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN +static uint8_t _usbd_ctrl_buf[CFG_TUD_ENDPOINT0_SIZE]; + +//--------------------------------------------------------------------+ +// Application API +//--------------------------------------------------------------------+ + +// Queue ZLP status transaction +static inline bool _status_stage_xact(uint8_t rhport, tusb_control_request_t const * request) +{ + // Opposite to endpoint in Data Phase + uint8_t const ep_addr = request->bmRequestType_bit.direction ? EDPT_CTRL_OUT : EDPT_CTRL_IN; + return usbd_edpt_xfer(rhport, ep_addr, NULL, 0); +} + +// Status phase +bool tud_control_status(uint8_t rhport, tusb_control_request_t const * request) +{ + _ctrl_xfer.request = (*request); + _ctrl_xfer.buffer = NULL; + _ctrl_xfer.total_xferred = 0; + _ctrl_xfer.data_len = 0; + + return _status_stage_xact(rhport, request); +} + +// Queue a transaction in Data Stage +// Each transaction has up to Endpoint0's max packet size. +// This function can also transfer an zero-length packet +static bool _data_stage_xact(uint8_t rhport) +{ + uint16_t const xact_len = tu_min16(_ctrl_xfer.data_len - _ctrl_xfer.total_xferred, CFG_TUD_ENDPOINT0_SIZE); + + uint8_t ep_addr = EDPT_CTRL_OUT; + + if ( _ctrl_xfer.request.bmRequestType_bit.direction == TUSB_DIR_IN ) + { + ep_addr = EDPT_CTRL_IN; + if ( xact_len ) memcpy(_usbd_ctrl_buf, _ctrl_xfer.buffer, xact_len); + } + + return usbd_edpt_xfer(rhport, ep_addr, xact_len ? _usbd_ctrl_buf : NULL, xact_len); +} + +// Transmit data to/from the control endpoint. +// If the request's wLength is zero, a status packet is sent instead. +bool tud_control_xfer(uint8_t rhport, tusb_control_request_t const * request, void* buffer, uint16_t len) +{ + _ctrl_xfer.request = (*request); + _ctrl_xfer.buffer = (uint8_t*) buffer; + _ctrl_xfer.total_xferred = 0U; + _ctrl_xfer.data_len = tu_min16(len, request->wLength); + + if (request->wLength > 0U) + { + if(_ctrl_xfer.data_len > 0U) + { + TU_ASSERT(buffer); + } + +// TU_LOG2(" Control total data length is %u bytes\r\n", _ctrl_xfer.data_len); + + // Data stage + TU_ASSERT( _data_stage_xact(rhport) ); + } + else + { + // Status stage + TU_ASSERT( _status_stage_xact(rhport, request) ); + } + + return true; +} + +//--------------------------------------------------------------------+ +// USBD API +//--------------------------------------------------------------------+ + +void usbd_control_reset(void); +void usbd_control_set_request(tusb_control_request_t const *request); +void usbd_control_set_complete_callback( usbd_control_xfer_cb_t fp ); +bool usbd_control_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t event, uint32_t xferred_bytes); + +void usbd_control_reset(void) +{ + tu_varclr(&_ctrl_xfer); +} + +// Set complete callback +void usbd_control_set_complete_callback( usbd_control_xfer_cb_t fp ) +{ + _ctrl_xfer.complete_cb = fp; +} + +// for dcd_set_address where DCD is responsible for status response +void usbd_control_set_request(tusb_control_request_t const *request) +{ + _ctrl_xfer.request = (*request); + _ctrl_xfer.buffer = NULL; + _ctrl_xfer.total_xferred = 0; + _ctrl_xfer.data_len = 0; +} + +// callback when a transaction complete on +// - DATA stage of control endpoint or +// - Status stage +bool usbd_control_xfer_cb (uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) +{ + (void) result; + + // Endpoint Address is opposite to direction bit, this is Status Stage complete event + if ( tu_edpt_dir(ep_addr) != _ctrl_xfer.request.bmRequestType_bit.direction ) + { + TU_ASSERT(0 == xferred_bytes); + + // invoke optional dcd hook if available + if (dcd_edpt0_status_complete) dcd_edpt0_status_complete(rhport, &_ctrl_xfer.request); + + if (_ctrl_xfer.complete_cb) + { + // TODO refactor with usbd_driver_print_control_complete_name + _ctrl_xfer.complete_cb(rhport, CONTROL_STAGE_ACK, &_ctrl_xfer.request); + } + + return true; + } + + if ( _ctrl_xfer.request.bmRequestType_bit.direction == TUSB_DIR_OUT ) + { + TU_VERIFY(_ctrl_xfer.buffer); + memcpy(_ctrl_xfer.buffer, _usbd_ctrl_buf, xferred_bytes); + TU_LOG_MEM(2, _usbd_ctrl_buf, xferred_bytes, 2); + } + + _ctrl_xfer.total_xferred += xferred_bytes; + _ctrl_xfer.buffer += xferred_bytes; + + // Data Stage is complete when all request's length are transferred or + // a short packet is sent including zero-length packet. + if ( (_ctrl_xfer.request.wLength == _ctrl_xfer.total_xferred) || (xferred_bytes < CFG_TUD_ENDPOINT0_SIZE) ) + { + // DATA stage is complete + bool is_ok = true; + + // invoke complete callback if set + // callback can still stall control in status phase e.g out data does not make sense + if ( _ctrl_xfer.complete_cb ) + { + #if CFG_TUSB_DEBUG >= 2 + usbd_driver_print_control_complete_name(_ctrl_xfer.complete_cb); + #endif + + is_ok = _ctrl_xfer.complete_cb(rhport, CONTROL_STAGE_DATA, &_ctrl_xfer.request); + } + + if ( is_ok ) + { + // Send status + TU_ASSERT( _status_stage_xact(rhport, &_ctrl_xfer.request) ); + }else + { + // Stall both IN and OUT control endpoint + dcd_edpt_stall(rhport, EDPT_CTRL_OUT); + dcd_edpt_stall(rhport, EDPT_CTRL_IN); + } + } + else + { + // More data to transfer + TU_ASSERT( _data_stage_xact(rhport) ); + } + + return true; +} + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/device/usbd_pvt.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/device/usbd_pvt.h new file mode 100644 index 000000000..7607b9895 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/device/usbd_pvt.h @@ -0,0 +1,115 @@ +/* + * 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 USBD_PVT_H_ +#define USBD_PVT_H_ + +#include "osal/osal.h" +#include "common/tusb_fifo.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Class Driver API +//--------------------------------------------------------------------+ + +typedef struct +{ + #if CFG_TUSB_DEBUG >= 2 + char const* name; + #endif + + void (* init ) (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); + bool (* xfer_cb ) (uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); + void (* sof ) (uint8_t rhport); /* optional */ +} usbd_class_driver_t; + +// Invoked when initializing device stack to get additional class drivers. +// Can optionally implemented by application to extend/overwrite class driver support. +// Note: The drivers array must be accessible at all time when stack is active +usbd_class_driver_t const* usbd_app_driver_get_cb(uint8_t* driver_count) TU_ATTR_WEAK; + +typedef bool (*usbd_control_xfer_cb_t)(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request); + +//--------------------------------------------------------------------+ +// USBD Endpoint API +//--------------------------------------------------------------------+ + +// Open an endpoint +bool usbd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const * desc_ep); + +// Close an endpoint +void usbd_edpt_close(uint8_t rhport, uint8_t ep_addr); + +// Submit a usb transfer +bool usbd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes); + +// Submit a usb ISO transfer by use of a FIFO (ring buffer) - all bytes in FIFO get transmitted +bool usbd_edpt_xfer_fifo(uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes); + +// Claim an endpoint before submitting a transfer. +// If caller does not make any transfer, it must release endpoint for others. +bool usbd_edpt_claim(uint8_t rhport, uint8_t ep_addr); + +// Release an endpoint without submitting a transfer +bool usbd_edpt_release(uint8_t rhport, uint8_t ep_addr); + +// Check if endpoint is busy transferring +bool usbd_edpt_busy(uint8_t rhport, uint8_t ep_addr); + +// Stall endpoint +void usbd_edpt_stall(uint8_t rhport, uint8_t ep_addr); + +// Clear stalled endpoint +void usbd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr); + +// Check if endpoint is stalled +bool usbd_edpt_stalled(uint8_t rhport, uint8_t ep_addr); + +// Check if endpoint is ready (not busy and not stalled) +TU_ATTR_ALWAYS_INLINE static inline +bool usbd_edpt_ready(uint8_t rhport, uint8_t ep_addr) +{ + return !usbd_edpt_busy(rhport, ep_addr) && !usbd_edpt_stalled(rhport, ep_addr); +} + +/*------------------------------------------------------------------*/ +/* Helper + *------------------------------------------------------------------*/ + +bool usbd_open_edpt_pair(uint8_t rhport, uint8_t const* p_desc, uint8_t ep_count, uint8_t xfer_type, uint8_t* ep_out, uint8_t* ep_in); +void usbd_defer_func( osal_task_func_t func, void* param, bool in_isr ); + + +#ifdef __cplusplus + } +#endif + +#endif /* USBD_PVT_H_ */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/hcd.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/hcd.h new file mode 100644 index 000000000..eb53d2e80 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/hcd.h @@ -0,0 +1,179 @@ +/* + * 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_HCD_H_ +#define _TUSB_HCD_H_ + +#include "common/tusb_common.h" +#include "osal/osal.h" +#include "common/tusb_fifo.h" +#include "hcd_attr.h" + +#ifdef __cplusplus + extern "C" { +#endif + + //--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +typedef enum +{ + HCD_EVENT_DEVICE_ATTACH, + HCD_EVENT_DEVICE_REMOVE, + HCD_EVENT_XFER_COMPLETE, + + // Not an HCD event, just a convenient way to defer ISR function + USBH_EVENT_FUNC_CALL, + + HCD_EVENT_COUNT +} hcd_eventid_t; + +typedef struct +{ + uint8_t rhport; + uint8_t event_id; + uint8_t dev_addr; + + union + { + // Attach, Remove + struct { + uint8_t hub_addr; + uint8_t hub_port; + uint8_t speed; + } connection; + + // XFER_COMPLETE + struct { + uint8_t ep_addr; + uint8_t result; + uint32_t len; + } xfer_complete; + + // FUNC_CALL + struct { + void (*func) (void*); + void* param; + }func_call; + }; + +} hcd_event_t; + +#if TUSB_OPT_HOST_ENABLED +// Max number of endpoints per device +enum { + // TODO better computation + HCD_MAX_ENDPOINT = CFG_TUH_DEVICE_MAX*(CFG_TUH_HUB + CFG_TUH_HID*2 + CFG_TUH_MSC*2 + CFG_TUH_CDC*3), + HCD_MAX_XFER = HCD_MAX_ENDPOINT*2, +}; + +//#define HCD_MAX_ENDPOINT 16 +//#define HCD_MAX_XFER 16 + +typedef struct { + uint8_t rhport; + uint8_t hub_addr; + uint8_t hub_port; + uint8_t speed; +} hcd_devtree_info_t; + +#endif + +//--------------------------------------------------------------------+ +// Controller API +//--------------------------------------------------------------------+ + +// Initialize controller to host mode +bool hcd_init(uint8_t rhport); + +// Interrupt Handler +void hcd_int_handler(uint8_t rhport); + +// Enable USB interrupt +void hcd_int_enable (uint8_t rhport); + +// Disable USB interrupt +void hcd_int_disable(uint8_t rhport); + +// Get frame number (1ms) +uint32_t hcd_frame_number(uint8_t rhport); + +//--------------------------------------------------------------------+ +// Port API +//--------------------------------------------------------------------+ + +// Get the current connect status of roothub port +bool hcd_port_connect_status(uint8_t rhport); + +// Reset USB bus on the port +void hcd_port_reset(uint8_t rhport); + +// TODO implement later +void hcd_port_reset_end(uint8_t rhport); + +// Get port link speed +tusb_speed_t hcd_port_speed_get(uint8_t rhport); + +// HCD closes all opened endpoints belong to this device +void hcd_device_close(uint8_t rhport, uint8_t dev_addr); + +//--------------------------------------------------------------------+ +// Endpoints API +//--------------------------------------------------------------------+ + +bool hcd_setup_send(uint8_t rhport, uint8_t dev_addr, uint8_t const setup_packet[8]); +bool hcd_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * ep_desc); +bool hcd_edpt_xfer(uint8_t rhport, uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t buflen); +bool hcd_edpt_clear_stall(uint8_t dev_addr, uint8_t ep_addr); + +//--------------------------------------------------------------------+ +// USBH implemented API +//--------------------------------------------------------------------+ + +// Get device tree information of a device +// USB device tree can be complicated and manged by USBH, this help HCD to retrieve +// needed topology info to carry out its work +extern void hcd_devtree_get_info(uint8_t dev_addr, hcd_devtree_info_t* devtree_info); + +//------------- Event API -------------// + +// Called by HCD to notify stack +extern void hcd_event_handler(hcd_event_t const* event, bool in_isr); + +// Helper to send device attach event +extern void hcd_event_device_attach(uint8_t rhport, bool in_isr); + +// Helper to send device removal event +extern void hcd_event_device_remove(uint8_t rhport, bool in_isr); + +// Helper to send USB transfer event +extern void hcd_event_xfer_complete(uint8_t dev_addr, uint8_t ep_addr, uint32_t xferred_bytes, xfer_result_t result, bool in_isr); + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_HCD_H_ */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/hcd_attr.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/hcd_attr.h new file mode 100644 index 000000000..06011c63c --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/hcd_attr.h @@ -0,0 +1,105 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021, 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_HCD_ATTR_H_ +#define TUSB_HCD_ATTR_H_ + +#include "tusb_option.h" + +// Attribute includes +// - ENDPOINT_MAX: max (logical) number of endpoint +// - PORT_HIGHSPEED: mask to indicate which port support highspeed mode, bit0 for port0 and so on. + +//------------- NXP -------------// +#if TU_CHECK_MCU(OPT_MCU_LPC175X_6X, OPT_MCU_LPC177X_8X, OPT_MCU_LPC40XX) + #define HCD_ATTR_OHCI + +#elif TU_CHECK_MCU(OPT_MCU_LPC18XX, OPT_MCU_LPC43XX) + #define HCD_ATTR_EHCI_TRANSDIMENSION + +#elif TU_CHECK_MCU(OPT_MCU_LPC54XXX) + // #define HCD_ATTR_EHCI_NXP_PTD + +#elif TU_CHECK_MCU(OPT_MCU_LPC55XX) + // #define HCD_ATTR_EHCI_NXP_PTD + +#elif TU_CHECK_MCU(OPT_MCU_MIMXRT10XX) + #define HCD_ATTR_EHCI_TRANSDIMENSION + +#elif TU_CHECK_MCU(OPT_MCU_MKL25ZXX) + +//------------- Microchip -------------// +#elif TU_CHECK_MCU(OPT_MCU_SAMD21, OPT_MCU_SAMD51, OPT_MCU_SAME5X) || \ + TU_CHECK_MCU(OPT_MCU_SAMD11, OPT_MCU_SAML21, OPT_MCU_SAML22) + +#elif TU_CHECK_MCU(OPT_MCU_SAMG) + +#elif TU_CHECK_MCU(OPT_MCU_SAMX7X) + +//------------- ST -------------// +#elif TU_CHECK_MCU(OPT_MCU_STM32F0, OPT_MCU_STM32F1, OPT_MCU_STM32F3) || \ + TU_CHECK_MCU(OPT_MCU_STM32L0, OPT_MCU_STM32L1, OPT_MCU_STM32L4) + +#elif TU_CHECK_MCU(OPT_MCU_STM32F2, OPT_MCU_STM32F3, OPT_MCU_STM32F4) + +#elif TU_CHECK_MCU(OPT_MCU_STM32F7) + +#elif TU_CHECK_MCU(OPT_MCU_STM32H7) + +//------------- Sony -------------// +#elif TU_CHECK_MCU(OPT_MCU_CXD56) + +//------------- Nuvoton -------------// +#elif TU_CHECK_MCU(OPT_MCU_NUC505) + +//------------- Espressif -------------// +#elif TU_CHECK_MCU(OPT_MCU_ESP32S2, OPT_MCU_ESP32S3) + +//------------- Raspberry Pi -------------// +#elif TU_CHECK_MCU(OPT_MCU_RP2040) + +//------------- Silabs -------------// +#elif TU_CHECK_MCU(OPT_MCU_EFM32GG) + +//------------- Renesas -------------// +#elif TU_CHECK_MCU(OPT_MCU_RX63X, OPT_MCU_RX65X, OPT_MCU_RX72N) + +//#elif TU_CHECK_MCU(OPT_MCU_MM32F327X) +// #define DCD_ATTR_ENDPOINT_MAX not known yet + +//------------- GigaDevice -------------// +#elif TU_CHECK_MCU(OPT_MCU_GD32VF103) + +#else +// #warning "DCD_ATTR_ENDPOINT_MAX is not defined for this MCU, default to 8" +#endif + +// Default to fullspeed if not defined +//#ifndef PORT_HIGHSPEED +// #define DCD_ATTR_PORT_HIGHSPEED 0x00 +//#endif + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/hub.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/hub.c new file mode 100644 index 000000000..fd4dbd04a --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/hub.c @@ -0,0 +1,388 @@ +/* + * 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 (TUSB_OPT_HOST_ENABLED && CFG_TUH_HUB) + +#include "usbh.h" +#include "usbh_classdriver.h" +#include "hub.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ +typedef struct +{ + uint8_t itf_num; + uint8_t ep_in; + uint8_t port_count; + uint8_t status_change; // data from status change interrupt endpoint + + hub_port_status_response_t port_status; +} hub_interface_t; + +CFG_TUSB_MEM_SECTION static hub_interface_t hub_data[CFG_TUH_HUB]; +CFG_TUSB_MEM_SECTION TU_ATTR_ALIGNED(4) static uint8_t _hub_buffer[sizeof(descriptor_hub_desc_t)]; + +TU_ATTR_ALWAYS_INLINE +static inline hub_interface_t* get_itf(uint8_t dev_addr) +{ + return &hub_data[dev_addr-1-CFG_TUH_DEVICE_MAX]; +} + +#if CFG_TUSB_DEBUG +static char const* const _hub_feature_str[] = +{ + [HUB_FEATURE_PORT_CONNECTION ] = "PORT_CONNECTION", + [HUB_FEATURE_PORT_ENABLE ] = "PORT_ENABLE", + [HUB_FEATURE_PORT_SUSPEND ] = "PORT_SUSPEND", + [HUB_FEATURE_PORT_OVER_CURRENT ] = "PORT_OVER_CURRENT", + [HUB_FEATURE_PORT_RESET ] = "PORT_RESET", + [HUB_FEATURE_PORT_POWER ] = "PORT_POWER", + [HUB_FEATURE_PORT_LOW_SPEED ] = "PORT_LOW_SPEED", + [HUB_FEATURE_PORT_CONNECTION_CHANGE ] = "PORT_CONNECTION_CHANGE", + [HUB_FEATURE_PORT_ENABLE_CHANGE ] = "PORT_ENABLE_CHANGE", + [HUB_FEATURE_PORT_SUSPEND_CHANGE ] = "PORT_SUSPEND_CHANGE", + [HUB_FEATURE_PORT_OVER_CURRENT_CHANGE ] = "PORT_OVER_CURRENT_CHANGE", + [HUB_FEATURE_PORT_RESET_CHANGE ] = "PORT_RESET_CHANGE", + [HUB_FEATURE_PORT_TEST ] = "PORT_TEST", + [HUB_FEATURE_PORT_INDICATOR ] = "PORT_INDICATOR", +}; +#endif + +//--------------------------------------------------------------------+ +// HUB +//--------------------------------------------------------------------+ +bool hub_port_clear_feature(uint8_t hub_addr, uint8_t hub_port, uint8_t feature, tuh_control_complete_cb_t complete_cb) +{ + tusb_control_request_t const request = + { + .bmRequestType_bit = + { + .recipient = TUSB_REQ_RCPT_OTHER, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_OUT + }, + .bRequest = HUB_REQUEST_CLEAR_FEATURE, + .wValue = feature, + .wIndex = hub_port, + .wLength = 0 + }; + + TU_LOG2("HUB Clear Feature: %s, addr = %u port = %u\r\n", _hub_feature_str[feature], hub_addr, hub_port); + TU_ASSERT( tuh_control_xfer(hub_addr, &request, NULL, complete_cb) ); + return true; +} + +bool hub_port_set_feature(uint8_t hub_addr, uint8_t hub_port, uint8_t feature, tuh_control_complete_cb_t complete_cb) +{ + tusb_control_request_t const request = + { + .bmRequestType_bit = + { + .recipient = TUSB_REQ_RCPT_OTHER, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_OUT + }, + .bRequest = HUB_REQUEST_SET_FEATURE, + .wValue = feature, + .wIndex = hub_port, + .wLength = 0 + }; + + TU_LOG2("HUB Set Feature: %s, addr = %u port = %u\r\n", _hub_feature_str[feature], hub_addr, hub_port); + TU_ASSERT( tuh_control_xfer(hub_addr, &request, NULL, complete_cb) ); + return true; +} + +bool hub_port_reset(uint8_t hub_addr, uint8_t hub_port, tuh_control_complete_cb_t complete_cb) +{ + return hub_port_set_feature(hub_addr, hub_port, HUB_FEATURE_PORT_RESET, complete_cb); +} + +bool hub_port_get_status(uint8_t hub_addr, uint8_t hub_port, void* resp, tuh_control_complete_cb_t complete_cb) +{ + tusb_control_request_t const request = + { + .bmRequestType_bit = + { + .recipient = TUSB_REQ_RCPT_OTHER, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_IN + }, + .bRequest = HUB_REQUEST_GET_STATUS, + .wValue = 0, + .wIndex = hub_port, + .wLength = 4 + }; + + TU_LOG2("HUB Get Port Status: addr = %u port = %u\r\n", hub_addr, hub_port); + TU_ASSERT( tuh_control_xfer( hub_addr, &request, resp, complete_cb) ); + return true; +} + +//--------------------------------------------------------------------+ +// CLASS-USBH API (don't require to verify parameters) +//--------------------------------------------------------------------+ +void hub_init(void) +{ + tu_memclr(hub_data, sizeof(hub_data)); +} + +bool hub_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_interface_t const *itf_desc, uint16_t max_len) +{ + TU_VERIFY(TUSB_CLASS_HUB == itf_desc->bInterfaceClass && + 0 == itf_desc->bInterfaceSubClass); + + // hub driver does not support multiple TT yet + TU_VERIFY(itf_desc->bInterfaceProtocol <= 1); + + // msc driver length is fixed + uint16_t const drv_len = sizeof(tusb_desc_interface_t) + sizeof(tusb_desc_endpoint_t); + TU_ASSERT(drv_len <= max_len); + + //------------- Interrupt Status endpoint -------------// + tusb_desc_endpoint_t const *desc_ep = (tusb_desc_endpoint_t const *) tu_desc_next(itf_desc); + + TU_ASSERT(TUSB_DESC_ENDPOINT == desc_ep->bDescriptorType && + TUSB_XFER_INTERRUPT == desc_ep->bmAttributes.xfer, 0); + + TU_ASSERT(usbh_edpt_open(rhport, dev_addr, desc_ep)); + + hub_interface_t* p_hub = get_itf(dev_addr); + + p_hub->itf_num = itf_desc->bInterfaceNumber; + p_hub->ep_in = desc_ep->bEndpointAddress; + + return true; +} + +void hub_close(uint8_t dev_addr) +{ + TU_VERIFY(dev_addr > CFG_TUH_DEVICE_MAX, ); + hub_interface_t* p_hub = get_itf(dev_addr); + + if (p_hub->ep_in) tu_memclr(p_hub, sizeof( hub_interface_t)); +} + +bool hub_status_pipe_queue(uint8_t dev_addr) +{ + hub_interface_t* hub_itf = get_itf(dev_addr); + return usbh_edpt_xfer(dev_addr, hub_itf->ep_in, &hub_itf->status_change, 1); +} + + +//--------------------------------------------------------------------+ +// Set Configure +//--------------------------------------------------------------------+ + +static bool config_set_port_power (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result); +static bool config_port_power_complete (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result); + +bool hub_set_config(uint8_t dev_addr, uint8_t itf_num) +{ + hub_interface_t* p_hub = get_itf(dev_addr); + TU_ASSERT(itf_num == p_hub->itf_num); + + // Get Hub Descriptor + tusb_control_request_t const request = + { + .bmRequestType_bit = + { + .recipient = TUSB_REQ_RCPT_DEVICE, + .type = TUSB_REQ_TYPE_CLASS, + .direction = TUSB_DIR_IN + }, + .bRequest = HUB_REQUEST_GET_DESCRIPTOR, + .wValue = 0, + .wIndex = 0, + .wLength = sizeof(descriptor_hub_desc_t) + }; + + TU_ASSERT( tuh_control_xfer(dev_addr, &request, _hub_buffer, config_set_port_power) ); + + return true; +} + +static bool config_set_port_power (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result) +{ + (void) request; + TU_ASSERT(XFER_RESULT_SUCCESS == result); + + hub_interface_t* p_hub = get_itf(dev_addr); + + // only use number of ports in hub descriptor + descriptor_hub_desc_t const* desc_hub = (descriptor_hub_desc_t const*) _hub_buffer; + p_hub->port_count = desc_hub->bNbrPorts; + + // May need to GET_STATUS + + // Set Port Power to be able to detect connection, starting with port 1 + uint8_t const hub_port = 1; + return hub_port_set_feature(dev_addr, hub_port, HUB_FEATURE_PORT_POWER, config_port_power_complete); +} + +static bool config_port_power_complete (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result) +{ + TU_ASSERT(XFER_RESULT_SUCCESS == result); + hub_interface_t* p_hub = get_itf(dev_addr); + + if (request->wIndex == p_hub->port_count) + { + // All ports are power -> queue notification status endpoint and + // complete the SET CONFIGURATION + TU_ASSERT( usbh_edpt_xfer(dev_addr, p_hub->ep_in, &p_hub->status_change, 1) ); + + usbh_driver_set_config_complete(dev_addr, p_hub->itf_num); + }else + { + // power next port + uint8_t const hub_port = (uint8_t) (request->wIndex + 1); + return hub_port_set_feature(dev_addr, hub_port, HUB_FEATURE_PORT_POWER, config_port_power_complete); + } + + return true; +} + +//--------------------------------------------------------------------+ +// Connection Changes +//--------------------------------------------------------------------+ + +static bool connection_get_status_complete (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result); +static bool connection_clear_conn_change_complete (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result); +static bool connection_port_reset_complete (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result); + +// callback as response of interrupt endpoint polling +bool hub_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) +{ + (void) xferred_bytes; // TODO can be more than 1 for hub with lots of ports + (void) ep_addr; + TU_ASSERT(result == XFER_RESULT_SUCCESS); + + hub_interface_t* p_hub = get_itf(dev_addr); + + TU_LOG2(" Port Status Change = 0x%02X\r\n", p_hub->status_change); + + // Hub ignore bit0 in status change + for (uint8_t port=1; port <= p_hub->port_count; port++) + { + if ( tu_bit_test(p_hub->status_change, port) ) + { + hub_port_get_status(dev_addr, port, &p_hub->port_status, connection_get_status_complete); + break; + } + } + + // NOTE: next status transfer is queued by usbh.c after handling this request + + return true; +} + +static bool connection_get_status_complete (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result) +{ + TU_ASSERT(result == XFER_RESULT_SUCCESS); + + hub_interface_t* p_hub = get_itf(dev_addr); + uint8_t const port_num = (uint8_t) request->wIndex; + + // Connection change + if (p_hub->port_status.change.connection) + { + // Port is powered and enabled + //TU_VERIFY(port_status.status_current.port_power && port_status.status_current.port_enable, ); + + // Acknowledge Port Connection Change + hub_port_clear_feature(dev_addr, port_num, HUB_FEATURE_PORT_CONNECTION_CHANGE, connection_clear_conn_change_complete); + }else + { + // Other changes are: Enable, Suspend, Over Current, Reset, L1 state + // TODO clear change + + // prepare for next hub status + // TODO continue with status_change, or maybe we can do it again with status + hub_status_pipe_queue(dev_addr); + } + + return true; +} + +static bool connection_clear_conn_change_complete (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result) +{ + TU_ASSERT(result == XFER_RESULT_SUCCESS); + + hub_interface_t* p_hub = get_itf(dev_addr); + uint8_t const port_num = (uint8_t) request->wIndex; + + if ( p_hub->port_status.status.connection ) + { + // Reset port if attach event + hub_port_reset(dev_addr, port_num, connection_port_reset_complete); + }else + { + // submit detach event + hcd_event_t event = + { + .rhport = usbh_get_rhport(dev_addr), + .event_id = HCD_EVENT_DEVICE_REMOVE, + .connection = + { + .hub_addr = dev_addr, + .hub_port = port_num + } + }; + + hcd_event_handler(&event, false); + } + + return true; +} + +static bool connection_port_reset_complete (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result) +{ + TU_ASSERT(result == XFER_RESULT_SUCCESS); + + // hub_interface_t* p_hub = get_itf(dev_addr); + uint8_t const port_num = (uint8_t) request->wIndex; + + // submit attach event + hcd_event_t event = + { + .rhport = usbh_get_rhport(dev_addr), + .event_id = HCD_EVENT_DEVICE_ATTACH, + .connection = + { + .hub_addr = dev_addr, + .hub_port = port_num + } + }; + + hcd_event_handler(&event, false); + + return true; +} + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/hub.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/hub.h new file mode 100644 index 000000000..c4d544193 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/hub.h @@ -0,0 +1,196 @@ +/* + * 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 group_class + * \defgroup ClassDriver_Hub Hub (Host only) + * \details Like most PC's OS, Hub support is completely hidden from Application. In fact, application cannot determine whether + * a device is mounted directly via roothub or via a hub's port. All Hub-related procedures are performed and managed + * by tinyusb stack. Unless you are trying to develop the stack itself, there are nothing else can be used by Application. + * \note Due to my laziness, only 1-level of Hub is supported. In other way, the stack cannot mount a hub via another hub. + * @{ + */ + +#ifndef _TUSB_HUB_H_ +#define _TUSB_HUB_H_ + +#include "common/tusb_common.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//D1...D0: Logical Power Switching Mode +//00: Ganged power switching (all ports’power at +//once) +//01: Individual port power switching +//1X: Reserved. Used only on 1.0 compliant hubs +//that implement no power switching +//D2: Identifies a Compound Device +//0: Hub is not part of a compound device. +//1: Hub is part of a compound device. +//D4...D3: Over-current Protection Mode +//00: Global Over-current Protection. The hub +//reports over-current as a summation of all +//ports’current draw, without a breakdown of +//individual port over-current status. +//01: Individual Port Over-current Protection. The +//hub reports over-current on a per-port basis. +//Each port has an over-current status. +//1X: No Over-current Protection. This option is +//allowed only for bus-powered hubs that do not +//implement over-current protection. +// +//D6...D5: TT Think TIme +//00: TT requires at most 8 FS bit times of inter +//transaction gap on a full-/low-speed +//downstream bus. +//01: TT requires at most 16 FS bit times. +//10: TT requires at most 24 FS bit times. +//11: TT requires at most 32 FS bit times. +//D7: Port Indicators Supported +//0: Port Indicators are not supported on its +//downstream facing ports and the +//PORT_INDICATOR request has no effect. +//1: Port Indicators are supported on its +//downstream facing ports and the +//PORT_INDICATOR request controls the +//indicators. See Section 11.5.3. +//D15...D8: Reserved + +typedef struct TU_ATTR_PACKED{ + uint8_t bLength ; ///< Size of descriptor + uint8_t bDescriptorType ; ///< Other_speed_Configuration Type + uint8_t bNbrPorts; + uint16_t wHubCharacteristics; + uint8_t bPwrOn2PwrGood; + uint8_t bHubContrCurrent; + uint8_t DeviceRemovable; // bitmap each bit for a port (from bit1) + uint8_t PortPwrCtrlMask; // just for compatibility, should be 0xff +} descriptor_hub_desc_t; + +TU_VERIFY_STATIC( sizeof(descriptor_hub_desc_t) == 9, "size is not correct"); + +enum { + HUB_REQUEST_GET_STATUS = 0 , + HUB_REQUEST_CLEAR_FEATURE = 1 , + + HUB_REQUEST_SET_FEATURE = 3 , + + HUB_REQUEST_GET_DESCRIPTOR = 6 , + HUB_REQUEST_SET_DESCRIPTOR = 7 , + HUB_REQUEST_CLEAR_TT_BUFFER = 8 , + HUB_REQUEST_RESET_TT = 9 , + HUB_REQUEST_GET_TT_STATE = 10 , + HUB_REQUEST_STOP_TT = 11 +}; + +enum { + HUB_FEATURE_HUB_LOCAL_POWER_CHANGE = 0, + HUB_FEATURE_HUB_OVER_CURRENT_CHANGE +}; + +enum{ + HUB_FEATURE_PORT_CONNECTION = 0, + HUB_FEATURE_PORT_ENABLE = 1, + HUB_FEATURE_PORT_SUSPEND = 2, + HUB_FEATURE_PORT_OVER_CURRENT = 3, + HUB_FEATURE_PORT_RESET = 4, + + HUB_FEATURE_PORT_POWER = 8, + HUB_FEATURE_PORT_LOW_SPEED = 9, + + HUB_FEATURE_PORT_CONNECTION_CHANGE = 16, + HUB_FEATURE_PORT_ENABLE_CHANGE = 17, + HUB_FEATURE_PORT_SUSPEND_CHANGE = 18, + HUB_FEATURE_PORT_OVER_CURRENT_CHANGE = 19, + HUB_FEATURE_PORT_RESET_CHANGE = 20, + HUB_FEATURE_PORT_TEST = 21, + HUB_FEATURE_PORT_INDICATOR = 22 +}; + +// data in response of HUB_REQUEST_GET_STATUS, wIndex = 0 (hub) +typedef struct { + union{ + struct TU_ATTR_PACKED { + uint16_t local_power_source : 1; + uint16_t over_current : 1; + uint16_t : 14; + }; + + uint16_t value; + } status, change; +} hub_status_response_t; + +TU_VERIFY_STATIC( sizeof(hub_status_response_t) == 4, "size is not correct"); + +// data in response of HUB_REQUEST_GET_STATUS, wIndex = Port num +typedef struct { + union { + struct TU_ATTR_PACKED { + uint16_t connection : 1; + uint16_t port_enable : 1; + uint16_t suspend : 1; + uint16_t over_current : 1; + uint16_t reset : 1; + + uint16_t : 3; + uint16_t port_power : 1; + uint16_t low_speed : 1; + uint16_t high_speed : 1; + uint16_t port_test_mode : 1; + uint16_t port_indicator_control : 1; + uint16_t TU_RESERVED : 3; + }; + + uint16_t value; + } status, change; +} hub_port_status_response_t; + +TU_VERIFY_STATIC( sizeof(hub_port_status_response_t) == 4, "size is not correct"); + +bool hub_port_clear_feature(uint8_t hub_addr, uint8_t hub_port, uint8_t feature, tuh_control_complete_cb_t complete_cb); +bool hub_port_set_feature(uint8_t hub_addr, uint8_t hub_port, uint8_t feature, tuh_control_complete_cb_t complete_cb); + +bool hub_port_reset(uint8_t hub_addr, uint8_t hub_port, tuh_control_complete_cb_t complete_cb); +bool hub_port_get_status(uint8_t hub_addr, uint8_t hub_port, void* resp, tuh_control_complete_cb_t complete_cb); +bool hub_status_pipe_queue(uint8_t dev_addr); + +//--------------------------------------------------------------------+ +// Internal Class Driver API +//--------------------------------------------------------------------+ +void hub_init (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); +void hub_close (uint8_t dev_addr); + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_HUB_H_ */ + +/** @} */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/usbh.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/usbh.c new file mode 100644 index 000000000..b8439addc --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/usbh.c @@ -0,0 +1,1204 @@ +/* + * 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 TUSB_OPT_HOST_ENABLED + +#include "tusb.h" +#include "host/usbh.h" +#include "host/usbh_classdriver.h" +#include "hub.h" + +//--------------------------------------------------------------------+ +// USBH Configuration +//--------------------------------------------------------------------+ + +// TODO remove,update +#ifndef CFG_TUH_EP_MAX +#define CFG_TUH_EP_MAX 9 +#endif + +#ifndef CFG_TUH_TASK_QUEUE_SZ +#define CFG_TUH_TASK_QUEUE_SZ 16 +#endif + +// Debug level of USBD +#define USBH_DBG_LVL 2 + +//--------------------------------------------------------------------+ +// USBH-HCD common data structure +//--------------------------------------------------------------------+ + +// device0 struct must be strictly a subset of normal device struct +typedef struct +{ + // port + uint8_t rhport; + uint8_t hub_addr; + uint8_t hub_port; + uint8_t speed; + + volatile struct TU_ATTR_PACKED + { + uint8_t connected : 1; + uint8_t addressed : 1; + uint8_t configured : 1; + uint8_t suspended : 1; + }; +} usbh_dev0_t; + +typedef struct { + // port + uint8_t rhport; + uint8_t hub_addr; + uint8_t hub_port; + uint8_t speed; + + volatile struct TU_ATTR_PACKED + { + uint8_t connected : 1; + uint8_t addressed : 1; + uint8_t configured : 1; + uint8_t suspended : 1; + }; + + //------------- device descriptor -------------// + uint16_t vid; + uint16_t pid; + + uint8_t ep0_size; + uint8_t i_manufacturer; + uint8_t i_product; + uint8_t i_serial; + + //------------- configuration descriptor -------------// + // uint8_t interface_count; // bNumInterfaces alias + + //------------- device -------------// + volatile uint8_t state; // device state, value from enum tusbh_device_state_t + + uint8_t itf2drv[16]; // map interface number to driver (0xff is invalid) + uint8_t ep2drv[CFG_TUH_EP_MAX][2]; // map endpoint to driver ( 0xff is invalid ) + + struct TU_ATTR_PACKED + { + volatile bool busy : 1; + volatile bool stalled : 1; + volatile bool claimed : 1; + + // TODO merge ep2drv here, 4-bit should be sufficient + }ep_status[CFG_TUH_EP_MAX][2]; + + // Mutex for claiming endpoint, only needed when using with preempted RTOS +#if CFG_TUSB_OS != OPT_OS_NONE + osal_mutex_def_t mutexdef; + osal_mutex_t mutex; +#endif + +} usbh_device_t; + + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + +// Invalid driver ID in itf2drv[] ep2drv[][] mapping +enum { DRVID_INVALID = 0xFFu }; +enum { ADDR_INVALID = 0xFFu }; + +#if CFG_TUSB_DEBUG >= 2 + #define DRIVER_NAME(_name) .name = _name, +#else + #define DRIVER_NAME(_name) +#endif + +static usbh_class_driver_t const usbh_class_drivers[] = +{ + #if CFG_TUH_CDC + { + DRIVER_NAME("CDC") + .init = cdch_init, + .open = cdch_open, + .set_config = cdch_set_config, + .xfer_cb = cdch_xfer_cb, + .close = cdch_close + }, + #endif + + #if CFG_TUH_MSC + { + DRIVER_NAME("MSC") + .init = msch_init, + .open = msch_open, + .set_config = msch_set_config, + .xfer_cb = msch_xfer_cb, + .close = msch_close + }, + #endif + + #if CFG_TUH_HID + { + DRIVER_NAME("HID") + .init = hidh_init, + .open = hidh_open, + .set_config = hidh_set_config, + .xfer_cb = hidh_xfer_cb, + .close = hidh_close + }, + #endif + + #if CFG_TUH_HUB + { + DRIVER_NAME("HUB") + .init = hub_init, + .open = hub_open, + .set_config = hub_set_config, + .xfer_cb = hub_xfer_cb, + .close = hub_close + }, + #endif + + #if CFG_TUH_VENDOR + { + DRIVER_NAME("VENDOR") + .init = cush_init, + .open = cush_open_subtask, + .xfer_cb = cush_isr, + .close = cush_close + } + #endif +}; + +enum { USBH_CLASS_DRIVER_COUNT = TU_ARRAY_SIZE(usbh_class_drivers) }; + +enum { RESET_DELAY = 500 }; // 200 USB specs say only 50ms but many devices require much longer + +enum { CONFIG_NUM = 1 }; // default to use configuration 1 + + +//--------------------------------------------------------------------+ +// INTERNAL OBJECT & FUNCTION DECLARATION +//--------------------------------------------------------------------+ + +static bool _usbh_initialized = false; + +// Device with address = 0 for enumeration +static usbh_dev0_t _dev0; + +// all devices excluding zero-address +// hub address start from CFG_TUH_DEVICE_MAX+1 +CFG_TUSB_MEM_SECTION usbh_device_t _usbh_devices[CFG_TUH_DEVICE_MAX + CFG_TUH_HUB]; + +// Event queue +// role device/host is used by OS NONE for mutex (disable usb isr) +OSAL_QUEUE_DEF(OPT_MODE_HOST, _usbh_qdef, CFG_TUH_TASK_QUEUE_SZ, hcd_event_t); +static osal_queue_t _usbh_q; + +CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN static uint8_t _usbh_ctrl_buf[CFG_TUH_ENUMERATION_BUFSIZE]; + +//------------- Helper Function -------------// + +TU_ATTR_ALWAYS_INLINE +static inline usbh_device_t* get_device(uint8_t dev_addr) +{ + TU_ASSERT(dev_addr, NULL); + return &_usbh_devices[dev_addr-1]; +} + +static bool enum_new_device(hcd_event_t* event); +static void process_device_unplugged(uint8_t rhport, uint8_t hub_addr, uint8_t hub_port); +static bool usbh_edpt_control_open(uint8_t dev_addr, uint8_t max_packet_size); + +// from usbh_control.c +extern bool usbh_control_xfer_cb (uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); + +//--------------------------------------------------------------------+ +// PUBLIC API (Parameter Verification is required) +//--------------------------------------------------------------------+ +bool tuh_mounted(uint8_t dev_addr) +{ + return get_device(dev_addr)->configured; +} + +bool tuh_vid_pid_get(uint8_t dev_addr, uint16_t* vid, uint16_t* pid) +{ + *vid = *pid = 0; + + TU_VERIFY(tuh_mounted(dev_addr)); + + usbh_device_t const* dev = get_device(dev_addr); + + *vid = dev->vid; + *pid = dev->pid; + + return true; +} + +tusb_speed_t tuh_speed_get (uint8_t dev_addr) +{ + return (tusb_speed_t) (dev_addr ? get_device(dev_addr)->speed : _dev0.speed); +} + +#if CFG_TUSB_OS == OPT_OS_NONE +void osal_task_delay(uint32_t msec) +{ + (void) msec; + + const uint32_t start = hcd_frame_number(TUH_OPT_RHPORT); + while ( ( hcd_frame_number(TUH_OPT_RHPORT) - start ) < msec ) {} +} +#endif + +//--------------------------------------------------------------------+ +// CLASS-USBD API (don't require to verify parameters) +//--------------------------------------------------------------------+ + +bool tuh_inited(void) +{ + return _usbh_initialized; +} + +bool tuh_init(uint8_t rhport) +{ + // skip if already initialized + if (_usbh_initialized) return _usbh_initialized; + + TU_LOG2("USBH init\r\n"); + + tu_memclr(_usbh_devices, sizeof(_usbh_devices)); + tu_memclr(&_dev0, sizeof(_dev0)); + + //------------- Enumeration & Reporter Task init -------------// + _usbh_q = osal_queue_create( &_usbh_qdef ); + TU_ASSERT(_usbh_q != NULL); + + //------------- Semaphore, Mutex for Control Pipe -------------// + for(uint8_t i=0; imutex = osal_mutex_create(&dev->mutexdef); + TU_ASSERT(dev->mutex); +#endif + + memset(dev->itf2drv, DRVID_INVALID, sizeof(dev->itf2drv)); // invalid mapping + memset(dev->ep2drv , DRVID_INVALID, sizeof(dev->ep2drv )); // invalid mapping + } + + // Class drivers init + for (uint8_t drv_id = 0; drv_id < USBH_CLASS_DRIVER_COUNT; drv_id++) + { + TU_LOG2("%s init\r\n", usbh_class_drivers[drv_id].name); + usbh_class_drivers[drv_id].init(); + } + + TU_ASSERT(hcd_init(rhport)); + hcd_int_enable(rhport); + + _usbh_initialized = true; + return true; +} + +/* USB Host Driver task + * This top level thread manages all host controller event and delegates events to class-specific drivers. + * This should be called periodically within the mainloop or rtos thread. + * + @code + int main(void) + { + application_init(); + tusb_init(); + + while(1) // the mainloop + { + application_code(); + tuh_task(); // tinyusb host task + } + } + @endcode + */ +void tuh_task(void) +{ + // Skip if stack is not initialized + if ( !tusb_inited() ) return; + + // Loop until there is no more events in the queue + while (1) + { + hcd_event_t event; + if ( !osal_queue_receive(_usbh_q, &event) ) return; + + switch (event.event_id) + { + case HCD_EVENT_DEVICE_ATTACH: + // TODO due to the shared _usbh_ctrl_buf, we must complete enumerating + // one device before enumerating another one. + TU_LOG2("USBH DEVICE ATTACH\r\n"); + enum_new_device(&event); + break; + + case HCD_EVENT_DEVICE_REMOVE: + TU_LOG2("USBH DEVICE REMOVED\r\n"); + process_device_unplugged(event.rhport, event.connection.hub_addr, event.connection.hub_port); + + #if CFG_TUH_HUB + // TODO remove + if ( event.connection.hub_addr != 0) + { + // done with hub, waiting for next data on status pipe + (void) hub_status_pipe_queue( event.connection.hub_addr ); + } + #endif + break; + + 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); + + TU_LOG2("on EP %02X with %u bytes\r\n", ep_addr, (unsigned int) event.xfer_complete.len); + + if (event.dev_addr == 0) + { + // device 0 only has control endpoint + TU_ASSERT(epnum == 0, ); + usbh_control_xfer_cb(event.dev_addr, ep_addr, event.xfer_complete.result, event.xfer_complete.len); + } + else + { + usbh_device_t* dev = get_device(event.dev_addr); + dev->ep_status[epnum][ep_dir].busy = false; + dev->ep_status[epnum][ep_dir].claimed = 0; + + if ( 0 == epnum ) + { + usbh_control_xfer_cb(event.dev_addr, ep_addr, event.xfer_complete.result, event.xfer_complete.len); + }else + { + uint8_t drv_id = dev->ep2drv[epnum][ep_dir]; + TU_ASSERT(drv_id < USBH_CLASS_DRIVER_COUNT, ); + + TU_LOG2("%s xfer callback\r\n", usbh_class_drivers[drv_id].name); + usbh_class_drivers[drv_id].xfer_cb(event.dev_addr, ep_addr, event.xfer_complete.result, event.xfer_complete.len); + } + } + } + break; + + case USBH_EVENT_FUNC_CALL: + if ( event.func_call.func ) event.func_call.func(event.func_call.param); + break; + + default: break; + } + } +} + +//--------------------------------------------------------------------+ +// USBH API For Class Driver +//--------------------------------------------------------------------+ + +uint8_t usbh_get_rhport(uint8_t dev_addr) +{ + return (dev_addr == 0) ? _dev0.rhport : get_device(dev_addr)->rhport; +} + +uint8_t* usbh_get_enum_buf(void) +{ + return _usbh_ctrl_buf; +} + +//--------------------------------------------------------------------+ +// HCD Event Handler +//--------------------------------------------------------------------+ + +void hcd_devtree_get_info(uint8_t dev_addr, hcd_devtree_info_t* devtree_info) +{ + if (dev_addr) + { + usbh_device_t const* dev = get_device(dev_addr); + + 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->hub_addr = _dev0.hub_addr; + devtree_info->hub_port = _dev0.hub_port; + devtree_info->speed = _dev0.speed; + } +} + +void hcd_event_handler(hcd_event_t const* event, bool in_isr) +{ + switch (event->event_id) + { + default: + osal_queue_send(_usbh_q, event, in_isr); + break; + } +} + +void hcd_event_xfer_complete(uint8_t dev_addr, uint8_t ep_addr, uint32_t xferred_bytes, xfer_result_t result, bool in_isr) +{ + hcd_event_t event = + { + .rhport = 0, // TODO correct rhport + .event_id = HCD_EVENT_XFER_COMPLETE, + .dev_addr = dev_addr, + .xfer_complete = + { + .ep_addr = ep_addr, + .result = result, + .len = xferred_bytes + } + }; + + hcd_event_handler(&event, in_isr); +} + +void hcd_event_device_attach(uint8_t rhport, bool in_isr) +{ + hcd_event_t event = + { + .rhport = rhport, + .event_id = HCD_EVENT_DEVICE_ATTACH + }; + + event.connection.hub_addr = 0; + event.connection.hub_port = 0; + + hcd_event_handler(&event, in_isr); +} + +void hcd_event_device_remove(uint8_t hostid, bool in_isr) +{ + hcd_event_t event = + { + .rhport = hostid, + .event_id = HCD_EVENT_DEVICE_REMOVE + }; + + event.connection.hub_addr = 0; + event.connection.hub_port = 0; + + hcd_event_handler(&event, in_isr); +} + + +// a device unplugged on hostid, hub_addr, hub_port +// return true if found and unmounted device, false if cannot find +void process_device_unplugged(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 + for ( uint8_t dev_id = 0; dev_id < TU_ARRAY_SIZE(_usbh_devices); dev_id++ ) + { + usbh_device_t* dev = &_usbh_devices[dev_id]; + uint8_t const dev_addr = dev_id+1; + + // TODO Hub multiple level + if (dev->rhport == rhport && + (hub_addr == 0 || dev->hub_addr == hub_addr) && // hub_addr == 0 & hub_port == 0 means roothub + (hub_port == 0 || dev->hub_port == hub_port) && + dev->state != TUSB_DEVICE_STATE_UNPLUG) + { + // Invoke callback before close driver + if (tuh_umount_cb) tuh_umount_cb(dev_addr); + + // Close class driver + for (uint8_t drv_id = 0; drv_id < USBH_CLASS_DRIVER_COUNT; drv_id++) + { + TU_LOG2("%s close\r\n", usbh_class_drivers[drv_id].name); + usbh_class_drivers[drv_id].close(dev_addr); + } + + hcd_device_close(rhport, dev_addr); + + // release all endpoints associated with the device + memset(dev->itf2drv, DRVID_INVALID, sizeof(dev->itf2drv)); // invalid mapping + memset(dev->ep2drv , DRVID_INVALID, sizeof(dev->ep2drv )); // invalid mapping + tu_memclr(dev->ep_status, sizeof(dev->ep_status)); + + dev->state = TUSB_DEVICE_STATE_UNPLUG; + } + } +} + +//--------------------------------------------------------------------+ +// INTERNAL HELPER +//--------------------------------------------------------------------+ +static uint8_t get_new_address(bool is_hub) +{ + uint8_t const start = (is_hub ? CFG_TUH_DEVICE_MAX : 0) + 1; + uint8_t const count = (is_hub ? CFG_TUH_HUB : CFG_TUH_DEVICE_MAX); + + for (uint8_t i=0; i < count; i++) + { + uint8_t const addr = start + i; + if (get_device(addr)->state == TUSB_DEVICE_STATE_UNPLUG) return addr; + } + return ADDR_INVALID; +} + +void usbh_driver_set_config_complete(uint8_t dev_addr, uint8_t itf_num) +{ + usbh_device_t* dev = get_device(dev_addr); + + for(itf_num++; itf_num < sizeof(dev->itf2drv); itf_num++) + { + // continue with next valid interface + // TODO skip IAD binding interface such as CDCs + uint8_t const drv_id = dev->itf2drv[itf_num]; + if (drv_id != DRVID_INVALID) + { + usbh_class_driver_t const * driver = &usbh_class_drivers[drv_id]; + TU_LOG2("%s set config: itf = %u\r\n", driver->name, itf_num); + driver->set_config(dev_addr, itf_num); + break; + } + } + + // all interface are configured + if (itf_num == sizeof(dev->itf2drv)) + { + // Invoke callback if available + if (tuh_mount_cb) tuh_mount_cb(dev_addr); + } +} + +//--------------------------------------------------------------------+ +// Enumeration Process +// is a lengthy process with a seires of control transfer to configure +// newly attached device. Each step is handled by a function in this +// section +// TODO due to the shared _usbh_ctrl_buf, we must complete enumerating +// one device before enumerating another one. +//--------------------------------------------------------------------+ + +static bool enum_request_addr0_device_desc(void); +static bool enum_request_set_addr(void); + +static bool enum_get_addr0_device_desc_complete (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result); +static bool enum_set_address_complete (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result); +static bool enum_get_device_desc_complete (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result); +static bool enum_get_9byte_config_desc_complete (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result); +static bool enum_get_config_desc_complete (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result); +static bool enum_set_config_complete (uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result); +static bool parse_configuration_descriptor (uint8_t dev_addr, tusb_desc_configuration_t const* desc_cfg); + +#if CFG_TUH_HUB +static bool enum_hub_clear_reset0_complete(uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result) +{ + (void) dev_addr; (void) request; + TU_ASSERT(XFER_RESULT_SUCCESS == result); + enum_request_addr0_device_desc(); + return true; +} + +static bool enum_hub_clear_reset1_complete(uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result) +{ + (void) dev_addr; (void) request; + TU_ASSERT(XFER_RESULT_SUCCESS == result); + + enum_request_set_addr(); + + // done with hub, waiting for next data on status pipe + (void) hub_status_pipe_queue( _dev0.hub_addr ); + + return true; +} + +static bool enum_hub_get_status1_complete(uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result) +{ + (void) dev_addr; (void) request; + TU_ASSERT(XFER_RESULT_SUCCESS == result); + + 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_feature(_dev0.hub_addr, _dev0.hub_port, HUB_FEATURE_PORT_RESET_CHANGE, enum_hub_clear_reset1_complete) ); + } + + return true; +} + +static bool enum_hub_get_status0_complete(uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result) +{ + (void) dev_addr; (void) request; + TU_ASSERT(XFER_RESULT_SUCCESS == result); + + hub_port_status_response_t port_status; + memcpy(&port_status, _usbh_ctrl_buf, sizeof(hub_port_status_response_t)); + + if ( !port_status.status.connection ) + { + // device unplugged while delaying, nothing else to do, queue hub status + return hub_status_pipe_queue(dev_addr); + } + + _dev0.speed = (port_status.status.high_speed) ? TUSB_SPEED_HIGH : + (port_status.status.low_speed ) ? TUSB_SPEED_LOW : TUSB_SPEED_FULL; + + // Acknowledge Port Reset Change + if (port_status.change.reset) + { + hub_port_clear_feature(_dev0.hub_addr, _dev0.hub_port, HUB_FEATURE_PORT_RESET_CHANGE, enum_hub_clear_reset0_complete); + } + + return true; +} +#endif + +static bool enum_new_device(hcd_event_t* event) +{ + _dev0.rhport = event->rhport; // TODO refractor integrate to device_pool + _dev0.hub_addr = event->connection.hub_addr; + _dev0.hub_port = event->connection.hub_port; + + //------------- connected/disconnected directly with roothub -------------// + if (_dev0.hub_addr == 0) + { + // wait until device is stable TODO non blocking + osal_task_delay(RESET_DELAY); + + // device unplugged while delaying + if ( !hcd_port_connect_status(_dev0.rhport) ) return true; + + _dev0.speed = hcd_port_speed_get(_dev0.rhport ); + + enum_request_addr0_device_desc(); + } +#if CFG_TUH_HUB + //------------- connected/disconnected via hub -------------// + else + { + // wait until device is stable + osal_task_delay(RESET_DELAY); + TU_ASSERT( hub_port_get_status(_dev0.hub_addr, _dev0.hub_port, _usbh_ctrl_buf, enum_hub_get_status0_complete) ); + } +#endif // CFG_TUH_HUB + + return true; +} + +static bool enum_request_addr0_device_desc(void) +{ + // TODO probably doesn't need to open/close each enumeration + uint8_t const addr0 = 0; + TU_ASSERT( usbh_edpt_control_open(addr0, 8) ); + + //------------- Get first 8 bytes of device descriptor to get Control Endpoint Size -------------// + TU_LOG2("Get 8 byte of Device Descriptor\r\n"); + tusb_control_request_t const request = + { + .bmRequestType_bit = + { + .recipient = TUSB_REQ_RCPT_DEVICE, + .type = TUSB_REQ_TYPE_STANDARD, + .direction = TUSB_DIR_IN + }, + .bRequest = TUSB_REQ_GET_DESCRIPTOR, + .wValue = TUSB_DESC_DEVICE << 8, + .wIndex = 0, + .wLength = 8 + }; + TU_ASSERT( tuh_control_xfer(addr0, &request, _usbh_ctrl_buf, enum_get_addr0_device_desc_complete) ); + + return true; +} + +// After Get Device Descriptor of Address 0 +static bool enum_get_addr0_device_desc_complete(uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result) +{ + (void) request; + TU_ASSERT(0 == dev_addr); + + if (XFER_RESULT_SUCCESS != result) + { +#if CFG_TUH_HUB + // TODO remove, waiting for next data on status pipe + if (_dev0.hub_addr != 0) hub_status_pipe_queue(_dev0.hub_addr); +#endif + + return false; + } + + tusb_desc_device_t const * desc_device = (tusb_desc_device_t const*) _usbh_ctrl_buf; + TU_ASSERT( tu_desc_type(desc_device) == TUSB_DESC_DEVICE ); + + // Reset device again before Set Address + TU_LOG2("Port reset \r\n"); + + if (_dev0.hub_addr == 0) + { + // connected directly to roothub + hcd_port_reset( _dev0.rhport ); // reset port after 8 byte descriptor + osal_task_delay(RESET_DELAY); + + enum_request_set_addr(); + } +#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, NULL) ); + osal_task_delay(RESET_DELAY); + + tuh_task(); // FIXME temporarily to clean up port_reset control transfer + + TU_ASSERT( hub_port_get_status(_dev0.hub_addr, _dev0.hub_port, _usbh_ctrl_buf, enum_hub_get_status1_complete) ); + } +#endif + + return true; +} + +static bool enum_request_set_addr(void) +{ + uint8_t const addr0 = 0; + 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 != ADDR_INVALID); + + TU_LOG2("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->connected = 1; + new_dev->ep0_size = desc_device->bMaxPacketSize0; + + tusb_control_request_t const new_request = + { + .bmRequestType_bit = + { + .recipient = TUSB_REQ_RCPT_DEVICE, + .type = TUSB_REQ_TYPE_STANDARD, + .direction = TUSB_DIR_OUT + }, + .bRequest = TUSB_REQ_SET_ADDRESS, + .wValue = new_addr, + .wIndex = 0, + .wLength = 0 + }; + + TU_ASSERT( tuh_control_xfer(addr0, &new_request, NULL, enum_set_address_complete) ); + + return true; +} + +// After SET_ADDRESS is complete +static bool enum_set_address_complete(uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result) +{ + TU_ASSERT(0 == dev_addr); + TU_ASSERT(XFER_RESULT_SUCCESS == result); + + uint8_t const new_addr = (uint8_t const) request->wValue; + + usbh_device_t* new_dev = get_device(new_addr); + new_dev->addressed = 1; + + // TODO close device 0, may not be needed + hcd_device_close(_dev0.rhport, 0); + + // open control pipe for new address + TU_ASSERT( usbh_edpt_control_open(new_addr, new_dev->ep0_size) ); + + // Get full device descriptor + TU_LOG2("Get Device Descriptor\r\n"); + tusb_control_request_t const new_request = + { + .bmRequestType_bit = + { + .recipient = TUSB_REQ_RCPT_DEVICE, + .type = TUSB_REQ_TYPE_STANDARD, + .direction = TUSB_DIR_IN + }, + .bRequest = TUSB_REQ_GET_DESCRIPTOR, + .wValue = TUSB_DESC_DEVICE << 8, + .wIndex = 0, + .wLength = sizeof(tusb_desc_device_t) + }; + + TU_ASSERT(tuh_control_xfer(new_addr, &new_request, _usbh_ctrl_buf, enum_get_device_desc_complete)); + + return true; +} + +static bool enum_get_device_desc_complete(uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result) +{ + (void) request; + TU_ASSERT(XFER_RESULT_SUCCESS == result); + + tusb_desc_device_t const * desc_device = (tusb_desc_device_t const*) _usbh_ctrl_buf; + usbh_device_t* dev = get_device(dev_addr); + + 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; + +// if (tuh_attach_cb) tuh_attach_cb((tusb_desc_device_t*) _usbh_ctrl_buf); + + TU_LOG2("Get 9 bytes of Configuration Descriptor\r\n"); + tusb_control_request_t const new_request = + { + .bmRequestType_bit = + { + .recipient = TUSB_REQ_RCPT_DEVICE, + .type = TUSB_REQ_TYPE_STANDARD, + .direction = TUSB_DIR_IN + }, + .bRequest = TUSB_REQ_GET_DESCRIPTOR, + .wValue = (TUSB_DESC_CONFIGURATION << 8) | (CONFIG_NUM - 1), + .wIndex = 0, + .wLength = 9 + }; + + TU_ASSERT( tuh_control_xfer(dev_addr, &new_request, _usbh_ctrl_buf, enum_get_9byte_config_desc_complete) ); + + return true; +} + +static bool enum_get_9byte_config_desc_complete(uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result) +{ + (void) request; + TU_ASSERT(XFER_RESULT_SUCCESS == result); + + // TODO not enough buffer to hold configuration descriptor + 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)) ); + + TU_ASSERT(total_len <= CFG_TUH_ENUMERATION_BUFSIZE); + + // Get full configuration descriptor + TU_LOG2("Get Configuration Descriptor\r\n"); + tusb_control_request_t const new_request = + { + .bmRequestType_bit = + { + .recipient = TUSB_REQ_RCPT_DEVICE, + .type = TUSB_REQ_TYPE_STANDARD, + .direction = TUSB_DIR_IN + }, + .bRequest = TUSB_REQ_GET_DESCRIPTOR, + .wValue = (TUSB_DESC_CONFIGURATION << 8) | (CONFIG_NUM - 1), + .wIndex = 0, + .wLength = total_len + + }; + + TU_ASSERT( tuh_control_xfer(dev_addr, &new_request, _usbh_ctrl_buf, enum_get_config_desc_complete) ); + + return true; +} + +static bool enum_get_config_desc_complete(uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result) +{ + (void) request; + TU_ASSERT(XFER_RESULT_SUCCESS == result); + + // Parse configuration & set up drivers + // Driver open aren't allowed to make any usb transfer yet + TU_ASSERT( parse_configuration_descriptor(dev_addr, (tusb_desc_configuration_t*) _usbh_ctrl_buf) ); + + TU_LOG2("Set Configuration = %d\r\n", CONFIG_NUM); + tusb_control_request_t const new_request = + { + .bmRequestType_bit = + { + .recipient = TUSB_REQ_RCPT_DEVICE, + .type = TUSB_REQ_TYPE_STANDARD, + .direction = TUSB_DIR_OUT + }, + .bRequest = TUSB_REQ_SET_CONFIGURATION, + .wValue = CONFIG_NUM, + .wIndex = 0, + .wLength = 0 + }; + + TU_ASSERT( tuh_control_xfer(dev_addr, &new_request, NULL, enum_set_config_complete) ); + + return true; +} + +static bool enum_set_config_complete(uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result) +{ + (void) request; + TU_ASSERT(XFER_RESULT_SUCCESS == result); + + TU_LOG2("Device configured\r\n"); + usbh_device_t* dev = get_device(dev_addr); + dev->configured = 1; + dev->state = TUSB_DEVICE_STATE_CONFIGURED; + + // Start the Set Configuration process for interfaces (itf = DRVID_INVALID) + // 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 DRVID_INVALID + usbh_driver_set_config_complete(dev_addr, DRVID_INVALID); + + return true; +} + +static bool parse_configuration_descriptor(uint8_t dev_addr, tusb_desc_configuration_t const* desc_cfg) +{ + usbh_device_t* dev = get_device(dev_addr); + + uint8_t const* desc_end = ((uint8_t const*) desc_cfg) + tu_le16toh(desc_cfg->wTotalLength); + uint8_t const* p_desc = tu_desc_next(desc_cfg); + + // parse each interfaces + 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) ) + { + tusb_desc_interface_assoc_t const * desc_iad = (tusb_desc_interface_assoc_t const *) p_desc; + assoc_itf_count = desc_iad->bInterfaceCount; + + p_desc = tu_desc_next(p_desc); // next to Interface + + // IAD's first interface number and class should match with opened interface + //TU_ASSERT(desc_iad->bFirstInterface == desc_itf->bInterfaceNumber && + // desc_iad->bFunctionClass == desc_itf->bInterfaceClass); + } + + TU_ASSERT( TUSB_DESC_INTERFACE == tu_desc_type(p_desc) ); + tusb_desc_interface_t const* desc_itf = (tusb_desc_interface_t const*) p_desc; + +#if CFG_TUH_MIDI + // MIDI has 2 interfaces (Audio Control v1 + MIDIStreaming) but does not have IAD + // manually increase the associated count + 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) + { + assoc_itf_count = 2; + } +#endif + + uint16_t const drv_len = tu_desc_get_interface_total_len(desc_itf, assoc_itf_count, desc_end-p_desc); + TU_ASSERT(drv_len >= sizeof(tusb_desc_interface_t)); + + if (desc_itf->bInterfaceClass == TUSB_CLASS_HUB && dev->hub_addr != 0) + { + // TODO Attach hub to Hub is not currently supported + // skip this interface + TU_LOG(USBH_DBG_LVL, "Only 1 level of HUB is supported\r\n"); + } + else + { + // Find driver for this interface + uint8_t drv_id; + for (drv_id = 0; drv_id < USBH_CLASS_DRIVER_COUNT; drv_id++) + { + usbh_class_driver_t const * driver = &usbh_class_drivers[drv_id]; + + if ( driver->open(dev->rhport, dev_addr, desc_itf, drv_len) ) + { + // open successfully + TU_LOG2(" %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 + TU_ASSERT( DRVID_INVALID == dev->itf2drv[itf_num] ); + dev->itf2drv[itf_num] = drv_id; + } + + // bind all endpoints to found driver + tu_edpt_bind_driver(dev->ep2drv, desc_itf, drv_len, drv_id); + + break; // exit driver find loop + } + } + + if( drv_id >= USBH_CLASS_DRIVER_COUNT ) + { + TU_LOG(USBH_DBG_LVL, "Interface %u: class = %u subclass = %u protocol = %u is not supported\r\n", + desc_itf->bInterfaceNumber, desc_itf->bInterfaceClass, desc_itf->bInterfaceSubClass, desc_itf->bInterfaceProtocol); + } + } + + // next Interface or IAD descriptor + p_desc += drv_len; + } + + return true; +} + +//--------------------------------------------------------------------+ +// Endpoint API +//--------------------------------------------------------------------+ + +// TODO has some duplication code with device, refactor later +bool usbh_edpt_claim(uint8_t dev_addr, uint8_t ep_addr) +{ + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + usbh_device_t* dev = get_device(dev_addr); + +#if CFG_TUSB_OS != OPT_OS_NONE + // pre-check to help reducing mutex lock + TU_VERIFY((dev->ep_status[epnum][dir].busy == 0) && (dev->ep_status[epnum][dir].claimed == 0)); + osal_mutex_lock(dev->mutex, OSAL_TIMEOUT_WAIT_FOREVER); +#endif + + // can only claim the endpoint if it is not busy and not claimed yet. + bool const ret = (dev->ep_status[epnum][dir].busy == 0) && (dev->ep_status[epnum][dir].claimed == 0); + if (ret) + { + dev->ep_status[epnum][dir].claimed = 1; + } + +#if CFG_TUSB_OS != OPT_OS_NONE + osal_mutex_unlock(dev->mutex); +#endif + + return ret; +} + +// TODO has some duplication code with device, refactor later +bool usbh_edpt_release(uint8_t dev_addr, uint8_t ep_addr) +{ + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + usbh_device_t* dev = get_device(dev_addr); + +#if CFG_TUSB_OS != OPT_OS_NONE + osal_mutex_lock(dev->mutex, OSAL_TIMEOUT_WAIT_FOREVER); +#endif + + // can only release the endpoint if it is claimed and not busy + bool const ret = (dev->ep_status[epnum][dir].busy == 0) && (dev->ep_status[epnum][dir].claimed == 1); + if (ret) + { + dev->ep_status[epnum][dir].claimed = 0; + } + +#if CFG_TUSB_OS != OPT_OS_NONE + osal_mutex_unlock(dev->mutex); +#endif + + return ret; +} + +// TODO has some duplication code with device, refactor later +bool usbh_edpt_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes) +{ + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + usbh_device_t* dev = get_device(dev_addr); + + TU_LOG2(" Queue EP %02X with %u bytes ... ", ep_addr, total_bytes); + + // Attempt to transfer on a busy endpoint, sound like an race condition ! + TU_ASSERT(dev->ep_status[epnum][dir].busy == 0); + + // Set busy first since the actual transfer can be complete before hcd_edpt_xfer() + // could return and USBH task can preempt and clear the busy + dev->ep_status[epnum][dir].busy = true; + + if ( hcd_edpt_xfer(dev->rhport, dev_addr, ep_addr, buffer, total_bytes) ) + { + TU_LOG2("OK\r\n"); + return true; + }else + { + // HCD error, mark endpoint as ready to allow next transfer + dev->ep_status[epnum][dir].busy = false; + dev->ep_status[epnum][dir].claimed = 0; + TU_LOG2("failed\r\n"); + TU_BREAKPOINT(); + return false; + } +} + +static bool usbh_edpt_control_open(uint8_t dev_addr, uint8_t max_packet_size) +{ + TU_LOG2("Open EP0 with Size = %u (addr = %u)\r\n", max_packet_size, dev_addr); + + tusb_desc_endpoint_t ep0_desc = + { + .bLength = sizeof(tusb_desc_endpoint_t), + .bDescriptorType = TUSB_DESC_ENDPOINT, + .bEndpointAddress = 0, + .bmAttributes = { .xfer = TUSB_XFER_CONTROL }, + .wMaxPacketSize = max_packet_size, + .bInterval = 0 + }; + + return hcd_edpt_open(usbh_get_rhport(dev_addr), dev_addr, &ep0_desc); +} + +bool usbh_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * desc_ep) +{ + usbh_device_t* dev = get_device(dev_addr); + TU_ASSERT(tu_edpt_validate(desc_ep, (tusb_speed_t) dev->speed)); + + return hcd_edpt_open(rhport, dev_addr, desc_ep); +} + +bool usbh_edpt_busy(uint8_t dev_addr, uint8_t ep_addr) +{ + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + usbh_device_t* dev = get_device(dev_addr); + + return dev->ep_status[epnum][dir].busy; +} + + + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/usbh.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/usbh.h new file mode 100644 index 000000000..8411cad28 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/usbh.h @@ -0,0 +1,99 @@ +/* + * 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_USBH_H_ +#define _TUSB_USBH_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +#include "common/tusb_common.h" +#include "hcd.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF +//--------------------------------------------------------------------+ + +typedef bool (*tuh_control_complete_cb_t)(uint8_t dev_addr, tusb_control_request_t const * request, xfer_result_t result); + +//--------------------------------------------------------------------+ +// APPLICATION API +//--------------------------------------------------------------------+ + +// Init host stack +bool tuh_init(uint8_t rhport); + +// Check if host stack is already initialized +bool tuh_inited(void); + +// Task function should be called in main/rtos loop +void tuh_task(void); + +// Interrupt handler, name alias to HCD +extern void hcd_int_handler(uint8_t rhport); +#define tuh_int_handler hcd_int_handler + +bool tuh_vid_pid_get(uint8_t dev_addr, uint16_t* vid, uint16_t* pid); +tusb_speed_t tuh_speed_get(uint8_t dev_addr); + +// Check if device is connected and configured +bool tuh_mounted(uint8_t dev_addr); + +// Check if device is suspended +static inline bool tuh_suspended(uint8_t dev_addr) +{ + // TODO implement suspend & resume on host + (void) dev_addr; + return false; +} + +// Check if device is ready to communicate with +TU_ATTR_ALWAYS_INLINE +static inline bool tuh_ready(uint8_t dev_addr) +{ + return tuh_mounted(dev_addr) && !tuh_suspended(dev_addr); +} + +// Carry out control transfer +bool tuh_control_xfer (uint8_t dev_addr, tusb_control_request_t const* request, void* buffer, tuh_control_complete_cb_t complete_cb); + +//--------------------------------------------------------------------+ +// APPLICATION CALLBACK +//--------------------------------------------------------------------+ +//TU_ATTR_WEAK uint8_t tuh_attach_cb (tusb_desc_device_t const *desc_device); + +// Invoked when device is mounted (configured) +TU_ATTR_WEAK void tuh_mount_cb (uint8_t dev_addr); + +/// Invoked when device is unmounted (bus reset/unplugged) +TU_ATTR_WEAK void tuh_umount_cb(uint8_t dev_addr); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/usbh_classdriver.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/usbh_classdriver.h new file mode 100644 index 000000000..8bc2622aa --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/usbh_classdriver.h @@ -0,0 +1,83 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021, 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_USBH_CLASSDRIVER_H_ +#define _TUSB_USBH_CLASSDRIVER_H_ + +#include "osal/osal.h" +#include "common/tusb_fifo.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// Class Driver API +//--------------------------------------------------------------------+ + +typedef struct { + #if CFG_TUSB_DEBUG >= 2 + char const* name; + #endif + + void (* const init )(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); + void (* const close )(uint8_t dev_addr); +} usbh_class_driver_t; + +// Call by class driver to tell USBH that it has complete the enumeration +void usbh_driver_set_config_complete(uint8_t dev_addr, uint8_t itf_num); + +uint8_t usbh_get_rhport(uint8_t dev_addr); + +uint8_t* usbh_get_enum_buf(void); + +//--------------------------------------------------------------------+ +// USBH Endpoint API +//--------------------------------------------------------------------+ + +// Open an endpoint +bool usbh_edpt_open(uint8_t rhport, uint8_t dev_addr, tusb_desc_endpoint_t const * desc_ep); + +// Submit a usb transfer +bool usbh_edpt_xfer(uint8_t dev_addr, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes); + +// Claim an endpoint before submitting a transfer. +// If caller does not make any transfer, it must release endpoint for others. +bool usbh_edpt_claim(uint8_t dev_addr, uint8_t ep_addr); + +bool usbh_edpt_release(uint8_t dev_addr, uint8_t ep_addr); + +// Check if endpoint transferring is complete +bool usbh_edpt_busy(uint8_t dev_addr, uint8_t ep_addr); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/usbh_control.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/usbh_control.c new file mode 100644 index 000000000..9204576ac --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/host/usbh_control.c @@ -0,0 +1,138 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020, 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 TUSB_OPT_HOST_ENABLED + +#include "tusb.h" +#include "usbh_classdriver.h" + +enum +{ + STAGE_SETUP, + STAGE_DATA, + STAGE_ACK +}; + +typedef struct +{ + tusb_control_request_t request TU_ATTR_ALIGNED(4); + + uint8_t stage; + uint8_t* buffer; + tuh_control_complete_cb_t complete_cb; +} usbh_control_xfer_t; + +static usbh_control_xfer_t _ctrl_xfer; + +//CFG_TUSB_MEM_SECTION CFG_TUSB_MEM_ALIGN +//static uint8_t _tuh_ctrl_buf[CFG_TUH_ENUMERATION_BUFSIZE]; + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM DECLARATION +//--------------------------------------------------------------------+ + +bool tuh_control_xfer (uint8_t dev_addr, tusb_control_request_t const* request, void* buffer, tuh_control_complete_cb_t complete_cb) +{ + // TODO need to claim the endpoint first + const uint8_t rhport = usbh_get_rhport(dev_addr); + + _ctrl_xfer.request = (*request); + _ctrl_xfer.buffer = buffer; + _ctrl_xfer.stage = STAGE_SETUP; + _ctrl_xfer.complete_cb = complete_cb; + + TU_LOG2("Control Setup (addr = %u): ", dev_addr); + TU_LOG2_VAR(request); + TU_LOG2("\r\n"); + + // Send setup packet + TU_ASSERT( hcd_setup_send(rhport, dev_addr, (uint8_t const*) &_ctrl_xfer.request) ); + + return true; +} + +static void _xfer_complete(uint8_t dev_addr, xfer_result_t result) +{ + TU_LOG2("\r\n"); + if (_ctrl_xfer.complete_cb) _ctrl_xfer.complete_cb(dev_addr, &_ctrl_xfer.request, result); +} + +bool usbh_control_xfer_cb (uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) +{ + (void) ep_addr; + (void) xferred_bytes; + + const uint8_t rhport = usbh_get_rhport(dev_addr); + + tusb_control_request_t const * request = &_ctrl_xfer.request; + + if (XFER_RESULT_SUCCESS != result) + { + TU_LOG2("Control failed: result = %d\r\n", result); + + // terminate transfer if any stage failed + _xfer_complete(dev_addr, result); + }else + { + switch(_ctrl_xfer.stage) + { + case STAGE_SETUP: + _ctrl_xfer.stage = STAGE_DATA; + if (request->wLength) + { + // DATA stage: initial data toggle is always 1 + hcd_edpt_xfer(rhport, dev_addr, tu_edpt_addr(0, request->bmRequestType_bit.direction), _ctrl_xfer.buffer, request->wLength); + return true; + } + __attribute__((fallthrough)); + + case STAGE_DATA: + _ctrl_xfer.stage = STAGE_ACK; + + if (request->wLength) + { + TU_LOG2("Control data (addr = %u):\r\n", dev_addr); + TU_LOG2_MEM(_ctrl_xfer.buffer, request->wLength, 2); + } + + // ACK stage: toggle is always 1 + hcd_edpt_xfer(rhport, dev_addr, tu_edpt_addr(0, 1-request->bmRequestType_bit.direction), NULL, 0); + break; + + case STAGE_ACK: + _xfer_complete(dev_addr, result); + break; + + default: return false; + } + } + + return true; +} + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal.h new file mode 100644 index 000000000..b2943cc79 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal.h @@ -0,0 +1,113 @@ +/* + * 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_H_ +#define _TUSB_OSAL_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +/** \addtogroup group_osal + * @{ */ + +#include "common/tusb_common.h" + +// Return immediately +#define OSAL_TIMEOUT_NOTIMEOUT (0) +// Default timeout +#define OSAL_TIMEOUT_NORMAL (10) +// Wait forever +#define OSAL_TIMEOUT_WAIT_FOREVER (UINT32_MAX) + +#define OSAL_TIMEOUT_CONTROL_XFER OSAL_TIMEOUT_WAIT_FOREVER + +typedef void (*osal_task_func_t)( void * ); + +#if CFG_TUSB_OS == OPT_OS_NONE + #include "osal_none.h" +#elif CFG_TUSB_OS == OPT_OS_FREERTOS + #include "osal_freertos.h" +#elif CFG_TUSB_OS == OPT_OS_MYNEWT + #include "osal_mynewt.h" +#elif CFG_TUSB_OS == OPT_OS_PICO + #include "osal_pico.h" +#elif CFG_TUSB_OS == OPT_OS_RTTHREAD + #include "osal_rtthread.h" +#elif CFG_TUSB_OS == OPT_OS_RTX4 + #include "osal_rtx4.h" +#elif CFG_TUSB_OS == OPT_OS_CUSTOM + #include "tusb_os_custom.h" // implemented by application +#else + #error OS is not supported yet +#endif + +//--------------------------------------------------------------------+ +// OSAL Porting API +//--------------------------------------------------------------------+ + +#if __GNUC__ && !defined(__ARMCC_VERSION) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wredundant-decls" +#endif +//------------- Semaphore -------------// +static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef); +static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr); +static inline bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec); + +static inline void osal_semaphore_reset(osal_semaphore_t sem_hdl); // TODO removed + +//------------- Mutex -------------// +static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef); +static inline bool osal_mutex_lock (osal_mutex_t sem_hdl, uint32_t msec); +static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl); + +//------------- Queue -------------// +static inline osal_queue_t osal_queue_create(osal_queue_def_t* qdef); +static inline bool osal_queue_receive(osal_queue_t qhdl, void* data); +static inline bool osal_queue_send(osal_queue_t qhdl, void const * data, bool in_isr); +static inline bool osal_queue_empty(osal_queue_t qhdl); +#if __GNUC__ && !defined(__ARMCC_VERSION) +#pragma GCC diagnostic pop +#endif + +#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 + +#ifdef __cplusplus + } +#endif + +/** @} */ + +#endif /* _TUSB_OSAL_H_ */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal_freertos.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal_freertos.h new file mode 100644 index 000000000..aa102b15c --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal_freertos.h @@ -0,0 +1,174 @@ +/* + * 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 + +//--------------------------------------------------------------------+ +// TASK API +//--------------------------------------------------------------------+ +static inline void osal_task_delay(uint32_t msec) +{ + vTaskDelay( pdMS_TO_TICKS(msec) ); +} + +//--------------------------------------------------------------------+ +// Semaphore API +//--------------------------------------------------------------------+ +typedef StaticSemaphore_t osal_semaphore_def_t; +typedef SemaphoreHandle_t osal_semaphore_t; + +static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef) +{ + return xSemaphoreCreateBinaryStatic(semdef); +} + +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; + 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; + } +} + +static inline bool osal_semaphore_wait (osal_semaphore_t sem_hdl, uint32_t msec) +{ + uint32_t const ticks = (msec == OSAL_TIMEOUT_WAIT_FOREVER) ? portMAX_DELAY : pdMS_TO_TICKS(msec); + return xSemaphoreTake(sem_hdl, ticks); +} + +static inline void osal_semaphore_reset(osal_semaphore_t const sem_hdl) +{ + xQueueReset(sem_hdl); +} + +//--------------------------------------------------------------------+ +// MUTEX API (priority inheritance) +//--------------------------------------------------------------------+ +typedef StaticSemaphore_t osal_mutex_def_t; +typedef SemaphoreHandle_t osal_mutex_t; + +static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef) +{ + return xSemaphoreCreateMutexStatic(mdef); +} + +static inline bool osal_mutex_lock (osal_mutex_t mutex_hdl, uint32_t msec) +{ + return osal_semaphore_wait(mutex_hdl, msec); +} + +static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl) +{ + return xSemaphoreGive(mutex_hdl); +} + +//--------------------------------------------------------------------+ +// QUEUE API +//--------------------------------------------------------------------+ + +// role device/host is used by OS NONE for mutex (disable usb isr) only +#define OSAL_QUEUE_DEF(_role, _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; + + StaticQueue_t sq; +}osal_queue_def_t; + +typedef QueueHandle_t osal_queue_t; + +static inline osal_queue_t osal_queue_create(osal_queue_def_t* qdef) +{ + return xQueueCreateStatic(qdef->depth, qdef->item_sz, (uint8_t*) qdef->buf, &qdef->sq); +} + +static inline bool osal_queue_receive(osal_queue_t qhdl, void* data) +{ + return xQueueReceive(qhdl, data, portMAX_DELAY); +} + +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; + 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 + if ( xHigherPriorityTaskWoken ) portYIELD_FROM_ISR(); +#else + portYIELD_FROM_ISR(xHigherPriorityTaskWoken); +#endif + + return res != 0; + } +} + +static inline bool osal_queue_empty(osal_queue_t qhdl) +{ + return uxQueueMessagesWaiting(qhdl) == 0; +} + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal_mynewt.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal_mynewt.h new file mode 100644 index 000000000..6882329c1 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal_mynewt.h @@ -0,0 +1,174 @@ +/* + * 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 +//--------------------------------------------------------------------+ +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; + +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; +} + +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; +} + +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; + +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; +} + +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; +} + +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(_role, _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; + +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; +} + +static inline bool osal_queue_receive(osal_queue_t qhdl, void* data) +{ + 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; +} + +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/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal_none.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal_none.h new file mode 100644 index 000000000..a1f997cf2 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal_none.h @@ -0,0 +1,204 @@ +/* + * 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_NONE_H_ +#define _TUSB_OSAL_NONE_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// TASK API +//--------------------------------------------------------------------+ + + +//--------------------------------------------------------------------+ +// Binary Semaphore API +//--------------------------------------------------------------------+ +typedef struct +{ + volatile uint16_t count; +}osal_semaphore_def_t; + +typedef osal_semaphore_def_t* osal_semaphore_t; + +static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef) +{ + semdef->count = 0; + return semdef; +} + +static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) +{ + (void) in_isr; + sem_hdl->count++; + return true; +} + +// TODO blocking for now +static inline bool osal_semaphore_wait (osal_semaphore_t sem_hdl, uint32_t msec) +{ + (void) msec; + + while (sem_hdl->count == 0) { } + sem_hdl->count--; + + return true; +} + +static inline void osal_semaphore_reset(osal_semaphore_t sem_hdl) +{ + sem_hdl->count = 0; +} + +//--------------------------------------------------------------------+ +// MUTEX API +// Within tinyusb, mutex is never used in ISR context +//--------------------------------------------------------------------+ +typedef osal_semaphore_def_t osal_mutex_def_t; +typedef osal_semaphore_t osal_mutex_t; + +static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef) +{ + mdef->count = 1; + return mdef; +} + +static inline bool osal_mutex_lock (osal_mutex_t mutex_hdl, uint32_t msec) +{ + return osal_semaphore_wait(mutex_hdl, msec); +} + +static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl) +{ + return osal_semaphore_post(mutex_hdl, false); +} + +//--------------------------------------------------------------------+ +// QUEUE API +//--------------------------------------------------------------------+ +#include "common/tusb_fifo.h" + +// extern to avoid including dcd.h and hcd.h +#if TUSB_OPT_DEVICE_ENABLED +extern void dcd_int_disable(uint8_t rhport); +extern void dcd_int_enable(uint8_t rhport); +#endif + +#if TUSB_OPT_HOST_ENABLED +extern void hcd_int_disable(uint8_t rhport); +extern void hcd_int_enable(uint8_t rhport); +#endif + +typedef struct +{ + uint8_t role; // device or host + tu_fifo_t ff; +}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(_role, _name, _depth, _type) \ + uint8_t _name##_buf[_depth*sizeof(_type)]; \ + osal_queue_def_t _name = { \ + .role = _role, \ + .ff = TU_FIFO_INIT(_name##_buf, _depth, _type, false) \ + } + +// lock queue by disable USB interrupt +static inline void _osal_q_lock(osal_queue_t qhdl) +{ + (void) qhdl; + +#if TUSB_OPT_DEVICE_ENABLED + if (qhdl->role == OPT_MODE_DEVICE) dcd_int_disable(TUD_OPT_RHPORT); +#endif + +#if TUSB_OPT_HOST_ENABLED + if (qhdl->role == OPT_MODE_HOST) hcd_int_disable(TUH_OPT_RHPORT); +#endif +} + +// unlock queue +static inline void _osal_q_unlock(osal_queue_t qhdl) +{ + (void) qhdl; + +#if TUSB_OPT_DEVICE_ENABLED + if (qhdl->role == OPT_MODE_DEVICE) dcd_int_enable(TUD_OPT_RHPORT); +#endif + +#if TUSB_OPT_HOST_ENABLED + if (qhdl->role == OPT_MODE_HOST) hcd_int_enable(TUH_OPT_RHPORT); +#endif +} + +static inline osal_queue_t osal_queue_create(osal_queue_def_t* qdef) +{ + tu_fifo_clear(&qdef->ff); + return (osal_queue_t) qdef; +} + +static inline bool osal_queue_receive(osal_queue_t qhdl, void* data) +{ + _osal_q_lock(qhdl); + bool success = tu_fifo_read(&qhdl->ff, data); + _osal_q_unlock(qhdl); + + return success; +} + +static inline bool osal_queue_send(osal_queue_t qhdl, void const * data, bool in_isr) +{ + if (!in_isr) { + _osal_q_lock(qhdl); + } + + bool success = tu_fifo_write(&qhdl->ff, data); + + if (!in_isr) { + _osal_q_unlock(qhdl); + } + + TU_ASSERT(success); + + return success; +} + +static inline bool osal_queue_empty(osal_queue_t qhdl) +{ + // Skip queue lock/unlock since this function is primarily called + // with interrupt disabled before going into low power mode + return tu_fifo_empty(&qhdl->ff); +} + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_OSAL_NONE_H_ */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal_pico.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal_pico.h new file mode 100644 index 000000000..1c3366e01 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal_pico.h @@ -0,0 +1,187 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef _TUSB_OSAL_PICO_H_ +#define _TUSB_OSAL_PICO_H_ + +#include "pico/time.h" +#include "pico/sem.h" +#include "pico/mutex.h" +#include "pico/critical_section.h" + +#ifdef __cplusplus + extern "C" { +#endif + +//--------------------------------------------------------------------+ +// TASK API +//--------------------------------------------------------------------+ +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; + +static inline osal_semaphore_t osal_semaphore_create(osal_semaphore_def_t* semdef) +{ + sem_init(semdef, 0, 255); + return semdef; +} + +static inline bool osal_semaphore_post(osal_semaphore_t sem_hdl, bool in_isr) +{ + (void) in_isr; + sem_release(sem_hdl); + return true; +} + +static inline bool osal_semaphore_wait (osal_semaphore_t sem_hdl, uint32_t msec) +{ + return sem_acquire_timeout_ms(sem_hdl, msec); +} + +static inline void osal_semaphore_reset(osal_semaphore_t sem_hdl) +{ + sem_reset(sem_hdl, 0); +} + +//--------------------------------------------------------------------+ +// MUTEX API +// Within tinyusb, mutex is never used in ISR context +//--------------------------------------------------------------------+ +typedef struct mutex osal_mutex_def_t, *osal_mutex_t; + +static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef) +{ + mutex_init(mdef); + return mdef; +} + +static inline bool osal_mutex_lock (osal_mutex_t mutex_hdl, uint32_t msec) +{ + return mutex_enter_timeout_ms(mutex_hdl, msec); +} + +static inline bool osal_mutex_unlock(osal_mutex_t mutex_hdl) +{ + mutex_exit(mutex_hdl); + return true; +} + +//--------------------------------------------------------------------+ +// QUEUE API +//--------------------------------------------------------------------+ +#include "common/tusb_fifo.h" + +#if TUSB_OPT_HOST_ENABLED +extern void hcd_int_disable(uint8_t rhport); +extern void hcd_int_enable(uint8_t rhport); +#endif + +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(_role, _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 +static inline void _osal_q_lock(osal_queue_t qhdl) +{ + critical_section_enter_blocking(&qhdl->critsec); +} + +// unlock queue +static inline void _osal_q_unlock(osal_queue_t qhdl) +{ + critical_section_exit(&qhdl->critsec); +} + +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; +} + +static inline bool osal_queue_receive(osal_queue_t qhdl, void* data) +{ + // 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); + bool success = tu_fifo_read(&qhdl->ff, data); + _osal_q_unlock(qhdl); + + return success; +} + +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); + (void) in_isr; + + _osal_q_lock(qhdl); + bool success = tu_fifo_write(&qhdl->ff, data); + _osal_q_unlock(qhdl); + + TU_ASSERT(success); + + return success; +} + +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. + + // Skip queue lock/unlock since this function is primarily called + // with interrupt disabled before going into low power mode + return tu_fifo_empty(&qhdl->ff); +} + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_OSAL_PICO_H_ */ diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal_rtthread.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal_rtthread.h new file mode 100644 index 000000000..d5c062ac1 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal_rtthread.h @@ -0,0 +1,130 @@ +/* + * 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 +//--------------------------------------------------------------------+ +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; + +static inline osal_semaphore_t +osal_semaphore_create(osal_semaphore_def_t *semdef) { + rt_sem_init(semdef, "tusb", 0, RT_IPC_FLAG_FIFO); + return semdef; +} + +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; +} + +static inline bool osal_semaphore_wait(osal_semaphore_t sem_hdl, uint32_t msec) { + return rt_sem_take(sem_hdl, rt_tick_from_millisecond(msec)) == RT_EOK; +} + +static inline void osal_semaphore_reset(osal_semaphore_t const sem_hdl) { + // TODO: implement +} + +//--------------------------------------------------------------------+ +// MUTEX API (priority inheritance) +//--------------------------------------------------------------------+ +typedef struct rt_mutex osal_mutex_def_t; +typedef rt_mutex_t osal_mutex_t; + +static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t *mdef) { + rt_mutex_init(mdef, "tusb", RT_IPC_FLAG_FIFO); + return mdef; +} + +static inline bool osal_mutex_lock(osal_mutex_t mutex_hdl, uint32_t msec) { + return rt_mutex_take(mutex_hdl, rt_tick_from_millisecond(msec)) == RT_EOK; +} + +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(_role, _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; + +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_FIFO); + return &(qdef->sq); +} + +static inline bool osal_queue_receive(osal_queue_t qhdl, void *data) { + return rt_mq_recv(qhdl, data, qhdl->msg_size, RT_WAITING_FOREVER) == RT_EOK; +} + +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; +} + +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/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal_rtx4.h b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal_rtx4.h new file mode 100644 index 000000000..f7e88e322 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/osal/osal_rtx4.h @@ -0,0 +1,170 @@ +/* + * 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 +//--------------------------------------------------------------------+ +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); +} + +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; + +static inline OS_ID osal_semaphore_create(osal_semaphore_def_t* semdef) { + os_sem_init(semdef, 0); + return semdef; +} + +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; +} + +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; +} + +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; + +static inline osal_mutex_t osal_mutex_create(osal_mutex_def_t* mdef) +{ + os_mut_init(mdef); + return mdef; +} + +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; +} + +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(_role, _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; + +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; +} + +static inline bool osal_queue_receive(osal_queue_t qhdl, void* data) +{ + void* buf; + os_mbx_wait(qhdl->mbox, &buf, 0xFFFF); + memcpy(data, buf, qhdl->item_sz); + _free_box(qhdl->pool, buf); + return true; +} + +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; +} + +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/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/portable/espressif/esp32sx/dcd_esp32sx.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/portable/espressif/esp32sx/dcd_esp32sx.c new file mode 100644 index 000000000..a9c311324 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/portable/espressif/esp32sx/dcd_esp32sx.c @@ -0,0 +1,854 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2018 Scott Shawcroft, 2019 William D. Jones for Adafruit Industries + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * Additions Copyright (c) 2020, Espressif Systems (Shanghai) Co. Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#include "tusb_option.h" + +#if (((CFG_TUSB_MCU == OPT_MCU_ESP32S2) || (CFG_TUSB_MCU == OPT_MCU_ESP32S3)) && TUSB_OPT_DEVICE_ENABLED) + +// Espressif +#include "freertos/xtensa_api.h" +#include "esp_intr_alloc.h" +#include "esp_log.h" +#include "driver/gpio.h" +#include "soc/dport_reg.h" +#include "soc/gpio_sig_map.h" +#include "soc/usb_periph.h" +#include "soc/periph_defs.h" // for interrupt source + +#include "device/dcd.h" + +// Max number of bi-directional endpoints including EP0 +// Note: ESP32S2 specs say there are only up to 5 IN active endpoints include EP0 +// We should probably prohibit enabling Endpoint IN > 4 (not done yet) +#define EP_MAX USB_OUT_EP_NUM + +// FIFO size in bytes +#define EP_FIFO_SIZE 1024 + +// Max number of IN EP FIFOs +#define EP_FIFO_NUM 5 + +typedef struct { + uint8_t *buffer; + // tu_fifo_t * ff; // TODO support dcd_edpt_xfer_fifo API + uint16_t total_len; + uint16_t queued_len; + uint16_t max_size; + bool short_packet; +} xfer_ctl_t; + +static const char *TAG = "TUSB:DCD"; +static intr_handle_t usb_ih; + + +static uint32_t _setup_packet[2]; + +#define XFER_CTL_BASE(_ep, _dir) &xfer_status[_ep][_dir] +static xfer_ctl_t xfer_status[EP_MAX][2]; + +// Keep count of how many FIFOs are in use +static uint8_t _allocated_fifos = 1; //FIFO0 is always in use + +// Will either return an unused FIFO number, or 0 if all are used. +static uint8_t get_free_fifo(void) +{ + if (_allocated_fifos < EP_FIFO_NUM) return _allocated_fifos++; + return 0; +} + +// Setup the control endpoint 0. +static void bus_reset(void) +{ + for (int ep_num = 0; ep_num < USB_OUT_EP_NUM; ep_num++) { + USB0.out_ep_reg[ep_num].doepctl |= USB_DO_SNAK0_M; // DOEPCTL0_SNAK + } + + // clear device address + USB0.dcfg &= ~USB_DEVADDR_M; + + USB0.daintmsk = USB_OUTEPMSK0_M | USB_INEPMSK0_M; + USB0.doepmsk = USB_SETUPMSK_M | USB_XFERCOMPLMSK; + USB0.diepmsk = USB_TIMEOUTMSK_M | USB_DI_XFERCOMPLMSK_M /*| USB_INTKNTXFEMPMSK_M*/; + + // "USB Data FIFOs" section in reference manual + // Peripheral FIFO architecture + // + // --------------- 320 or 1024 ( 1280 or 4096 bytes ) + // | IN FIFO MAX | + // --------------- + // | ... | + // --------------- y + x + 16 + GRXFSIZ + // | IN FIFO 2 | + // --------------- x + 16 + GRXFSIZ + // | IN FIFO 1 | + // --------------- 16 + GRXFSIZ + // | IN FIFO 0 | + // --------------- GRXFSIZ + // | OUT FIFO | + // | ( Shared ) | + // --------------- 0 + // + // According to "FIFO RAM allocation" section in RM, FIFO RAM are allocated as follows (each word 32-bits): + // - Each EP IN needs at least max packet size, 16 words is sufficient for EP0 IN + // + // - All EP OUT shared a unique OUT FIFO which uses + // * 10 locations in hardware for setup packets + setup control words (up to 3 setup packets). + // * 2 locations for OUT endpoint control words. + // * 16 for largest packet size of 64 bytes. ( TODO Highspeed is 512 bytes) + // * 1 location for global NAK (not required/used here). + // * It is recommended to allocate 2 times the largest packet size, therefore + // Recommended value = 10 + 1 + 2 x (16+2) = 47 --> Let's make it 52 + USB0.grstctl |= 0x10 << USB_TXFNUM_S; // fifo 0x10, + USB0.grstctl |= USB_TXFFLSH_M; // Flush fifo + USB0.grxfsiz = 52; + + // Control IN uses FIFO 0 with 64 bytes ( 16 32-bit word ) + USB0.gnptxfsiz = (16 << USB_NPTXFDEP_S) | (USB0.grxfsiz & 0x0000ffffUL); + + // Ready to receive SETUP packet + USB0.out_ep_reg[0].doeptsiz |= USB_SUPCNT0_M; + + USB0.gintmsk |= USB_IEPINTMSK_M | USB_OEPINTMSK_M; +} + +static void enum_done_processing(void) +{ + ESP_EARLY_LOGV(TAG, "dcd_int_handler - Speed enumeration done! Sending DCD_EVENT_BUS_RESET then"); + // On current silicon on the Full Speed core, speed is fixed to Full Speed. + // However, keep for debugging and in case Low Speed is ever supported. + uint32_t enum_spd = (USB0.dsts >> USB_ENUMSPD_S) & (USB_ENUMSPD_V); + + // Maximum packet size for EP 0 is set for both directions by writing DIEPCTL + if (enum_spd == 0x03) { // Full-Speed (PHY on 48 MHz) + USB0.in_ep_reg[0].diepctl &= ~USB_D_MPS0_V; // 64 bytes + USB0.in_ep_reg[0].diepctl &= ~USB_D_STALL0_M; // clear Stall + xfer_status[0][TUSB_DIR_OUT].max_size = 64; + xfer_status[0][TUSB_DIR_IN].max_size = 64; + } else { + USB0.in_ep_reg[0].diepctl |= USB_D_MPS0_V; // 8 bytes + USB0.in_ep_reg[0].diepctl &= ~USB_D_STALL0_M; // clear Stall + xfer_status[0][TUSB_DIR_OUT].max_size = 8; + xfer_status[0][TUSB_DIR_IN].max_size = 8; + } +} + + +/*------------------------------------------------------------------*/ +/* Controller API + *------------------------------------------------------------------*/ +void dcd_init(uint8_t rhport) +{ + ESP_LOGV(TAG, "DCD init - Start"); + + // A. Disconnect + ESP_LOGV(TAG, "DCD init - Soft DISCONNECT and Setting up"); + USB0.dctl |= USB_SFTDISCON_M; // Soft disconnect + + // B. Programming DCFG + /* If USB host misbehaves during status portion of control xfer + (non zero-length packet), send STALL back and discard. Full speed. */ + USB0.dcfg |= USB_NZSTSOUTHSHK_M | // NonZero .... STALL + (3 << 0); // dev speed: fullspeed 1.1 on 48 mhz // TODO no value in usb_reg.h (IDF-1476) + + USB0.gahbcfg |= USB_NPTXFEMPLVL_M | USB_GLBLLNTRMSK_M; // Global interruptions ON + USB0.gusbcfg |= USB_FORCEDEVMODE_M; // force devmode + USB0.gotgctl &= ~(USB_BVALIDOVVAL_M | USB_BVALIDOVEN_M | USB_VBVALIDOVVAL_M); //no overrides + + // C. Setting SNAKs, then connect + for (int n = 0; n < USB_OUT_EP_NUM; n++) { + USB0.out_ep_reg[n].doepctl |= USB_DO_SNAK0_M; // DOEPCTL0_SNAK + } + + // D. Interruption masking + USB0.gintmsk = 0; //mask all + USB0.gotgint = ~0U; //clear OTG ints + USB0.gintsts = ~0U; //clear pending ints + USB0.gintmsk = USB_OTGINTMSK_M | + USB_MODEMISMSK_M | + USB_RXFLVIMSK_M | + USB_ERLYSUSPMSK_M | + USB_USBSUSPMSK_M | + USB_USBRSTMSK_M | + USB_ENUMDONEMSK_M | + USB_RESETDETMSK_M | + USB_DISCONNINTMSK_M; // host most only + + dcd_connect(rhport); +} + +void dcd_set_address(uint8_t rhport, uint8_t dev_addr) +{ + (void)rhport; + ESP_LOGV(TAG, "DCD init - Set address : %u", dev_addr); + USB0.dcfg |= ((dev_addr & USB_DEVADDR_V) << USB_DEVADDR_S); + // Response with status after changing device address + dcd_edpt_xfer(rhport, tu_edpt_addr(0, TUSB_DIR_IN), NULL, 0); +} + +void dcd_remote_wakeup(uint8_t rhport) +{ + (void)rhport; + + // set remote wakeup + USB0.dctl |= USB_RMTWKUPSIG_M; + + // enable SOF to detect bus resume + USB0.gintsts = USB_SOF_M; + USB0.gintmsk |= USB_SOFMSK_M; + + // Per specs: remote wakeup signal bit must be clear within 1-15ms + vTaskDelay(pdMS_TO_TICKS(1)); + + USB0.dctl &= ~USB_RMTWKUPSIG_M; +} + +// connect by enabling internal pull-up resistor on D+/D- +void dcd_connect(uint8_t rhport) +{ + (void) rhport; + USB0.dctl &= ~USB_SFTDISCON_M; +} + +// disconnect by disabling internal pull-up resistor on D+/D- +void dcd_disconnect(uint8_t rhport) +{ + (void) rhport; + USB0.dctl |= USB_SFTDISCON_M; +} + +/*------------------------------------------------------------------*/ +/* DCD Endpoint port + *------------------------------------------------------------------*/ + +bool dcd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const *desc_edpt) +{ + ESP_LOGV(TAG, "DCD endpoint opened"); + (void)rhport; + + usb_out_endpoint_t *out_ep = &(USB0.out_ep_reg[0]); + usb_in_endpoint_t *in_ep = &(USB0.in_ep_reg[0]); + + uint8_t const epnum = tu_edpt_number(desc_edpt->bEndpointAddress); + uint8_t const dir = tu_edpt_dir(desc_edpt->bEndpointAddress); + + TU_ASSERT(epnum < EP_MAX); + + xfer_ctl_t *xfer = XFER_CTL_BASE(epnum, dir); + xfer->max_size = tu_edpt_packet_size(desc_edpt); + + if (dir == TUSB_DIR_OUT) { + out_ep[epnum].doepctl |= USB_USBACTEP1_M | + desc_edpt->bmAttributes.xfer << USB_EPTYPE1_S | + (desc_edpt->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS ? USB_DO_SETD0PID1_M : 0) | + xfer->max_size << USB_MPS1_S; + USB0.daintmsk |= (1 << (16 + epnum)); + } else { + // "USB Data FIFOs" section in reference manual + // Peripheral FIFO architecture + // + // --------------- 320 or 1024 ( 1280 or 4096 bytes ) + // | IN FIFO MAX | + // --------------- + // | ... | + // --------------- y + x + 16 + GRXFSIZ + // | IN FIFO 2 | + // --------------- x + 16 + GRXFSIZ + // | IN FIFO 1 | + // --------------- 16 + GRXFSIZ + // | IN FIFO 0 | + // --------------- GRXFSIZ + // | OUT FIFO | + // | ( Shared ) | + // --------------- 0 + // + // Since OUT FIFO = GRXFSIZ, FIFO 0 = 16, for simplicity, we equally allocated for the rest of endpoints + // - Size : (FIFO_SIZE/4 - GRXFSIZ - 16) / (EP_MAX-1) + // - Offset: GRXFSIZ + 16 + Size*(epnum-1) + // - IN EP 1 gets FIFO 1, IN EP "n" gets FIFO "n". + + uint8_t fifo_num = get_free_fifo(); + TU_ASSERT(fifo_num != 0); + + in_ep[epnum].diepctl &= ~(USB_D_TXFNUM1_M | USB_D_EPTYPE1_M | USB_DI_SETD0PID1 | USB_D_MPS1_M); + in_ep[epnum].diepctl |= USB_D_USBACTEP1_M | + fifo_num << USB_D_TXFNUM1_S | + desc_edpt->bmAttributes.xfer << USB_D_EPTYPE1_S | + (desc_edpt->bmAttributes.xfer != TUSB_XFER_ISOCHRONOUS ? (1 << USB_DI_SETD0PID1_S) : 0) | + xfer->max_size << 0; + + USB0.daintmsk |= (1 << (0 + epnum)); + + // Both TXFD and TXSA are in unit of 32-bit words. + // IN FIFO 0 was configured during enumeration, hence the "+ 16". + uint16_t const allocated_size = (USB0.grxfsiz & 0x0000ffff) + 16; + uint16_t const fifo_size = (EP_FIFO_SIZE/4 - allocated_size) / (EP_FIFO_NUM-1); + uint32_t const fifo_offset = allocated_size + fifo_size*(fifo_num-1); + + // DIEPTXF starts at FIFO #1. + USB0.dieptxf[epnum - 1] = (fifo_size << USB_NPTXFDEP_S) | fifo_offset; + } + return true; +} + +void dcd_edpt_close_all(uint8_t rhport) +{ + (void) rhport; + + usb_out_endpoint_t *out_ep = &(USB0.out_ep_reg[0]); + usb_in_endpoint_t *in_ep = &(USB0.in_ep_reg[0]); + + // Disable non-control interrupt + USB0.daintmsk = USB_OUTEPMSK0_M | USB_INEPMSK0_M; + + for(uint8_t n = 1; n < EP_MAX; n++) + { + // disable OUT endpoint + out_ep[n].doepctl = 0; + xfer_status[n][TUSB_DIR_OUT].max_size = 0; + + // disable IN endpoint + in_ep[n].diepctl = 0; + xfer_status[n][TUSB_DIR_IN].max_size = 0; + } + + _allocated_fifos = 1; +} + +bool dcd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes) +{ + (void)rhport; + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + xfer_ctl_t * xfer = XFER_CTL_BASE(epnum, dir); + xfer->buffer = buffer; + // xfer->ff = NULL; // TODO support dcd_edpt_xfer_fifo API + xfer->total_len = total_bytes; + xfer->queued_len = 0; + xfer->short_packet = false; + + uint16_t num_packets = (total_bytes / xfer->max_size); + uint8_t short_packet_size = total_bytes % xfer->max_size; + + // Zero-size packet is special case. + if (short_packet_size > 0 || (total_bytes == 0)) { + num_packets++; + } + + ESP_LOGV(TAG, "Transfer <-> EP%i, %s, pkgs: %i, bytes: %i", + epnum, ((dir == TUSB_DIR_IN) ? "USB0.HOST (in)" : "HOST->DEV (out)"), + num_packets, total_bytes); + + // IN and OUT endpoint xfers are interrupt-driven, we just schedule them + // here. + if (dir == TUSB_DIR_IN) { + // A full IN transfer (multiple packets, possibly) triggers XFRC. + USB0.in_ep_reg[epnum].dieptsiz = (num_packets << USB_D_PKTCNT0_S) | total_bytes; + USB0.in_ep_reg[epnum].diepctl |= USB_D_EPENA1_M | USB_D_CNAK1_M; // Enable | CNAK + + // Enable fifo empty interrupt only if there are something to put in the fifo. + if(total_bytes != 0) { + USB0.dtknqr4_fifoemptymsk |= (1 << epnum); + } + } else { + // Each complete packet for OUT xfers triggers XFRC. + USB0.out_ep_reg[epnum].doeptsiz |= USB_PKTCNT0_M | ((xfer->max_size & USB_XFERSIZE0_V) << USB_XFERSIZE0_S); + USB0.out_ep_reg[epnum].doepctl |= USB_EPENA0_M | USB_CNAK0_M; + } + return true; +} + +#if 0 // TODO support dcd_edpt_xfer_fifo API +bool dcd_edpt_xfer_fifo (uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes) +{ + (void)rhport; +} +#endif + +void dcd_edpt_stall(uint8_t rhport, uint8_t ep_addr) +{ + (void)rhport; + + usb_out_endpoint_t *out_ep = &(USB0.out_ep_reg[0]); + usb_in_endpoint_t *in_ep = &(USB0.in_ep_reg[0]); + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + if (dir == TUSB_DIR_IN) { + // Only disable currently enabled non-control endpoint + if ((epnum == 0) || !(in_ep[epnum].diepctl & USB_D_EPENA1_M)) { + in_ep[epnum].diepctl |= (USB_DI_SNAK1_M | USB_D_STALL1_M); + } else { + // Stop transmitting packets and NAK IN xfers. + in_ep[epnum].diepctl |= USB_DI_SNAK1_M; + while ((in_ep[epnum].diepint & USB_DI_SNAK1_M) == 0) ; + + // Disable the endpoint. Note that both SNAK and STALL are set here. + in_ep[epnum].diepctl |= (USB_DI_SNAK1_M | USB_D_STALL1_M | USB_D_EPDIS1_M); + while ((in_ep[epnum].diepint & USB_D_EPDISBLD0_M) == 0) ; + in_ep[epnum].diepint = USB_D_EPDISBLD0_M; + } + + // Flush the FIFO, and wait until we have confirmed it cleared. + uint8_t const fifo_num = ((in_ep[epnum].diepctl >> USB_D_TXFNUM1_S) & USB_D_TXFNUM1_V); + USB0.grstctl |= (fifo_num << USB_TXFNUM_S); + USB0.grstctl |= USB_TXFFLSH_M; + while ((USB0.grstctl & USB_TXFFLSH_M) != 0) ; + } else { + // Only disable currently enabled non-control endpoint + if ((epnum == 0) || !(out_ep[epnum].doepctl & USB_EPENA0_M)) { + out_ep[epnum].doepctl |= USB_STALL0_M; + } else { + // Asserting GONAK is required to STALL an OUT endpoint. + // Simpler to use polling here, we don't use the "B"OUTNAKEFF interrupt + // anyway, and it can't be cleared by user code. If this while loop never + // finishes, we have bigger problems than just the stack. + USB0.dctl |= USB_SGOUTNAK_M; + while ((USB0.gintsts & USB_GOUTNAKEFF_M) == 0) ; + + // Ditto here- disable the endpoint. Note that only STALL and not SNAK + // is set here. + out_ep[epnum].doepctl |= (USB_STALL0_M | USB_EPDIS0_M); + while ((out_ep[epnum].doepint & USB_EPDISBLD0_M) == 0) ; + out_ep[epnum].doepint = USB_EPDISBLD0_M; + + // Allow other OUT endpoints to keep receiving. + USB0.dctl |= USB_CGOUTNAK_M; + } + } +} + +void dcd_edpt_clear_stall(uint8_t rhport, uint8_t ep_addr) +{ + (void)rhport; + + usb_out_endpoint_t *out_ep = &(USB0.out_ep_reg[0]); + usb_in_endpoint_t *in_ep = &(USB0.in_ep_reg[0]); + + uint8_t const epnum = tu_edpt_number(ep_addr); + uint8_t const dir = tu_edpt_dir(ep_addr); + + if (dir == TUSB_DIR_IN) { + in_ep[epnum].diepctl &= ~USB_D_STALL1_M; + + uint8_t eptype = (in_ep[epnum].diepctl & USB_D_EPTYPE1_M) >> USB_D_EPTYPE1_S; + // Required by USB spec to reset DATA toggle bit to DATA0 on interrupt + // and bulk endpoints. + if (eptype == 2 || eptype == 3) { + in_ep[epnum].diepctl |= USB_DI_SETD0PID1_M; + } + } else { + out_ep[epnum].doepctl &= ~USB_STALL1_M; + + uint8_t eptype = (out_ep[epnum].doepctl & USB_EPTYPE1_M) >> USB_EPTYPE1_S; + // Required by USB spec to reset DATA toggle bit to DATA0 on interrupt + // and bulk endpoints. + if (eptype == 2 || eptype == 3) { + out_ep[epnum].doepctl |= USB_DO_SETD0PID1_M; + } + } +} + +/*------------------------------------------------------------------*/ + +static void receive_packet(xfer_ctl_t *xfer, /* usb_out_endpoint_t * out_ep, */ uint16_t xfer_size) +{ + ESP_EARLY_LOGV(TAG, "USB - receive_packet"); + volatile uint32_t *rx_fifo = USB0.fifo[0]; + + // See above TODO + // uint16_t remaining = (out_ep->DOEPTSIZ & UsbDOEPTSIZ_XFRSIZ_Msk) >> UsbDOEPTSIZ_XFRSIZ_Pos; + // xfer->queued_len = xfer->total_len - remaining; + + uint16_t remaining = xfer->total_len - xfer->queued_len; + uint16_t to_recv_size; + + if (remaining <= xfer->max_size) { + // Avoid buffer overflow. + to_recv_size = (xfer_size > remaining) ? remaining : xfer_size; + } else { + // Room for full packet, choose recv_size based on what the microcontroller + // claims. + to_recv_size = (xfer_size > xfer->max_size) ? xfer->max_size : xfer_size; + } + + // Common buffer read +#if 0 // TODO support dcd_edpt_xfer_fifo API + if (xfer->ff) + { + // Ring buffer + tu_fifo_write_n_const_addr_full_words(xfer->ff, (const void *) rx_fifo, to_recv_size); + } + else +#endif + { + uint8_t to_recv_rem = to_recv_size % 4; + uint16_t to_recv_size_aligned = to_recv_size - to_recv_rem; + + // Do not assume xfer buffer is aligned. + uint8_t *base = (xfer->buffer + xfer->queued_len); + + // This for loop always runs at least once- skip if less than 4 bytes + // to collect. + if (to_recv_size >= 4) { + for (uint16_t i = 0; i < to_recv_size_aligned; i += 4) { + uint32_t tmp = (*rx_fifo); + base[i] = tmp & 0x000000FF; + base[i + 1] = (tmp & 0x0000FF00) >> 8; + base[i + 2] = (tmp & 0x00FF0000) >> 16; + base[i + 3] = (tmp & 0xFF000000) >> 24; + } + } + + // Do not read invalid bytes from RX FIFO. + if (to_recv_rem != 0) { + uint32_t tmp = (*rx_fifo); + uint8_t *last_32b_bound = base + to_recv_size_aligned; + + last_32b_bound[0] = tmp & 0x000000FF; + if (to_recv_rem > 1) { + last_32b_bound[1] = (tmp & 0x0000FF00) >> 8; + } + if (to_recv_rem > 2) { + last_32b_bound[2] = (tmp & 0x00FF0000) >> 16; + } + } + } + + xfer->queued_len += xfer_size; + + // Per USB spec, a short OUT packet (including length 0) is always + // indicative of the end of a transfer (at least for ctl, bulk, int). + xfer->short_packet = (xfer_size < xfer->max_size); +} + +static void transmit_packet(xfer_ctl_t *xfer, volatile usb_in_endpoint_t *in_ep, uint8_t fifo_num) +{ + ESP_EARLY_LOGV(TAG, "USB - transmit_packet"); + volatile uint32_t *tx_fifo = USB0.fifo[fifo_num]; + + uint16_t remaining = (in_ep->dieptsiz & 0x7FFFFU) >> USB_D_XFERSIZE0_S; + xfer->queued_len = xfer->total_len - remaining; + + uint16_t to_xfer_size = (remaining > xfer->max_size) ? xfer->max_size : remaining; + +#if 0 // TODO support dcd_edpt_xfer_fifo API + if (xfer->ff) + { + tu_fifo_read_n_const_addr_full_words(xfer->ff, (void *) tx_fifo, to_xfer_size); + } + else +#endif + { + uint8_t to_xfer_rem = to_xfer_size % 4; + uint16_t to_xfer_size_aligned = to_xfer_size - to_xfer_rem; + + // Buffer might not be aligned to 32b, so we need to force alignment + // by copying to a temp var. + uint8_t *base = (xfer->buffer + xfer->queued_len); + + // This for loop always runs at least once- skip if less than 4 bytes + // to send off. + if (to_xfer_size >= 4) { + for (uint16_t i = 0; i < to_xfer_size_aligned; i += 4) { + uint32_t tmp = base[i] | (base[i + 1] << 8) | + (base[i + 2] << 16) | (base[i + 3] << 24); + (*tx_fifo) = tmp; + } + } + + // Do not read beyond end of buffer if not divisible by 4. + if (to_xfer_rem != 0) { + uint32_t tmp = 0; + uint8_t *last_32b_bound = base + to_xfer_size_aligned; + + tmp |= last_32b_bound[0]; + if (to_xfer_rem > 1) { + tmp |= (last_32b_bound[1] << 8); + } + if (to_xfer_rem > 2) { + tmp |= (last_32b_bound[2] << 16); + } + + (*tx_fifo) = tmp; + } + } +} + +static void read_rx_fifo(void) +{ + // Pop control word off FIFO (completed xfers will have 2 control words, + // we only pop one ctl word each interrupt). + uint32_t const ctl_word = USB0.grxstsp; + uint8_t const pktsts = (ctl_word & USB_PKTSTS_M) >> USB_PKTSTS_S; + uint8_t const epnum = (ctl_word & USB_CHNUM_M ) >> USB_CHNUM_S; + uint16_t const bcnt = (ctl_word & USB_BCNT_M ) >> USB_BCNT_S; + + switch (pktsts) { + case 0x01: // Global OUT NAK (Interrupt) + ESP_EARLY_LOGV(TAG, "TUSB IRQ - RX type : Global OUT NAK"); + break; + + case 0x02: { // Out packet recvd + ESP_EARLY_LOGV(TAG, "TUSB IRQ - RX type : Out packet"); + xfer_ctl_t *xfer = XFER_CTL_BASE(epnum, TUSB_DIR_OUT); + receive_packet(xfer, bcnt); + } + break; + + case 0x03: // Out packet done (Interrupt) + ESP_EARLY_LOGV(TAG, "TUSB IRQ - RX type : Out packet done"); + break; + + case 0x04: // Step 2: Setup transaction completed (Interrupt) + // After this event, OEPINT interrupt will occur with SETUP bit set + ESP_EARLY_LOGV(TAG, "TUSB IRQ - RX : Setup packet done"); + USB0.out_ep_reg[epnum].doeptsiz |= USB_SUPCNT0_M; + break; + + case 0x06: { // Step1: Setup data packet received + volatile uint32_t *rx_fifo = USB0.fifo[0]; + + // We can receive up to three setup packets in succession, but + // only the last one is valid. Therefore we just overwrite it + _setup_packet[0] = (*rx_fifo); + _setup_packet[1] = (*rx_fifo); + + ESP_EARLY_LOGV(TAG, "TUSB IRQ - RX : Setup packet : 0x%08x 0x%08x", _setup_packet[0], _setup_packet[1]); + } + break; + + default: // Invalid, do something here, like breakpoint? + TU_BREAKPOINT(); + break; + } +} + +static void handle_epout_ints(void) +{ + // GINTSTS will be cleared with DAINT == 0 + // DAINT for a given EP clears when DOEPINTx is cleared. + // DOEPINT will be cleared when DAINT's out bits are cleared. + for (int n = 0; n < USB_OUT_EP_NUM; n++) { + xfer_ctl_t *xfer = XFER_CTL_BASE(n, TUSB_DIR_OUT); + + if (USB0.daint & (1 << (16 + n))) { + // SETUP packet Setup Phase done. + if ((USB0.out_ep_reg[n].doepint & USB_SETUP0_M)) { + USB0.out_ep_reg[n].doepint = USB_STUPPKTRCVD0_M | USB_SETUP0_M; // clear + dcd_event_setup_received(0, (uint8_t *)&_setup_packet[0], true); + } + + // OUT XFER complete (single packet).q + if (USB0.out_ep_reg[n].doepint & USB_XFERCOMPL0_M) { + + ESP_EARLY_LOGV(TAG, "TUSB IRQ - EP OUT - XFER complete (single packet)"); + USB0.out_ep_reg[n].doepint = USB_XFERCOMPL0_M; + + // Transfer complete if short packet or total len is transferred + if (xfer->short_packet || (xfer->queued_len == xfer->total_len)) { + xfer->short_packet = false; + dcd_event_xfer_complete(0, n, xfer->queued_len, XFER_RESULT_SUCCESS, true); + } else { + // Schedule another packet to be received. + USB0.out_ep_reg[n].doeptsiz |= USB_PKTCNT0_M | ((xfer->max_size & USB_XFERSIZE0_V) << USB_XFERSIZE0_S); + USB0.out_ep_reg[n].doepctl |= USB_EPENA0_M | USB_CNAK0_M; + } + } + } + } +} + +static void handle_epin_ints(void) +{ + // GINTSTS will be cleared with DAINT == 0 + // DAINT for a given EP clears when DIEPINTx is cleared. + // IEPINT will be cleared when DAINT's out bits are cleared. + for (uint32_t n = 0; n < USB_IN_EP_NUM; n++) { + xfer_ctl_t *xfer = &xfer_status[n][TUSB_DIR_IN]; + + if (USB0.daint & (1 << (0 + n))) { + ESP_EARLY_LOGV(TAG, "TUSB IRQ - EP IN %u", n); + // IN XFER complete (entire xfer). + if (USB0.in_ep_reg[n].diepint & USB_D_XFERCOMPL0_M) { + ESP_EARLY_LOGV(TAG, "TUSB IRQ - IN XFER complete!"); + USB0.in_ep_reg[n].diepint = USB_D_XFERCOMPL0_M; + dcd_event_xfer_complete(0, n | TUSB_DIR_IN_MASK, xfer->total_len, XFER_RESULT_SUCCESS, true); + } + + // XFER FIFO empty + if (USB0.in_ep_reg[n].diepint & USB_D_TXFEMP0_M) { + ESP_EARLY_LOGV(TAG, "TUSB IRQ - IN XFER FIFO empty!"); + USB0.in_ep_reg[n].diepint = USB_D_TXFEMP0_M; + transmit_packet(xfer, &USB0.in_ep_reg[n], n); + + // Turn off TXFE if all bytes are written. + if (xfer->queued_len == xfer->total_len) + { + USB0.dtknqr4_fifoemptymsk &= ~(1 << n); + } + } + + // XFER Timeout + if (USB0.in_ep_reg[n].diepint & USB_D_TIMEOUT0_M) { + // Clear interrupt or enpoint will hang. + USB0.in_ep_reg[n].diepint = USB_D_TIMEOUT0_M; + // Maybe retry? + } + } + } +} + + +static void _dcd_int_handler(void* arg) +{ + (void) arg; + uint8_t const rhport = 0; + + const uint32_t int_msk = USB0.gintmsk; + const uint32_t int_status = USB0.gintsts & int_msk; + + if (int_status & USB_USBRST_M) { + // start of reset + ESP_EARLY_LOGV(TAG, "dcd_int_handler - reset"); + USB0.gintsts = USB_USBRST_M; + // FIFOs will be reassigned when the endpoints are reopen + _allocated_fifos = 1; + bus_reset(); + } + + if (int_status & USB_RESETDET_M) { + ESP_EARLY_LOGV(TAG, "dcd_int_handler - reset while suspend"); + USB0.gintsts = USB_RESETDET_M; + bus_reset(); + } + + if (int_status & USB_ENUMDONE_M) { + // ENUMDNE detects speed of the link. For full-speed, we + // always expect the same value. This interrupt is considered + // the end of reset. + USB0.gintsts = USB_ENUMDONE_M; + enum_done_processing(); + dcd_event_bus_reset(rhport, TUSB_SPEED_FULL, true); + } + + if(int_status & USB_USBSUSP_M) + { + USB0.gintsts = USB_USBSUSP_M; + dcd_event_bus_signal(rhport, DCD_EVENT_SUSPEND, true); + } + + if(int_status & USB_WKUPINT_M) + { + USB0.gintsts = USB_WKUPINT_M; + dcd_event_bus_signal(rhport, DCD_EVENT_RESUME, true); + } + + if (int_status & USB_OTGINT_M) + { + // OTG INT bit is read-only + ESP_EARLY_LOGV(TAG, "dcd_int_handler - disconnected"); + + uint32_t const otg_int = USB0.gotgint; + + if (otg_int & USB_SESENDDET_M) + { + dcd_event_bus_signal(rhport, DCD_EVENT_UNPLUGGED, true); + } + + USB0.gotgint = otg_int; + } + + if (int_status & USB_SOF_M) { + USB0.gintsts = USB_SOF_M; + + // Disable SOF interrupt since currently only used for remote wakeup detection + USB0.gintmsk &= ~USB_SOFMSK_M; + + dcd_event_bus_signal(rhport, DCD_EVENT_SOF, true); + } + + + if (int_status & USB_RXFLVI_M) { + // RXFLVL bit is read-only + ESP_EARLY_LOGV(TAG, "dcd_int_handler - rx!"); + + // Mask out RXFLVL while reading data from FIFO + USB0.gintmsk &= ~USB_RXFLVIMSK_M; + read_rx_fifo(); + USB0.gintmsk |= USB_RXFLVIMSK_M; + } + + // OUT endpoint interrupt handling. + if (int_status & USB_OEPINT_M) { + // OEPINT is read-only + ESP_EARLY_LOGV(TAG, "dcd_int_handler - OUT endpoint!"); + handle_epout_ints(); + } + + // IN endpoint interrupt handling. + if (int_status & USB_IEPINT_M) { + // IEPINT bit read-only + ESP_EARLY_LOGV(TAG, "dcd_int_handler - IN endpoint!"); + handle_epin_ints(); + } + + // Without handling + USB0.gintsts |= USB_CURMOD_INT_M | + USB_MODEMIS_M | + USB_OTGINT_M | + USB_NPTXFEMP_M | + USB_GINNAKEFF_M | + USB_GOUTNAKEFF | + USB_ERLYSUSP_M | + USB_USBSUSP_M | + USB_ISOOUTDROP_M | + USB_EOPF_M | + USB_EPMIS_M | + USB_INCOMPISOIN_M | + USB_INCOMPIP_M | + USB_FETSUSP_M | + USB_PTXFEMP_M; +} + +void dcd_int_enable (uint8_t rhport) +{ + (void) rhport; + esp_intr_alloc(ETS_USB_INTR_SOURCE, ESP_INTR_FLAG_LOWMED, (intr_handler_t) _dcd_int_handler, NULL, &usb_ih); +} + +void dcd_int_disable (uint8_t rhport) +{ + (void) rhport; + esp_intr_free(usb_ih); +} + +#endif // #if OPT_MCU_ESP32S2 || OPT_MCU_ESP32S3 + diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/portable/template/dcd_template.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/portable/template/dcd_template.c new file mode 100644 index 000000000..977369300 --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/portable/template/dcd_template.c @@ -0,0 +1,136 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2018, hathach (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_TUSB_MCU == OPT_MCU_NONE + +#include "device/dcd.h" + +//--------------------------------------------------------------------+ +// MACRO TYPEDEF CONSTANT ENUM DECLARATION +//--------------------------------------------------------------------+ + + +/*------------------------------------------------------------------*/ +/* Device API + *------------------------------------------------------------------*/ + +// Initialize controller to device mode +void dcd_init (uint8_t rhport) +{ + (void) rhport; +} + +// Enable device interrupt +void dcd_int_enable (uint8_t rhport) +{ + (void) rhport; +} + +// Disable device interrupt +void dcd_int_disable (uint8_t rhport) +{ + (void) rhport; +} + +// Receive Set Address request, mcu port must also include status IN response +void dcd_set_address (uint8_t rhport, uint8_t dev_addr) +{ + (void) rhport; + (void) dev_addr; +} + +// Wake up host +void dcd_remote_wakeup (uint8_t rhport) +{ + (void) rhport; +} + +// Connect by enabling internal pull-up resistor on D+/D- +void dcd_connect(uint8_t rhport) +{ + (void) rhport; +} + +// Disconnect by disabling internal pull-up resistor on D+/D- +void dcd_disconnect(uint8_t rhport) +{ + (void) rhport; +} + +//--------------------------------------------------------------------+ +// Endpoint API +//--------------------------------------------------------------------+ + +// Configure endpoint's registers according to descriptor +bool dcd_edpt_open (uint8_t rhport, tusb_desc_endpoint_t const * ep_desc) +{ + (void) rhport; + (void) ep_desc; + return false; +} + +void dcd_edpt_close_all (uint8_t rhport) +{ + (void) rhport; +} + +// Submit a transfer, When complete dcd_event_xfer_complete() is invoked to notify the stack +bool dcd_edpt_xfer (uint8_t rhport, uint8_t ep_addr, uint8_t * buffer, uint16_t total_bytes) +{ + (void) rhport; + (void) ep_addr; + (void) buffer; + (void) total_bytes; + return false; +} + +// Submit a transfer where is managed by FIFO, When complete dcd_event_xfer_complete() is invoked to notify the stack - optional, however, must be listed in usbd.c +bool dcd_edpt_xfer_fifo (uint8_t rhport, uint8_t ep_addr, tu_fifo_t * ff, uint16_t total_bytes) +{ + (void) rhport; + (void) ep_addr; + (void) ff; + (void) total_bytes; + return false; +} + +// Stall endpoint +void dcd_edpt_stall (uint8_t rhport, uint8_t ep_addr) +{ + (void) rhport; + (void) ep_addr; +} + +// clear stall, data toggle is also reset to DATA0 +void dcd_edpt_clear_stall (uint8_t rhport, uint8_t ep_addr) +{ + (void) rhport; + (void) ep_addr; +} + +#endif diff --git a/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/tusb.c b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/tusb.c new file mode 100644 index 000000000..9583f509d --- /dev/null +++ b/Firmware/Targets/ESP32SX/components/tinyusb/tinyusb/src/tusb.c @@ -0,0 +1,245 @@ +/* + * 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 TUSB_OPT_HOST_ENABLED || TUSB_OPT_DEVICE_ENABLED + +#include "tusb.h" + +// TODO clean up +#if TUSB_OPT_DEVICE_ENABLED +#include "device/usbd_pvt.h" +#endif + +bool tusb_init(void) +{ +#if TUSB_OPT_DEVICE_ENABLED + TU_ASSERT ( tud_init(TUD_OPT_RHPORT) ); // init device stack +#endif + +#if TUSB_OPT_HOST_ENABLED + TU_ASSERT( tuh_init(TUH_OPT_RHPORT) ); // init host stack +#endif + + return true; +} + +bool tusb_inited(void) +{ + bool ret = false; + +#if TUSB_OPT_DEVICE_ENABLED + ret = ret || tud_inited(); +#endif + +#if TUSB_OPT_HOST_ENABLED + ret = ret || tuh_inited(); +#endif + + return ret; +} + +//--------------------------------------------------------------------+ +// Internal Helper for both Host and Device stack +//--------------------------------------------------------------------+ + +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: + { + uint16_t const spec_size = (speed == TUSB_SPEED_HIGH ? 1024 : 1023); + TU_ASSERT(max_packet_size <= spec_size); + } + break; + + case TUSB_XFER_BULK: + if (speed == TUSB_SPEED_HIGH) + { + // Bulk highspeed must be EXACTLY 512 + TU_ASSERT(max_packet_size == 512); + }else + { + // TODO Bulk fullspeed can only be 8, 16, 32, 64 + TU_ASSERT(max_packet_size <= 64); + } + break; + + case TUSB_XFER_INTERRUPT: + { + uint16_t const spec_size = (speed == TUSB_SPEED_HIGH ? 1024 : 64); + TU_ASSERT(max_packet_size <= spec_size); + } + break; + + 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) +{ + 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) ) + { + 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) +{ + uint8_t const* p_desc = (uint8_t const*) desc_itf; + uint16_t len = 0; + + while (itf_count--) + { + // Next on interface desc + len += tu_desc_len(desc_itf); + p_desc = tu_desc_next(p_desc); + + 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 ) + { + break; + } + + len += tu_desc_len(p_desc); + p_desc = tu_desc_next(p_desc); + } + } + + return len; +} + +/*------------------------------------------------------------------*/ +/* Debug + *------------------------------------------------------------------*/ +#if CFG_TUSB_DEBUG +#include + +char const* const tusb_strerr[TUSB_ERROR_COUNT] = { ERROR_TABLE(ERROR_STRING) }; + +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= 0 ) + +// Which roothub port is configured as device +#define TUD_OPT_RHPORT ( ((CFG_TUSB_RHPORT0_MODE) & OPT_MODE_DEVICE) ? 0 : (((CFG_TUSB_RHPORT1_MODE) & OPT_MODE_DEVICE) ? 1 : -1) ) + +#if TUD_OPT_RHPORT == 0 +#define TUD_OPT_HIGH_SPEED ( (CFG_TUSB_RHPORT0_MODE) & OPT_MODE_HIGH_SPEED ) +#else +#define TUD_OPT_HIGH_SPEED ( (CFG_TUSB_RHPORT1_MODE) & OPT_MODE_HIGH_SPEED ) +#endif + +#define TUSB_OPT_DEVICE_ENABLED ( TUD_OPT_RHPORT >= 0 ) + +//--------------------------------------------------------------------+ +// COMMON OPTIONS +//--------------------------------------------------------------------+ + +// Debug enable to print out error message +#ifndef CFG_TUSB_DEBUG + #define CFG_TUSB_DEBUG 0 +#endif + +// place data in accessible RAM for usb controller +#ifndef CFG_TUSB_MEM_SECTION + #define CFG_TUSB_MEM_SECTION +#endif + +// alignment requirement of buffer used for endpoint transferring +#ifndef CFG_TUSB_MEM_ALIGN + #define CFG_TUSB_MEM_ALIGN TU_ATTR_ALIGNED(4) +#endif + +// OS selection +#ifndef CFG_TUSB_OS + #define CFG_TUSB_OS OPT_OS_NONE +#endif + +#ifndef CFG_TUSB_OS_INC_PATH + #define CFG_TUSB_OS_INC_PATH +#endif + +//-------------------------------------------------------------------- +// DEVICE OPTIONS +//-------------------------------------------------------------------- + +#ifndef CFG_TUD_ENDPOINT0_SIZE + #define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +#ifndef CFG_TUD_CDC + #define CFG_TUD_CDC 0 +#endif + +#ifndef CFG_TUD_MSC + #define CFG_TUD_MSC 0 +#endif + +#ifndef CFG_TUD_HID + #define CFG_TUD_HID 0 +#endif + +#ifndef CFG_TUD_AUDIO + #define CFG_TUD_AUDIO 0 +#endif + +#ifndef CFG_TUD_VIDEO + #define CFG_TUD_VIDEO 0 +#endif + +#ifndef CFG_TUD_MIDI + #define CFG_TUD_MIDI 0 +#endif + +#ifndef CFG_TUD_VENDOR + #define CFG_TUD_VENDOR 0 +#endif + +#ifndef CFG_TUD_USBTMC + #define CFG_TUD_USBTMC 0 +#endif + +#ifndef CFG_TUD_DFU_RUNTIME + #define CFG_TUD_DFU_RUNTIME 0 +#endif + +#ifndef CFG_TUD_DFU + #define CFG_TUD_DFU 0 +#endif + +#ifndef CFG_TUD_BTH + #define CFG_TUD_BTH 0 +#endif + +#ifndef CFG_TUD_ECM_RNDIS + #ifdef CFG_TUD_NET + #warning "CFG_TUD_NET is renamed to CFG_TUD_ECM_RNDIS" + #define CFG_TUD_ECM_RNDIS CFG_TUD_NET + #else + #define CFG_TUD_ECM_RNDIS 0 + #endif +#endif + +#ifndef CFG_TUD_NCM + #define CFG_TUD_NCM 0 +#endif + +//-------------------------------------------------------------------- +// HOST OPTIONS +//-------------------------------------------------------------------- +#if TUSB_OPT_HOST_ENABLED + #ifndef CFG_TUH_DEVICE_MAX + #define CFG_TUH_DEVICE_MAX 1 + #endif + + #ifndef CFG_TUH_ENUMERATION_BUFSIZE + #define CFG_TUH_ENUMERATION_BUFSIZE 256 + #endif +#endif // TUSB_OPT_HOST_ENABLED + +//------------- CLASS -------------// + +#ifndef CFG_TUH_HUB +#define CFG_TUH_HUB 0 +#endif + +#ifndef CFG_TUH_CDC +#define CFG_TUH_CDC 0 +#endif + +#ifndef CFG_TUH_HID +#define CFG_TUH_HID 0 +#endif + +#ifndef CFG_TUH_MIDI +#define CFG_TUH_MIDI 0 +#endif + +#ifndef CFG_TUH_MSC +#define CFG_TUH_MSC 0 +#endif + +#ifndef CFG_TUH_VENDOR +#define CFG_TUH_VENDOR 0 +#endif + +//--------------------------------------------------------------------+ +// Port Specific +// TUP stand for TinyUSB Port (can be renamed) +//--------------------------------------------------------------------+ + +//------------- Unaligned Memory -------------// + +// ARMv7+ (M3-M7, M23-M33) can access unaligned memory +#if (defined(__ARM_ARCH) && (__ARM_ARCH >= 7)) + #define TUP_ARCH_STRICT_ALIGN 0 +#else + #define TUP_ARCH_STRICT_ALIGN 1 +#endif + +// TUP_MCU_STRICT_ALIGN will overwrite TUP_ARCH_STRICT_ALIGN. +// In case TUP_MCU_STRICT_ALIGN = 1 and TUP_ARCH_STRICT_ALIGN =0, we will not reply on compiler +// to generate unaligned access code. +// LPC_IP3511 Highspeed cannot access unaligned memory on USB_RAM +#if TUD_OPT_HIGH_SPEED && (CFG_TUSB_MCU == OPT_MCU_LPC54XXX || CFG_TUSB_MCU == OPT_MCU_LPC55XX) + #define TUP_MCU_STRICT_ALIGN 1 +#else + #define TUP_MCU_STRICT_ALIGN 0 +#endif + + +//------------------------------------------------------------------ +// Configuration Validation +//------------------------------------------------------------------ +#if CFG_TUD_ENDPOINT0_SIZE > 64 + #error Control Endpoint Max Packet Size cannot be larger than 64 +#endif + +#endif /* _TUSB_OPTION_H_ */ + +/** @} */ diff --git a/Firmware/Targets/ESP32SX/firmware/generate_bin.sh b/Firmware/Targets/ESP32SX/firmware/generate_bin.sh new file mode 100755 index 000000000..9898fda41 --- /dev/null +++ b/Firmware/Targets/ESP32SX/firmware/generate_bin.sh @@ -0,0 +1,48 @@ +#!/bin/sh + +set -u +set -e + + +merge_bin(){ + esptool.py --chip ${IDF_TARGET} merge_bin -o $1 --flash_mode dio --flash_size 4MB \ + 0x1000 build/bootloader/bootloader.bin \ + 0x10000 build/openffboard.bin \ + 0x8000 build/partition_table/partition-table.bin +} + +build_and_generate_bin(){ + cd ../ + idf.py fullclean + rm -f sdkconfig sdkconfig.old + idf.py build + merge_bin firmware/openffb-${IDF_TARGET}-hw_${HW_VER}.bin + cd firmware +} + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +cd $SCRIPT_DIR +rm -f ../firmware/*.bin + +export IDF_TARGET=esp32s2 +export EXTRA_CFLAGS="-DOPENFFBOARD_ESP32S2_V1_0" +export EXTRA_CXXFLAGS="-DOPENFFBOARD_ESP32S2_V1_0" +HW_VER="v1.0" +build_and_generate_bin + +export IDF_TARGET=esp32s2 +export EXTRA_CFLAGS="-DOPENFFBOARD_ESP32S2_V1_1" +export EXTRA_CXXFLAGS="-DOPENFFBOARD_ESP32S2_V1_1" +HW_VER="v1.1" +build_and_generate_bin + +export IDF_TARGET=esp32s3 +export EXTRA_CFLAGS="-DOPENFFBOARD_ESP32S2_V1_1" +export EXTRA_CXXFLAGS="-DOPENFFBOARD_ESP32S2_V1_1" +HW_VER="v1.1" +build_and_generate_bin + + + + + diff --git a/Firmware/Targets/ESP32SX/firmware/openffb-esp32s2-hw_v1.0.bin b/Firmware/Targets/ESP32SX/firmware/openffb-esp32s2-hw_v1.0.bin new file mode 100644 index 0000000000000000000000000000000000000000..25c0bb3b1b128641b971bd85c191b67750ecd949 GIT binary patch literal 609840 zcmeFa3wTu3xi`M%k}DfX0z?q3JHZJF2ACuWfr^>QjYM)8l5o*#CzE7{jOND71cHZt zorxBL;weE}!N;D?q^1T6N5EF`LQT+8R5%5zJ=TInYFe)?S}wWH{(kS;Gs)zFZNGD# z=l?waXHwVv*5zIAde^(wde^(w-kZvD@Gq!=pay~(2x=gxfuIJ08VG72sDYpcf*J^F zAgF<$2LAu9floB*MN!AKhQBh#)Cj&m6|;CVw%))P_5Xs3u|$)W$p;uil(9?33<51e z2tp`A7=qDmmuwEP+G4GhszhtOsIw;{EZ0?ui8_1IyE%A_SW_wd(Bcxblw^asa zSJztXH47_Qoz>nDz=$U+wm2kneN!DVU#v)%T9-c}-TK1R+g9f_H)n}<2V`t&6sv0+ zHY_Q5o%~mp$?FflT4rrhs_GLIU2+O+PXSRqv+Udt(30U=vZQ@xl*#* zMZ5I@Nz^r8uU4g{vPLpe?g>9~5r3R8`q%u$#sD z28XzK0clpW)LJ*xOI80OHcOW*oYMpSB+Dx0t>u*T{v^;H}j>L*wm*;YFitF?ZESW>i7 zlq|N|Eh3hrt)X#?Xm4n;RZ0sNE>wm-yQH{qf(f5rXW1Z$l@0X{i?!Y^)-}~Stc_Uy zI!s?_LmjQ}g<^3%_&RDN5ev&9V%3YAt+lmcg(PBWG)h&zCi1BX(Q4<@kNQi7WP&zx zEtQT2+ZGXZN6e1Se!VH`CwI_P`X}oYml;QsiuPKp30>9oo5>-AM~0P84l|^$vN$Ya zL!)G~kX>9zCQ)3$M>#;$QMRfSOBkJNveb&Gj}^Uf1?)3;`nY{dfA96G7LWmj#fDC* zZ4GrJiXkK3P+d*df##}Gzp4KI`i9N*%&!x+M#-T1tY~eic)zs8&ZO!lyEI%x-C?8{ z9x2IH(5ER@Ju)b4oi*30l%lmUn?=ApCc~IWG9`1#l6;#}Ko3W4Gr7vv1>?F&vRSLQ z1ay(llz@7plxiB@naXHFEx#V!+__?br9!F|1OC?7X?hR;m2GL1#L_acSkb2_7D)9Q z95vVDs7tL9bqi9K*!{oy)Minq{G?MXJV^P%W%Iz~ zv12=G5bbr?(Z$3%YrVA&dnINSnHYQ0^+eB>sx7pZVH%)t|KM6_wO_AF(HOd9(P7&{ z3}}kz18R*Gollhj(XI8Hu-9EJrcqYS zP6p$;GD-GEGatqY3~N@6RC&K0(?jtR7m8Vy+RCO{ivxy*?{nN$7_lM{ZOrQCoXO&F z@*4EYH<+;W8?2SezLp60qJ}00->tyAMzTp6fm3nR3>nSSx2s^wX{slKERwS%RblI~ zU`6@-6sf){SIK8~IgijD9)8UA*^Zf*wjf7CE4T1X!56ZBVE zqu3;+C}0JSveEvjsH<8I4Pa8NAgzjBvSFL~6!)zj$<7pKl{#XdK$ejX zV!rvU$bvvuMmZqSkPjOH&Qh$bu{MqrUQgrPDGHA$=qk=+omE2M!pJe zc-#uHXiY)EzhFnwQthdMhR|MPfi)ou#Sh*(ak01pPHfHIfU`CYPbDdlAsuPI}>Mz~caImT?ZI=`5$rmSp4N(+kDDcLE7nJgtW zoqjQ)Sz0Q7mMq3kg1N$OH*;f8R{%;9cJzPHXP8q`1Nxm{<~theBQ0#G z@;j^lh%}eqag@?IeGKU+f5lW%EM}S3@Of7uQCB27NK|YQ*JYRaj%}b(`ah~B%6-J- zm{J>@ktu5hU$BXrEx0Wb>mYI9#{Vy{Q095SY{T?(1k64L^I2=H z6qC=*Mt*iOtclOK>BtF%qX9Smx;G$YHDnvf;s9;ZCaaw;!C3y_0Ia8|Ci zZfLS$P_bEyevdq$Ciz{oxnkP*R6KrW@CN;Lpj`+GP76uR6@VNnF+RpT6QRthH2tZxnA0Kqrhk$S#Or3N=olK+zMpXj5~k@18%_5bWG#-_y! z7cWd*keZ&dU_}W7XmuIMRXZNkX@pxL_3> z);dW{OJs6O+w$k+CSZeeDosh~hCEzT`lXDrDPvy25|t}(wL zpogRUl%{&$9WBjYzre$jnyPSCEc9LHHayvN|LDv9mS# z4xV-P3V0>8_`O|B|JSqtuOt@_5YJ{#%Xfee zBYXtf-vGn)hKzoo&hE{SWpIfiaaHX0Avwc&tD2dRUw)w=qY9x0p%$SL!GX|>unpx8 zBb-KvVg(t~5!NIB9)zcNstn>oD#Lq^stl}2WjKel{M_qWl1FBPGBc6R{(>hJ1F`{q zC_t#aRb|*!o|%E(FbC2G@FS`2VcC%H$B@v#79(Gl|0ri)VC8uTjLN>-E6?OK^0TtM zT~0(is@0I81>Oz>k8&1{_NFpuo}PR#6^+TiH$ip;2A+v?8T(j6MQ9LZ=Uz}5uA(e; zlQ$zD!HKY^4+0!lv6(3E{F%z|5Xq9!g+8dfOx7^@)gjrCIhFllvp3^70%J454GptP z@)DOSZmS$y!dwr?7s6RjD2tuXbZH@R%{+4xWgd6CkGNTsM|3q&5)mmh~G+I__c}D_O@{Eku0kMrkYc!Yq5t zvMYmX-*icr$56EPPFcDWa;|;TYq?847}`^>)k)f(W!ltG&FW>cUcXpxa3oBh#?t1l zPL*SS!i2xCPLY>RSL0W=S!=vgJ{IXIkk5yl*18gjamt<4(e;{?1@iVQH>M=ZE3Zt) z@8&C0QWE69^n~O0zkAdviL%ZPkvti){G>I3eY2cUISF~sSB9*5-)#wvwZeux%+1M-pEeP zMGteOQrW=}R|;XzKCKYx`qGQ&oN3Rb*;8(ipDC^DsisC}$mskhRFpDZw#@MLKSnm0 z98g8N20t~SmMF?^4z)}rM0$!3C;L#4*Gge}eOdDN);(4)S%`7C{6*OLP}0uZ#N8Mw z7laT;ttpLW(K4-3BbPk;sxOO1A6c?jn5M*ryd0%PtY-;3(IOXUs%zA_$K_Izli0(6HjYBpLtJQl$Tf-dfZP9_gF$*m%PuU-`jS{+ZwTF z*52s0zj}AIM)6;9hpJ~@sM|gL%)_d-&%B+gUHhY)`^9~#j%4I&FCh2N$UPeE&)20h z*PpyX1ccig?)uQXwU5Vcx`gKTQghY&e|X}mjCIqIig^lbd*ADMpE}bTargtTyOlb> zBcknh-u9;%&lQS?GvD@l-d0Gzm^z&K8?Wa#N~TcfZjCzRb)a&QCcuRoF`Djg($U>ErG&%4iG~?l?b1*viR;sk(;HwgX+7pcQXBhS1{y?v4D91Zt8?sY3^D(nnD+~z&BKkD!<{1O!xqBEjPQ5cOQ6!x&! z?NiuIml(!O611*D@4j%Pupb+lXQ{V)saKffKGo&AiAI?Ukbn4gugBeOgWFo<~D^218avws(j3{5r(* zZEyFtz3%7Q&*JwdEv|19@6(Y&;W0H4975fWUlW5fA+ELFgQ`8@=`;7LmPJdiha6NL zQ%i3Gr2h)xb5_z(FLJ$}BCqS>5co*HmHoQtn5&J;@M6WC&GUNlyy9`u^?M@G#nYIC&5yc8sp}Ku8 zGyGhf9WTYORcX~Fe-maOoYWCbOqY0Ao8G&u4MW!&k*d1Iv(PK1MPs5Qxtxk=iD;9D zQdK%m;F;&Wl%_&y=WwaJqe(Y6w9_RfKJGrK>q!Vr85nB&2A_T!A##_pzR2*kYT9lj zQ-SCXO{z+C{h9jZi6#9?p`q>FQM$p9W2%Gd^o<<}q37oJM3dU4dOcIUuE&YA=l5Fd zwjDyK`%{hkNV~heMgN*8OmcZ5dA6yCw zWC|MnMU!4+xp0|;G<}7MA&$*hn2Rl0_`5ITD(%i={z44v7c$lfUnNX}KMDL2fr;b~ ziA3v!<1!W4sCcjPt4M3g;QImph;R+zZ}i3p_Xn$0237%ow2J&1G>88hke-HML?FMm z^*v^Af5Z&g2T)=72;}!V(LQ4t?0F#fV=3We>~p03e&}WR zq2z;-PfR}P+)8i8Mue9UK%TMFrZUuSR2f3hzqJVM2p2Z145yH{0Dj zAVAHm9e565dJT9W{5!%M2=dL0T?4A1$9ya5A{;>&LLeWPeB*WZc{AJypCJgKYYoFW z24ObNA3s323*kP5-UyXpEAVKf%3y#mpN#(3+z?_Y0;)yE7T@p9xC6n7@D9SPT5raV z25*K5Sb{x{Ap8sg7Wy{Xfx) zQL(OvC_kLN4ty<^+V#qG2AhLGM;u6_1$73(z%H|jJ?BCW49N#*_eVJEBq5|BXradn zAf1EgJUKgjb~uhdQ$wVS=Jvqre7OYtyjDB0Tmg7?E0z3?-- zpT{qEJzAB5=P1|bjkF2^uY63?r+WDqGfx zpY(9!7O#P~lv=}h#=O5@ODBc)B@z+!Jc$I1PmE1?WAfI&vsniAm}l2x`d#r+wBMm9 zy~bDtCbT(odfr1{x;gZu%rL%hyQ$A57N-BZXt>}wJ^9#V(e02f$UdLUxZa!kY~?|% zYaO{A>*0c#%A1xi5!Cv`2SvT()NAq2-Z4KxOn*>6SB#yjKJEs`@UB-x;ci{O>~>#{ ze>O{;A2shu^|nMNY&((0SgQNv(pdEg9W$jn5`L@RHkSmLQ@SoqeL`fyqvW^=cS~30 zx=1B)``sN^WVgv7%q=g!tiLTPc1eV=4FaW+K*D5_Sh!F6yNqZk969&|U&%{n+-!JS zbj>Fc^iZMEeF++2=P}LgH%gaeDv$S-pE)gp$(_2t$%6W(OXvf2;8Ef3o-brUSO7VW z3Af*^`$Bf^j~4ff)T?$zy}I!?b3z5py_#)*klE57>^$)|tYqjE4&U>oQ_fEEb_-rl z-_Viv-O^X=Oj`6;Zl>2emZ@-xcy!Jq_w3ib$+gUV6G52sC|YfI|G4ANvP&Lf^P}PN zxSdVTc+oXDV?7`)o=b)cMD6oGz3@k z(h!^}*CleL$Rieyfd7jf7i7gfZg+3#`Qvb0*8Nd#f98zh!1)P&)^lE-bEoS=;($@u zr|p@=*1q4{a}xvi-w&PED30ycueN%WP1MWpL@gt5=C0ji=w%(ww$KxxoE@7$FW>pk0=U3J#kmlqiR82{l*gK9*5KEsQ&$wmXluy zvxN7zza;)heIk?<-1yAih?bL|An(c0e)*orXZA+6ocv6fbv5yWU82L+zWAxewTZYM zdKd?Xt_!Wv`&-YnVon|h0ouVLEogsMcGVM&&sN}GOw1H@0%qjAC#R%44r-+#+5M9) zVVZmHgFe13>dBdE!Py#ZXcdK!ghWx-CugcBUsManBAwaM-S-Z8?j5@Dkti&1WfJSH zSFr9{)Kdy0x4$$SR^eFWMfH^KvLVl!q4QeTa)o38Jp5;7_I6yBKU_7ma~gh*MScac zJBK_4M5ZUQ6IbZY12V_9s~~C5Wx3tR+K-D8MO4BZI;H5;{=V!{3o{cUBKB15eq-qQ zB$W{ERECE7F5D=Diw{2t?fyt@FgOMi^$$Pz`Frsic0{8(RkHP~L-QZI>C%Gm*hl2n z9f=Xn)XJUHovC(xd>78I8ZrH`1)AJz^470H`N0DGT)Lrzn5jbe_LxXl+)(^+T$zYF z9$PScwzhm@C=*wj4wdw08K=tbzCq88L#}~AbSmD>cHz2g=Frk#%;@+C+Ufpk&@*G` zyvFr+Dn>i58>sW&!O4%n<_e?Y(2KGVLn~K9srX#BTo?KQ-RHzz9&Aywr%t;1 ziQLu4e+TJT2ye+`alM0Edw0NCk&UcVi{f+`{--K*zo(X7AM|`Rcs|tiDB$Ag{|!~q59{jUi_-=o!cFzl`f*K3sCcyjOohiP3`b4Ynv*WIOC zLRMwxUZA!5B9-EDPyd|gzFRyP>iQ*REQrLF<1Z+7P|bJQZLf2?bx`ekfpTGmX*Kcn z4fRyG#fxsOss6$8mW#1RRniI4)s{ie!ND&=F)j~K73Kbfl|OVbcD+h^o%B-vXYstY zyJ67t^dO&Q`ts?qKU8%b=hMeJ=y4B1bFTZS_BPtIB$vGQ;Q>Y1gX*0fu02#F{Z8KY zvF!RG#Y1sKrn!&Z6uae_VQ@wyh~+%-Kmk*(dKg6ZoNE`3Tj2uCL$+OI$KspitlT`&CduV?l~KYWBm7~H$s zcCTWyTep2d+9uvN^N&*HZn5pCZd^q5Cy@$qu8 zjssl3cMW>(9u&e{F3To z-<%t}L)8~2?Zt#X8$H-940@slT_=eAt2lM%^gUBswfj_u1*L+33iF=oAtm+2>3&Fc z!UwzM0gqqb;JY9H{H0Ow0ZtvE* zE>rI8v}G#X%*|b9k^Wsy`FvpCMyR1xBTRR;=mgE$Jnee5>434L8w=a`_Q0w4&ufwe zY&Ic1l=D=iZU;5=#{o2>b$yQ*>ROTWp33uw0pz%RLWb3Z7}E6cADu2^=%>7Itueu?3+evQAy8|h01LeZMyApj`-8^?K<-#r?RHD#>OHXAo5+;~vi@grG!fC^vag?I9nVzzxG+cfIfUu&vw zP;ZN88{Gpvo8^|dY7EJal-6mU0|PtaS~Qxs{OkT~ z=RoyLb?$%58@CRecvs%IWuQ7wz3sR1#?1rO>(#kuW!xKbCNVqI8yy3or=j<5)~Q~h z-ul&3{YR(8{oB9`sdIGqK?fhk4!k$5yJ*1EJm3nauEan4BR&_MGCjA$J^2PueLU?w zO?S?KXTt#87Y2Q6%#LFH)rkLVo#$S`{`io+?4qPu{Ek5Gc5>D z=>TT_hy7p=Zw33F^lcivBFg;(<)*8uH8ay)37W|CFTtbZ+wwEhVuWd_nQWVyvGk3& zF27fP=CN~vdfQ1Eyr&I3m8iR$c-%oe-XtEO$7R;D9(kuVp5+5@NELf8eS@|CB%aiM1WV|B+=?Z=~}-^e@fJE)3^7CWAi@vAEXyYAk8 zPtt(vFfl-0Prr=Ia_qSRJ7O`m#2MQyh66`Y{j5rQOMdFf1nseu*a48FYFO;F0W2t~ zTc&k}y_2N?%8XA)eNDA;?p(ARby)29HP&#-`Tp4b+KvM1*ktP1#cHI4*Yo9{sIzz~hGgJqgqwAE91*JW2_J0uqJET6Qb!Bo&j5x{p ztNsgzqMiRfLu^$YI%0IbEPA^8kGwVHT1G^!mHo~dLs*Ual96GGfL`gJi?!UcbZdm| zWYn(BQNk?e=B{1U`oSZEKb+LoxWlP`ujyhq(>t#?&+GO0FWsnLK^AuDH&lZOJ6leN z9ixK+ZUy3RSt^7-V6#;%#p7ulz4189-RY!qsZ@S6GPb{8an@b`(ZBXKxy2H4(W33R zm6p)Q#_lKkJx}(-A)iWuwyxerdkD^HHgSPw)zahcL1EHCjmtuX2h~Yo!cB8Kdc@xRnH1|LJ;P#~!*SGs0erWp(bYpZ-b@(1ge_GT2zO$oO-nhDd?fa7s zukZJ)=eg1~dF}ql&wqM2qTjPd$+;>Eu}?j@v84Zj=N@oiM+-R}w)APu=}E`JaJuWo zrP6{>9H|TXoew&+OrJzkDcgz>nHo3~8u z>=fNj2dxkxeA^JF20dytSEC2SRJsrmB+O_C@6*K?9q3o{mI z%5@Lse<+HMyu()4GGo%0@nIq9#(8t+#?NE(7OS^?mzBgttPK%#SMaQ~yQ|N0sn2yA z*Way-9f=FUG8J|n)aquDpS8T>E24V7&-2GV%VBlp^L(F+aa#Cr?Qzs< zn~{5owy1K$Wg$|S{qb1?gf8l8?Pl50`s~UVW@`6*x%Le$wl!R@Ndw*=MV{NQyGVL) z^m*F*;M<0}KIw%fu)n_oMy(rD+i=xzqjlxlf(1cj3Jc_o>RxjSBC# zEm1u`l;4hvQrNF*&HYq9IR__x7C&!k&EfL+t~)a`H!Ky@xsS*-AI5HDkhb>FV#=Mx>X{%ZviN7;{nQ@0FX?5=1G?%-X*doTe z{}ykF_^SJscsF}KT{Wama+P@0vDvYfReg!m<~_e8VP=f>g zEt-x$*qHR-7jTUaYVYda@!{St`_A_4mA|9DH?sA^Jzw^oUHfET{7LWWi09+g?l;A^ zia{ zsRvg~G@0(j6;sEHG!ftK^@R4hR#L_8kiKMWhPqAi>bH6Y%`z9M=1J0spwT@bFLSM( z@x|5n8Hca*CQjPrWF?p3*SHhG?yLVo}A7!G~H zeCZ-g2mB$6YISTcJcH&LLdad(I<@It(RHqOcPw){FCY3<^!B}Ft+Cr*2><-Gcn`O# zyA#yATCuNuQSvKQi@Utygt+|&r(@?%lJ--V|Et&Y+uqzMSnvD1u>5cnzDiJk9NT@e z*K@M>e27a+%GmZfo~fPaMFZRSZg}d^*wV0$pHk&ldp+H~u7PVH$)z_S;k-v3w}`GT zs&qU~q<2}OT*oMNq&up;yTU15X)Xx3`r5zq(_p@HlfA;2nhQ8kv z`i`6!TCJXR=H0H}YXvnOUf8M1c#me=n}~J%2gonRvhE-EdVWGu{((f;b`3>zpC|0Z zFu{XdMC@^K<@#}N!nAPLfAl8IRpWO@%rbRrOxvX1xTkteChgk4)03?Dnp^2NQD@xK zi>FpRhh9lR&Y3mQzDtA}UygBC@7SE3<8pS4&A}r*FjO8y`Z)R$ng2;I=EeKjkLEp1 zc*wxtAnp?-x81EgGlUj2t zyjRX-7H>QzrTdy+KQT%U*>|7Dd^stFy<>Co$K~XX%gG#-vpX>+C8PI{PNm-)CGrO$ zyK5BwNkSIhzu`h9;dggp!qy|=-lz*}qD^-xN=fj^ss~4f&@Wl?m>g1Rk_tbGDp9a?AlHYV;b*G#7AyGD_ddNONn2z_5^GA3>% z(dZ|jnI3x9PxD)%F-<@-0}ph4ZM{V_jT6wshQ3N?BhqKvOV>_SfPObkHjcA~&tz2Y z`OVdH7b!n+zVcjacg=jB)cKQmZYA&$u9?XTV*CX${QeB>>;WIWUVE+`Ol89Aecv~fm1qqhKA0RTfVWXme+jSO!Tj~f|HJFe`n}?|ulODWIc~k?S*UQsi>R<*?W$3txBn;U z3qIDQcK2J4b#eQ~^^XU?ao6zN4W*u&u8HuWeP!*QWLG>%sXU{dM$GZdcHs;qJ(Vj@ z9e4XxTuda)oEjH>ZRei2sB0}J@&2 z<*ta4Hu|qBZIC|$-6lV@GG@wI@6~SaRgd@T5%)VCUwGp^7hLDA&YPyrO=2L<3l}m) z<+YOVx83)jR?`c|)w)6^Jao@wA(^M70(M?2+|J|p&_I}qcMujRZ!%Zwijd~dR`{FE zRP$~3t}}0oLM<+T!KxzjBe%GxZr?p!Hn=uzE8b3W=H&ssTi2Hox}*w_pu@(oX+XHc56SsYq!Vn zdCfhOggiuW*?%znh}$isZ~Ia{7=hfH-JT2F{>OgoMKodN2S?nO5)${Fpyz$G&XSFy z^D>K%786C+1H@igf>Z|AXfvPg4Od}kP}CVdDJ@`y?7`A`9JXPX@^%p|n)L9tnya4g zUEP%yh1X({u`Il!Pj;1GZ7V0<>ES5&bLy11?_3pqm1w@jIj`gnDDtF~aXO8D`Sb&i z3A2+Q#(hckG|^Rmbx%xs_|jRiZ5OVzcNmCQgj-<|0k=CmU481gqw&w*s_jh0!*MXJ zo~CoXdu8|jS3#PZ$lBeBXqpRjG^XQSxieMwjLeNe_1rVU!>$$7(Uc`uT}!C_M2#~( z%60b@Om~-_D}^5WC5ERSV2Z=yvs)?2SCLMcg|3pj(zb&~>n2Bk20@t?7 zvEh-QjrUt7U-eApYKS;|=`yScdg(WbFe9D_rOz;6p0E*VBYj^xMDBK1KM`~5yk+6L z-Iga}mP1*)-PR{!bi3VkPwc*=XyN#kUEH-y3didMG*zLQD_7`zq{RPz1yA99@grCG zGchB%{5X-8<3&2rfjf|&es9XzPp@=;cE$7Al_PkctldjDsh4GEcC_m+S75E;{&EG} zGWol0dExUO;+8dh>*d*N*bDfaZ2L;!K9U~*S7W|**S^D8rUAs>@~B~cJ0xVxa&P^nt`Qz z^sRc+UeWEog2~bd4`Y{uNLxK{HBuhF%vz)Mtx;mD7`O6@@uycP@>Al^-HLSWyaypE z#@I!Ny5YY!-jCn&McvWTKlVKJ>GO@I2l@{FTkq#jOm=QRu=?9K+!3e?bid zH4xN5Py;~?1T_%UKu`lg4Fok1)Id-JK@9{o5Y#|W13?W0H4xN5Py;~?1T_%UKu`lg z4Fok1)Id-JK@9{oFh&FWO)540n+o)=Ak}ZO4?l?aM^LU}cur6M{!(Mj7UiE*_7?)_ z->IU1^NBUyz~HD6nU+$QBPQyal0=<-xtMFQ)=E{P zqd~OP);3gH9Fpj;*zXrDwhi_;kk>_$1pib$Un63b#bNPP{knRUkjU5Ca6{FWTDv4B zB#7}NazU#5MMZwQSZlRA;^Nt&{OsICl?`=`4fRsJ!@kJZv>z8%LgtBh*`!*@f)?tU z98xp*OhnUE&s+49FduI-&oC>cq?6J4Rg&FdYoHnAsI`l6E5xET1qJ^|PBUq|)@*k) z*en|)NbTo2D&MSh-dqLY9a5DURcb3NmG`5q%El(tw%8nqb6uy_<&TW5$<`o(LTqYW z=o`uL<54KpHQE{+Vl@V%$tH=^cgzP%wL`K&>yl{SVs}V&qD`_(jyTcO;DExdmRiwn zsg@jD#L5Pnt*OyrZKzKMO+`b4!&<+=-`v;E8}Jjyww|bO*6GvNFUQnOnuuRz?fnYR zw7PX8{Y*<0@6UTcTwi7^5bLQg4Ndh`$)bI;)lpdkacFj1>(QZl$h}O@i5u*d*4kPN zH1AUlLl4J|mt=1=D+*ZD*kE%kvNu#NLS9;`J#`Ue;qz>I)%1BphqT#RZ>6DNo2u<* z$z~H*tPpcbP3GcNW9GC?+JK1+4TBIWthYDRO7W;Sc3uax&xaW!NmH9Gs**Ng{_}c^ zklu{&b?uQ{qHl7Ebq!TbwGw7O)<~_zUSqalB!(sZnt8-U5S#3fyRxB53Y=4;<{izs zYMZnX!&s#d@p(6j#@N_cYlXSN#KG89H`wZUeEIe0F_-U?U}ftqb<%Q?{&e>1(So_e8#dRoFctnVJ`~s`V}`P)WoBec6-^sNsku_(OAaf} zVy8|LSt(|EtyJoe8by}3ITJWrt0Ml!e@`SrXMYq-I-MXYM7YZNUGQQr)Dk|@8@QpxQ{eWloFv0>b3!ei1_ zTQ@Y>l#$1rZLlSCJHw-*rK$?+&@Ni)t3bt?T8=)JGlUq>kk9 zblRBo;*qo|i{jK0W2VxPxKSSv=Pf8SM$X$8=X`x}&eIpaEuij_fcVmYczQs5SwK8R zX_(|OrYH?7aiw7;t~9K~m4=nL(lEtKjU}8Z#dFqYWf|9$=J?`kjoH~H6felkSxH1Z zo>P=P9M39VQ>pzY@tZP9nU6Z~@_SgkA*gB>46Sa}W{{79$uC3J}&KR3S7XY(;2A zcpSlvZ~);b!s`fU5ORjCDGhW}eGP06T;hc!lg}Re>?~tGTSp!fHY3`*_#6hEua;Of z+Cqazc0*GI?8vauR`~kq?}t|+)drSkHGnAu*{|Kt0Z4WfwW1gO&OMs8TmOwkr&%gwsn)WO3K`Fw`6NzrmVG= z+9rvaY{*5^B*id4W36>VeH{#~Z_S&sjo2;x&5qES@^iCs1o0Pe;gJ_eJj`=UKGBvH zWENWOcHTdV!$w*2;MP$Y@93~_J~@1u4K>yZSXZvMVg7z*fqL;Xtx+kCjhhf6uG7g&Z9;VFbe2&WJ}LeNfT8Mh#; zN2o<`AnZhV3gH04&k;@{oIyB;kgZk1CB(H;uoe;Y2zdx)2vrC~)9gb({Vt@RMtH@S ze+GEL7bluai1#9}sVpNLAqrt8f{2iau$=Z^-&sKc#wJHjj?Bc)=CD~RrRA{OTW?>G zRa{cC##EM*4gD+WqO$y=H94h76Fq>b=qJF(TG?c4!oj4<>QH!`O@ z&f{B0(n67yN)9^nh!rCqm){Ly+>e2K-DIQP1AZc!oE$mMhs2)dKRIy{h8?ZGvaT`F zZmTpmS{yaWbWm7u2Sv?r2CA*i<8#clQhgHn@h}Es&k?A-2!S6Spr3j>PCgRn>^n;| zDQCz*&HvT_%Ot&6SSkZ&g>_3;jyc{pQLV z3qQCO6&LaSit`jlGo`v_?i?}3dGcNWlb>=Gk zBqnZZfVVzBsfr(D$yp!UW{qTNTtq);sZm)jod4JGlIp9HSfavGOc2xjOAvV|3dkY0 z{)-`g9e^XUl$eC;HXFJo^3xd(NI39dU$M#N)ESnKcjERHVqT8XWG>CWJI7pTT>p>8ksq=7JmN#^yQCR^R?o*R39h!R z50I@EON@o0WfQJP$zQ^ql1;4Ig4+jcrT?ast{0Rm2t{6NRkH}u>YM7!)DE_1Mz?7s z``CF#bGBx)+1}L1ua&Izm9Ei7{7)(DCT%*4`|NH5gBa?_^xq~==77OVzylfzm|%W;H- zc&nJ==Y7X0*1SD1AH~71%TagVrNvq1lAO|jfoWQ^KT2heL#PVy367=nFM)$p48hxP({8#kL+ z6LHwrsutXV%b5-Kgta+Y**T?I=G+1^E+|WKvXc@LCN7413sBfnp_IVy(@w?9sNxlJ5(|4$dG*e-d4zFVJa*lnLRbTQ?B@C?iiL};$qVwS3L zjg9kL(OP3cem1RsbJ<;{9O6%NM{h12o;P|kW=pQIv~0L+ByGwm$xos3(t_f3D5rF) zKW)~JNvANHeSY2}cLvb9q@3(w%W(py`*U4YTqN#IN=l09qJ$nIfi;Bz4rE5&4;u7UN z<>yhBUzk(8rfeKfSXL9}*eJe!IkHN!#?i0MDJg~R9hYBd9HAu{a`TJwOY``VZkV=c zZK*laIJzy$BYKdRL>-z3=ED5aLStE0USR)GhHLR`b8%6@U1Rd8 zot&cLH7oPXr6yz6s6JC${(cu33$I(2XDrGtKxv?iCD@g)InmJb4hNR4%DKy2R$Oc@ zC@xw#YV6RS$+!}==n3R#ettQ|&VxW13v!B9mZ9T;ZDEe(mgMAczI1jDEW-*~lM^6G zV42BSQpN|tr|7`4qMUVR-X?&>vW5*_|xTq|rsLY(5pPP#*KT5uJB^XRD z#a*T{S_}ca*A$q>&2h@(UNB9B%=}UFfXe*iT~Lg9>$`OI_lL@}*O&_OvtSd<1;)&r zf?-*x468o}Yb_^xg0jpKV^LO~8Oz>WoNGp6WzKNj;xgknIVq2Hft6TXWQGQE(JVCM zmxanpa|&{@Xu0MV`{f>0&Mh^G>RSz?>J}Ocu;b-q(Tobv8SyPCE-1(}X04j2-f)>; zQND%Y=be+QTv}7Qc$Jwa%~?gG#&kH-oL@S2%_(IiIi?&4o^3Xj6pz^Ctm5o^TDktV z0&K{dA~c)LXHaHAan>qYa>I1l#zL&_Y;KE)fxmt}W&1QgIxn}tSenPXAIL|c_jNpg z|GNC#eE$IY`S3gvkc{bATE1S&FuJ2{@^GEfvSO2Ig0lSFVc9utVR1GY$5CyL84pf} z=|c-+6dep0vPS38#xXXpv@}0(PviVd#?sPt#U-QF3bM_7 zz{a&pIwH9-!^f4Gin(twsz0P>v(oud`J~H<@`*pXQH&msqAyzyJ5!3q!QGS5Wpvgl zEh{l%!HlZ2&ZKOD+K%dr zFP}V^QTdP+`v`5H@Foqv{s(E!E6l%o^J#U)h?XRhZKwJ}Bq89$OaL zU6)^!O{-`WPd@8%xyLsupZaVp$sXBY$JFzcj~l18#dtn3X1v!IjSbFG`@azNj}^NGZwcI zPL5yl@pRls`->-Pt4MJcl|>9ALR-uPbcF@kONN`7pgiBSggbBJX_TE^F*tttlss$` zd{vFKH=IAgI#tU2hKNo}@IL}%Rm#^C4ZFqWJicj-ZZohvD=*(Pwr#jSW!M10*{A^>HaWwzj9314#xG~NiMyQT_Q@#v)Qaj?BdcIPZ~i z`B8CfeIH#GmI}Ix?S56x*u9On6F3s2_2R7J0?dcPe4Jdz@>C|U-#fuUZp_&FWmH;& zlMNT-dh40`=o=n#^oM8K*fvXtz4(zi;%}!c%jDA^9rZ`b3o~=FIsY7(6FN*3+y$R} zg7UHwb9PDQN=S-bYIu1L_uJ2>w3JQ^Wy8w$llo;1q*GQfj-0v1H3c|B`ng_@7S_yE zMD9M<{PpSA-=<$?&Qkg_kxs_SV8-TT&LRc)B^cj-U%%EU24J+!3FN0Nm+SK}ts*T$ zDmuve$R_G9-CN)$(dQFizdh2S*;G3292O{3Sw78Z8kQ09{cWx*F39D5`v>C|NXOg! z2V>!<=W2!B&(FPUv;YBi&`(P$nTT{ES-(y{etubSG)HxMf;PWyT>X5A=6d5wHmcM- zays|RI+CByr|e2tp};#H6O7+jpO)wOZKuyg@blwkzAf0FM{VF}#GR1QdCILnaP|-Z=j8Ck8Uh647l|iollN) z7QbX2onK}w4X{6X>#&k>ZG(%L(fu6G&n`9R;ZVvKQ$X8+<^C>Vf}u+za!f>%oxd^% zN1I`pCZaLn(uX#$0A9E(Cd1?((&764d>}mugN+c zGvPha1axa~e#>8pE2oL%T~m}_xUvv((~ZbTv*TpLQ69%|zvScP!tucvP|WC?r3-VE z=R2PRRd~2_8K?$oZ^APkdj12{0`-0Ocn>fP=m3TT9|A@I-M~oT&w-PGr+@Rr8U?NZhGyt{0dw?N82QU=)I4}%&2pA4L1&jcG42%S7Z$>-7IY0rJ z4x9`u15N=p0;d9Z0;7PPz-Zt}U<~jh;4~l;S;lnW9N-M#?Z8;zJ-{1)PT)+S8#oL2 z3XsJxb`GclUIMCtLLB-7Oa^L!dB6~06_Cw*B3uz)3(oPypruCj-lYQ-ICDsldm9 zF~CE>X~5Tk(}CxJGk}+Yu|Pq`GSYyFz{NlV@HXIjU=^?yC;^?oYTzE=2H*i;4e)iK z75E| zTHpa-2=EvBY_VACjk!t1>h+lE5~{Os({*C(GGA9Py2GB5;K1`Gvm1%?6Lz;NIzz$oB3U^H+L7z4azA;tk%0GtkN2F?I> z0%L(^fHwdKfir>PB8&qt8+aqI1~?nI3pfY(b6^Va955A#wU?0w#GKDq3@iuU25bc` z0UiP_1-=7J2lfKlF6cJ}{RJ)tGB^4~X`1?@MEABs7-}l zfOuw|5eiHPh5=2$a9|CPbwOW174UJO8u)Xd7Wf`81ULv}XP~b%&;f4;s)6M|4e%kL z7I**{0(=J;3LFH60p~2nIV)gD1DV?=Bb61pdC&j&U5i39q*xV zKFarOoOps~=_URz)9f`(jw*bMBr0#J+wi74-pIyhb$C=Q;w95&U9<9lou19pw{Oi3 ze9DOT1U8!SIvZX}!xR6-{H@9Orbc`cichTQYaseOiQc6mYWl*&_X?EQfKnU(gm1ag zl*vCIYm(|Kw}||KKD_|tZ-_qVHLWiogB9U#swSR;2F{ zo0F7&^7jr#y+1TuN2*ue5mUaU4XhM67K&t&#a6q8zu17XT)b6RQdZ1;+wuJUrD7R< zksGhG$BXuwhNjvo{t1in%4xD_tiYFv^a3)z9P@ujJAeLsT%8q{07WB|P9OZxYfII& z4V&p37XFDZ+Q;D9#R>`Ul2zgBtV+DSzHowf&PwSEQ)#K2}w*}0l$B+^eA7!GT+$ET z?x?G4sHcg|$I{;h-V{u<;!_Q?o?hi-c+-mJTw@h{{v4#o&X=!a+}K=}m6_R8T`k$l z@ImbGdpM2F=zHb;6O`Z^&wx)DS4xh2NJ8VJd?JGO>nwQtloX9P$<5zOYx1Gl(uj9V zdB0H18k<-DU+UfkKJKF2|DWAWw%Ig=f-O|8OKBm+w7E3BP+GdVl}3AsO$t@PB~8*M zHaD_Kn?i*GMFBwy7DcUEkV}y&;3)@$`m-tuDhhg#Qx!dksHiBYAFGxN;MGxuknndxikYeAK~s;je?fGs^Er~e{(wat9EaMRE3>tD+0 z5e^im&TB-Wk#d_KA0`5MT+-6HwzoAMzv*-7aOn8i@xu7ZN)$XBHg>lpd*jpMB{r?_ zq_?ffrdXUx>(#n)IHiza-F?no2Ny`mAa77YMY}NG*I&!o;(DF)qvT3JQQyv+$#>1@a=U_9S)_*0UxR-jyLPYwT3QN0K;>}m$%9| z&0-bR;PPCV!E+MQy|Jzh=zFpB}MBteQ`BF#Cr?X$pftFDp^ct!+ z(s&+@v@JYGy1${VXWFIpy{^;ack6pg1l7eXlQ4za<6JsXHuiHfuw8t#A6Q!DsFFRKYIBMWBF(^Gl zm^)%dRG5K2dx=WLrh|s(BH+{36%gxP?}eA)Bg+VrmF`1S7eh5=xaNdRXH(1a;JmEJ z^Osd)DAs|z2Pqjo9RsB|sw!(-M(KxbJE*poBPcH4ln3>J%u%hs6)jCIZR=YW^>lS` zkW>C0K3!iL8rQXC`%U{fCo7-}caDxXv^CYE(40ZDyJ!6}ZbDeI(Z-q8t$W%T;nMW0 zP^#~3u}z;|JDsAmyOL%rGnwc^t#W+}P60i>Ms#y&bmRm7nK;*>Lu)u)@2q^T&@~XM z?~IrDA90M;xaZGL*rtPD8-^Oz#TF!(?*5wf zbd#cmeP!orcddx6G?X4Q^W}Y7jL4qu>-i44*&#!Q?Tm)+TMjFXt5&9e8K<|EU7s|i zh?8S+n;hE!eSKBM?CuYG{i@3M*&4~9=IT=6v^BLo zF~7M5cH8mJ)txlX^&+-L`MTyX0e#|ax0tq;W_xs2CFkk+uoox~mzJ!QE~x65EpMCT zfkINnaz)&|K0N~kk=;|HFKsncDW?_oOrx#J@#$1EXs;JPeYfVK9@JkBai_0tAA+>~ zucZ~#K2EH*x#6Zlx6-JrI1}RP%1}QJZ~KfzUDmU!Y7W1Gqt34PAE=-~+UwdmIJ&$u z)4`^r-rgf4woqw|xnr20Et}d-t~Ap5WYy_`x}4n!kR}#QL}|L4T;6+^aWw|tmWJy| z=odXd>B4#jCQzAv(xVMyjR@7I^uIo6h(5gUnOr`co8q?LwyPl>J?q`zXZ`e~P7?rtaO zkJf)rFaZ0ITHm?!>q@6}sbGC?hb42F-!w!U=UyrgqEL*0%U;@@X*q!8Bs2G$lyoma6uNOrCMK%uks zTB1xt;#o;>Y#E89>0F;my1SG(>zZPXL%L=ZO9D`=g}y9y!H&$v1PGuW=h591#-TjX&oGflhgDD?OCBncaT=n#T9AXDAD7S$bDmlr;&|h z(;AXHl>WehM(dU(d#h?xbrq-LOj^q%kfIUl;80P#xl3-XjqAB}qqD1TZEJjOcUxD$ z$Km2Z7=_?+!BoVXb+j=~61DX36$C-HlzBrI?QalcEzPVmNxEBaWv7>Jwgi-hkGrYW z#!ST4Tr&_e0@o%d*7YnaJZ%6)3EL0P_G}*>iJx2wd<+l+I8vnT4|8Nr#1win?#=Pu z%=2cBH!bKeu!SGx6d0 z@BF!&)CRS&EI#!G_1E9prHjw#AbgxY-Cf-z+uEBgN4X)ZPCNXmpA>U{PFmp2Snkgt*sFyGy!KGOq0#JB!X7 z{D#&eD_w8P=srwK3-=~<`1+nzhEr1*Y@E}-kjbTyPV%rrNMvm+K~4ZJ$Q+5BX+HEoXhn;6a$-Y_!;T2SL|gbMDeOu%#2stS;|{=%bGe}$tU|z-l{u{EBI6m zNI|8y{<8R*;V-+{4MWr2BsZ97acfscOYz0p)M_i<(ABf9IHM;kUewmpv4KPM#lCAR zuGdDVEoNpj97kj^#kG;-!Dc=w#sqdTj=3RKtE-0z<5Fha^wC{vn|>qoxyUag&qs{; zRpf=puQ7iU`EBGyOlH30E8|TiTM~EFefz@ScIRC_?Z$WBy6*n#zW&7@{@|A{|GD(& z@!?e2ipdYG*jD+so@Z8{`LRh?Uh;46{rp$|WBWG`{`D8XT0NzzJf0Jp@SEnxdT+13 zs^J57yz7C&t713*>DN!by6aol|G0j3&uK>;Qxg5p_5Z!*=5u#1df$T=-}UxS%>D3x z{q3K>{mg&vdF;`}zh3pB*1P%>d5x38g{RJa;yZu*$wz;8@9T@NY5QdU-S7CytM7gK zoey68@ef>dTwVUmc}Jh|kDopJy|2CT**|Rj^s0}X^~iDe+}Zx&O)o6@*7=i*BJ(dS zs5s$Ef4t!zFMa>}KR@G(W53#ZeeGvHap}Kz{`md-m%Z)mvKj9_sr!h}9sEBp|NOrW zJbC=r=H2_A&-LGM^^JF5{@wfkaBIO2r+$7R2I|6lJ9Z$0|2lYdk;t8&7M4Xe-X`TYamyJP3g*Id>4>hfO~ z{dnBBo-hCAlpPZ;&so!ZPV?H9`TzMJr~PI2jnVs# zId9GS^%EDBpS$EI9p5?P_2S<>{>k56bM05|xx0E*%i?JN>8*Pw{W9;0xt}lm;P=k| z*}^}Z^v3HS+4t#3?)lUseeY~|@0<@DbMc?^pE~Z}&bVpbjDH;Y$M&Bu{r(re`jIcb z_?hoqf7+%o?ak-cEV*s|l||2;@b!X|Uq1Wb1y64JuTS6m@YjBF!)u>AwsZESE0^`Y z|6?=0eD)8Ic;%$kjj1;JU)D>VYi-SO7ZsZ>E`)C4>L&?zG#f$CNB5CYU*i@1x|3@? z^T(n7&gE3T&ct`7x*?$Os~h{ceQ@T2vRNhT;^hlwm0-+TP`XqTclOx3*sSG5zcC8w zM?nUC7UBF9NAO|Y{m1&Q_FlHzTo_G0+c(BNE?(Giu1Fhp)iseaHJv&P-^1;|c6Z+5 zy1P;?EEc;Bgnt^)MOrm@Az0kT=aH4^lF5ZMeiz9RH>~-IR?(r4 z=Qhh-wmcGnS{$VSbywaWyj{a5G>4W4*J#)qAGzA3z1=P!?LD0MwM1>S&+d4o zw8>58vNKsecJ^sGIVb7X?=`}2I%pOaoo^P__m-9>t$e{=Zd1D?BS2|ca*3Ad-CbN> zyRoLUhLHCi;Lbt9RlBffChN7}Ti23Iagh{vt56^YnzMy((pDch^3?KXgy%wc2Q-y7 z&80VC59b&&=UfV(JPI#AFM*eP6xh~@AIyecOeXZz?U)nLGg^xoN8hIna|#|Vfo`=i zv&EETsm;UO3%GL)TJ^nUGc(ib?mp_4aIAKYuQc_Iwe=;*nm1#O;|+A5hAy$1z?A)j zNd<{1iE#;;`cB|)!VP(eC0n^u?CQM4D`@R*KFYyBA_UrQ!`w4d+Fy%Vz_v9t!-o?FEU(8D!xDk3RSQN)5;<%dcN%+|XotHfR zKJbgd-$M8oiSKD>zJPr%bla=OCC;B1OEeSa0J!ITm=_3>N0@HnxUXVdVh8XLuormN z``HJrN#yBqU?2GBfF)nZOKkc>UScz}b`bXVpOOCOpo`yEaDU<{>_5T{bMv3MXYG%9 zi9Md)Vx=EiIUmbQY&%EVbLc;L{>r|uDE2SmU1l7ayvxMb!K2aeE;GzXyptdCPI=OA zFz-}v4u^M{ac1)F7V1O;W!(ytQ>Qj#767&{(MFE{<*<7Gy7`6)g~Fi-|H9!&I47JN zj)upC$A{zLl5iqi5v~kZg{#9g;kxjJ;l}Xh@a5qv!dHg3gs%#JD7-anBB4k)5{cwQ zawB6Rd68HoKN63e5}6!1EpmFKFfuK2Mx-KA8L5g?M`|MHL>5PCBkzn{7-@{Gj;x8S zjkHGEA|Hrc9=RfNWn@d_s>p{US4XaiTpQUDxixZI$uTqbC;)bA1s%gp~a&?k+&vF;uY0%%S-3TUY2R+Ye~)0_&LktrFN|@ z_W@j4Caz&=Refn`Nl8T+G#9O2Z^x_&j9IU&wqw?rq$3%m139v8khT4&IQ>$ML4g8F*BgT zl6g!C&nlfqX|Fr0Y+h;EtaWG2oTqu>S>-rk%$n!si79`Nn$!=h_-hS|x|SL7isqiv z>~dAp%NMQm^BH1&|jMCVI$*G~4c5PTAXX{P3;hA50;RX!B0OvNix5zEBgT87Na zxbaMr1o_E!L*nhzIhAzGVeX=BefiV;Wmk*iqvkDkeu8P|FL5mV9>RSyW8!NMF-Cr6 zU<_ULZ8wJQ0e>Ci=V->z$MM_yup7TccMtUL16~5Z1AJ8@;t8L&x3D&?s;<(8lG}e7 zy2eK)JheGKeL5GZ_oZ6BEFfM;sq3YLB9#p5ZLe-Rh=-}Z)l7QD4@s7j%s^A|Lbr8; z*kbC%Jm+#Y8n$iR5-Ytow5+mS2bW1^2~qaVR)dAIg#%tVrnodGUd-H4cFiFtJxMzO zc2XXL=R394s#CTbF%qXJj1{Y-vFxw4w08^7wk^-L#Pw;lfyFhp>QoI)Q%5sO6s$KK z4(W&PY})_Wm1!0j7Z+aT2gQ5VH+ z&LpnpJpv+Rg%>ZZuBlvkj$P}OmU{|=%5)Oz0EJO|8*YyEBIT?3speRtZCmvl(Om7H zm(N;m9@QWCG$fsKxF319wb;E~}%R)jb4>UST%`f+6#Ld)p zr+&XRhC{q@sdmRHw&|7Keu1?gZW7UT1Q2AK8y(6AN=;Rd_vgB!5GQhNd z?TDgyvzEb!yVrfzf^g)Qnt-kred-FjT%Kw$rL%oGr+vG&Yu0g|qO9J^GKTtaW7!G# znIL~9P)-i@r9QeEckx<%^jz#RwT2Aa84Blqyt~j#UUl{6K&4yTm5hlca#%kV0CHD& znff*3clTddQ;p#FeEi-%A(j}=x?)p?-{;215~BMAVGF?*gkp)OSRcuM34W&lw}aFA zh&IsL)n)H0w2e*is$V;SwQ;bX6;9*&TyP~o=A0w&a~RVkOTTUqozgPDct(QI*d9N&$}{GbzA81ir^hURuU4Fr zJ@C67`-R8H5_<(FxU!mXax8J{k#ZgWx45?2*8Q>)Y$MdwSA4!yM^!J=byXXi{PK{I zJTW{K<-}ra9I<*rw)z$e^aHmem{{e9A_}dOnb&AP&`RqB28$siQ zZfWV#F2rJPgNtv~r!9n4eVRo8#4HxK1Kj41dR&>q?e@6LdY47-Fe;zQ_HpQC(sJ=< zQ5-3rj(VJ^oS9jjhw?@*|=iP&7mX<8HWx;f_^evLLbU9C##mfx3 zZ;=D8{arYSu9wBVrKGBi9)h0g_fE=w!VKzkaf~$yeJ^se+n=FMKM_kLfbGE3i^nDA z){aX&`d}<^K4$AHvBaZ~AiDu}?TsZSfZrtiOR>a#|3UlwaV)U~+zw!FUVh@)x%k6f zGBw4wm$){U#1e&1#}c;?mt<Mitb^0*ylBp?7 z0sa;fZd)mSnTJSTws&H_osYZ#okxj#G;{sE0X=BdL2K8dSmIgQ^$WmDzwvN6E_x`&}Qf#N(HM--q9B=!pM&$V0r0ex>|>hyRN2 ziJ9>;Leel={wvJ0@b3h|1pF6nJGiGw zQzrj~Uj_a;@B#lN1Alf7^@@5D$U>Kpr}LFBKyu{yKrP$KjZt%!ZX2Fx2$i|3PtVmI!|LW<}1hu zCOw#!m|sKN0=5BxLmzF;^SJw73!wGHE*{uvv0i`ln_aLyx4D%1nOziiBp(B_<35!`aMJVX8qa? zL>>I(+#2ESXR4`P>2>AgFnrp({3Y;`-5p+}%+_=n_V(=Nx=PjA7w~pq7h92RUb(u! zbO*7O%|go9m#?7V_xE6$zewD_iREXBc8$*yGj<_!W9rvtUhtkf|`9wVIE$GTYO0B+eI zh+V`d{_nvozl6RQc#ZhyN^!?`GD_n<+$U{xeqX@8XA@}y5+7uZq_s}zN)(CGq-{Tt z=lv_(bNHKbDdPlwOXPp%%GBz`ZOuUlrDd-7zZi2des*5QTp63#Tn z3K0KxVZKOu4*(aEUg6{>IYzM0D4c$;fuFEBmM8*RflWYK&^yXJmZt*@pU+lg#KYwC z%yclH*BxG#ybW{G<)jI?&Zl2;=2u8t4!A-5nC;e9HI`p5dK$s{N_kYCwO*mbqxcc?LL<$`H1>Hf~Wb&+nunR0l=qq^D6x~}>%R}P!8Z@-dx zBhU|QCk-!LA4}}V+=h8OZ~%J_^U(eeF|Pu&)>63J!S5!_(?HPf?!#U3s4d{0#Xg$( zpW7)wX4l*mc3&M+ zYv*u*Yqw^+mtV8rZsphIqRw3pgd6|iSmJ8nCDN|_(Ou**el_`+IIvdV4eosAp?ff& z09IWCPk<)~w_gB%M5`J5cHmL`X#Oo+H{Uyfr@?9NEu7?fo51Y`0zN(gJ@N51((xSj z(eUvJPj3};_5v>g6G*$|8~JlOA8XjauFL#>-BG z17&~eTV8-4`cS*?zX2H>a6WDOQ0@IC@$jRry(^r`>lMPi00iZ=85*jmt(fQIcQoZy zDZMRbg`QSFFMO@BA{G5rK-x;Jz*_pE0R zgtYMtZCe*F9a|k(KX(+KC%RiiD=in1o$}eY+3`QZ_cqo!>GGlPv3*X7LUHdA{+4W| zKL8HESLxC;e2ltAe_H?yw+4#i_m!JiD}S1B+ptqdMMwQe!Hu+g+=KE--0I3F58NL7 zj;4GhpA|g=t^I^c04I8!klP>wDYx59I@rPDg>}K|K3o2t#<}`L7-Yb`=|^tEUvcCrg~opu^< z%6w&pv89UxFS<5mAdMcSD->MPd>one&yA*DhpWrh2IWWB4pZ-30PCtUf8vU_uHTg* ztFoFb>U$?$Ntr@3!>vsGlUuoW({~~EEjuaSyJLx7%3F14(jCZuzDSz{REOHBOU;1R z(dT2{eh=fq=UKDfjx6Oi`~oi#b}Mc>vF{?>g%ak_4#W2&Bb3(pNmM3 z`kT$fwF>)a>hm5??;)ihdPU?-!lk9tg1)hEDOd6g)-Q8fLohVi>U^!LN?dvTUujeAyCUv!_lCo2s*=Dk15uhQo9`dv~6{_n1o-}|0O z{2)kg+AV{w!#Tg1dDEJLtF+kJVeIkp9lMR;g*_}UxR_1y5T|Dyyo}b{(X@DjZd8Ks zx&C!D)+W$FM8>LC@e!7hYOR$eRmc*oPjGW_pn=@NOT=wGb{x}}EK7MSlv9<#*w?6W zuf4C|AW7@(xya2q+S@qrgJ0$$SeZAtak37Sabsp(fU6zA)uKbu*B{512+Fhl#)rP; z@!S?xRk|z!N1kT2cKJ)=@f6_YzYYx+xueMc5Lfz<2mh?CR5kiRulIH(cQ|=5Vaq!(vpS9kJBnQ#f2Ud>fmQpc*|XT+IjQa<42 z&&v3i&bm`IjkfXxZ*azWeOjs!e4Bb5Zkp~iAME6lu&MQwJmw-4YT`sl1Xi)8v9GsF z$8kj8%kU~HIp*NiZj`pPU82|QnGka!YV2x1VXN|Xl@8)TE9*C|x0hvb}bN zYR+AXd%WXZ7^5Tu^zC+O4VM`1DrR8Emx z(w>osdf)c+zfsYGd+-&v~S#W zZX`Q;Q=v4Is8pZT30*_c*}8^qlUogGrS503#Ao8HP4xElf|8=@ss*J*Wp%CbdaiAx zLM|vPn#ot?5*%iVkKP~k1dsJ?omE{a{8S)G?^+vQdG2`&W)>|*Y`vhoC@9Ybvkog9 zo+%%Q>qK39S64UWsff^R72R1yjnt0?v#n9HV2)@{T|+zR;Cz^`IJVNpJKA*SQ0_~( z0?|#zgVQpO|Kc&Kc->w0&IE^}GV(aHuAxNhv^$O-&DyoWI&Tilk`jPuYM$QvQ8!Q5 zQaM`vL-_S2puy&D7A%iN4U~VO&aLrLnTu z%vgDBR%}je_A#N&ryabkZ~V=9mxuR8&B2(tHFVXjF|GtZEywib-5k0x91G(tGvdE< z`r`IGw=ZGMs57&IUG9Tn+F+PA7^V$|X@e1K?vb&ZZsk};>~palvD;&J# zFLX03y_rJ=dt;~IB@~-HF*LPsR;Z@XCuQob;n-=h(=&3Fh*iWYV^vefP7L91to3L1 z#_D1Xv3JMrB3lP9n`IUqylhQm{K|ijv78&jD-9i0Y=ztmZsSk)#OsMH4*;ioWA2a^tH`7GKhru2xQ|^wpV5P zKkXkX2GQIbd#~lk;LN@+cEK@58JJD|wuOf8*!i*d+q|59@G^#T{p*Y07_L!tE6I2g zQjsBiJC!U%U~!}`#};8$O8UY!8OLzxxZ>JOT(JvX9q?a`vDNaDnVMKrtl6bM)|>V;><=*Tt@n zeN?%kCD@!nDYlDJIe5@kZ5d*v(3e5wZL!VzoJh5|Ue1vRXkAI`T6EC%DSX`>`+{0f z(9)F-Hg+e4H_v-zv;!OY;*i0dGNPxCYsLQ*Vly zaZ_J=BMU8eN?^G2_h~&~w%&cPik_avjq$17UJ2Oh8fV=xk`BWZ2I4J?q?-|_46`or z`+rtpQHl-sy;h1KYq&yFQ)tDp$lKXPl8T6_X19{{l>O4h$gH~1=dfnr#RNH7 zS!=4Q{X7YG`2=?D3O5J`cd3IXmtyBFt@XQuzDu} zJ+8EE-Wa~+c`D$qS>y@GV^Qe z4m`IW=fP>t#5Hn#*;$Czo9r>JbXx10dh2xrimNo?PM=-lPSuVrLVa%N(BUZ97hl`c zXtz-)SWcYtfcyT?B2#82n3K58=t#`L-$ikz){`MJd7deXPv+Lt9&F6R zyXFU39e-!MciAvx zN=t}qk!4k?rmnPvcA0c>apZ0V=f+F8DhFXhl4IDlvny>y0bA~QnQUh3sJErIerB?U z3o(e*>H>C>Wni^Jma^0Q!N=z}3Ju;C5g)um^Y)cnWwH z2+sYelkY*D5Pdv5&TxE7Vo>@+l->BD5og#}x1ZSUSRp*RAd;#CDpIz-pU*no;-l$ar*&V2=g@G<2xxg%IlSNu1tl01$;U9 ztJ_@}a66zmlYi2mlYXi8>|Rowii6*4*o!Wqo&c?Y%B=zOS>k#D*o6JU59Xuqx&m>Q zyBhht9-YtZvH2?boTF2XIsY>17eJ4+)V_6@Eer9NISq(JLgAde#TU$wdXJXD9z#PT)%1dI9Z6Z=L{~k9c^BhBInbkU}v=>RGO3R$3 zzN-&$-)IjssK0ua^kmU3wRD}}sC&NU^M9-uYYmraxP=;h;C9+almwq`2<7aiu|Z79&DC#1neJ9avQ-zH98>o%Wk zzfGE9`{}iFho%%5+`U1qRt-7OvFc{hn0b8bvGY_uc8*wVnS=;;klq&&7dXFaBE5!FmX& z2DU9l|NLJ1I_%wFKu_vR`H3e0>2S&K4)9L^&A8>=lb?9e#E{`!MMLrq_91HNzFK7Bc2KXqQLjCsO-hUtY0~&x`@ZmYk`E}eg^FV%L)kFD-_WSb_TLJM< z{&(X3IIsoWUO@Uh!tW;RcJNOF0S|ZnyW`=7;1bXp4G*i+9C2wJ>HlbTFBWy{u(U&~UtpiAsj=NuU~+w-)z|U$)vJ1Wg>>RU)t(Q- z9!NX4hi*uh51T}0X;5CM!?7c3PlGRzo>5HF2>jHZoXw3P<;ys?0;qg5-GcKdrKJ`9 zEvcpSNo-LNP-)4z%h8yCdEYmXT>#x(8?5_sHmmHB0()l{D`Ni^P;<*#P6MROiY_{9 zyRBvW9JqxGbX!txVO~R@q)idJxixD#b=!zBy5XAb67ycnc%!`zjro=K;L>|^7Td;& z{*+z+gU8Mq&k6$uQb9Qr+^vNbwz)-jNJzKQ;ry~%>FIQP!ohxzI(z9k7PGJ_Zr2M0 ztf_m}+kIoVckZ{nhVdBCNN+R}mbwsh+00qB6m|8*F{Ie`pZK9NvP|>)pABwLU0(yf z?d4yoVrd?(>Bp(9%g<@4ntaTNui$Yls#02F(9+-3-e)iHR{syLXT-~9aYY;&hopY?BC7oOyMTt-H~+cCuY^*I8kn__4o-oO7sm9Z}n$OJq=PNE`#wT(SUOuPqs z0)>~6bOt3}I=yTbQetD=IyI0Fc@cnJ!rO@LIgcP>*RU;T2ZfV7#CpBx4Dvh>JdjIO z*raF2HB1;E5ohJO*+avz-xx%JLuu8#L>ZIa@`heSqKDPo)utOM4qjH$SHk%bn%!jr zwmNi7@UoL`ZJf8`*Qfvb-Jd`y`gxjQS31NBXPsUt`DI`Wv^dk>A_-eh$BTfM5)I>Mw2#dWdjK2s4^7NNZKmdm1{A z15Xi_ESqj{&tcy$Ag%)3a+Z%vcos1RXRaI^&q^3~6?UZlSmUzX_0wFwoUvD?#$ffy zneIWK>}7vR#;YbhXP^rU5#*TSw;({~m=0CdNsU#X{O@33vG>|LPxI!{_a*&^Sx6`^ zQnGj35Amn&ywJbA!`L^(o$!Sm@oOK(ZHQE?a0tf6IoqYn>HdEaSp{o6rF$p#=l+(T znDSbFq9iglu?747=-9;eF=G=i0(+s<%+;NUhkh@!(+djAgb}g^731z$DBuB}bJ$_o8x7KEsDW1V^E3~Y^ zU;2$r`<3M0gLYJQ?4A0n+t=yo82i+v?DOL_i&oax@J{u+l1nRA8LpzSF%vCU+1lM@163@=uxrLmVsA9NZB?HXye=xN6ef4e&Cv$kHnjiJjbd&C(!;-sz> z5`P%DlUZDF`H{NOFmCky_9imqqB|-UazWz4YG4&`KF|+r0&W4e0}lavfPKKTz-zz( zVA8D>3r_&%0`q}Y!1+Kwpu7E*u5;>Ym-~BvoT7Mu^Rq(vOHvqtqxEn1LwfqUxola} z2IM~4nsBtbrQ{?{{4{zP{;up?$Ge-X1mwA|yVyRNsz60Sd<&-b70DGdy&p5QoLt(t z{s}#xPF^$M;?~vD{o?3?4>dH5a*(X6U51oAwaz6n?mQ3gH;c3?zqUeb6gHba9#W!) zLT|+dKE1mAvv-=_a}kdRw+%OwDppo)Td?tF?Mo?*dQf6hyjFRs>C{zMo8rsup^Wqw zbGzyux*_LS2eJIdOLu7bq`Gpli6C#?ybhvP>5I$qU;UtUrwb0;$%l1g zsm(hX&fe9`dm8qRnDlpxl}mWPt_}_IWAnh3d6Hjvf0mnRZ;qD31>PLarYle!^I?Tk zMM=8TA2yAYK+gcVfF3wUF4@&dGqq3e3~^I=rFmfK*eS5XDIT#^v;%)>92do9;&`)E zzAP>I;afhC*)+-D(7IOD=rsZ3RZHGAcWILUUPmn`H_c0Q_npQuD+|ONp>8Rffx4B| zw*~jQ+c~A>TD-AmEibXLN?@Yuuks4w9GcD`JaQV|B=Hx~k4RxjjV_vU-DmoR7F5bc zrn{m6tv;#k4Q+TV9`qEin+>bHT`GstqUY^HszY1V%+@`LEVW*}4WNC?u6D*EGzQgc zsAH$oXhc;OO++q~<1R-xQ&$hK@X6MTb(&p)Y2-oR8Jrwj&+9MT1bE2%-n03o=5u)b z_9>tv9GrqT=^OnmM7EsbryjB&4brG;lhkTn57LZSrbGE1gtxU_qs3bY`=-$zqRzB- z>uG|KV;!!&1!;D9ev@K3MBM34;!x4scAxP)MfP`+x6)dZE)%qAn<6ihw00-0^e~FzCJ5`E@akR%7(A2 zSWyuKCa$D?FJxemlCh})obYzw z+l{sE+bx)a0o~!~NO{>L7`z9xg9?n7tCOY+XeQ1gVeKc+fY@z9R3fj zpFtmDePm6eT@6K++S`=0y<&5V&DE*UksRtY;1mFn0{Vx>j0FmSaX_=4BcsJ>fmEt@ z?1VQODmG9>q+VtVMdq-@X8mw^$)?frBtwO&B@lTRBxgFp@_ozG)-O) zSJItL^G|t{U7I65+pW51 z50O>qTl`3hA9iNa)x;=Hu3dkT%npI)bU$Hl`gU=9zd_S=E*eRMas4TK67~t9z|YX{ zfj9#{NgG7G!;N=AT>gfZSJ3|k9M*+Mbe!ysxDp<&UI$?)g=E8uie>dl$(>fLs;+3L za3X|(4o&KFJ^fY))QgNM#YGWL**Qq?QN^vH_#1E|Ci9_tQ7*p!^;1gr@sSL(6;H^$H28} zBW8kjGjdybYjKQ}nUUkM4Tc&)N1_W>;wimX#(>@8-Z@tHo!ta|^BC}FXR?g(lwE2a z*PSh0USAvU=3Tdz^sse^e7g91MjyZL0uB*>ZT<4m$6wvo(Xr8a4+J0s9gCfX*a~So z7Y09D9ceWhY~0i{{f36jgxe5r9YGgW9NE~)F(+wVO)`0szLO1AZ;H7R2V#=tj zs|3B6YoL@hd<>I`VBmODp4cl`h9ThZlGa89=egIn@q*@X@dOECa)9cFl&aFjkzF4s zb2n;EKU+PyR6LeQ+oJM7a7b~|TpcQ?OoIi!uOhpBsG5aAv z2aBPR!)fN^ChHR2*=p0-}R9Bgd1SKFy+h(hiZfU=HHLzG+^98Dv#`iWwtPzTkPvQO4mS7&#A`RZ%VI zQU-~1M?v4N6Reo^&GW5WwMcy2MMh4j^s1TS!<(fM6(x7f+6jD}U{{PucOpUj>H~dk z^9_R4BX#vK@OC4Rs%zZ)^0rrT3u17hqdB(RWp^DXQL1{n&5*%vSuP5r8M9ssa_MBA z%bPnqim2;bd4U><_VlxHdSBZG)6}*`y-yU77KJyF=?6U#OC(0zUda%&j%7nNSzWWF z;yu|NbiiB8vt|yvM=+=lZM4VfMOP1nr#g8rd^JxL27PzXy}T(sy(4ey345znC)<$> z`cF3!q=!FS9^$PSkhE{muck9{k5ptb$M-`mu4cG$Oo#L1i`&;8aPtK$(kgnR*P>+( z%|ivxsr05_$hPiT?@$cmp0%f!?cSZVuRnLK{=oFJnIm78(SH;U&_U46)QLo7P_v1!P1fDRV!s{5fw!}X)8btc zlIgi8%Tjof_1-p%UOhiK!R?C%|VieNmF|Ei^>LzwCp6=yi>@$szmX!%}ll&PQ$*NwG&SHQDE;zQQ56q zS0m^q*SRpZUBYqSE-lwA9Ug4;`h6BB@wFat&BVzc8)>@ECG98kTkc|0QmbyC4$&Em zlRu<LLPuh(Z}l6AI<-Qm)p6gPKqhcv<7WcVN;yK zvdao>th%jXaTjGDt3~7scVrrM?60t!MA2C7mogV%)c#9H@}JGmfqFu7&!{Kb``5dY zs(a1o%d5xs7smR|o7<1@lXkaiQ>!=XMso>VZ8zYxwg!(ex{==9HJ|kJFRQ?nfo4QV zwb}|HnQbO5`>kkfS4jlr$QTFc*?>v+_)QB5wYPd(ljF-x%;1$~%fqZOv({gk?e^;N z6_3rcdR$bMcU2bg|GdVHsUjM!+^kf@)$8U;lHxc$9NlWEb-6&X!>5=^lKgIZEUT5a zh8<1jbhc;Ej!7YdUC+iOOH`q>U9)uQ%4A)|IW@`JWl6gnDOAwZ`}DuFi`gK9<>0c= zyv8!E*s)GAit)q8eRjMA{#j+Z$?0{79juF_x!}gbbl=n02{Ae!rUiCx)aC7Yalxep6ono(!^e-9Z3&W-qIAbG?JV@vTbc}cnxhf z-mE#hix)NRo{in~7{M)PC`>As9UL7etIDSB45$ot<>lEoYI>wa?_dVxtT=Ar)Ac5+Y&1}-1f^dFYx_8ipeAm$I9Rx3t}TX&2Zl+jtqly`($v(d#Frnt zGmTbH)13zAfrKEtcJAr8Y;S{O$>8}$f(J^#x@CNGQX!>XsDUhw%+S2_B*r9bO-DcEU7^c@v zi}U6P6C%Hu?4_5r4T-8OJIWzE^cv^H9?yKV!t9nF8)`i!^FqY zFizsQwd?4kTWL4=M&Un5OC}#|O>~DWZ7&XM4$&W{LngaYNmX6isqLE8Zu%v2j_=$mx3qc|oehU45Z5p-^1kD}Qj_$xc4&FkW&5*47>PMR+2pS= z85%|-yk^@jIVsu*7bfbeyR5!i3K%1gN$KFWe?t7rY@TrjxpJWMn)Z=_RJ%&!?HKCX z+5pbCFKJ)Hvuyvao$48wY`mS|*wt4wCp`42|i}l6izlR{fT{cQL zgJ!Zfj^n&}_0DzPJeva>xj$!64k8T}Zk)AV{1lbQAMBqcMR66dZXKIDb z{`dNpq>gnZI~l=hm(3)m(pk&StzT?s3H5Dy+oGn%oqv1b=e!{zA7`dL;Nsj)_=R^H z8rq(rnTxypN~C4>$m91HS`soE9U~>F|dQT{@egC7PQtcj3PrnjNZ7 zx(sKA-i9cgHiIh4-Kop$>%4``vFumZ=<1p#nY+oQUZQ?>Sc?8lyKy-4+z;I#+I+Zo5Y%^&417(gJbEFM7CNjo`i-aOB9KDG84=$G+60XpmAG2qXtdI?nTZTktNV{OK zw^+-1Ove)})6dqHt{%eHRYx|S0r>##NzJtVELhr)Xb(8qfZVM7Kq@yZoYz?*dYik; zUXJbaXwy8LT^SGXnRej@fAF{E`yl^;ADQ}%afXdE(|<1b!EwoJ zW?XU)ek0jMGsuVR!Osr`)-BVnc!OWyU*Y8^h&wZ#0bXuF*dRWoNq*%=Zn_6X`BnXl z0fY5Mj{tWhp!#?ea5V5XU;>b{HL)-cC;-L-6M#v;2|yf}0u%y8KnYL|%mwBH37{HS z3@icafCgX{0O_n>JGgG37w88z0h@s>z}3KYz*b-za0{><*a6%Q>;!fJ_W`?shk!kR zbZ<`w)R+r_Gk|vh(}5yDN^`|PDL`}OZZl(M0=hK09MIuveM`S~%NC_M&+^B_K{$50 zR=t_81i(*aAl{#-4}MpVGF(t*3MU%ZjWV3dIEYU)wvIBK>Ov4s=C)CWQymGyDehZF z87`g?PIYqoD8nT(!l@qb7-hK48R1m-Zy#kiF+a$kc(il0;b49cPHk-0Xv4w8Ae`Fe zeWMLW;(~C}0pC5^a1>w=PWAes(T0l;gp;oMp3#O&41`lZvv;)NHV=eT-}LBc!%=`i z{!~{VA9c8JI-L5zCq^AECml}x=2N2%mzNHwzV_)+hZ~;`r~Y~0sKdq6;U*GJKlL*~ zzw3`? z5(Q(&9TCevY*}c>M9qWf_;I!@)I8? z183`lpT188fGbz~c2nsgEh2l*mFD@c$;LD|7^IFD^#Rvy|o$#(0@$kQS{UdAzz zLjG3rWL=BIAIM9l#mnQZ#=9d z1%XCN73DDuPYO9#@2Uo+5icz1FPX_6liQ%Oyn3^WBYt{6?Bor}p{ohXV^v>|YFbOr z^s{YESb~nhkP!Xs^s1WF>na2aIU^8n;NUQF>6THtP9%GH;Y!oth9q9aGc=84R~g}= zi|hdXHqo|z)u!s%H||v>E-bXyeVj3khobn6PzD+G*S1O91coaS@^*M!44O+2YBX%L zc?bfGval3}mMi)2h`o$$N`a>#We;CnZ8=OjgNh+dX0*Hx8qRR+HvKDFI#g3>iV~A{ zttnJ3bi9$w^ziE1+>z^=?b{B&vXMT^@*&*xwegJZ^-6FVyNTXfYWjofDa}IuvO39< zxUHxCl~xm9Ra#xO@!#r8A+@4***TRf7bP3sQ|HfEW;O-Y^KKXKpcbI6JKet1`zP=) zb4VJtGgmsZdvn@*oPl5nUhUa;1WJd^{-9)xgdZk(qe$A26p%8rejrEH_5x|*%)q%) zQ^|nO$o-0zrWVBSE4v%INPEwE|9wsi*Ri)X4eK{6d_4lrMX2-D)~^_X7rk@13Y$*N z^;GOZ@!}79k^}pu@>3H>)JgxW=1wF`d*N+^1?wrtZc1YZyq%J!2V$KFw~qJ zu5m-QM-d+3r}<&-nPXCYRg(47Y_oJ|$Off6s9H4 zj^Q6at)Jy~3lO+1@NQZo%WX%7+xxtm*1>Yync-G3i4T5SOUUg$AfWjbA5LorxjmHO zc8+(`T0w4mGu)cI+j#Hxc!t|JAD`Bq3imBQtdjk1Kx;&;FZKOBAaMH)6MeY}L;Bb&KCqpIlT?%X)KgpM#!2(p}|YI~;BbO?Gvs zoGjl>EZy$8`VTo;*?nK9UQ!&`HzySv+SarwF&!ahGw;4Xxbk#sx zcNQLagL{bCH&t9y_)Z*MkZ1t5pU<7Rz~k|P#HI_dPb^3jysaRy6}S4{Gt+bwPW!Is z6E-_u(tt|hFwxq0d+{H{yB+&W#}p*Yu|wnC53UuM4`k6VJu``dF56e^8KqNGV`)DP zy(Rx}d3p`|6z(LP3+(cFdJ+5W*q2~t)0_j%)lvh>py>Ei2)%Y_DZiq9i|7FXp8}e@ zp=;$2^2jflT-({F!k9OYGU@7cdvZ-|*lQqn7mAGNr_T-d&et+B>}i#ik3>twYI*P@ z(m0=T$qY3+J5(n1bhx~Kx^SX%02-@~D@bex76ZbSCZ#sRg|sM2 zvI~{PMw_lSg@=#pO?9W}>9-AB;&|#E5b*O+>VwMYKHP2tWbOd6qoa-m3TDH^wGaP6 znQ__j!dB>S@^L%??H~^7GZ)LYbW2Z7=}~{G5po2_q;Q?rjpUl1cm|6KN%WO|w$&Qwe)!8k})vGDcQ2i&yQl7Nr2$ZXx zit9q$lxEy!drj43a9Nb{AwQe&(+%LK%;_6?0^WT`hC8Ztbj3-lx*dey>)rJ&I+Lap zB-(*T;i+)jz?Ja5IRm#FTs7a@GH_3WTgCTIkE7L-7c}bVkZG#*C>*rtGWwx-Y2x&7 z7ch$LoPn2ypSuJ^HdKE5*0!QA%Qm}JFJbDl8f2kYF;Dfa))m|K9R2FP zwPhSu@V5I;j!wLB%G(ncf8b>7lM|2pV=CX% z67v=nfh$g&e@-d*vc&dv<+#mC{OpD~xSyFge)l~5%ul>`-vazCOf0!Hf!~Tm`}j)y zS0=8zt%@+!iCyJ2gsVw>@7s$AdrsmHs}>W!HgVn(`Nciv!NzZyd7V?tkCs-N+URp8_WK#;gqwb7{&31=pLB$hqc{1B=Wn_pCGVJx`k1`#)uVb@z$p{f#%8 zyyJgkuKHw)*)bt$u1~a@&HE0T(^E@LG+JuDd+tQ@ot-Ij$@3SQb=U2+rS|NTKb&PP zdnZypn}NLx&5_>$Y7^#rA5UOUnEE%cA5&reSX=?V!j!JAF!h&In0eP$;HScz|9RZ* ztuUAF!TmcGX2*~5^L&N*#-H%_MuquHq|(gGuQXSVuY^venQ&sIIsep3bH}twXjYop zvn$P|^DE6`m6g!1yp06C4qOSSGh7B#1DbZwm;xy7c|aww6!;PF9H6;eEieQ4J)rrU z=F>L;nq!K-=7xXp-L0i|DU z?ndAlZx^5Hy?sBX!itWn;1@b3jQ zub13GVP(z+l%6L6mBse}@j<*>4lDu|0&9TJ0!cu;nhx{<3ZpsmV}SVdYv9koCBOn; z44{1e4Y(Fq0f@h%u?~0x5KZw`X}TV`0MOk1Tp$9J07}OJKy(%E9^e%~>D~jxflmSA zoARV`yBqiwAReneydU@ta6BOTEr9CPRe;hk0oVbw0trBAISA|nHUmq66renp0#OeW zF_qTu0&?F8ddsfEP8D|=7sNmqW>&?UwQm1 zVp}3U_2Rkuo_xixlr3}OU)yGdF~yf0KxrU0XvzOi=NNO_Oll2PvF;tle1oJuHJRPi zla2W{*{(iTVN@n6WA`hm^5!=&-vxZ{(iHR2?t;YqK*2=?iKTqM3)tUNkeGz|Q#^5O;tdj)vkAK8RDa0(+)^*_(h* z0oCO9V&Gxmr+n8z<8S0&=BXZJ|J?e5#Hvp?_+o}#xVDV%FJs@m!TGxv^K0Jzmww(D z8U+M#3}?RjK}YwxjRlFGiwhF^4q&kVx%dfiuU$Q07jDV*?z`=y1Nd*vtBmN)w_=PVS^wTUR^H;lU5%*}V zG0Q5Jna;+}j0sNT>Ly-8$v{+8R#n$5qIVgX1UWxzTiZUcuDzqPt6TfX>>u}UyjX4I z<99JPyuBdt9bn2G1&J2En}7nK1o-35g2a2kKY{rGpzi>L9|`0))9rcxorzy~i10z! zApSc1%6|}dfFF*A;u1cHQ?vqmrhnNl0lo~Je`i7BCg4ZFU-)i?#*u)`vpmTDv3ppD zJ>lTi47+d_WPIO=edjLc@AH`d=Iy`vVnO1iQ9uyKaONMs?C4(crGmtod*KP+0SxwE zgr5Mn|EmLb;TCingU!tNFB%koDe(!{>*HPO&4mvaBn}e)grOZqNZk912=zZxyTu)rY7zb;fN7S zgSPhORB>DL8mD~C3xn;`mz`5Q!#WiA_qK4q&U7yfpWf2G#-97^Y}vq#)WzPw+EmPn zRH)|Ou13UGmH+aHI&^T^3CL)txxm|j4+8q_xn|!dzWk%@^Jm_*^T((DckP)A?%H4d zk0b9W`^+b+L1CVQxL?(LK*#mY??Ox`L`8oo2_r ztIjK5Uu7O&wzlKz_f(k$H(vO{s$W(mu6v;W#Cs=J*S-AwGcO$XuIih9yfpgcE1RqT z`<=ghEIj>z>fN9D$;PVw-&ZgCZ|c`y3lbZFr-6xlzYW;)YC+;T;4^_Pm7xa2>GJHQV|LvaZo#3{OgJ=4GJ@4#&}P(WTQ zfh&O9_?`rfufYeIf4~&Te%{{;5=9fnS!m6$3wKJ!cL(;~H=N%t%qzY9fdd7JM@9ib z9EUP1|3RPo&w=zD%CFq6%ysDw{Eg3d-#KFk@LxD$z#jM;$yEHu4T}H2$(wNHxGCJx z-h3uBF7Z*~FOLBpr}%&KI(f*5f0E~+@Nd?i8C1eFmqmgdSj-rYVRr1CaCH`R{Q}Rq zQFRZ5ayn#K1J7C#*k9V>zeC}%Ib(C9p|NSp*s&2XW9=_(ai6()cpf{3kC;LUnJxC;N>rVqktfeLz&-z&JCvy zq~}n6<#zClL4J$f_vVrT{N(Zh`W|9X$xjj8ZAd2{9I;}ZGrU{7GixJ2M~ z9r3S$PanyM{}#_f;oq!Jsxo!lJh{rsf&8L6-L3~){{Qa&)FhM0x!5F>ulC3OzOdxj z(88aF-W&owRcERnuBfl7tsTssjPEydmyT{okoUI8DW(L0$RK-ZmFmGJocBIe-;Vi->_ZIYUU=BQB4JC58CfmATB#M{wV zhn@rTa3uQoT(dv2J!}KdI~IELqE)6d`n?=;86O`FMZcC~J{5}oTaLLm6y2F)9uGyg z=a_w==szQ7OQdgWj(Ii*kI*cLVjdAa)=U}Aw>dF1apuH(L&SA?D6}IKy)$GUQ7%cA zP4_9Gw--thwzeda(3KPVAW~lT?K2M51 zo@1(_UxpV~hIZtb2g4J;5;1=YN3V&5c1EIKikRO=j@XuCek2a~um?k@Pfj8;3r%=1 zWWE%NK9yr0Q<67_-@Z4;JROeSn`5>|zAN7c&BV~y=r6GTGaUUe+t^ARKb2YR2qd5v zN4K!e?QqnSc=>sz8B%0p)&$JrJHkJYnV*HHek^7_8Hr%OEAsE5@5Ib+a-w&}%r&`x z3-j@zyvYy7%%5T|;8`}{!=Y~#m{&s4H^!PTgqLIA9Eo06U>=E_i~Y`=zlQHGFgwOX zKUZLWI_BFEKAtI1a5FV@y!iIrQ1rHN=og{r4dKY1oai@l%y&HpY!y3briJ2@2=IXk zZ-mTeL(wNfp_f9@zl6+}!%^N}`)Bx=fL*rU6yo*lXqUP4Xq&g=YtSwDcqsaR!saWS zGBkTb(Wk@am!WIm1X<6&VEPRqmAU^q{o0Ux+poOSqQ&Mc?3RbQWfS&YlCCB_eC!@?1PUTyFsBo9(L|(`-zsL&r?$DzB z>a%_onfSxp(49Ha*KN?9qluEfBaetu0WX}*IPkPj-8@8=f&y2M-_o$#3w^V?|jsuJ_1F~|L}*nBMSm>(CL zm-C`uEjE9QMeuVl7X5ySc``rxK#93>Y!u8D1#C0@p&+`yz+9^q_NgPHe?H3Gc|`QT zjxygm;?Xc4-#GfO)We4-M87$~>{Cbj@riT_znMto+ILLH$4kx6HGjBsQuHTf=AlW) ze7(%v_V(!AW#;#9Kkmjd^Mm7#eZACt>G~YMX8tQa9s4IviC#a`JbB95*zcHpH_?B7O7#1)%mY&gHn9r0G~%z(=yGy&MSlmP< zB_*XB6%{2K)@tbG)@D?jQtmBEEK0m>NkxT)wZEd;Y*Uoq^L1uE!_4gGgXQh^=svz3 zeeL;t&g*mjzt8);&-t9saK@3Jk}Y*jpg$1CuKcX>r#a=wqxg6_aeer<9CKTMtM3NF zqYRTEliq{D@JtSK+?(ukC#l{5EL%CEw)7Jas06U|aac zKwxJed|x03v+-L26ek8BiG{zC6PU>f{~TjVF$hc9rsnN|O@+Mqz8}*mpy_CTz}%#o zd@y*)dsmr11Pcc3RDB@VJeBjJCvwf+&{2=%n$NBXe=gTN6ei~Gl?A_8X?}##SY`gG zP8f+CJ-*7^7%jMemHA#Ye8(#D(N&kEYH#bMHh9gF_51CV_4|;uem`%m-%nfXmr;uK zdwmEqrZa%?HzPGcbYZnr(i_740kh*w8+huecQb&g@Pi1}XVNv62RSBCqe zW_D%x#;ECy$j&GBQ7^iYhd-Yqt8*+Te1FK?5PFMcj*!Bx4ZklyyJII0+Sy_;aKxd! zKfN6D%CyQf?hmZ9Prv$*U1TxC5+Q631oNK^n}^lPKg|jEtu(iV!q=@dKMm2#pIi~% zz0y3oB7DzEb88q=+xA`3#-qcQxAB&qyM0+(F!|iq5W8UD2Z8V{!JN@h_<@l5TBd$o zN^kjm(ZerkFfy(02nO~9!`}>=?@B&w@vW>|e%OacFMA9zsqQnwSD1GsOi-)J|APBU zSwqEFJ+_j0>x?h2G(QX!{4i|37z~ex5tA2vE^KZJh3{HvJ{mgWeJjmeD2%?@xgz|V zu=&*rh5u4wiUeHn-QW)p0d)6Ik2W7!$s2V9_;bgY?nuEWjxmo%!tXoAd@34#_Goi! z?gMQ1{rBPF?;Koj!ANfi42}$be6G-x$@P+28@bB|9O07-(i#+q` zK=|=I^AN);&pe7DlZP=I9?dg11jA3~O6d$^?uWyJgscqT#sXnw_`P{%YGwG7tIX3Y z!+%<3J{bvrewF!VBs|9ei-Z|lx8^o8*t#@tkaeM2FWMq_;V*QIBw7V*h%JwL&ML-%*3H#u77-J_(La}PvxJ(^@05G zXHGPCALelV$HT%8pJ?tmd{>a0UloLZexeyUQV||HGQ4=ASv>ONBJL>+_pdQ?g(4OT z!}qQ+_aA+04mUq2x*_o38uQWP#eDksaMwv@_wfb$*O;CY!arSOzI8(QTWib>F@^u= zq(rqMha4I+=O?ZYeckJfxjhj6Ou#<jM94pi@4>j2@8sv<>m+1+Ds4F#M~aSxC0f z39{^tRWH0!op3?;x;*pKKwoZOp1I$y!aODT!efZ_$}a`JzfyXCE^HnRhQAXwAIb?6 za$ipPww2O9&xXxsLJDEAGQQH>yrSU#m1ggX@SQvpR`{=zg19sAp#Z9fq<(vhc{mvU z!7=7WmN}f+35DN#Y+~p>8VcWithq7F*^S9?`0-=S?JL9ob*$O9k^>#1k?>sq zH2m8lGoFtkzOVFkAzPfv&6d%T%Dyl~+-^Q9xgPabD>7JNKcDl0MO*I+U+ zseK?PFq#v7Ajfz9WAh4sWXb7QhkSG{5?@VlV7Udi)ZUc$gXmuKw#2|2vMD?0k29P_rL zK9gfc1BKY%-NB=8$uW;6qbQmmQp=R$Wb&H(WuYf!(2bX}s?%-ie*b-vzx4SvM=+IK zhC+NN`QIEkoc#)G!(t!b7&OlXIFqAReGx{>CE%a5x2Ljq1!WayM_XW(;$p-r? zCUtUhx92@8WCHJ6VRmWEU`|Y}VDEy<`&OK93)ZULo04#6)Ko~xz9qarV%`>>i?E|I z9g#Aej4-CcUx=8SgW(&{+M)25R+@XP+5bQ&JiOBUI28WSO7n|Q_}P#dTA}OvR)oK{ z!u)#0p~!UI6*~ zq!f6}LT_&UFGko{F#L^RVqN*0oG?>LXDIy8N;4Rm2y*j@$W{DENHlzJ)O=MLKZ*%| zi1}%5_!Ebi>sCD+9*Mr=A^svVu$m1d{N!Nr&BIbiUanaTQwqg?;>$fTnI6^r8<1!h-?L?dUdC z-?y=mWnQ5h+5bH*=Ok(rkd3W#^owS7AQE1)Ht^CsW3JUhNrhyeKRS??6U_@889sK! zDe&xCq^xuIxn=x3?P?W-9ovO^&Zf!w0``2Q&PYyek2rn!AsWLR60_kY zTsH5BINwQym-@!9Xp1=il6tNo6~CWwo##xXI}=b|m7x2oh|`@4ud&awM{V-2jyRM$ z`FyDjZ{vO~;hNFX%h$tw8R5T6h4oX5<|zvN>8VZ@SQx*c4*JyTKqCAdm&pii)C;EHZ zCmeN{a_RNT!?Jc=dU&x;k~KnJFOg+5Z--F#Nps>GM$7xR}wx-_;e~fDgT7`y(8j0kP1)8 za~t8c?~FK&sqjQU_9z{~*;LFZ=MdpjgjZ#S?>@l{ZbLNU6yfKlo=?=Pi15OHi#VsG!V~F~5$+IvOjdX`;q`=LFlDq)BjN2H;T?qc zWrY_KS1#urHPupSVKa1So+Hhc_=YuxKN4T<$PWz`2I5HtqIeZxob+@UJ2aHZ)e&ai zmE=Y;Iq9QvIGquvNN(X{N>egWug`7&0SvMDxd4ppSSIlB+|oGKG&s(t9-6Y4b75r;YGz-L%}?)64Vhg=LgqbI*`RxaOWwk8sUB6NERU z(n*Xf%{{J5^lu_ub5F@lnR8NJ^1*DqQNeSshf8nY67G8l&#q@B;X4VJ?n-}N<6+Dr z{Dq8%y`*zkR{9zb`v~8b6|V76-G)1KE8Bm{Y#BSdvC-!KQo?f-x;`OI6KCd%3|xc0Oz_y-F0_}GiYu$>v;uTS@z5*S!=j^nUODtA^`@8_$eaBUx&OtNK91 zxhOL~+n!x;{cvL*@p$QE>OPd5tBIT5X0ka4;dXn(+Y49!;fQmdM}GU^hTtSOY4x+^ z%qO7vqe=TVE58!B5jd~$PzE z;544n(zNBQgq!$e(mq_u&z7?RuKZKUvJ|Gu*{ZU1z*XOqoZqwJ@wv2VfMahzJwIDN zHXzL|I483#j?$cj+yCi^(~-&9c>Ca5?@j8~to*da)9u4)Pk7LW({{)x+##9ej1_TT z4>u0i$XE-#G*x$w6(-gXD#CK}PTSd>TjrUx_O@-Rd1rbgsb5=C=~}K1F7H2+Tt_CS zd8fkVdNMi9J1uZ_-r+iZoN3-kt#h(C%{zXwJqxEkeOUeZh@+!$p`2sfG3vV}Q?cZ1e;<-q!;~a~(!vuqTjfFFF(dhxTTZ6N zM`kY~b5sY(R2$q&GC3P>masuMuX0MJT$d<|?hZ-G)G+Z*BqXhzmXl0P!g0aq>uX#(hjGyk7 zE#o&vWy@&(`BP?DYSa(1W&GwJ*)p1c?#hg3`$)FT1a;Vx$=SY@Ez`T(d;XCvQ}clL zoGn|%PnXD+k-o^*GaA3LW%^0;{8U+@T;;8-;a~A(ni`WXO=c!(PNO!cg`XjQfILEl zDcQH}(+W2`mRt`ijihWSUG1NhekJ0(Me)j0@lxpy!}mNCaqd>MrMR|@#^K8M9HjrO ztj;PP+&3s-TD(%_sern#MVw~k*_O()*R4wo{=j&|*_fH9O}7HB{u>eJ=}gX+r%vg9 zGvd5Ile29=(6ahQyc|g+|4EF;3U2*mQu-$JaKyRUjgn|rD_hbxesUmv(@Q)~Vwuoj zs{Xc|(l=F)M4Vfd=4gtG<#8{Uz0P&M9dUj_cp~d1U0ghwzW2GLi(3d!U$;wkkH{(^@VLWuG97V zEVq6#{Cv8&?7II=1*bg}Ew>Sy~Xz2;wmqeb{l=1X zanIw)J&LUMlrHwux6;L#`j!)(Y5KMhksKo3rzlKlZ>sMHDW28G)K3X;?+Ta#`9&t2pg=$i98x*e>E~Up6KOr9p-2f*&t~nvCjjRnX zU0nTS#QC7wuRWD!r(51Rc-N(s$MVWILg4H#BhH7o4h^N^+wvB}b^MC)s=Wh?1&tuAO4zBWd$#M6aRGOAkGRx_|UBvUUb2K+hz;)oKmBpr6r$m!3!RaQU zH>KNLm(5B4RsH^;eIT2r^q=d};@LKn{%a= z;YvJB-%9^^*(j11=|4Z6BmGxR{q$URTWd@=!gTu7fW%2G)F@J!Y|*v3QRjZm54|bgwm}=*On%h4L~YBn$u_7_yj~liK$p{1LT?N&!*L|s zlpP*T*n&2)8YLlHvv7q+L=*4AmpI#YhCXb7^IHEE!41IK{ngo29UPUj9L{yNSoWVp zehObspVl84_1y22K5aR7b>hr{5=k4Yb=(g5#FIYt+k=umEjudeoUY`e$Gd%O`(OH0 zdr4mJBBW2dC`(N7j?RoXOIRCeT05HS^m0m{?u08>@Nt>(q)+|!cBD^dh-cpom!#s^ z^^)}I=!>GxCj3xMinHq_>3g~Cb%8l1DGLcPuqm_2U$Avi5wfOsOC9i=xhd;D=gL<+YspaSYDOzE@b;aZ%@!swdaULRxuKKTaPX zbzY_5B`(#E1t&zEZjX5CN58pG{phFT)sM^V`whgRi8W|vs(yC-svo`dvD#Dp*neWw z`8C&}52WHX{5fsiep1x)-HF<|7f!Mq8cC&@YU_&PsIy)iNS9N!^#Gigu2oy_gv;7% zvHhTYhE9$;PboQ&-bZTdfm5Q{?+6^}Q?+&9i=&R_ve2GXIXh_*qxy%q@av}-oL@gx zz?Huw>O814zMo3hmbDJ<0G!uc+y>{@Pa0VHr$wDz_@Vu&H2K!cC+5Mz}^zBa|#c+zdfuS#;S&*U^FI$iExGdYb3KOHPvv4!@G zDZj0m@nkFd$%|}7KY5X@=r^us;P#Yz%ZqHqX&+9uqMt0uR!qGIpdQJ-we2ZeQSW5D z<`LP7`^j&+qHIsq-Nuux*!=3`9CLk&v;Cm)m6@ECMUAIAxKa;JtDQuFtzM?c)xvk2(t&b)g4RarvIge;%%Xr?>8KI=pp9H{n+A zk{45$l}WCaUF+pvu-fz8vUJBj(sS@bXRL8$#I}1KVXjMz1Klm{ftLK6xBi#zaNuJ2 zY2(MH`FAo8+wu5(^DxD?F0Jl1p5Hv|XJ?ha%e#MzuJ!KUM#9e_=f7lbDCmsZ@sQo# z>faJL$#pjOeEn-HpNgluMBl4_Yf00}U_4W1)W3aj_4uK4Gr8xpvzn1nFMC3BLkHX; zO7jhwX%e)o+|Rq^yd*Q8+NJV3?>1Ar)Wg|tI+Q`OBcxL_XSTz6wWr#p&nHc_%P8Dc z_@OsvmSwqi;qhhbS9*Qa^IVAP*ACaJ{FF$d4pv4~zd5*K1#iqOi|XgPL_FOs^}F9Z ztor%QL#kii4N>O|{Is%IeK1Ry-`u8tcU^W_6u#WvmR~v5E`Ir`UHt0c(=H8?3AqpgwAc%UWC5zLniJ0$1P>PrBEyA7r=fA)eR1k96-eoY%Q$6>YhG z$iFGMuDUw2?vjZKIIn#e^}`&T*FKy2VLx2fxh&h(>W6}E?|ABm7~GNg+3o403v8O| z2S2%1Kls_f>IXkNSpDE9OX`Pm%2}ORPFoiBLl@k4#l17to|cpS?7Bq#beE88twsIL zOWU>Rd!o+Uc{;6KY9!Y)#G8Qgk|nK0{bWgN(HY`-$&%Kh2Yfh{csW@ryCt~?en)2A zB}+Tuy!N>zOMd+)S@NrcWXW&OOR_XWeqQyHEFJLSBujp}PO{{+=T$@;$x?n#)OiVh z=s#0+SN9n8liYIc*7x4%L1&m1Pwh4Ym-S7Cl_#~^EL;PAXiq9n%c z%3J;0tsc%x$Ee*};PO=esZ?34zJpq}?s-+KJ^M*@FNX8d2dex3#@=$Q*V{gq%s6mf z`c8GPhO^&!gigv>D@bNa-p78Y+J)!R=LpHn;J+u=7+IY3&kS6?qGWN>Kl5-UnVju+ z>7SzadzVxCrxvamKRZq7AAJMjHP2c7!*?DRWael4Rs!xPXVO1@y4UV4`^@9gKYqGb zdtrXEtGzHkeW1PN-F@D2t-Y`*A5ME=e)>RrVf%>p1+_~g)$exS`2gI`4@8|@{LtDI zH$syb=?k61T5g{~>vzAs1g+ow_7b#y_gmZR9D>){UV2975WLpz;`YG#?dQ+H?fOvE z8Kh#Nnp8dQ+@x~|etSMThfw=r_5&2JJr&QkoxUlr`DoPPhf&$HPjQ_zrPZai@Xj6H za~_W@t84ZV@6|k$E&tLrYwz^dZPGR6a9*~Y^lYn7Jn30K9VI>M_x?+|#_#>tEZk1= zv*+2kPU;go_N8l_Pk4`g>6&V|Un)vgJn5S9A@8vvUDFQt^2~US6*g3m`6R*l&-0SyG7jE;tV}2IV%73L49Q9P`aiS?q!*2 z+B!(r48h$<{Iqtlb&#$px!1crrEALJ)?}t><4M=l!+F`w(lzaHUS*N48HW3}%rtG^ zO4pR!=Uo=*8rLQ2pu2=!S-=?d`!22&PT%j1W!BxcSv8!WJ=zTCH&=AQ`F$@q2&c8w zxJQ2ame6l4wHHp`3wp)d59hkHy4!xxw}gIklfEVNo1644q2Ibd-xB)GO`UM%^wA;o z2bZo~+xFDAgjFNnbsvSx|4h{L+srAr2Dq&64Xo_yTf&lE-g;2q68g=P`j*fko|kQ` zZwc#sxCS_{c|h}-z9noWUbE_6k{X+KzLTzyTh9KCtXrM^os#WC>E(jYd+TiJZ>E%J6 za!N1n@`)$CymmC|{FpL@u1NKTt-JK{)E5qV2WfST^zyt9C%xS8Melc>(#u_NS@x*S zPkMQZez;0y0qORy^zsPtuJGWbmy5m>b>5iC*>XxRyDpKY?h<=((#x*P=A@Thm(AJp z1xYfSlU{D6EMD>=z1#(NB5@P(q>vJ2u{I!_*M6z=vg@+*lV0BAlb`hRUN|qAmtLNO z%W5;*57NsOyS?K{FS{cNie7c-_%OCKT z3F+l(xEnL`bEF5Qm#5&e>^GaA^m5CC(ZqM2$~dt%Z8_=X{4wwSb?N02I4^rcdbtWt zc_->%+uEjSzww6iT3_oM?{>JX-)*q*^o@5voY#2JH{Qc=n>_Ne-<6R6_ z>=94jc)Ko92i+z5LEm`0E}LtIt0GOWa`wU1`*0(0t#B8Ua9W#LIh%mn1DEw(fo+#L zIM-#DC8QUfNteyVmf$EnrY1%*D>{Iumh}$Y|Jl!Yywj8ebA@BDiwQ#j?Ub>(aZWzvf-=^f!#!-(? zezIY9!WHd_IzM8Fq}3s1!;Hf(d_C&ySCUKV+BTi_N!QvwQ9Yb+*?nIGH$U#Z-YJJG z`i3`G3s(hq3dyFG#kO}VTq|5wKRGH(51i|=%d&GBZqG8@4BQ;~y@k0xtt_@(7T_u$ zjym^}X=q!@7O7EL3Ujfu;a((8g-YwEGPt~nsORtg)WA7#Z}EuN0yhA6xd+z`7kcEN zy;R#~LvWRF#UAm-;F{s8Jh*AN5x6&da0lQb--8)xR zk&~Qb%}mqwZ8KcUcf8|u!FBp@gK&Ltw|dlJH(c>|qs~kwXZv<9TFk(SIosiMcI*P;Cfa(*_UVI*J?Ar9A>)K9~GXg8SsWWb>VX+xKMD`ELa;akhQt;P(77n)r>^ucy*&P?{lr zB(LjNQRip6{!u0ugKOUB-98nH2Zv~7hDizmJyVk>vJ$KOgXIsa1<++%Qw?8vpA6%=+%|176b0Oyse`a%H6>v_o_^;9s$WNfs|pm8BC= zvG2o*;maer&W+3;q5nwn^KSc8!jB!9>v_(h0nT?{5^jokJ*>?_Pp8sNwY&OxxqV4L zU0*>wKV4V{=cfzX;Joyf+O`+CE=}(2JF#Ji&Z1oBRT=|VzSxzYl+IGT(r`PsNNaDFy!3!I<6=!WytvqNx=)cq)R>r&rZ zSsR1v@Zcmf({O!ouho3<*_6yoQf(tS)tLq_9i_NB)3BT!Qx5NYrUTB;u4skxvnzVw zrrwaWEB-rG-WkN#mRG;6;HNkB+X{ZVQNOL=rBhW;BUSiD^agDadNMOl@iF)VwYkpg zSQCWGPjz+NAW7OXIq;Q@-g7yREUQ=d63=VTt3nZ*ay{4Fn#Y>q9>-T1(#QGp&0~JD zC%x(?2hyv{$-x|a<)&O`LV9T~)jlt^hx~eszTT=}yU^-&rZm?;WIhGi?{TI8}!R_0jX$Q;~nmb(C*tLn@xF z`z&F8@@vRvIr;r-%b|3J7g3oA_?1 zFO_agX{udZm)-x?-XdNe4}|(N%jHi?IQbi*oTw-x*JaK;M3aL z>O9$r?KfiEdenUeZUN4#Js05eZ}KjS&SS*jG#=CPv+|e1mN{_;gv)d5rzUdrZNzsd%>TV{m@oZtFb8MEAk#fkM*Jd5l>& zn_uKKcdoPb(|L@d_ayTxNO3kloyXV@=k=ak=P~?ry3S+d-JF})^INK)9n)QK^Ke!M zmpEINLAcm0xz5q_Rj52wmIl>9vQW{J>jcI9YbK}j7|wfhonP==T0hwQbRJ{vt={?R zoLn88&F_ZHG<6=M?Y7+PJ#^bHI*+j%&TG8uJjMi^E$7{tX?DW-*;P7^;b+t9JchpM z@S5j!9%I3W(|HWPxlZRXmYeHJ;QegxD!9pB>|o?7^sQ7Kd%3lHTFvm|@6UDqo%}<8 zO!1v`ol#$Q!~5BDLvVif+?e8d?9XZ3PQ%sr=Q__yKCUQnWjvK9k1VVAJ3f-@dCpRL zzj7eCZ=po8^uEqET6=bht5F@L_x;W`O7Huvv8DI@*4WbfDV@S~dRb<5@1s);ZQ`dl zr1w+0AuCPkeZO3zR3C%wPinA5&R>h~Fz%46k8`xbs{=ovV_HMI6E{Km2N zE$VMiuAx7Y8Z&B6J5I~t{l;l6oZmQYh4ZrMHBNirx(C_sj?m`!9-QYeeE!|u>j*W; za<pAyrN~ExGQ{7#@Tx@ZdCt{l=7pZWr;q_N_FACww@K;aRw!Waj6ntu=h&``Ep3XDB(4UKYvHm`^;( zlHdNQWXW%T^!a3osMe*)nUzn;(gbDkl55FQ&u4Qzf0IYDGzq6ZPOG1lGs#ll=e);* zWN8}i87`15h*;&#lUUrsbM>@sN=8;aRqAd12LVK!yQW>@n$GMpNT5jTZ z8VO66ky*IPuP5I{-ja&zC{KgDtsVEChqQ;A_l;cVR3)cp)8>>pDw%Ft4jT*b{01VH}dV=#NR^A;HpTmEYPxdsTktZnanCckWhuRj#v__MgPsM|)L%wu!zSihSSO z?$ozK6F^Js*8LG&`N^`TIutcBu4+-uda@s~jq)q>C%o<8eQ?!2Tx6yHJYNjwwO=Cp(t%rUucZOr&qnBgYot8ZMu@%CT{EX_gaLTJ zeUVW(zkQJ@IKO?7dAQ!6bN(Wac7OT7dFDr$(|_l^7Ak@BItQr!s)Cy(-Z^w;s4JCj zE7@3=ZP!-)F*or|r|tHb?Q=c2oE=hrAlLKzC)puwa9(SA*&#h}AHYwOGpoyGhZO(G zo0A=~8_sJEmmM+>cZElq;9#s45_vY)`MF1$vO}Ebyx&F14rzfq*CU?nka4)D#XXjiftbdLgnG{+ z`$L+K=2Bd0yvwdF3$F6CQ)Jhc=B{>k_6jP@R$m-yRCR<;s)ufG}Nz7eD9Q$ zxdOr}LEaI0&aEn!4etynydDf3ndh8Bxf9`z@ss6>7v&}1yQZb9{F({x!FS`k`RynC zlvH@kH{I&Oyu>-iWV(L&@1kB-_OtSzBmQJ6pHkm^CXUXt>(4|v|GcrdmpHFf+MzRC zy%qB-U;Z(9iFbm@@=0IFEjQM866YkI$(CD<^$9rnp~9uSm8QnJ>k{#Fm-y~kV?AUs!*e2l3Nw z;f(rcajJhadD(LeWlQx?+o=Ao%WfOhe}H&)U*L_Yc(!eZ-AMRp@vQ!ou5(=)XXDL6 z>~Yh)G&4=S|xMcm_nwh5BtOD*#{LqF> zPHi>-XV+s*OPrsa=N+HtT&8sX2L0ShUfY!yNM>l;U1gqy`)3bM{p_5Ol#k6z^^tt2 zpWEPy6>m!>=hM%&Pt?z@%kE3{b3bWb%mbmfXQrus-Uny<`MM>}uWhH%aUWMYwry*C zby>$-S107(KDVU%$76X;EEWIH({Y6-Cd)@Ww^P+t8iU1f4=A|%`O99sCND!jD-F5j zWI6E!>FdJ&vvl67!cEU|31L&3b-GWw?07!o-}a&8qLwtRKEHdZK1%cX#($3+?{k^) zW{JDU<-U^1NiG&#?vW*~h&YmqvXk->cEI<=VX!9uzNX4k0q?r(I@T@2Y2Vd#+3~Cq zk|eV^1;5aK^2_gk(oe&*jXiVqWUAf%EImCOM_#oqDMPp(UA zGg}tfq^?WjtgOiGU%ynsL z+IZR(V$IPkUpo%jUE<=DKW7dtfX=-oGb=jQu#$1=i+5EIO=DKX|`S!*<;@PrjZ_IVsX=-oGb=jQu#$1=i*))Cj z#w@43G1sNVv-Q*7nCr4R?TxuEjk9TLZ_IVsoc6|Cm(6K!%yns;EsOTXT$jyhZ_IUR zoGpv?#$1=pX>ZJRX`IbZdtoIE{(JK z>34Npm(KawTN%$D^5@x08F6eTOE!xi_dl5n{nC6so5e5QtV`ghw~_1)&4sQzh=cfh zu@O|pzn8Meu5?{?oBgf($j4r>V?*{z$2NSzXRyHY)-!e=(=pq@D9#(*<8^wT=_Cw?J}Hx2heqC`PusQ zcyqSjcP_*2S%#ZghFgH^V-C0P1yrcyCamP8kl)Cggj=KFEUpaBb!m05<*ZqTYw_l6 z-MhUxo92)=XXA}|b2i@eGTeb>xPn}_c5dBupRmhIm*J|H;hMcUTZb-h&emaY8E&^X zXVcv4&Dk{fdvm4P>l(>{_P_Qc2N&Tde{RHX{VBW@44#tb{6L)c01okg-v?`*l^51V zieHiplb0S|c~E#_PrBYGyzQXyMEX5G;X^**yFJ3y26BG~>jT@bFJOIO}T5g{bPNQ==?O9u;>#++WPsd4{Vw~ z>jTTZfc;FH-(PusVAFg7>jPVd7qC9C`T495Y??mn0~_z}WIxlE<*&S-Y18~GuMcdR zf5Y{G?Z5v6>jT>^&-Xjw?o^ho&tAa#z_!Z^SRdGUFJOIOjRtL^Q{m3 z+EeQT*JZc0&-%dT_X5@jw(c)rec*c*DWlxjqS>uG9sJr=%Nxm6b%em1A=le=QG3TvjIP=ra86BN7S;V!G zcPn}CBJT$B9R{uB-Amp*s+0Q0)+_BF)p_kX5vL6pbN2H-)BpWE zM@uuM<)-cWvVF_X3zAO4SK7MQt8Dp-Ctva}e?`*QR+8?uts-Ar4DzKCRj{We;_QDdb|k0-Q)fk-1;X^4 zpe8LKZ0gL2Gy0l{Q&JIe2EqJ=*d5i(?Mc+&H-J{q33|aGP<%PJeAXsR1f~96o4Q)M zc=6I@>grUkY8y=aCj6>RCLYMc{(PH>H*hb%6MvxH#PdziJTkH8?1ByxfAxx>Y5O-5 z-vGDgUBtl;bsB4r?!@oX{Y@r*BYr;qyYXv!P5e6iI{fSLo0IoZyU-xu1-jXfnRt*t z?%XhB;%Ada+g&DpBL3Q8%7syu&q}BNl?w@5`32_T7ftmpxGz!fj>8w?Zhy#zOgSfE zr@P$q`J<$hiu*sjsCu6GDt~r)5;l0c9VB~u>AvQOh4|!23-Rg`7vi7vkfw zh4@~sD+pJX?(+xxuC0|wPx)*7jIvG=Q}YtmKQ9WzW!qkM<&O0&+csXI^m^VJbcR6N zr&l?h-~iZtSkh7WJwlp$cn-g7 zKXGw0Tps}A#H-z~sRxinUK#y?sApWJ&9 zxGg&_i=7^8;opi_IrnF*t33aVjoY@gZre&iJI-j@(Yn6*>h(LWY;E1Pz3mM94`DBV z*^ZZ;vAyZ?%`e-yeq-DAmX~fMu5PxsZCro#_RVcg>tWY#p;SxZ+nY8trUje!whb-o zH*Ifvd+e;UVi(t*yS}dW?CSOBUwqyh*O!%E^cR+ed^fgSVauU1aBYtLm(tg~{GYa* z7h8Msm8Zr|-f?E^oO5cbt7DhE;r!Y+pMCLpv2)L^j-7w@x@sQWl50GkzjAA`;!TY) z74*uctsAe2ZQQnX)8@;s+`gf0^R}(NaW`+hYD3HBw0OxUjY*bo{biT6wzRBwQ)@CO z?}%;Mu(_qF@ywW-y=nXQ^=+}z_LJLbTk5bwIzG0+29=h#=bBACduD9S$vf7>S~hQO zik;k;yXnfU8_%SwxlQe@+naXmpn&$)oOemh`tvTn_zf4YKey_(A_6=KXJG%ipn%b^xUEj8S!`2;!R%)V{ z=E@ySjq;->w^E$gj?M3&s3*5mjn-3XYSY%TW7GN#jg9MDw{2@VGj>tamTlXwiEX)Z zM_cT&rWg%(`BvKevTI@|U&+61O*>wi>k~&wry^d~)OK}K(^kdWu(dHgGI=IPN9>eS zVrx`RT8->BZrHkYTU)Go!&T%P+j?b7OAN{1e$ASL#zJ$`hSoFm&w7>KHi5ZJ%FU*j zOlR}f9ZlQYGE$0d-rBY;*4Er48A{~3Mr~u`u5a76zO`-pT6<#~-&TGpEc1bq*xA+V zE_nU=OV+*Ryi;wyi4v#^ZmYF!-`3i+y@eXMd2HUHan|H!q6SDr+tIx3$`;x=Q8Xf) zm?%_6g!NmS+S`(G6L~5imXU(;UrNQzSACO+n3;w|%oaX9A@$=@dEU{qeH-OLim$Ry z>cUncwrO({MW(r9$xPK}s;f;*b<@T3JZt4|BO0jqGLTk09p+}px zZ{2{XY~Q|ZI~sIH6XUos*3xuUQ%mf!&24&c^A0OcDq6Cym)hgh*lW+SqXN!Ku*#hw z%3HT>Jw4F~9$k{&_3ofi52xjol&kGcTQ+RgRIr?EEj@Iwe5JT#hkHv}qKxVFqmL6& z*V}5HW@{TeEs@H4hEZeF_VwGYYTB;L^!7<*XxnHh;q_ZKZ%vc<9jz^!+hSL5Zfj~A z9ox2FwsfC(Mvb8`n~sXWbs`Vj_v!grHo+0Ab;I_iL=nu6rVZOSHeQ)5>n;RrH@h00WOE;faUZQF_+v%M+Ca}^smY;1;X+`L0= z)c9J$WUFr2VW%?JCNVN5V=PDywyU;oW32DMX57(~n=o?{^<{#W6@qP9+Wt?a){#bb z{bfgNLav+7*wVDc8Wr1iw5@O4z8U49S=LSEG}PwCbNRVgK%1}aCT7mM&E<+FDD#PWCUNV%jgJaEfe{A!%*p_XL z*d^8?W`<3(#gmrwwkz9`YYlo`gYLw$>~Qgyuau;%-`Kpl73+6v)Ar39C1VH6bgk?) zNoDMsZCA!nXtBm^O*=FRB-aG74Oe0z?6`7AYtz=o5(X5@t{2x`bnc5a@u$`e$+8}t z{_B^<40FV`EhvtwHg9Z-UEPfRiv7uy9V7kiO(;~XirA%FH?#hTt*cCqFm#GLZZc|P zZTiF#EZHpTi&56}k~s$%`CG|ctI0|9ccPh1dYh|E)$BE>OoM@(ziOE#NN)xxk8BxS zAY3xInYEGl$z&Nq$njsZUHrohF`eE9|5IJCT>cB>r8c;F^VZ8SoD;HfH6}m|U8s)vuXXv4t&2UeuJjM48(~12&CstnOyu8%;PEO~`4=Y_$8vFSX`7!gywr|1y zqP>cLZ!EPrePf;ZPBj1f>tau?EB(_tr*mCR@4AM;b*&>y#fh1SGj{n36Eh~>$~lO6 z+HpVF&HMj?pozDE2-w5@0ktvL<6s2LzBuAc{?<4T{$$qdU%3#U%Q5kNVCFgFbcF0) zq@yzdgj=`vB<2mmJ3$+$TQp9~X#r>Y^8sh^+Six^x4rWa$5h8FL2tu3@%ba3hz!r` zJ^by9G^(j0yc&FaWx1)rH}i{&wfJn;Jxd0GeWr6m(HVSef&cdTi0S)rAYKG?mO&&R@o>x#pYR_#oiiT6_VlC4L>~ z2IFA=84;)O6z#X_+{A$g7ZsrI_uEjZf6|c;t%+zl`(2laKoO-SsKp*@J;CqXB zBlpc{7`yCExLHE&UP&3aPU9lTk3eo16ZM4Ei90Xim=98jD6M`nIbMixaL~dz z=TLIzB*#~g6McyH6S!|;x#k~Y_hSc7KE*Wr>?qUObvz%79A;)NM-i}RJNC8Hjq){z zBR^pFoyOrm6XALnCjcUW2kXJLK?mtH^r$J|nL@=eMDOJH zVRSwJ<+11JYR86~kp>&~?Cp*6X#*m5=0~X4dF=H+L)#Y7XJw2X>UaF{KSI;#T-D-Y zH~0Ckp&#DH-$*(?=;R|?y@YjcI*Sz#pjKeH0kreh{?te2~LGMTC`tMli&C zj>2mC7(9F-x`+EJuDj5W-Jl&*<)ixvuSG@_OwI*Kp2ILTwf<`a^jH9_jO*^*OaVc|{_$#rOZeCohr!F&k=F5Q< zEpR0@iL~pvt{w?E9q%?y17TI%x8s-M7lBT$L-_OLxs&U?V4CZqH=6hXuE)6^;JO5q zfeKIwszEKNnqy7$kTHWd22Af{j@d)lJSe)DafROuYPs$KyTK^wP2g9wp%d}VB|Hn5 z0L>uk!f9k$saS|t64nNG@_Z+LKiC8MxZj80OS*+zJD?R*0cpS({nQS6s8b(U%k>O? zG5$+*O~2*UQRg=?e}Ga@3tB-B7yu)HA99Kxz%O_+X@F8t0UALsP#^Bc9|a|(TmBBp zL!5>UJi|NRGQxJgbRpgd%DLW4|KuHpypjJbh;dzfsd1*ej8o6^-Sx=9TglhV3lEy^ zha5UOUh<#l<(p}i9^=%}kAv46XMuQ;_tKv?8mEo4%gDC^R087^onJ(n{DRZDrt>Oi z>~v(h=AjGBbBpJP*3%!L6YK(e!CLapZ=;{dyX0R<1GIwyun(B)D1~#IGfAE?%5v$Y zm#+BqJ@?$Bx)vc*)o-yn?9fL1R?q|b!A>v&Cc!kA2MZwbFXRVGKpCh4)u0|Uf_Bgi z2Ej0Bz8U?FzxIl)uQ%o$l{;yJY3^T^vx0)f&Njtu>&)8@ILFX_5#UgtBK)nXFTCgfqkHu`<^5}j9;FgxbNe- z8BXQuNA~Br-wWnI=sTPNCVg>}SJOs3rx~RezaIp--ibc~Pz9Y6_)~zM?%aoe05BtU z7B*vB0<4Tq<+}yDjc-8{WhcB2G=omi1-d~G=mqWXp&s}HUVxa2^+6=z} z?AJZm1=JQJaKGbvADHGkuf@c_K--lf1HD|&g8iWFsHn5=$f#3>9|4u;u6Am9rh>2t z&ldn^OCrrt{22F2|5C22K^>?htQJ&*X6}pe%RmV@wf?rRmTpB}z$aSI%2nJ6p7}MH z1E1=p%|JPM=50f!z%Veaqzy)Zc{}a69e)Sq0M%dsOoCak0P=aJ6ZC@#P{i{!pb>O{ zx*L#nFa;uA$R+3i|8&Meybu2X$bUC=0nK0(l)~*Vk2 zY<`~a291pKPW(d7xT`INDZ*;d4Y4a3tM~`bPjC+LYuk}cJ5n;Z^iECpWoP6^cJ8CMwUPGPn8~!cPHg((&^IYCL7|S0e-UotCjO+G~2c2fPxoi1`0DtI1 z?5Pqyq31rvUhs$6SL3?yx~S8KU(*+KX73F;BcwG7O0P!_hOjx`Nu6o`7&A;C*At)t zd5__%T-)&Zg9-7$4#p@joX;u%ji8A87$^nP7p!*PfL{T|;PT!@8-g~_0|vn;7$C3F zUS#flv@iHL&vpPm`P)rd$3ZdAzWiF+nKWAP)yG}st3w|5u0Rte9uwi`Ea=g0{+4?ZD#(nvhX;b|4IYzRhKj<50B{0T1V(~Uv zZ{z0-V$)ADp381TcYsbX1ongR`&gIoZ1Y>FGxd{=F^Ml3p26RB3(x8Pdh`P*1?8X; z)PQ!-1=Odb_yb@!_$Jr`y6Bg^w^HZZ=zq`$W3_#_Se6?}i3A#Za7y!dy6ik9?un(B`(B~iq)`Bun0jfX^s0WRp z1+;+<&;@$I0N4q3f!$yam;h5?4$Ont&7=#;Km}+7t)LThgMKgwhQTf{4kp1Im?xYo~7%mxUUA0 zb*r7?ukdXX*X>{gNdHaa=OI_d{IfInW^CqLST=KiA>n4`)90Ip8_(ku+Bt(~{_B}} zUt3(90u^uG@fw{8y&ZYS#csNOaq*s)7<1{R+#zrG+_SiN>0^tFYe}a7oCQk2#ehEq zA8!SNAY{{2f$7+$^97E37S7h+p-6gZr@cB7HQ3Mr1Mn|x4zRnUvnY9 z;}C1+3VZP?Rggzo?cn|%!x-k*L8Mwj$UV+R?1OBKD+Vf!R&81lL%;t*Or()K3`~H%+)v}rfq7v1X%|oh>Od#x1;bzzOn|*$4(tbcAI9 z{N&C_%3aahNC)hr-RvApn@A3e@QXnyC^$shY% zaXrNKM)Io%H>fP23mjO@w_4Q09k1Pl_0oRIqo2HozzEn4roc4V4;Dbto#;E@fGW@o zT0uAH1-rm*Fb(EFlY$mrn8dpQanFMp7e2Dyj?Hlgbf>MP82W$j2 zV3ubJ?xKA`2N(c~GmPH?=f-R2x(>ySeTHcY|Kg|4HgkoZG(ohl)*`Zj8UR?K>+b zxSs-dGG_MS?+2gYT5%1}4{}|I9|L!AU5Z}@hPbZ5uLXB;{fS#%`bF(lo5gD{*h|=c zP|R3bu79U!Bgy7|`nBuRlnw9$*vQ+p=XN63eOpcZxodeNp>$2>gZ%T_(c_t5M{kXP z;}+kAC#aX0q4 z$#PSCC%hljzk+9yuIFQT)P>1f4qjjXM`49942vw|dYVfrBG;eH4VaJ}<3gYJ$m^nV7qAx!(1 zp%KP2_w}HWvR``GZ7lB&t9Z&)aK^f14@TYiw4AgMn z42HQL!Dp7vnUz1bxS0Q+j44nGcP;*&Ur}y+y>loa>;Tt`zh7M3_XPeQv5i}?*{Ls0 z5bRR>?%ETox=bE&x?Z6))Y?Aawx!%Y10pgV7mvF!H=ged5?YD5<_eIJ`p6Zht{8q}_hTjW1 z;TrJAz$BOj<_qWwPy$+bc9^u9d9IRs6y{jzLZ*v*f zy}gL>Og-`Sz9GVOE7$u$4{bh;KMuM8&4K`e7@+RTrMS^V9=-80d9tJ!0z4_v|fRyq6N8n}+#8gx4GcZ0qE9&|#l zrMASQS@x+&K zy_0nG(N6={GjE}--b1=P*U0?|7B-%{Jp3MfoiETHT#U4vpF{T%7KB>?4gtBK5Cp(;ya$co7xQd4r~?7o zr~OCB4&gedQ1N4Agfx2b4<(HLHq%G*o_g^&@m1VM!Qi2cG5mb-T(8E*>6HHpCD7>@CU#cm<6rG)m&eDFW-7Rn9QibD)`Lhb42&yHd^M^Bj0qbDrkdM=!c8 z-|G0A-k+m=w#7p=cjR>Pbf@XH-yU+w-03)+6IfW(e9S|H*@qV~VeC19#rSH%%!tnX znXvZeO(X2Rc7-b|q*!iGKhL0*zDIl!?;- zhG!d1S*H`PoYDAxX0Zxia}WvLMj4!IJ9*cAw(_n_%#Tconx`0RovIct0BC{(M7ash z1a&(`UekcU2kluI2c{E7_6gMjv2Ob9m1mn1-~QBb^Z1_`G$WsmHQhJZ3(5Vx!6!a9 zeB8Xf{?E-%-Ld(pCyq9QFNwYBmw~)vPCo2Y*KK<0?#P>77dUa_Q+KX0cNLj6cLdH? zVRVsu6u{i%H92zb;oQf zh~~ZSiB+GvJ0~232%Nm?Q+ICU#N0&Jy*W+y4(EOG#*le+FfaFOK~wZLUcMK-?R@Oj zZ=RlO%ibN_6gjEmnLEyT>WSm;*3GKCRh0jai>K%6vEUsQPn~?i-S*Nxf7HuXlm}1A zsXc1P3Leb4>z={Wb5Hr=MQe}Pba?L0oV&uG`dW@E^4&o*`AF=`t9Ry{{KHS4a?By$ zIqj*hoc7db?ySAzw5OhU-|(F$nJ2dV|D3%GSW{QlH@?qF2q7Gm03rfylVBhqS`xef z+E&maplt;!UZ$N9Gt)0nos9qP(u4t1;pMA==8eKoPHW}Gbl-F1^XX;PkA`1-naOasUV z;VqP8iiZ+Bbls?F(7AQekbn0#`fJx~Sx;SvBgx5@fj4{9_9WI*`<21O8rHGa9S4OA z*CjR31n3CJk~qIUs)C$kjU@$@n);ZLz5+_|3x8B*2p3A_?f&{lHD}jz3m{W39eh+1 zrBSzkdi|6J-4ZI!sn<2>7Ku9J&kJ1nx`U0Ct1@ChX$)?H?;gO4p^?jrK(xpank);s}OT&Y? z7_oXksN!nNYl6bGI%((rCifh-i#6tmym;M{o?iI2zc(_+yzhFt;z)dgxbmP+xb8Zx z6NKx$Kyg11#3?8(rSW69*h8Vl+WnMnBtp`RLI4{vW{F6p=}{|gNT+4K3q)UY-Nd?G zgs-EBN@K>D|8PCkpaaz}(}Y8;@u9_BjMcNBW0M%O^LmC>+PB|+V28txuW}EZQk-NFk3-zJ;8h6TfZyuc&p&hBcHHgTbiIAkbz7sI z{~ayp%G4cVL^B}8IX6p+(G=7CSGNnsKkrX3X!1q7T?3M|e~PB<)Aq-%`yRXA_C;II?AKjJKMv@QyH6>&F*?+;TCU}C#3=7+b04K?HSb@CfFH?V z?Ji=oZFIkXm5b#H6HLi=P!oxIpMsxa1 zY7T~G`vcdzTw|*jUgsaWZmZMz9-_HTDdgIN*9T67@ef?LozT&gUlNLd8)yu(HPVCz z&O1lO8Hx(tO>J%rqluy3>_B4GE(%l^Dx}kRt%gQ4)w5~jSO|XJpg+en9VmH%mVARk z)Q5BgaEDY+meGWqcBI-WXugxeJ3@q%>u8)DuTG{QNJ>Id0E9Qmm8pfq>#jPQRHw#I zty-9bxJ-{aE>rq*8f7Nr-0hku_OLCkiA}B?bi|Eeu(dj71nb0G6-^jPin^H;x69Z8 zAp{P6`1Mu>>J3E=E#L9uu9r7m8|^FonClow#1S66RyhK3gAV}&9Z4uFeeaiX7$0J# z&lJ-43ZsQccPcTAtc#ea7^dAn?DG#d2|B?)j6^Zxp#J;ede-O{vJJ%i{jm4jVLHv& z<~BqwW3#>)KB+vUFd}AL#?qkm_j4Pj*R#ix`LAfp5X$6-M?x|DAPpI-{gt1#_YM0b zRCBK&4O`O=S})BME)9zh?~}fnY3!eg{N7>TIppUE=Z39jS@-fp|EFOSd&CkZ#vk-` z44>2p9mD(~ic$&^rf7K2u-!zCq=$?8gXdV^84B>pFfUR7vD}Y&mERkH2(#XG(07{V ze>lwVq4|96utR+$Jwm$U;D;@#zV~Qe+c5vKoL3#lGsYn=%-2HmT8H^R$axiVUX(TF zU{}kc4_j9Hj??VF4fEwRyUC?>`_$IEUA%3CHfh4a?d?7<1v)y+|4zoSIgl4&j6ww) ze2p~o@G$>0&9q8>sl#vW@l$o8|IKic_7EdoYsAkD{8-hmT9+?Y$p#1d7NgR>iqyE2 zbrH_FyfEyH&z`HR+<#I5t@JQ~#>A@Obse2>9wJ%0hO<`%B|R088-Z|YC*#yQ^-i-h z%UR$oc0S?c`I=$FT4KEFimC`fDWiW8|3AaF#xTARzq;zm;a$79Xm924&Jzr$hCc9C z3?DyU<6?Zy;iGS|H7-(BwXw>fJ6q3}4OdiDKR;al#`vt~hw~fp8vwMMIXk z=rTHYlBi>ZPF^<_@tcRsE7abt!+|KZG!~Vv2+!IyJZHa7>TEoul&;{%dZRJ*EnS#b z2Y{=7aAhSK8E5>jhwW-obcVM05Nn-AgkKI@7mc)E%qP?YljmUT%M6GqpVvHEf)LOaSwD(lm$q!rCAqZdgL+ z#SWtjbSiBqcK=BFR4;c|;t!5&8J7k?o$`XiPEz@HldKC@8m=uDB#T2sgfDW+2 zz>qYVmY7O}FNTbh30Osl@WYUpOmZg3G<-KSQtX={V-i5c$wYsdrkU6a=;n(u)btkqJ)~j+ zU+GRF{A8&BTXQ}--fIcNbHi1 zCE68*^+DE&M0j(^8bz|-W)93dRnn@=c1>8b|A;X|h`Ov4b`Ob>6sa*1RZ-ZcAu)pJ zyb7dA;j}x(6J3)+cx}i?V*u8@t`J@wGHPiIkbhPP4MWy2!~iajal0WhWpjDP*baxb zdjC*`)1h|8gCBg=kTaeahMZ{tXiMk!4mszV!jo2wmMLAPfu>Y&(as1}5Gh?7f{+4X z=+@FrN=fNzQ1Cl|0zec)uY;o%RLK?CSFuPcjaKt704pFO42V7%WBeP7KGZvh46BrS z2Y%Tcbd0`yC~0n>pX~<54y|KQqGv5meUh1rJIT0RtHmiU(8Uu&Rv19h}b9dQ742_fw5u`)OkdalgORdc)sr(8i`6*mh3lkNMG|R7+E;MX&pl z*_GCux=q)yPn+6k8_3kATAGVCkra*bIxX&gH`V3ZXdU*u-=YDme^4IeKR8sYc0|`< zBH|YYf|!rEF~0mEE*2tZy_ZgWd?R;Hi{~_!FN|>=5{%nu>dwaPg4Ko}V>y1Lmm6sV zW-yHSji2uu#8ePe%tKPYpQ_eD zzi{`Ek%n2Z&o4|FviAAwEm14r8q$eZ{6f-@@rvJ(jkV&FbR;AUSugvoTU}AdgM>*# z;w6+*NdPkpNf-V61i3vm#S!-6NPhfCL~Ab&cl#Df8yoQ}K0|-4+wnV3d`kY^LVt}F zjmJvdE+WmVr)?6(4H>sL7R4yd+z>bB)R>cF4k^TxM)BM}K4ge479JmLe|(TG7M>gg zSr2FqDWqS4Wv7&M9d0aY#Lou&h|e{GuJppF#h6EeI~2rxeGpln`-K~W;yM4qSTJ?p zUi_TJ@>irk*0X-|RlrE#x@HiCquEw9 zY_koQ{~4aF;X@&HLAk8{qrt=%gT3v8sEH8oKL$B~o5DT9#kyU^;>#|Mn(9slKeY@OZFA~wMftxD+Umkm)T4=ZRw0AgY)YL&D;yn! zv41O1Ihq%SJn!v!at8m};4N6%_*w>dRyr7={-?0wZLpLy6NS||nCiWQRfaH|{U(tP zMt|)Z0PH0h0e)p0rp$R;*W}uNxXA{SfUl(j09$&;pl`=ulVB2d42oM{Jp>I!Gdz*X z{CJpM`ieoyAdJ7|OoC!o=wfaHr(RUP&9-Qx-8F7zI>QBT*syWK5?#@C&5`)nCs~qm zedz>Nl}u6#ZJ|q^3?hZDyO-`{TNAOkX_m@feO43oeh3#-rXbM!)td*Ya;>Z{e23wm z9>g5rnSNpEuE--5nnS_T^RL2_Rdxr7ZR9r$HmE85Te?WS#bMA;aelI@IM&;T;AofB#^z$;7rY zwDe6B6o=h)Ut)U9fL*I|*z|J-Z3dg0Wk_SZ^er%f%W`etF{af3xsPi&#n$0G=2=Bq zIO&+HEx3Cy|NdplH~*4toRXlNF@HwdDJ8n{_+My=DU`fEO`CG}%r$=w$+y?2Ny?4# zx-bVb-xLgm`fN}uA;1+Bo0fIwV6Jk0+QBKoGX1&|9Yg_|+O6ExD%&#uy9deFfDpVH|Nhf#Nc;$c_>&29khlE z@Bod4R~OB#weiZqDu>Ok#*#F?RBgaGt17kGRb*?=97mWvhh!(-ot?v2a#-QJYht(n z*O8$Gy7<1t`o9WaU9)NhLt=2-lL0DV5atF4uhyJY1Sn83nDJ3J*CNvh!$sVlZAd1D zQeya%JW0|1Q%D#5yYpw?n3+C)V4F76VeYtAk!DfI{j`;;`K?1-G#nMq9ayEL?hV4C zq;eCtBZCDmLWv`$ZPO#$XhfwZCQWcjfCmb9@>Fmgy@$sXgXrQ`j*ZHULhSf)op1eL z*V&3XliM7DI?Lc^|1Xt-@w8I(-AJu+JFTDmxXuIrPjzPg|EkmY;q7(S^3H3!98Kx~ zZ@;Cy{q`1b5C4d_t#=Z~mbTiFcE(~5(1vP8`Kn^6I~>h}5i9)Wn$+h)=cPiPHlPN~ z*ufWP@Q(+AiEAUxk6qi9X2-megx<5yC3IYqKyR-G7dSQ2w%)MMW$bfVq2!vekJ?65 zHjJ&e)9)}Z|G(#%hBzv2(jPoR|5%KVo3ep6+$I@veWK6*s0KqVo=OzD8bi%^z;7{z z6m26TtWd$b8eBpVY23!Cn0p~)3=qkofG!8!CvU@}iX?R5d&2J-A8K)hTS^F|6&ou+ zPQAA-|Kl3a5hU*Qr;BY*jYxCwNHv<1s?lmFy_!R+k%|KJ9`QeLlU^)ID7~-vEt5iT z(%TwDOv>PrF>s-}Ck4pGxV#;Qwdp6c8omFWR_jY6v`R1f)8F}jptYDJF&SE&ZsWnh zfZcAnLRz6OVjYlCPF;Owpf+S@`p0zm z(z$LFms=iV)*T`=kBsM30M92iNePwClgfH6@L(z%Mbj#y`KK&f!g~Xf+iy=+Vb%rL zgw_EFGv7MUpbktebY7Sk$6LK)u>p=XX?4H_ffF z&i{oLl6Im{aJ6^9okWmF(E=y`nvC#eza;1X0r}CiZGWI`yFLE}IsXOZn=nJ&%A~l<L>pTZNEZUV;QpVLfQ4VmF3C0tjz^{sa*a! z0L0hm!|IreSvLygc})=@b%akXF5-P;KzL+8{5^FKR)I4zZ^VnFbIHJmE^++_X}Un7-hr43zH*5+)Cn!C*HH1dP(^ z5u;QS>xfbZ=B@y)dw|QNuBC>dW3$cn;=xV&I|dT#g1oV`yf->f9=Y|Q71i!M$Rf%c z7y`lG8nE56NJov9pFFV3QLC<>n4rOYo-`3E#o@eNrlJaiuxk}RAeDGHHC>xm>t*KF zYO8kHT9QcO?zqs&9ZlNf6<7_2NS7P=iI8PJdLRh`4U<0BQ9XX(SjbURt3gl*+5zKA z5A*>38<9&Xcm}q$q(?k3puUDufsY9@H{JbH&cG@$ensRwwkV7km9VcA9 zDlYYaO|bC-_yRJ(C9E{eg#8glZ_)V5MI1Jo>aeI2c~2uck+R6CPWdnDCa{m16y*(Y0O|wIX~|)@IG7g3U1* z$$6Q}^Bsy!5zZBx*E^J(BAr_{S2%(;jd$+a-1vfm3(kR|$%WM9lGvwX9Y@NCIBKV1 zmYPQs=FtRY8B3mYktgq`UXR!%N1nST4o%7JaqeL{?g!J?`u~NDW(4DvGWoq{gqR;hSAw^MKA zF;hpQw(-?tnyOcHb(&MkdiAkr<37JC4z*GQ8Eb!3I^$;P(1(tt|Fo!euRjMz5#*9n z<&tXDyQ}uLYD1%r<^Hsw^ol=r314^>3j>kOdCxCAaMgIXhc+7e>xf~TwKL1$3Nfsw zR%#heaTcv&pnAln=~Fbjw}|Jn;!2V=$OYFum}MfRWJJS+<|fKTSy;9KV{I|p8br8^Kq}fS{%;nneGT1##X$+Lj<#&|^r(CWgBm82 zhgeU^Rh7x^LkE)(9b1|{7L{^&BbXs;#t&p9pg4fR_}EPhIXj8sEiMo`vj0u=ah# zR;R0~8m?K)Vom>#FFdtu&O@a9IGd9|+(HVXtqVzc=3KYyR|u;XAUt7qGEL4S3y;&J zJW`|%%UO&1-L98uLJk7&B9L=G0%e|PR+Uc5<|1D>M$xOXf#WXs{=*2D%^_}&ng;G8 zZuc?@JB6t3rLeD2*s|F~I4B3;fq(T2nr|d3xZoRPOc}tGC3t8!XC}fif+zz|C&GR? z0QdVo1d1_$1wl@M=iDU+;5XlkKn|W$68*CQ2Ph zmcp6DJKdKk}dW2ApfMB(PpEBhNshzL%IkL&Bkf;ljXjRn&yk6ZVSjA7ML z+;M;j|HSmDMn>8*l3L~^#yz(Lt@RKwL}K4a+hNq2&0i$Po3MeCQIZ4AGNB}lCZ+@u zBQi=rG)*vlp^LX+_rl_0GfM1{tj!jg5FKSR#qTamFilKIk2CHZt?OmN#bO+Bp*a`; z++?c3n_LPIWt}j+vQd0-6s{6Pu!$u~ZdMtEU?|yov_-5LZOe0*ha=hfCBaKeG~6r| zo&5vYSip*tsrG6V|1gSg6R||n!Bf7CE&B~=niOQ1q)Ln(BfdZzF6+k)gm~Z%Uq`sX z#8=p|S>&i?P3beNY8Lr&`{dGDY-=X@azR*WEDLe|ax$y>1yN;y`qH1V*bqAM7~7f- zI?HmF6Rrv6A>KzV5nK&LHG>>&VpTIqlY3cN0pUKRY18Gj8N}PwU$&6MJxSB1p-5Vs z1;N%d1S8|Z5mX`A#9};n&-P;^lDI-7!mENr{CYp_Z=FrznrNB3-5edhUT=R6h6ZWce>jKEh)o`K4&9&f9_-JFrr%flu|XI3M$&I%e-71< z-iH1hYASf^`*X(9UyKbJru$R$3o#>JPk#E_=GD^3*~Ww0xROMLGF7KM$V$Hi zFUE|_qL?kD#}H>n;;4IyLS7Gf%V>EAEr0N`_ci!PG(4gJ0+ z`kREw!iIh+v9UBQ5^DkT1O0)aW?;jDXP0ec(|V)bka4>9-YS%Xq)r} z{YeqG)|$7<&F}XuKslq3L(5t3Xbb=WH(k5v`{AU7p<82O5<JjD@IB$0MQzb^%KTonxc*2u<=LTxeWUsPnP58B>n{l{KTrJ5jbIHtjkz?~`I zHJw^0U-bnnlmtAPl#YEov%gfsE&7XWE)4mpa3<`Eq25|?&|e1W0N*uS87vJk(6^6j z)^96>{au>y5<7p!UfLJkeaZ3l?Dj_E2YV}Xrl??x5HSb+5E|%*5n1JK(LLPf^Y=Bm zT7=;~NiEa;VV_L*2aQt8UM@(bB`a2ys7UF{iIn6IWRh!_B_u^VHgTWHi!Yk~j+|~c z1tmm>)3`6tv`r^fGDz%_`O}Y5`cBC7eb8w1?WM~j^q0N^2Y2x0&-cNL?x6kyj2!TK zgjVyXed!8LNks|{bM%MU`(WjY`lqC>hQ@j6+lsdRaBH1q{@?$AmAYkEW9NUu@-P^= zP&z~_X}@Oq`f`8>Kg3bcT4tBS$vVQZj&eA|YYh&4mE$R= z!C|R2Bf;XxuFb+X+fi6sfN!Crq_!B}lHBKtl;5He*uMnf*xeIv_zn7zHCz@smR)ag zooMEwtM~OCeS@{TNZY&ZLZ46QONkYPKI1@xwW*=u$pDY)`rwlX2keGm3{q_Je(SG) zvNFG^B~of{FxiU=e*m11ggv3d;ezHytxISQiy;PIgwBu!wj0(c(NtqPX(;Ey*UsOg zL9dd~!e)y$bB06FJRP50MS4W$w3)TZa$;`0oR}Uz^TmnHbaA$ID8rZ=BH`JAYYuj)KSl>G25)CU#z|VX5Vdyr)l3(&igLLy{-y@8249 zld_r(TAjW*Ec_OENk{chTrsgOXtbh85v8t2*=a4OLrc_cy9ZseMz5h2}F90@KB%lSp!OBtXaRU zBVC(04U(Dc=s2e3AMC3*!L=$;5HqH7@130{Pe&&pe5gjGkb! zgTzF2DcUFY`NEO>JAvzy-fuW9Z{Kv(YrT~UdY@Y=uU9$jkr8@L z?<3<~KX8Q~xMDLj>*C=g0vvpqr{B_guUx@I_XMq{{n8b#k~$OlFXU=NVLhK-pv}*& zRc?JByOwoIN8~IAjxRbRxtN{xtlLG-YTD0T@tp(RCBnHYm@$mU8$uT^J@H`d&rSyp zXmD|!B!=V8a_#@R;`<1ItA&p!pm?;QyfIP#-ep*tQB920&|ok!Nz9F)i;-uPzP2mq zqqZyj+m}I?@nA#jiJ35V&W`Te7>E4&)+;EfOSV;^DYPxmGG^bEWeSX{Mds$P2=X{{ zD#-iSD~D8kCT$H)c|D*xovHmcEp=K}Gah%9flg}ZM1%uis*`LE));3rj+jq^-%WN- zsLgyYv2Nz6F%{=HHRhPpP*OWCPP}k%ksPW?BL8kE*qOrq`?bQI2PggeHGOkkbKQhm zP1P68wJ{x+nlHtihEr&pBie~$AKV?Sm~(cfjJXutu?JwM#=^kG;QOTclL_ZSVjc6- z_j1wa)Mc=G@xh#qeUUM>6XLj?Z;szRr8c-Ow01&WY;9uQpBsn9G_bgA%f?B?$H zdj`XeNp@76YJcp?jWHMR*!2f4{44DBvd#anH5+X9$4Sy1`c+pN*f+w3l~;^Q_v(wU zq=(<4NBYvwFQ+6XsRQ%ViYxD`U*NEYfFHe9T~!rS&$?VeJ2UTuN0N2br3QaJre5t5 z?yoVBs_Of99#@^D=fu;$ujJacI}LpPl_bitq{&W00AJ1(hnmjnGOn3*!T46+pEyy8 zLiImPL8Vk)vgmFF^!;~`ilQw+-oz{1`_i;OKA?$eRKnMmPwe@OJ)gDbEA077dw!5T zUuDk^w&x=&#GbFQ=ZETa^9BrzLsLEZ3LF|blP)z?Nz$~u31ciQ)uPML7zh0$b1|<6 zP1Jy{i6)7c#@A9`QB|!uv2NZ7k+WOJ!KlKq$Do;YscsX4iyZ9}uJ|Tkym*8OSET0} zAX;@=TU~rrtv+#LNd1bU_kZ9*pu4zNoT_+pjXBK2^0@QR4B8gqXF3;UrjE!?oy) zVWsY|?Kdv_Zd`71eJfnQY~9*0Q(By8y`y|pp;SBPU7Nw)?ONB_uvVIF6~ z)-mg}>+~hAAJ*8FT<=N)_ACwsNta>my&03B3|^~2W9Pp-an8p&9Or~NfXu7@%5O|T zI{p1gSvhmy7D~!0yO(UezB~(St5PLhz)@SodJZ_wX3kkOZ@|hdW1e{9@`fd0E0^e& zGmqlwkz3d<`ztF(u(hU;tzWLoQbA8pDvY0CKkDp?MJwkGlrd|W>W0fzrD3*GT}d$s zE7sk@p%Q!jQ5scw$y|FBNnD%ah~iXeC?O@S3=F%XwSGrIc|tG{F3otlF&%qxMH$a( z))g$zD$|g)S*0o?T^%jScse;OM@5kRFoI*ssH!K=YL;iIfQx=Hhp{9ttKfewFE8MB zazSxlE=^!tKlYolWVzsCs5`V#5KrKQ6XawxZhVu*i^*nvVKGyc|j!R3AjVXAE246yuE!zs@KP=rgwsH+;T$H9m3pfei1Xj8AI+bbb zlYjc*=^BQ+zV+Wv*RX}Iu&sZWzrkCN%HKP-dgO1YH{&u_Vf}hf0~57A1|H)@m&G&U zIrfx%bx8Wm|E#7|6DH(emd=Rvj9AiO5Q0=IvFat#-_ZR+Q7xxT zEnoGN+Z8Q6+R$J=s}W{hHvW?W{G0-`E+VAG6%7P#y@LPf5+)XhIWIa_n`c^=zE;03 zH+OvAGToew1FN;!(Zv25W1APYa-ME$@5*^$Yt7k3WZn2|c$jjKmc4{vf-+v-;M!>X zfMDI6?NVp2AW<*lk1w25Ays^j>|CWmH%*Yc?T?MX>srm^2Jz9C18DPtJgX*AdZ6}AqqT%F2?UCvdb+B`Le8i2O`3lUVxMN**E0=vHeJ4w+&1Wf?xu&9)%S z9q59Iv`X%G*ltyWG1B^NlK-)Pw$LO0b$zx6L9r+#} z`Tl9-``XC&(8%{-;2XC~ddHt&pT;>7)5j(lQWCN!B!ovA1XbC1lF)Kb!iSj&O^Jw7 z@8s?#*8*5#{6^Yy)Xs?8z3>BE5ZNHdgXQ>Vi2qWKLu<%aNFJm`*3cx}4*4rC3CMJ& zrfy8IuNbMdFd_Sngzy+z%`{Tl!m3Q9^h35Rfh07op`_eRcCRw?moSr?6r9Os&>@th z!0DzWLd?$u(zl+$WH_20kN2_{n}74u60LE>{Og|KZQpxK@TR_%_9v?y2)nvwh~%06F!`o&@_oQbu7{6U*e+C5GVXGts0`wy%ojq zBKrGpi-X7d>|1dv45)|Cq!c*R!55ACbeR}7xa%(cjM3mk{q)fwCe<7z;lk;h7~Tqr z4VUFOT<#-&QUK2c;4uVBOaLC;XmFzbuF>HC0Z%bpE^|^hLS3(1xpBjsjW~l@YG=#x zAib)xUy`C^V*IBc$2NG~MGqAAOyY7y7JWK~AA9L&dhmyT@>4-4+!#Le($Ql~>^WXD z@(l@maY<0Eng~BjbY!ndwY#1Rfr3jg#U-Rq;zCp!!p-NLbqqH#2fV}bV8Wi@R(Q;V zQ$lDWjN^sDH7*+sM}4g-pE9;Gt(K{3)9nu0(57o;N#zVVS{aFGtm8m#?ct^@+X=1y z@g%4#~Dh zrp)L3(qy((Prl4lM{QAKTL})-(o8l7hEzWN#kSZa${_1o)TDmm^u-NZZ2b8PPp8iW zkG|WYGwg3myN90KGy70~!Fv}mvsLC4{969b%X;$SoHQLsFuzSJPg2nSO_uA;Oh``t zGBfyKqkQ^@BxIVl4$3-J0i6o5Q&HVQ9X6Q7F#TgI9h^csnu_#*&f$O5uNyXIGjr_B zJSUS79$K2swk`#vs>~+$?-99w_tE~%$&vfFLdy#mf84)+ycp=;JtN=UfiLafH?+s} z7tJ@aa0>Ibe#TfyFx{1qo`f!k$@ho2u_>YGQaFOYXSu1kOFB`7b}Ss%<+3#iuwF4S z47y3ze&YT8MRa#VNcohAsCaU?O;dl?n!Y>JW@FBLo93cL;{$lJATyzhJAd zRWG|xp0SV)p5scLeSh2HDH;_OPW=F-D`SMNIr4YDIe&z%Jo=UC>Y{XEabdncKo2D) znUVrwnet_(>foo45e%)&6nr(ZOi&xU72}G^3RsPAkO~Zx^L`>PIA=6xR10j_SlYC? zXfd(A?8k{s9KzQpT}+OzZoQD0F)uYWvS_Z-X3rXMX!VmW;*wF@>M4pG+7zNciMC{^ zF3zFjEGtI-&d+-N0^&M{P9H-lcgo`{z6{`8T0(rxmubPf_d(7&Nsg=fA2THCx7Re zOX!!spVbO9~O;Eq-hK-o& zGqNhDr7Mwa`IA4}6*OmVScyxsa#dJd5SE4p*GgS{SmkqEC@v-c&R?|U&dM@`lf`HK zMaS=`e4Y!9(@0zWMc>3#+PP5N&p=u3P1#|6x1F$}Z@=^M#3+Af-maQtHf){mxjd~8d+>G=~f6@BLsNZ627!f=k z5%oNh!>o^izVIkJ!a8w1=nD^CMQ!({Vy3B>d>_i)d2Z~f&_iRzgJ@q^;(y7*y9bTH zH+I6R$Uzq~a8);OUdM4KIWAe+4t!%~{%3jxt|}BA58WNajq4wGZrrJHC&$HU#P@(- zvvd@Hi;H=W`JU?!J;$YJtRDhiy=F8|rVBZtr$QHM#M7W(g+8EH%}Y_A3VjgEC0nh) z7lb~D1@6@-uS6fjrf8&9fLB=b%BXln)M~8oj8FReE=QH2{jBu~lxG8c5jUVb6J=mf zh=!vw-#Tn{6JbgmU@fR}Gbz_5wYJnOLWxp0f&5G$wi+9IJ7CRQ|?!eZ+;p{06&)6;JNrlmXR~?PB0EE*Ntm zb-E4vO4jy0&uShGjar3gA=XCh30`x8EwhlLF9&&!vN@?FcU9%2wNI_vm>nNn60a&* zP1erM^7qbvS&ehxwt7{cpXA0n(u%+9UAZ=F<#W2IcU2CBlW|1g_7r3DG-Ko1lfgoM zaO~~*=EKLDPbtr-c#i}l+O8NkVQtibo&<=?HSWOu)Kimg{P#WU8o<#LsX3#WU%|J!SQYYzaA?ZMH}D%s|2`I8?+ zyIsWi!hTL;?DMyNW1{EK=$S2ITlf7N8C|^yl_sw46 z&0eW?UujH;aH7{b7RQYbuGb&wb;DKJ zr(|Ppz1P+IJ&RGH@$Kz}3d*b>v8jdKz2aXFSUX+r_s>LKChc{-zPjEf*G8ePS1hR4 zzu4R2qWuL%ivK!*yKmef)`&nTK4%Yb95)%lbb2)K*@2rf$yf9ysXHrraf8g+>#gWr zxXU;mYttwV%g0GLfwn1Fy0;!z#h>eioBFw4K8DJGyitx*5W>^F){hV9xAfMtQtBRB zDu^FPOPLsP7A^HOE%kISKZcg#AM16fp&{$fRO@<2ZZVxfyvC&6dK^nk8eFU8*YskQ zY8~t6SM_osb+Qu^yw$JimDlZz^>w!=d~EybUf=58CSjwnx>wAq*RSfus~GvBSI#T$ z^*xHb-wKcRO83?4AL-?Pjz;p2;zwRr1o$Me0hz#hWm#|avfes1&q>uw@dcuV^?Xq; z&M2#$8sm*oU%b^)FUy)_pr?Ef&(a;P(Cv;E?;!lb-ev0C==rmPj=nf<>2Z$F?G^sn z5G=E?``}SsgvP!(V3k?&+;bVOHibXSmb<$~yhA zuIlBi(+@&Y=HH8qv1Z|qNZ^^b8iFpMx2n^dkn9c{A){4{sJ!U zD8?ICQBl$MavyWNiHl5}#c;~TeT~zmgk~9g`KF#~Z;w-17pc076c~E*g5De7@j^2f zS$+?|2I`vU)HPSwQ3}3o<*psOrcF_|;w=5onrmFl(phW`gm`^()yvI~hxAU2Zwomq z{bv!&CQ~Gc&nxPa3@UyBrk;D@*=kJT^5Osb6*! zmpCqP_T2j0J?4K)c>l&8&tMCNi}27b|B4gG>0=d;%dB>3&VHTsqdoj-2?C6_0Qi5< zK!*B|()cfs1{mR83F=z^cNqXLM6}WXbhywWNgq+_0b|_w!#$cQp)e+Hr>m*iw!K+@ zRBB+ISQ1nH#f4%U*qA*RBb&kdC*p=59@RV$Dm}KxWo)*%n8fL<1^VUB&8f4RaiNK? zm$GrA*u>~2(aubSn1rOJH&^Xwz9%F(8R`g)QCGcUt2$*#X#5MTdU^(N2*hQIP!iG zUnyZ^ON-p}_z1mcZhXZ*CYjWs20HlZ9FQfyEA+S|KDVD= z3uSfo1|G}#rCVmD)N&HqAbP@CAwaJt4B^av%AxNU!cHy`Z7ilH<{4ct-D zO5-&TUnJR0qzJ@(9+bo{J+$mHs;hDL)y|TlT5N;a zvl-d>fhi*)A_+XB*H+E3A4=2Q98T1@;&+I)xDx9@+wsVBI z-NZHWHV*K7MhoIl0G{nC)l?Wz>4$`yh39V;PQeE=&lJd6F|kHXfFN#$X2W(B%<+_0IE{1%;o?%Bfte%+wsRUhUO?~4zqXI#QeHX4i8wIIr95+p zGs=PONQt2U)`R}n`*RMST|90E6&=Ic)}O};ofes(aemtg1lm_=6I>F04*TeRPLR^?7NMJAet!!PxUyN z?-l()u-cyJIT^%d8uxmvuX+sZl%&!0U&`s?9#3rAn)rPN-|Qr>+ou!b(<(phEbVDu z3Hl!NuI!Pv3xW9{HFcOiWD%)f*#jXm4HCs1ovvD^lN}xUpV35vA&g(%1Fy53ha=*$ zV+{HYW#8#?PU@wq*=y0Fv?9R*tf zF(*GRf3I9Vy%pZM(ejyc`SpB;Tt5A_^6ROh4bTBB>2cL}Q_%W}qeW92QxR{N2yYI( z`Xl4X_a^ont_ur*I0T!Mf~4mkc7#>yd#cjHY}<7A(Bb6Fr~DC!_>#vvzUNwOKni1f zAb;f^?*8QrSGrD%14TR+k&(qs$}7mgTM&F?kG+87Bk()MP#laI)T*1hj#;kF;PbLF z3TXVUsW`8JUGlG{;zx1!LKx2C=TlRt{rvgVI2t=gbKb;{*iL_1r^@3L2jJZ!c(k>`C2RHp954u}i|KmN@EfovC zmhL9s94xtKo00c+cl+DOtM+=kjVp2IS#GmH=FAzUlSnZcc`%7Sm&7}(PtVhW<4@pV z9Bb{|Q~7E8v2Ne7?k3kB;aIovdC#c*C6Gmnep4eKJJ+fM?5(EkU5T4wPKc4SI1dj(LGc)WHwBE@-rc^vyG1DX zuJ5)!DDckiTy=VMUUjZ1Lr720LvN#0r@c7LUQD03u*bdLYNjP`}s{tDf1&^Q?uu5WG>hazYXjv`>i!tA0E%JDyyP;?4>UbVni(6peg&y8f z9aZuh}o*&;`9o6k*>hQFg_0<$Ss6#t%c4xJAB+=NIa*9p) z8MKwluOHLxWxJrFI{b)#5wO$6Y>)RnN;=g(D#{mWnk?OreYb%Xb^DB7Go36+cRN8- ze(;{`GR6zmy|j7XceQ`p)gt)4$GRlFAl>8f9tpJLMi=BiHGqeQjgiD8X_KIEld|5O ziw6;25%`xVYaMC!cYokw9$;sj_qQSFm0lkis3(+{|>Mk2om@JtK?_wW{RlVAG4A}a{0KTx&1(9ZwYa>nt2at=dVwDsOh2ky0^D(ZL*^bJjy zX8&ipK;I8CMlGz&r{Bwd{7MxhMI%~pdW-e%E z_+|&|WbEOry@1``v|T`HI2W5*qVX;$!qOLh%C0QRuI=7Kp*PtTlwsGhA&ri(+ZJBxnaY)k212?UG^nZ6&=6cE1ejCAJdpDtKJKpFsD8miSwk$ z!iv`qc>6l_UvwsEANde-;Kfe4A1|Sx^}+$1OTh`aqPeVb=zzDoQ!jNwnLN@3xX%M{ z=K&}6$Z!@?G>;Vr4tPK5)Ss2%1_AeR0Pdd@UzZGLg8SZC&;p|<7Fu;t8oZsEtg-)q z_k&LJ|8{0*Ev{7=%(d1)xxZ8VpC7=g63;jLB3p2;^mUzEuatg4%jL7;r32oRo#qy~ zoE7Dc1-+3Lj(n-ppbsjtGg9vXZ&M)mXeT_vpB%vU7v1Cp-Z}D{3iVmm6+nwm zE}T->^P>FeKH%NosXqYzEUZC$8Uh#uivOQ-ds1cobRO`&(y8A=+r7{YxV-_mos|9$ zWw-=Vv=OH80dIAuzD|Z~0^CahxECnC(=r^rONBW!4C__!=UxzWFvj@V0q?d>b2*y1 zup6y(pcI{de%}ebC$$|Y8%F|94%BB!d9;Mac;nzP?n(VPh4 z{Y7UIGw2Ho(6J75D6DTEP{C(O&<^jiPUMYDlB-as*m8inI_AWW%q;@36okQEhqY%Z zR%OWb70LCzcCWSVfH$|(pbZ${L&+#*gYwjcfO2} zg6#888c2zODJfQF6l_@4ysminlHz*+!-{7!Iz+9wJmc}eSppoB#m$(qnn;3UYuS-Z z_A=&$;0IK-1?3aNamKCYVH`VVY93}5I2O!n{o21^o+E2!bU5y6+aJayX2y=3G&v_~ zDZz0+>Gh0~5|S6*LGPz5H;nTASh&Mb9y^{E`x&uBFqWS&j_?JzxI^WS9=-*i2mA&D zgO`N^Ws0%p-q`k3!TFAC*Y9Yw>v zVYG8)$1n^o%^uC1+HH8fHnoBti7gLdEkT5Lp~lMN4voVU7}!9P?Goni7;Ao|10FBj z#W_(%UU4Zif7_UhK}Kk#uMz_#u&a}!@+T~2IRY;1b%`{RwS zq-|)vt6q07nG124%ioCG#oMEa?cg+*qb3f<*eI9Ltb2?6jl{#@#E6}FA4tq3Pbd37-np_iw`#VJHl@H|2rFm_& zDCGJ^nA0KeVu1$!-dpLbg;^aUopdQ}R78e_868$x5@j5)dZN-*;^kL-`p?_-pYwmA zbYKxx6OM-uZ1Bb&O$yriO19_NeRcy|luO$bpzz;6;9@}`J@`(D*!zl4`+2+eb6%vy zvEvCCw?QGD`vIj6L@Poa^g08TEnz~3c*8G5QeuVh4*cPSD$r3(`0x%B*3O`_n7CWc zNBJ#O86|}Za@B{ zf<;SG)0CF%N;z@1w96m0LhY_5{9(#V-%ID(51!{=lesJZ-lO5&a_m+5i%Y2rzLAFR z(YQ+zg;zdz?F;j|&hrOo+JTr^$lgzbbZ5}ZpF?zSAV=-{-MRJ`&qK}nww`ORK7UAg zGKk+r^Y|C(FR%sV$=xy_Z)wpsX2+8}LwZE6Wqw3@>LdF!_y?6kE5s9=8T_B2_xEQUn1aE088RrD7pQn5M87t`=P z8ey@_p5^EHpVNc`F|%prBQz}YCxh~5X&_7On{lpvq0FCY=i2iE{3)b){6hMReIoDT z^MJocPLqFKhlkzavyIQl>LVaeZ(u3e#2&MqK#ymNo3T+q$mNACL_-|<|+$EDN zB?%A|yTKIepx+V``jMyeBTgER-sV|nUYE75VBHg(!&xl9kI3)^=TID1UW(zY&MINw zu)zmgs#|Bqcr&DkT>*b1J^N#4kJ+>6Ye-p)J&TpS4%?-h`W+jD_@gjQE_&%(KA-@( zl*zfPG#ADi{)9o3A<=D^#J5v&N5{52)}beF#f$&>`7+hosHIU+_5UAj-yRTUmHz+E zg<*tIfdNFMWCmUsOp6&n2Gf=q5eCgxFy*qj>!5?+W*cUyUB1gZ8Vo^w1uM~P7p5lB zt=*s-W)_1ulHvt#<*l@B+G@AWZf1q@`#k5J;bPjh``fR79GUk$=XuY0&U2n~&T|c* zZQB>$ljdvIJSchnX8>&5SOvn11Wha#9*^CR7?k*ymNsMXW^4_1os!M<^Fem0u(=&q zrqEgEdUw7DsU)rim{Vl&kf{EIKR?6tqR*Aj#0gsnXzMHQ34ak$s{!qUiFv?NT%vi2ds@I==h$U4{*jdylt z@#fLa&Un|?0nW}?n_TYf47cUXa(0Hef`f&p08eLlemQu8(-w!-e?7Jg9UBnZgC{t7 zEuJdz6nICcMS4f)gh%m3j+Jtujyi(^Nld6SwbU8mp7&U!D;&msd&wI@<$ES6x8Ncv5oiB!S9>}Ahba-lZs-YklLWwgo4`q!Z(_~4YoNAKxl(vCKbiP zENX*d6AEf$@Qvp~e;Xl_<`0D#@PINDb}AZB5Xut720$+ld4}^eCiJ4w$8WTqZwSc@ z=Typtn+O03I~5JWjSN7f2mtNQ_}YIx*#5K-%i2>Z7sgY26m}{agzH&*#X$QLzV_z_ z+n*IeSbHkv!f0xb!cIklFp9OmaE8q}zJJWu{vU(w{~>U!J(Y5y{|wrruv5_>Ts}hx z_-LSgudn^bgYC}?=TV7crCj)&+M}>j(IEVjwLd=4e!s8%hhlrIRmNRtL4mzA8HmFe zYc$BaaqbLTsh`YLJ-T0lz}Zq)n}5EX&0pJknnf-Hn+42^?UjC9vxxB+wBuh*31k8w z{`-Dg&QtEkPxU^2O3)FzV@+P>xhllSXZA}sI^<~I}=;lQSfR&^L=MrUl@}l1%`kH z5?rvkS$Ty4#oDC2LKFNu5p+IPqq*};Y>K@sFMV>5rtHj;Hg>h;G^J;f^0Hyey7-L2 z8j!D%L;GE1Ei;uxme(7FIdL6@d7HiZJI)lmB<25VEYSpHyJ|mQ@s*$^()jbb03~)NL^C`O`kSIDeWy^e^b*qtiaRP-;Fny`(J^_X*9}(@A+W1^V|+Uz03$>)$yIvT(VF zk%e~}k%ecFC_oWcl0)suyhl!6g&gvRlf$SO6*NkMp^<{al`ab^EV*Liupm|2a?K@4Au(_+>wb29gk~=n4|(JW=mHaV0ar$`oah z(DM#Zz)$dk^QVXxe0Ivm3qCt#``>@T3;ud)V2^nJ)W9Bb=9EYVr%qjy8F=+aPJs%% zmBXljzm$IE9$}Ox|3+VH&Xq!hA7(ZyGr-MFRVVVQPF#f{Ty=lAbmISTfB1-i|5x^h z*;mqn?be?|3lkkO7&8+;fGPaAhFQv*-)1}@+;XCR+O^5SJGeDmlOHT&w6N4q3rkPA z?)n$BuyBwT?ii$nVjnFOUYi!a(a#}T;1>>~1>0$&1=>^w_l6d^kW1XX)_@eNGIn0r zTC#n)NqQnr`m^a__5US3l>FD|Vd9VI;f9~42Mg&W!f%2BDaUY>nt|XgHR1Lw<<`a9 zR|wnRt>6A`&DxERD$lnDlvow2$nwy_FpD&E zMn`6rMmTda)6ua6il28+@-vOOk|n7D%fxf1wM?-z3t?L3b+?-S4{Y)&Wk20dK4X}sj5?5s7`4!krF2-r=45rGKQ8sea?=6|!@%w& z$qad$7uP>}GG{x>(K?9+O*-*y=_DKu+A5)r%*@q1Dl3SJV&-E(FdvIH8gnOPOu@$y z8405^N-dIH>DDldCRclU5&SQl8m=yyS3pGJeLvmiO@}KQEC^(LfDw-N;*w)xCbG%Z zcE;2F`U#nS*-3+Sl>SbXBULFGZim)rcv@MlhD_*ro&D_)MPEN>=v3mySR?tsoqR^p z=}v8P8{xC@8Rb3tqF?{(lNslz=pEWT*r7{^ibH$P55D%uT!r?tdI#EnLhV26*UzE$ zD1OvlH>|z1MuYaXgYCW4{*!*ancAc1h3V(e?k5(i)t z>Q$Ose4l5{8dMf39DjNbM7@(!OwL`+%A});@fo{RwJ+re8mn+DBcb zec90ViW)0xA2rba7_~pquMej7L04(NYH0hwnhMrFW}tm9wLjLczjOla`%e5AeU72+ zl{EruA3M-~Keg{g+AC`R&nw!iT69Q398D0if9Piz98puxAjA(qc$*;X@7JFt2q=aT z0O&8FacB>MYMNPl-9Y=@)c);${b6d4Vpx0XLHj@tw4F*rQ*cHrgD}wt0k@WZ$Y^$p za*k`=v71iN`RVBszVp-5Cu~2Y|MVSu{fU9Q)c>CF-K8Eq;k!%Kp19`mX}5mu2|7Og zBYo%FRf>Kfm*zot=KxU1|dX|8Ja~T-(8?huy7R?dd<` zZhf`8)cND}pC7;SAe}v^)gN+_zUJBKdPWE1{B$txg!d$)gKOTULI=rU6v%+xrGh~Q zK#qRIHK~B^Qc@xVThA~ua9OS(1Cp2&%4<{8pBUZQ6ateE!RVjYB57fKk+v|rC=90s zSw^@aOL?VS!ywlU6T)2ez7;=wF8{0dt&ziF|9{-KHe5+3{F6UTCzh~6Z9({~+Gn@Q z9kSZF1&t6nLC+z4d|V{m8o48`CLT`F;YBi4K#@9sy3(c~U;f>9M2g;=-u!Kh{j&O@a}X?8qU zYLRA7gI>7mRj$+_Rg@y@Ky^k)X_?gX3nb5!DlGKwFI*{t!!P45AZ~pbr`k^0Xz$01 z?5%*)*3|xbn`2F>ZiI2V!Q76hI?o_xiL~l5?gh$V%^!dPlH!k4Z~KsX+fi>L)$=?o z)^qKndQWhiaZQ3+>Jd=C^7S?m#L>$zjeU&6N1|e&U!f!9letb)belU`tnd{oC~&$< z&`GOSz`S@O%py!1X_Wiyum2(}jaBUf%g#7H` z3>|RcRI%Q zOCMgc&0MSGU8hzfd`i6$%dPr-t*oUhj&PX8`7CI&fR!pZF#&DYX`!w+U{pBfNs~0N z9HqH&#ZrA_=>^`9o`4+Vle1!BfTqk`vmQ~g&2@-FlFR_axBm4Vr#4aOVV`B?)=(H2 zu_=xrd?gS~=~lE%Z=WlKFdQ6zM|g*-Gi@tj5vMjI z;S{+qw1^X>(!zy|A~eAXE(|fy(O1|#;CC?nMx>)KXuLs32zMA(h0q*fFu`pR3~pUI z!yLaLU9=CKJDJ@lOq^)GNERLY``Il6=Vc_+%25Y2fB25=f6|k)I?S*}xR}#ETBU-K z0nXN`{acYfqDI>yEe%&fSZ0{X;g(akmSR2&-yg&1jFJq<#iwXt#9(InIe}!*ky+|| zB~VO`8LS*sGtlz7@^hyJRWBWVM&jP|&Q1!@>|J%)S)%=R(M^Stakg?v z@$LBmv|jSfl~hVAp_{IILN$+mu&?Ad$;H0|?@=QUuDb?cfac|wDQ37$Nim_}YqD*l zJN8&_>@ndL!VelaOK!09lFAoXR615PNZViB*Z~tHCk*bNU(>X1#neZ(G>+_)Zg)2I zZ;1&~1$kijLeXeo>AP-Q?9`eKjp_#J_RgmMJ!3Ey?3=lmB(OTq1dc#RktidiIV3~ZD;q4&YsIRyj;1hQn-`VjY9|X;NcZ@6T4UeAO!fG6biU_f-)W7;bMIdN^O(K-?#UUrC9<_= zmCTlc;PT!-UL=LWL)+%Q+v&D|C%`r!U!$I7+lll}_{rCO3nNc{by`K>JF5enyF>8I z`>;Y0z?s^P=LWDkw)NjSHR|V=R$<*MO04Q4L;miN{1PcloU~RFJrJB5M`R-j4I0OY zOu(g{tnCWdS~cQ(SEZt)D;8=KRI$ZMmv_Y{ABpX3s&4)CILtKh&^1+_-<4%|8*AJc z^+(5BKRWJoClkOo`c*sKA0BUWC%-`a*_A!A#f^H^BDao67n+XO$b~;1ugRj~b}H6V z@wo7Z4T~qRzi+7TI^M8&qJ8nCTamx29a$E$ws$(Y*0+v3n@(m`_XSjX6{Q81Ucjva6Vvs)wVlR4SXr_^!Gb88hW0Z%l>9la#4xD^KLs{ByUL- z9W_!e`#b>B80~6=9>ay7SaQ};X7uk;I!cV5<3#U z;cQ+(V{Di_o7_xXe;QdOM_Q2;;@*6IvZ&ntIE^A5i?$U_byV+v#roc(tbS9SSpPF$ zeeclv>!^O6ul^*l{yMS#r@s0h4Aw_Trqh#!wBt2F?$qP0X~(_KO=jN}QjXUcs2~Xy z`c0lZ)#~63ss5(p83z50$20H(&xJ`;4~4Iua0C5D;rmUzqV6SZdxb<++jdnA-Swn= zwa7aL_T=l1^8r`clldK&e_~ILAkaVBldaq`IIZM{;#4gy3InsCi0?Bx&Sr&XPOnV9 zycJ`wVX1!!;4@>h3tz$i4e!xd`W9A(4Ab{_dFU3;jir*PR!q^3Ri3MyOlH>`V6@ zP>xJqz9%Ldaf8sp+c25hwN3_&wit%0puR@0`4h^0Yugp&>|*XlTlze3#mU;druJF*X9dBm6@1mOOK0xVRej!{ z8v%k~W3x3)UJ^c<@n_dtmJ*My3gR~!m+fI17ym39R{$HAYnM1K+v{vx5@|x1wCVxQ zG~!r}TAIBY2Hc$4lxlXTcDh4bn~yo&MuKpFcyQyQJyY;bS z&Zc$So+WU{`g2=VcfRdD?AGm+H`g4awxK4U2mx{ThlW$?>!xFT7AqME;S>O5uQuU8i=#gO)HM&_L?Q{ zJ%7|3dY3s5ksJ^M0B{h-to3&o!L0=I?S6nWMO&;sNH1qg7=`TaH!TrCN(rRGtdS7N zl4EA;7=rmVgPBY)7ZV&$f9oB9$#_f`gBfYb^TF(*qYpuKb@ZF&icsYQHOQhNptk|2 z20-6f%z$QLHUL*dXJ>zF7Vubx*8E%jxq@oy+x3UtVL;haNAV1@y($2w9=zTU%$awu zVyGKw?s`{bJx^O;xiDX<(mZ-J5qG^woc__HI+;bC7_M>H>m1I`C`N1WQ8U-As&)E) zXyF5P0X|^%qcGnm-#Y$}ll9||i4DVB!uA=&W#xK$vbK{gD^3%2tVJ4mKP_h4=E*oQ z8Jb!!k|{)7IqC?*t`A$(=3S~BHkbS+aV{A>yVj+l>xSK{Sj|y}UGIv!U_Eu+NAizh z*J1pz^;bvXmgM&rgZ*H^Q58j>qu0bQX;1j!D7FA^vua>Z_?8~vjjKtes>S)MUfdIe zFOR}T?flW223QSx|Db9qsf%{4o7`f6gnb>>jZHB^s=8z&rUqlTsRd;8qnV?1X5dUW9i?@aOl({d6K>9ffI9~CtEe%lzBcCtjkT)M ziYjJHa)qLL>%pTnike7!sg#SL+?2()XWBXzKBI}eUsAvK=oY3%ux*{}V=xJmi9H74 z)UZ{#bZNORCt_415?gExs)?@6s@(>Gtb51Ntud;Rtp|=Y?_jiYT-o~i(dO6bZ|^Tv zt=o?_Z>PU`{C)9Ab1nVtYT$7G)4y=EMJBS3-}~6deQEO>q?*1XiRsOygW4isMjA=(dfR$eQ-HZ97_=HeKJi6dbg(zv|yIRBeosC`v-$I+N>Ry9v;>rt6hUM7p|p~4ZETg#IGUehz7D;<&*Wl#D`(H zI@Nn02rIZQ+EzB13|JS!+}4{zPQBLuw#h4Z~WtE*x5?3#3 z1NY1h`of}H3}&WX`mP_S7~9vT9p%&pdHKGWx>ns$9A`%z2KLV)XRW?n*5Y)1^8@h1 z_u(&TAI9K|yJq#x6*IWSRGl`$R2tK?zOQamn}oxp0R;mw+Av11-wqv!H<&GUp!;Hr zvMowlm!gWH6`^vyKv|U-jkGlZZTuzL%*?r<0sQzHy>)$08QZ0zOB~_Wu2na5OBzbl z^I6~~oBtbdcLWh3l`l^6=3Mmu+aV3bcF#r0gB|t~H9+)`xi)LcU`j2Kkl%qP@Za>$ z=ehh&Bf6JV-eGR=*7i4Y=3B@5j-Q7+jZpF8tib=^GwQ}Sp%4s9 z43Z*t{JuoB%8RFt|sjW>1qr|e7c&)kGK|2{%Ku} z?#O_xOZ<@mTNmvSQA{6u;iA&`!(r@^y!C^Y#H|^J)H3br0z5Rm3{3ChyG)_`q#E}PZDr+`aiX=4R^+I zFtE)L<;UNp5p8`3nyXvHPJjHxG(T+_^UlYGPSJMY`o%14#S7k{G)qO#<1GPOA{k1* zNh_?Vvx&I+I*pL}GAoayiFc+?Q;9E{IMLST#@e+t@^)Fbnh*?exNUU5aQ0yR`v*z? z!%S^bu5N1MI^|dWOm3DeASvCwhgtPq)ALz}TW8@pT{ygJFKe4Br|4tj zDz(;FdYOH=HT$r0*DAc^*IcgEAm_oNj8LsL!C+0qJ<1lH8=0GMemYXc9m2+Mh@DP% z<%;fJT=qAAFBlJtcV+%k+IhO``|irN)oBlj66C<;w&LV}Ba?N{Dyzhx--yjQ(VA#y8?5+fD@2la^Q5cWryqoEjY( z**XIufDqwtm9k-Y-%9nJu~vm8S9ielswVIL`!ZK{td^{jf~lESM#;OiRgmR0jmF82 zP87|T9tzq#ylfCnsSX-DWlL$K5=w)7 zQL$3BU$HYlc$O-X?f=q6OSiO3XQoWqQ5rGA5KQV9Vr-q70O2XBgChoFa&J*fZEvQn zmO|`9k|wsM+QVl43Ry+%TcYF+EpV*5SYRCg{IjobD-914^*GsqsLAv=4gy62!ORb1 znH-poXkYXfg*l|f4$zQ=>2<7JgI2O`MTej|RJpQp$!h`4+j{vA($?Kq30yd&wa)5j zc?Bd4xCi0((Y;er?jN=xr?V#T~gDSVLf3b2!GM+21cSx4D%xo`J2aC8+kPwrr zTLj!XXitsI4lRugf?qsTMp;}Hyg`7?7Kt#22I!f_;iWx*Yn0e?NsLqXobUB&YWr_~ z?@ns7BvpzW$V(q?l+4ViPKm`#;@gH?P65yRCzbmZ*zBQA60B60@`Ik)DA43iLC9^;6BkfDL0b2Oz$;c?O%Ax@X|gM?<-dU|A$no?ZHKvZtdo zMk!(f`oGN&3X!b6j2oiFz{%}s|LQQK^!|IGP0OK~1bt{Eg|gBJY4$>{?uk-fnvL-G z((LtI*_zVG(QTdaOiW?>;1V>9k8!05A#Jh{kTorj4APj^a0yjX1=`5$+Hob?=!Fit zm{q)V=x$uhQly@{ImBSV-3-CjOP9OjS68$vx+95aAOIyT;?~Qn1nUC_muyfrkL#re zJJVD#tWAKdljCWOkHk6wc26wzDJoAPd^oKow8X6vf7#ZiwN#Y2<9)x@q_KOd;x7mczb2uJGJTk(xjl;+$8k*} zX_ZxNwuYOn5oT*73An6LX6PsCI;~^d5X{tltMC-{${1%T36H63>zo6TpztI;YIFOd z7Nyc$UC)(tE-YMA$_K(cp(F|`2Al*(Fvh)q)2~{SAu(&Ca$b@wlNc4K04;JTRLP1E z6dq{Pr0}(G={2~grbm--)Vnf`1hk?1^uFKsvEPFeu=CH#6@ zUCsjdAA4(aW>ZkH+MGFE9XSz!V0N46&_cGI@79D(V&SsrOn|$jxpOM+#z3ui4a~6} zoh@xmjL`Cm56uCgZBIR06uN01qL407KC;bGoZdJNXa7gq+lnCw@FxDy-Zp1PrL%LQ z>)U>syKx)AZ9K4@l=F59Vt_j{u0hPO)+kL*>qt(~RAe2xJH=ix{ZR7C4&rYmhxo_S zG9*h<c1vnFc(?KIEmy6S_TRMp=w z)R=eRXur}K-~MW$XIk)U5!WMjA0opEm-o|LnW`U`Be7tZvTc{H>f3(pU;0-%)7#v0 zoC!g;kRRs1E;V>2wx}&}InfcMNSh;^r)Cy4dY`K%p55u5Bf8tW{)YUh{1+5p19Ki* zx#GMo^bswx+iJ!hdPJ)u%_-hnvb)C=sGv|Y|hGnbH>M4OHDL9l>`9oJ3O zGLO4Cr+t#>tsW}WX@Q9|Subp|}Ia5?-_k?W3GLW)-fM&O@GhEa!@_&k(9VEPW z(5~eEaZx;N{~>N!5DUD9fYgq#))NP7H07bUepD{`;@CkJee2jk)dc#?O?X|MDpgG7 z&M0%*NA8!m9y-{3=pgm;cWEW=sB#k_B=fn|VRI=?Q3d2r=c*9D zZ%MSgkevd8wo8q98DU0ihPJMFtEtGUkZ^#6MJJ=nm|+Ij!XPtdX)~nh|8P(h0U(8# zDg%<_md@2E7KU{oVC^#>cw@B}|d9nYuKz=g2n zj?e|*X=d!#G;NHwVpo8yn%5qDWE+js6dXA-P_PraH+P?>Y|6+JB0P9-lLN{8+ILEu zS0CKODxU2>-w--sNBgDp}`!NJ66DSus>Chwp=8x=#?IU0zK{@gV#jn3eVo*@su^8iA9u}E+X zPpxGK5Y2QjCJo7h7ond)!j=O=FE1B_nK6#MW zkEa6)@%p}@ydEb(yH?VspG#d3gq4AR z`Ayv2_f}fuxv=YS#WJ4G#}4P|r-|dwoG~jaHxHrJu4nw*d);t;euU}_<>&pWjGyBs zm*zj~6Mw|^7}hTJqfgDy*Ak4pFwyaB-vgHx9bi>3<_Z>rYXPYtZrTJ~pZVfG0H=Eo z0H+fVxV{(VGoX(T_d~Zc7!RxU?0)EW-ea$6&K=rD!z5+(*l`YBdgEw^A;%HayyJi~ zi0j$5vP^0348hG1?2erU8nrEkM@MXkPHap{N789yPEF*T?I8tEg=qeAK#RcaT5jbh za-2P+Ttjz==j(xXLdyY)l!ep)pQo-P?Bct^v%Y7pe-1p0U5L1BD>Q#N&;g4^DTflf z*!&M9&6^I`e)w*4vF6DGa0HR&hUIFTpE%%>eNW|$8Jcwm9Po*mt*nmF=*R)QTNHgq zOP4eTLjay0rLs5mSF07X)+#Re^I2m5q;IVm_N~*0n}2`6^)-F!fx%DRf56-KT{FtW z49&d*pPHaku9B|WgHHt_$1c*i2MG`MV77a|9&du zGtgUza%TR$0X!@~J~QDL1Z+|eNv)tCiLlR5wuf)q!EUzRNBSEhgr|DeIXV*CmCd&w z0GDzFr0qwt5cZyr>F*A2i|3FW7;pG-{jkgM3w6Bjq>5+M11XclydhQ&{zMWAkE5ke z&JNldS};ZaC_6C;-AJ@u_XcbQHDY&gwoplX!ZP^|AOEpyrTJP}r7pH0>iTwMOM+az z=Tk;T^+^XBMsPhFSC$2Wj1a8SXTx$W8T8}(No-iZKUVa~k3C?!nEKN)=<59gGQ&0d z2V~H~eo+Sf`~5#5gI>8GGHCB7sU<^X(0pm?6*6dZ_>a7=Z`hYNVc%7R&{wqsckTm1 z!BGB98T8NcyR97#`{&A_f9#9zdX?x9YT*H2eAg?femUV!_~K76z|Qpw$J-D0*8g?y zl|tw$5}sfmIO`Yl(K+nuaT(N%215Ka@W=i9yZ?d)6oWJ%9i#!yM+298eu4%r^neCj zZx5pZ?~I>81KalIZQc8GX+Sf4p8l)8071iH|K~Jdd-6&eus!i}XyAsc(7>X-^(8-| z0i2nAnuwp}e_!82yx_4OA1}CWzw5PsK?iGl26#bb&j2s@pB|AHJa}z7_(s2i=z!lk zj1Fu((E%tI|NI)M!f=axh_5Y0(l90N%}cx*Da^T=PU;VP!43%KzsV0gKPMJLH31*w zHUBv+xbD4@7F;WS7A-j7ElNQ-aC{%^-i(WifsK%l5F zG)soVXc!LTF~E=j25DJE7`EF1!&}g&cMuE|fH7h?jJ3mIR04(+Fi6)b!r)9kt@Jx8>y5}FB+*3ow1)wizs;T$B#b2pxa^^Xp!xK> z%@I9(G0tiFP(Y}W)u?OYnnQYQCEro`s8I;+aUk3vNuZUnHR~sddF`b*$SnIdrVL3m>!>D_osdBDVDg*9UU*etT{U?^!1ky?SP!of&-QJ z%gbiy+2R9P{za!Xs?9ofK~UWU%iPnq6Mtxx_n`la=Urrb&A% zvpTlCtZe?XM@^XnrJ#M~T6NVn;M8QBV99I@j^>x9^A*$?_+F^^y@jv(D)V>xKKRj4&Ozv&b*!TgtB=9Ydt@;vq{n_0=@IHB`!Zsmh;2}vqr5y@;1~K=BD6ue6)`0 zy5^>;dRm`BAK4U^U(TkR2yFLFZ&T-DY&mJlW;^TNoYa^HTQ~Y@+Li!rVahal&J)tA zHJl|pGCbBIi|iV2cgNOP>$)J`6mBPUZ|lO@IJgU>MOiVT2oavXPEBB&#y4lKlFKbt zvpZHCt8IKLk;B%vK@Jz`J*l4m-RTEp6 z`|9xvP;ZP_?+JgsUowDMRIl6*uNd_T#Co-?9KhV+F*jMcC^8Lx2Om4 zUQTgp=zV&0Ieq6+iXsXQ2L`t>J=#PM`cf35(B~Gb!5y0(O_BdyKcpa@J-P!Ceh~fs zJ_VEq9jp&5J+po1eiml-B1`(G7?)L$4Dh{=_qKk#*S9h@U6b$Gp1C^TE1P>d zoAEu*-T}U6d$Lbq1zv?m2_HnjpLhEh-`2g<>FCs^y|DxA#hZM2IauEkKkEwsFT$Wy zUoUw%j5rZOci#P)j}_VCh!x$RHSurDvpD$A;K$Oek|qv}iFi@VUQ#@*!f=Qg*?KPx zG9%Z7Vch0#DZ{=wg#RrW;J1MyR`?j-H}?$rCcJmDxWj6dd(&Rk17KJ*tXGU!;kj1) z5`IY|Kcg_a%eHkPrijQByleZqE86j%xR6UTlq2x({X~C!{CEHfy!u)l!lhqGrccun zY3TOx|5=1|n~ab?xB-m4Y>`>k!coe7H<+zib6unyLG09~wcW(7!IW;HR^I*;tmP6P zeb^!|cWSO{(~u(26>&L#g2c3{n>ca`)#Wdx5J%2tQ>?y>#72z8-py)EU^RY#QDN&X zerTbW3**3Ro=7r-X_t0`X{%l)o;{xG+AgMmXHWLkb&a}QMUKDJ!#k*l6YnLS9l^f! zP0GZ&)+xT4yy~(8P^T%Y5kTb6TwzpXzzv1U$X6!3t8?G9SGB|0>9xLxEjiGp z1t8(`JxF(OL8okPp>MfP5><7+?c+MW#Rx)^p=(~UL!}|#J zs>OOcS-mo4^L-57ffUu9txx;meTaH1#dMOE@64*ZQE1u zq_TN2d;d1Ai&g&j?_aX-3h~_5KXii;W4Vo_xx*#$r}mc^gL{>4e`y@Pziha2e|eJj z7vJte4Ac8WiicEcL-v=){db!?0eZJ}V)F^^#4$lC+Ar9W`fB;4fc8+;NEj>l%m4c*#)66#nL)o9s=dxHDh|NqdsBE z|6{p<+t_=}n4YT%c=mvd^52mgG#B=!NBC?Gw}jIf!&{Le+8h=(`wR!YcVm2ILarhy zvatR}7E#&TagH;!Kz_VN0hbTRNn1RAa6+6keP|g3m?G$&)KP zm^s~|j)l)^D!$o6=5!~8FZcM&=^QCAr`xtgv?X=PQ~aKDnx#w1P0L_Qmr`3*qad{` zv!x@yPg`Z5qoi?pP3gw_9QMYBnyQUY+NC;swocobT;fU2{<5Dd*K6Gt&uE`{T}H~v z$nYWNb-zwoz9*SfdmN4bIyU~PDP*pp5&ebkNHMrAiU{~cL!lcMiRffxKSs87pP>a` zoT4CKE;5F1(}at;shkp>#-dMzvjk~n#p7#13VMX^v}oR7d*}QVic4U2M=_V8Lr$L! zDZ%vK&R{Yu+h!RUOkC;2-!pyZNT2=ZYNKo*EqFWPnf^Ee%|h3cA_HxA({dW_~M07dbz>p{45{8F&SW61wV7D+de2` zrWhjiW4bp)Ew9c9)sOB@k4n;qqMQy}iF}T$5ATLkyka7^p6%HT*$s+q z!_hAufZE)mU6%LOOD*+U_e7CPcyCET^oJXPD137LPU*g8RQ1Kfu+6;G0*VRu@sp{j zsfcG4aY1H%%ai*;VR)aTejzpmKBF;=y)yf4l#0 zowNRxJspTGAmdO%`FHwAbL$>&+C@l_ggpaNr04hWw_I%g(;jbGy8eMZxXyQW#99vwvWG>o7j>Uc z?13DLG`AP}TeNV9)diwURD|Fp2 zqlVNrB?Y=}+iQla>bj@TAp0eL`ix=rOB$Mk0bREVbJSw>X3iL9yA(xrhUmKa(3Jl( zUAL7(UPY;PF{fQD(Jq#1!e~CUewFb*N}5ORA&wYzar5h%5qm_h=^|~jl0A*R*c?cI zcxR?-Bzs783T#mpJse4`!lBC8FD@4E zhyAnciu3&~a*;nveGKWMUUYdU+o#!x8}T(v)a6OxN%5MGJy-PeuO8xBBdMS7dj|UX z9(x*bvH1-BVarU{yyNlrGkZus?a|Wo#_N8fhpv<8VbvG?oUe=CZ;?-uzW7hp#jDs? zaEi?2G6(dWcOnmx;$7|1RQ^f4iEZo8NprXy25fU?syz$Q>UB0vNdQ*D-o?#SEvnhiU(qIpYRqTj7mn^k$15PHzuVouTyhse$MX zIhqG;D@>femQX&h=I4JUX|D3vE_}Orsow5^HGwQwZC(U36p!ojw^Sz4Vtvp<0sgiQ zYYA@Q@_4`e7K{8|U&GwE<|YsS!?#o>q}#(r8M-Bg{89Sq-RV&?ggZS3oy6Q3L&9c6 z+i6j{0%oLddRJ5G*(uX$6V+kba85gy)5FQ4mvYjiJVDC&o*{NXckCuRpyeL21LBX8$q_4pKveQ)w^hyaIp*X@^p|rZY(kksG_#Ox9~+HO6t1ixs_%Ys^@ zTOy?1PD8o0P*SAbytqZ$E~6+*%*@{drMFyg?gB1y5npMh8SE;{XNP}fjMHh{?0_;R z9^@!B;9QM^ZwaoV!AV2dHQH;W7DhV+y}6 z`YKSu$$Ya% zbLrtp3uHQ3nL05tq(y4)RKh3M)@r~R6E+1c0ch|dknYE`hIupp#h`5>0~66CglMio zJI=0+sU92d?7R~ZQa@q^W8+}---ZrHzKm3VLcVZWsHjorRA^EMuv6`5jx9Mqqi(H~Mhcm?WDNjuXwJjHicu>P2XIWrtkppNS6M1Kk^ zioz^XpWdhn-Vj49KP?rxVHP1*TeozpwMak{+z%kv1jDQ-w8E!hh$h3;U?>Y4Hy>-o zoD=064>U-l{TeH_;qcvk7U|Ngsn3;UaYMfPaooj}-XiL`YB#$g*zOYr@@$hj*aw1R zvAo#y)cK|da&I<0wZ7>QXnMHG=a3l!fp;(AKz6}MR}O+<+tu&+E%1~i^=P<{7hSxk z_ZIrv7hOK_@b7+l-r5LH|DAc8$!>MOHy|WoLbsHyyL|JxSe(yh34*Vc$x9Mc zNHVA3%7W}o>!KaCtCm~i>pT!*t|VM+^Uxjtm_)}!*W?vNmKulF~2X- z@Z;`l&>&91y6A1Sk5rEhuxk=%#a&$49zLk!Y+BdE-2Yrp_qA!_m+qMOQOPWBxs@-` z#NS`Kyop<$4c$UiZ)+FyoF$ko^44?Bf_iU!o96m~?>-B9z25F#Ra>@1q)@Bj-~Ij* zSNXmw5T7rTmtDa9O~!C>dM4AiRCk>T^y!&x{oiNza+aVHcUg%ws_rN>UND~@{f>g3B1e=D%rltdlnLPhkzRRe{H%Ona0c*TXV^EU zM+O)~41SgK64TA>9|;AvAN4iV|Kc4}zcS21ycph~iJzGQUzq6}qGe(kzO0OVLYdGd z^~7?^0k^>HpP8jg!lCnFGXvh?ha2dJ%dhQ2)%&xiUI72dxAOSm<6De_y^EhlY!CSQa{Nbq zp?x8ttFtM#^<8v^>7SDNnEuI!ADg{TRPJq(R?TA!;TA*oTn-q?zU3z|a+Q8~mtVUd zU&aOW$_Xj2q91HSh=tyXRMGxXcvtrg;o-K)2LF6q4GU@?FQ@qs7YhH|pPUfyE_{2# zE^*7{$BH@)9}f8C;0~m`oDX93-e|~HbERu%(n84wx!?;}sKo{9K#&=uUDXgm8(8D~ zqCQk!nlh0*4n#uBOsOY4)?+=B~nBCA%-fQ8n=Cz=e(LmyO8Z zeRfXungz=05z_31KxH6`YYeLXm($_w0OrIuRwiOj*Qhl<<;_C zA!p>>U%{n`DbHQ~OnFZGtoL@B)Vu`f2avvDRUlNQfs8nQRm=DTB&?lGXKBHt8C9(+W=d|5vuV6lJR*`QujE5HQJW)l=- zAhBJEEb|}PWnp=@yOBacRaGz<_6@rTn~`V=>DFBVXq2CJq2cQ=L!N$%>6%H|mM zfg}S|K;wKL_ytA?K-CPRwmyBECt2|~x~h~WP~XO}zCr&vl8uiyK&v&NT3MGd+ zVXV#!WwV@tk2AoABESbeqYo8=`~&RkqycISWPNhEQ^c_=y^k&fwMfVQxCwkGAnceR`(fjXrg`xQ+G4wUwppA{3`>iS%@c!86IqAz!cZg>m zG`LG7u9qs{NUOHUGI14qR@3@V_lw)?PX{@)NXQQNmbDs0FWO?1R!!kHf2#k;z2zl# z77Z|*pAx$58K1sD(EGjbAC=EmEDM~b%!xA8KDklRuI!E)R2W?Po~bQJVL;)rNnwB! zY&K1onulviwX3LhWmwJ*p1OiAA2kGk38@iH7w^6xRBRt}+ zB%n6{C`I111&BbGY+T8JR&U&3m+R~~1QhBN29#SRE5l7E6Rz7Ujq2Hom1-M{gb>(0 zhD`)LMrC-d*iBJ|y+uOdxuW{=!16KWiRC#-OEOj_RcCB?@JnruuKcy~J>~C| z6vUK&i$EATnL4=+;l_~+-dPYck)@d46C&8(0UmCjYIp2Dl zsfRB(?U$$xTuqsrk%-hYaK4V2t(c~4(8O$rN~wK(qkLfb(Tv-6i_33Fbb4BTS+x8x z;|fSkbH_kl(i!619U|OBm^6gN80N(ozs&f?Ak8$)$kcXP@^@Pl2SwW89~@2_eDe_6 z@GfL4@b`LWA{QA=+b91=y#ivcB7MGlA>6e^@>;0}Vsauv+4}On(FXK98 z(bog|L7kv?6I3z_J@FX3)*Y1?F)@F4`?3ICfF)v>&d=6Je1g`tBC_}r4Ss`O@m0TO zg_~H+Bw3C+v_)nNGR{RTzt*!dZk7R`G-?X<=@Ao`LAXP)4b?5u<(4hq!-KB{N+jzgyHLx*ity2orR? zF&M~~nwZZNq{xI2(zoO9>-TGO870bdHM#m+(*PYBK!>p+9sa^chqw6X(DtWcbm&UF zDjkYcST&VhV5&K|4T$ zwp4<_&mBU8uH}pdH9i_tPsEIqM39Jv^v)%|{pD^y4F-rb$p4OL(4dLY$#nVqWzb~G zz`k^#y2OYZXG@Kkh4jb>>;w0RM91Ih+o13vzdmCrU|J9@&_{RP@*mTk%`r%K{BK0M zb3HB69sg?|-9djT>LK|J(@B5ndU87HFZm~@59lu!b^!bTE#FsH2mCZ2@9_J+!dAwb z(VSaR@VHd5Rg!7zSQw|#KiZM3wI|ijmO89D-Kwa&wH>i>8a&ErlX~9gbj`7e9Z9XR zDH_chys#&^6A{e>xm0sHrpZ^ECIyz~Eel+t&Vs@~S`${IZIo@4*2o-Tjp~hIHEOth z#%g`X-T;npG~D$ z3!2gn*yMbqmR61D%w6rv_q2YPfFAUmdVQlljL|7j%VSz z#IpGE#H4s#XqhH45?lqh1jbbO+k6B9V;NDQHd`;stFe63!QxIYhI>RLz;WP4!xw4m6mj?C$ryeS=Xvg{v)OSAJN(8$Bv zk=j`MMg9(X_-h0|Ka9!4zl1zoO7d_F9w-RTqP+lm_X)?};-BOf*nci$BjGj`p2W6OvbgNadniZ=tM6nkz$|8Ff)uuG*q4j4RUS4if2Y zG#Ilw#+)Bx`6~BIvnhm@b9hbrLNQlA^Dvs&o5A}$E$TUZd*=C_hdJ9PDluPU2gc#B4Rt3E@L zm?EFV{Ao2h>4cOF0gFi{(xv9{w_>yGIw8jpq-l5y6xB8w0t(Z*w{l5zt9k6LB*7?b z{z_B(mT1c6x)29mZzMj0b9!DP_upVqeX>5j>To~a~L2<)YNmnbN#fe-M zwmK35{e)H8)d{&;kvKdd!uM|wKEquN6Gc6sNu$gLk6E;%_CLXXlVl}@qdVGcR%`LCdriLv-j78=*@&J%`M47rg0_4q?#g+4Z z^2QjRWYYoio=4>U4UzW@;=wp$VmfqIqjXhR`j{L12Qu1QeXP^=J>}VE=Q~mT`yFmn z7Py}4tiIcv?P9W}UT4ik_;{mm2%$Du==FGuH7UMI#VrX#;(9_r1dRvl)KVY0mJ{u> zTwl|uye~5Z9TCUo`t&AtXK3bJuf#Up9~(I{+yz%5Yry>1)vn_8?sGeO&+QQIr8U&F zG&Wy5b`Dg*5nCopQ~`5#bRguED&RFfpx~+BZF%+1{3b5Hgm?Y@>DIr#nco~Cd( zo1X#9=4b8 zC3{J&`2lp5agxqBWKfEM_L5)IpA)9&xqVe0M0l(6mYY|(a@V^z@95pUL(mgOoF!w; zS_mQ%(o;uKTfd&urP&0eUd1LO88I(*@9-I9JpHCu0haO;c0jN17_b9s-%$lSpjUTX zVF%QP8dC3f$*=?R-j{qeJ0M;;o$P>IfzyZC0ga~I|{V za2-k}y9^3j{C^Ph0-L#z|0c|->`i2}(EN)x`N7y1{7%Hmro=GVy&>Ca)$D|!!RNl; z0&H_cqW_^k&+=xGwC!1ljzQ|YGlX4~nLV7%w@2={siZTRjQ8Otc*~oux4h|7ColOf zW~BSJH((UNE(%vkRQ_+RcI9UrJ6I?vDt@-P=#nH>V?zAr+XI~s+ zUwoZCYgK;NgSGSWkwj&2tUYV3eKBu;7HLxwYM-V4{BF7k`~!tTO~N>cS#|V$N3tgn zxl6`xiC}k$*OS>^>;3$aSp6e~0^`mLLl+nv?@`~ivOpY@?a#D`ffv2%P2|Er(dmRN zuJb_>yh!<1k|A!(*T$qr=$6SW5Q^rE32BkryO@6eCAuQxI*0pTw5yGn4YQ+gYRBPs zYqGO5+4Y`6|JBY6#1*vd$5p~L2O&rBE!a+6gcNWyObGrwaQ5yzgt#waU!0wX#Ar6C ztNC~*Y`Ro%b9qo$@m0yolRLZz0;CeUKMX#Aanz_&Jyi2pYk!_hMW90W+qN>?u2y^41;Ju zti8&XkqiqR{xU^|Vb6q3JCVx-1yw>Fp^<*0@co889RN7uo?oS!NV;<%%8^oIFvewQ zp`|s(*WH6O2670PkQtZl6pw&-FdzRSr^owm;4qAw9`C&&8dif=hUWC}-snFA|8!0d z1T-4R>ET*>0dfC+MotfYQSx9;5A%?m9=wI^Xr;*{M-g*+*s6S+_Cq%jzBCf#8t_cz zOyO@J*8pU_kXhvgQhtdLbEp!_IB&8!4{0)1oK7`m?iABxn42VKlb9!CoN3C=+zPYg zt~XYgLs2t?a$3Y=P7#f~b?Q#1JC*+4@Ex*$`Ya)^VnZJI&Tm0*>4k03L64$FwzZV)6~!2mg<@Zx4vFO8=i5BjIS=B{bt5cx6Ct zGpK-RF(VRW?#ko^Ez21l#L5b)P0L!|u@J&`1-n3+%?yqrX*%N#Zv}{CYMZ##w!5}# zZd2Bp7eej2{65cl-xQB(`KfCHCk+ja2m0(*?>4m2q?KY>ErWi&4t+8#i~*!hnbd&0JnuuI%iBFYb# zllSj~|Ee^eeho;hV3FgCB3!n`$%uCdw)N@wKq_~>$tD>lir&9KAK2~@1;Gw ztWzt&prt25DB zZmTm5Sj$t07XG!|y$ZByvhb@+1J?3*`ljDn?z{8Z| z8BOhmc9`8N+>lGY(+9rwXl|{y%Xozl>3q|{@g5QtjvBPgfnxBuoGuzJ^HuyW#rkji*Cyd(I41X zP;}dN3L#vyZ{KVdLU_+U__=_Js0r>p!)F_F$F&v|ZH_c<-&b5TYlp%OF5!Z(!_IA{ zdkbGjdY>N{+(;LcjURKzN@y@Cg}aEt%X&VmRa*yjyes#+SkG=ZX0Embaw zq)@m^BbFpoAuR4v=@%5spV^MNGzR z20L|LqFkz4q%4hG60tOLQABCNlK7rE;=vo);Z=yEHe8~|Fh-AW?4Sq2`xfMc)9zV3zjvSR)oSJ1 z_7)lgf~-(qK>L4Vp*-7|g%i)2GE;j&A!pWx&&KU;#H?W%k3;4}eG8(pBKzvA@z)nJ zr)@CRm&u%^VODEIfj(PaxVqqxY*qHx8cnghC5VYChy@$u7`q(C9>wD)>{=KUS}M=3 z%orQ0iPi*V%v?P!1HtNS-tVKw2S+P|n>crOuT>j?JLUO?&X#wTa15XVr_T*J_LpK+ z@r-^6qsGRgr0uK>eCoOrS8gqGSLb2D-GaWr(P81&m4mTQjKF=2*&1PhdthFk6&V3D zk{aB%Dr~*!BJn|QX|wvTbdxsRfXOS;XdJrFkRY=r$mc4vqB9GW2FHRWswI!3xIwaG z$%8YGO*+;{^9*Lj&+03`=|x@cJgjfwSbe69#qm_O+;mW<-q@@Eey?~CKLk@iNcM8N z+tFfwkGai>{SR|DKctP@=i&B=IvuZkqxrEdc8_EZI&Zg6?__w{KwMdSh z!z83HLB;|#OzS|#gm5v@{Y83Hhqx#5oke=GcbuVyyy8|C{&g=+!SZjL1bdrK zteN|CIy^CYKG@4VQ`>(0MtFa(W|2Sp3CbRJ!XZ1%!RF2R9?SpZH!ui=-Z3@I{kQa) z}1#6r3uBx(YZqRC@60_l$ za#b7)-^lh1uDyuu1)vok*+asZOB?nw*2@#=O{bwweu`KN3?YBiD>AqkN0Xr_r%v(LlOV$-Sn!{_1!PW$g8^{ zW}8%1N?imw@`=$X z#`;^Cfc3YSt-l%1wi*}h%`LUh%a4mzD}+UR7mCa8=jo^N!|uyFwLXk_2X@a-pUReB ztl6h3!|tm(RSNLjGu7Ozi0Bb(G2`LfOlaJVdBAY+22;lk`Tq5{@apyVE%cJRKHau1 z9X5%WEqw>l;lEp4e9@l3%INE^M-VJb4_T3M=(Pt&9(x6{#)f+J55)MiUGrT)z{VT$ z;j0r=&ogPmAP8k4dY_eM6nIrI8usR5<;~05vB`bDxATmo_wo&d8*xs?nrL;%trmGU z)V-$N*;+Pg@53|fWr=&IiN3T)?X~?qeYSkz*nW0EGvG@*v(++u@4QTV*4XRxBlgbP zuA#k4QS4rbt3@$;4X{tOsH|G~B1>rzuJzY`V2Ri}YnKANa>|Bq&lRUO(1nQYSLrw= zL>64&k5~(58RItycM<`!jZ2D1U!#w%QwPwsyyJ%Kq5=yHS4XTppoFfAFj;zE{`L)! zdk|b9`c4@H7F$#LB07W`3o{9T@iXSk6kTc4dNg9K=lmXo*BVE8n;w;n!0MM*t*$eM zt!jDHYYbnt?J=)0VpaWPb;d}$F{+~e(K=&v#VY(9R#EjBbTwd#WiNsPhus)dQShkA zsIJI;%w!zIUIy3CuX?1;c-<;`5weP23}&b_cHhrH#q(0xYhi|=hpF@_je9^+X zJw*R`%dG7~AYq}$eJ#}Q|7iJ1zdvUATMPQ#?WKO7Q*wMm7@wXlU4M}Wb#(ZjcJ=FU zCdbk^>Y`N0u_grU{I<8eLnrk&$+^_u??~^<-=_C{{axMH-^a&>%~g&sS;US;e|@)+&zxygWzsc{ zv~p2%Rkz{7HCa}mB2-75!tI0%hau4ks-`k(5>{EX2}P@9o4+)nWHR22Maf4z!madi zRiyo}u9_Otjv%QxBmjQfd%aU8s}VD900ko1R4(ZIkpQEJNFYiSn;8X8{UPqun{h5J z({FP_bvSbWmxR)ij<^e$i@8WqzL}R^aN3}~J}Z(lIOGNsE0+1?4SaErv|`V|ijA>3 zRe_;ORH^+}jhXD~aqj$fi|6eY@!Z)a`$qG8+n!yJ)0k}Z9Ab-*9TSr$W~U3gTM)z> zAOWX(pH}zS7lcym?gioY1racu%bPYqXI~JdX|gYf7KGh}!3>FeSrBwU#Pj7O6wS{_ zhaM^9D(Nx+2M4 zg>A(U?WP9JdRb99Q)URtEy+~mn`NbK3&T}b#k7+f5Oc?xAhRdP7sgyaDqc~UA*;gv zb771^ohGwt!x5dZqNG)Lx&>N*c5U>+7DcB?R%3ydntD-~)fg;H+F5JbS#4=3s}>q+ zHPKe1uCxOoP&DxdqmF3^;#FrPhDv9QW`4gkrkaQ>xaot;c|jAax$eMq6?>NtER$`S}_eE)POXAjJlHSP_avS;-?Ae>OnGDzXD!BgmEYuOR}+TJZ&c`Kg05m zZBgY${P)WM69x=a+mrSAkRRowe^0#P8z+g6scC(Q?y+K`Eroc|D4T#F5OFmw;lZ64 zL98Ef4zFtooWu8>Z6~z+`*ZJ+Kl3i;XWoVX%sb7`yi@ZN0vkynBa0B50FcB~dF@z(A;UE|% z3}ZM5vi963wdeCb9H)d}!U13+U={`ujzJ6u!8k$9a1bOMKAVVx|A!xked`@Wlgqn^ zCYN^s4}e*?vm@L9)&2o6hQZ;r@8UV0!OYrYcpgL0KNs+`2h0U;;{8pv1mkND z046Y)Si2w3=?valyKfif0swI?;Ai;jKy$$xyJ&0>R0&%Eg8(r$Hsdjr!8qY{JaY`D z+KT$}whFJ|g^WR!u#V+jM|odmc?re|YXLb1Q{7Yh@}3e_;f0JrmGFB!2LU4Q@9+pk zS-?2qc|eZAR9|6V-m`*$7cvG_LIs|K0Fn2%cnoDQPI#K-C784XQsETj>hG=_&@oRA90G05tpm+JF|9}mq3BdJXQ zCIV*RM#6C;!$B}k7{PE5WaB4Q#Nlr4!_g`XAshfE0%k!&I5Z3g!8lCrN70 zhCUn@gdcVS2Y`uyS@?b@aD2a$XhJYf_zsX`kZ|}CL>#=U564;IpM(RzM8GV3NjSb_ zI0(iGUoad589m}8dc5k#;d0C)Sz{t(5d60dQ1 zfI0TS7KzU79Q%QfPwT^N`{N+i_Wh!~RU_iIEl(9(RCjYsF!2kPx`VHX!y@-AT_EKI8DD`JpT55W(){`>yeqfA{>q3nqSN@ae)=nOBytpi9ME4;;(nv@pEBcu?a*Jz z|3YShWwdd+scm|D8|OK{!_`(y7|O5UFu?e5J1#H&i8P9g=PtVftrX+R_O_cmpYL#W zOdwi6d_|LOH#W7mjr9BjX_S|L=!&MmZtQ4pyV3J^q)}eq;wzeByYUoVru=mWEV4*xtq#5*6Xc}9BZ})m!JJ1r>pscw5P)a{x!J20(iXjAsf;N96I_V4+uWi4+ zVid$O)bz>F0zH+n3}s|WWpuDINOimOQR+6dq}fe)re4uZHx)$_jp`@}+m8Lh%i91Q#JGTKng!~w-Dqhgx;#rSsjdgh{-7Jo6m{41ITrlLqH zCb_^V72~Iu>59=T7861rL>ClMAyZMvZ37BfMTH#q7vlEzdM2ZgQ~pBSxmPrgnu@{+ z$EbqZfoNI!cMelp>vhU_VM6t0~OwJfoD0gy&ojCq_Q^!Z*c`Cmkw-UNhGZh+ci8nDq z(U~gK$LtVqZn>XmUo9c+s{SZN#>l4dqms{0TTGg^ zeMCNrka3r{Hz}f@V6*SAw8{vXfpyLJF=*Ir5jgApv?;=LP;*=CIdFh4GA$otP`s`k zoN8dzA3v6TI>c~LQtT?ug%*J*om_`mmCeVVd@t@BclX^ln!LEV+75yf7jR2ce%Me z^XBzb1ywD9u}=_Q+7)Rhv+atMm1mGx&ytSr#m?)y-jCJHUthSp+bK^=Ydr=|Ttblac5gr(t5+yE3-8t^0ByTeCdot zB!}ak6nE;hz4;-aH;jB&eV-Eep1%CCSIi~WOOR=XEe$KBD`e)#PPpKK)Az?)f}CG^ zZQrNPmOnmL^%S#&DqAw>i5YOIP|`YYKDTgeu;nw)tX+dKQ5Jpbp*zIWo^mbD`*9{kzaCosp_i7dGT4htXja|`GI`I_g zm*(QZy;qc96Ja@@g$uPpd4aN6p8Htq!;M_A78mm~$dTa2oq5ss$@RbYY(%ufzoe2v z+pN@ye!3~^pqEi<0knF__qCo*%il{d-|4O>DuHgk)L zA3>9c+&w&ZVhH#h|L2B4#{ieY8zQD?9)Oy7kT8EMh5;djjpnz#8=@jvx>}ekrI!qz zfppY49Cfv|-j`Puaa+vLQdM^3qM#gqO(^R9Qu3c*lr2`k8yMP6V##;}$@L_ubE3ia zqmGWN9#@@TJyY8mQ(aR1o9arA{kY4oW%KLye!_?3{H?L|G= zUClpx)k9?ZNuHdSq>S5@Bv_ZQyDs1Q)TVLliC>?}>?-(JX+3-;MVb>_2ZPD?v7R5y z)-87k$b(Y&kle)~h*JUc(+!c-{vdcQyOFhjMor1!JZgWqspFeM7B(Gw5rYMnnR1bX zphnVUb$@qjR_u@iAz&gXYJgS1A1oHDfDct>OOOynoRSk><0{n7jwJ`7p?0UL+Kkp( ztIMnH)pZDQ+~CqwH+ZYtt4~y))-*w{<^gXTjy2vu2eoJq8=xUgD;j8A7caAG74Sz> z7oo6+t5@8RMweCK!1sDL-%g)m4m;kv_7yZmiR+**83y(R-_y(L78~#tijgm^@2jLa z3;TS(uje0brS+Zv;Z|DT-QV9DSlCA_w};=7`m^i% zoo@uz_o6pIGX1aLxN3c$f-e-jBbCtB1zi{yHZQZH74DLM zOo84p7KuRgNq9`wC~M5K6$mF~3NEzflpCwI6s_3uOXHCCZb8>$efV$u5UNc{z|Xs;EA)VQTKcgxkGC-X@s`5NqTbV>r%Ae4s1Jf<5;=TeFL@yrg?>xqIs((mm(qY}G=~j)PN6*&>V1s0&X5 zAIytyEiGF6q29c8A>z@_8^8y?ll}Riv@Cg4sY;_P2nsO5A}~UsJ*N)33?^NbWr?6G z`?qmKo~CZ91W)~kJn)O+k1izSef;&`^ME^5;sFEn(3$d{J0&$6HorNDAyQCufi%%u zL+t*s@H%zQ*^YhWK@$bT<-gE zA`|qFNi3J^@7QLrMCqphMjD)0j^QRF+wf}eiiqo~bem%+;N}k$e-wguwfuD39Ta|a znzwB_*09h^a267INk5TcAQ%aqygmdWRk~3z@xTD{VTM6o9R>_j5cHEUKw7YAfWmkE zC4D+Wk#AwaXSPjI4)tnLIs6t>Bu?(%YFf1{CEVe7a;`^>g|0%;l z@TXY5xeCkm)nyFJ2tO8IH?`+-kNyLO<$Z=FvtQRzdzK7n&!B37;fV9&aG#_0bob~x z8ID6gsR`jI7=S}nUCVGJ_;J|2q}F`Zqun`a2k&DGX14UIU&H*;nI zJ-@kE`0kA)+;)q%fu3*0^T5NS18eKmGFJT-lCfUgBFR{1->`j~^7ArQ&6a?SwPH&^ z#(H*(C}S<(@-s5llUpET`Myl)FJtiw`>(CVWy!or^%EH@{q>@>*MF&uwc+&vYvBKC ztoPp!#Qp!VzV3#DF0%Uy$XM=ODZfC*N=6V3O>oVc261W4!O|+6X|Dae`Dz&p62`kQ zN$9_}I%V*7D3DcOpKxUv#uy+dUo1FZe#)jYl8GQ`s~44(md%;TBE6{& zM>5}#bWZMTzoc7`{V2tG(>;^@LejH2FKSgf?c{#))~~J=?_En>M!5fTc}v@#Q6*&8 zQq;CzKc!e4Og@e4gKLUsH4Gi0K?F}>@Kv~fDL+(x*_+K|?g4SzY%ejM_!jS0&S$=m zY%d0QBD?4U`HN}Qh9!?m;xWSM)Wo(prJ(FsM^ljbkUu_UT}pA86!pzkn}ToLMFedR zr-(ULjE3D6iwPLLsH~3<%3j<|LiXy-Ovny)ez}FRwtx?qoZX3@v9^t1f_81G5D>Jh zHj|)TOM><@o3m?0$4M4p^CAig$o+P65mow{A+& z)n0IgB{xeBxkjaI@}M|eS9_{F_mv*ylEPMYY*7g(o6(QW9VGFKv4feN7q7d70EM^e zYw`=#fxv*|1jP5+rKFOa-*9ow`ncw#%AA+rpEF(3&=m6 zLM2Gfvuv|c5En2qH2-Jf0zOCe`tSHlhO48Ds-KGs_y{4-59}9zDSu(VnElhZfZzd| zb6~sYl%o9E9zy#{SzJKGW*ilEALFltQi0@o_%AG3DC#FafX`MgV2cX>0I?IF!r&B< zsd(#XPz-C#HRhMz*J*+I@pZPq1Ush_U-7H|hxw)Zb!m+W=FYt?hMMHgzRp${nf}Yy zuQ|QIKi5BAm!_9zu9{wCm|lpd{Bw(KQBVt;Tc80B6I)`JWtX^@HtAp7G$4*--&U?@ z;M9Wgk3&ph@PFC&82@75OS8+=fwK$67!4A7HvQLq-+ykoO*BWCcJh9oF7|C6Y8B%*PbK0vby0e?ITY zR%QHJU-N8rkQmlb3J&VN_fn3n@G;@0O|>^|s)k!nSZYny^{Lp~ZcUMb8dhQw=u^j^ zC^pGGW;FA{3u8(mz-E8Nh}S=nX$J3+??F^T1vA;@Yr3=L#bHyGSuav>8heDSE<$cE zimNN)5GOLni0Q1zV2oL{s;8o&r=cyUtJ<04A=8I!(SCbe&GKqxixDvyi%@woWbxg3 zsmfXJsU}CwZS`H%aMWy|4MGd zH#bx5Gg7FH;s?Q$ke)*4>vHjYJxyvcKbrROOhL;YS`eEZ0>za|UWQ2M2qLS@=|X*t z$C`?cHkmBpW<>qAM4By8WcOAKujZATrEm)mTqcva;_UDO?QIcRHIoXJEp2F7m((&o z|B{IdM>OKX`LiD}agk;&s&M)2)h2l{neoF`4y|LhK^aohY>)Q(Bky#U%ZN7dDU}b) z;C3H|>y>xHQy4rWTa_xSae=k%VXGy!CH`=xW(WqiivLA9kSxmtkdfwDzP%sLU)Nx@>oane+=&AHsqZ#zFMrAcmR z3l{s0k4`ag@U1Jxsh7xDs$Ce7T{C}CfQ;hpiCcfk&V|=;5yT^QF0zh`A}+DYi>=qD zqwikWt7t$9$v*k|6o0sS8sWB~DTYIF>Sl5eX*(R6AL9>~z=pp@^qs#BKjdW_udvpkk%wrG+mlI=g~qjms^WtjOG0PCA@#0baH$~!3MZQB(;#R0#3e5?mbv7c0`Ze{4aQcy`nq>&rbknz! z2ot`pS9cy%jxZppGD9)Kg#YI@TQk*Y3KIP6zXz)6HDXR7Jf71R@RG%;r%+LjAXpI@pXKH zyp%HLGP1k456#@0SufrO`$QM_CQ=YcqjDvaxz3Nye=ewbt zt-yjmLTY&v&i*ZLp%#Y8{wj#_#OI@3TuYtnRBVIc9f|SXpN)1k>JW8gyuotB%YE3+ z{fV`^Q5V`wjq)ij>EA-bk50>Moe-6UmxZUHNbx-II`WEPhY~L9A8u}q>lbWr zzN%AAMu105z%RxVQ45UW|313&X&7%h=d&5|s|#!Q>hEp#hi-G;!%{C@aL!}}#bxMc zH5bEWhQabBCi%*0nZZ>tUT?6RshMxJe9;tSNS0+EMAt12$qun|U)U{i z>xYfdumCs?dzZq|R3V(*2&_bGJHAM^TbimcnQdIEg!_|-@=3vPQ`eRBIwB27wpwy{F_L9BqoVgcSIH1WR7Cf=G& z!X`qUPmEV?@h(X&x9lL?+XJ}Ux$R!g3m%aMhyr8OYjqutEirWZ`R`5e3uvLNe#uGunJeW#zt{6|Kl$iue)PtKlFK zL~tF>27hQqcI|H#>6tCo$NjHMsp#)%Xj_iK@u7JI z&OdK0z&w7W4nw3t`;L^ywgq`UG+^8Z<}!XW&1FSF+;kdbT7?V{ZUGVObsF^=zXyYe zo6?MW;fgFr>zqQVwzdn-73}rdbbVg4PHBplC%&NQyhp|Sp~QnIQ?!XMT*Te__#n2v zcdw*uOOT=-7sRb;(kZlc3a#Y~x4J1?p@Z$}<8_MsjWRQ;HlxO7XspQ4S5zs?T}rzx z+8(a3&q%BBivAauUG%ocu&K*+n1!(U?L|xn2JSB_|5%6I4KQQ`1Nz~0nb2gcSWIyo0Y>rC9=$sXsXy*R5(&xu0W)8x9`IqR)aZm_UL$4@ErhXbW zpk2zE<#t;fJZRWB`{XKg560Uo4esil_Q@H-6B}xmZm`=z>uh0mTX>y~OoAs*I~|qQ zHK7^9Hfa-+E8S0BV&Q$5MEKz)2&w+?KC4F;J-N}&eKM;)*g1-b>pnmuCaqZ%4*2&k zfjXfxDqM$Hvmd$WiEBRg7qKCBG$cmh8PQyHzaK>uL$L=ar2NM(70vg*9LiqqCj8OO zqL=1BTmqengP>$`c>@Mt9ruY)yb+#DHns#i74*4p$LM!Ma7ZGdJ9>$c+w~6B#&+Mu z%^|1?6qP4Wvqz=bEhj)F%OC5w59@AF)LD+(EnS$}@C9y1@Co!VKs|BtdGniMvL}zq zC2`-&9ckx|GEq<5$Jrb`c3S(6d%3PnwmpxzxD#IPkDH#|^P)X?8eIApN5XsT?8tl- z$<0yiVPjnJmdQf6=ZI#h@D|L~3ue-!Yl>wWjC)XAiJPP(CjI*sB@@jz%C(p=%)`B|aniVUe|#DBSfGN` z!r!2qlznFgq$0Rl$uO;+s4}{m5cMa2E>2vvqoNe*yHuW6n?!4m7K9*I&mt3fpG!m< z>vfIsPZl2mY2~j~GGL9$;I?mUQs_c-S_BS&MC3I`Vp@sar(s;M{v!J!%0@R*TyvvJ z6Fv$O-K{EZL@hP2qV4w1%2Y`H28?QUS&@RU%3P&VjN%)RDHU#3O^Vh zFtuRtLFaqK2P&2DE7}`~v`s>iS_VI!#1zBn{SVm8ftH6tsqhF!_2)79bzWA2Ry!4) zJxUN(Y~ak$8}X(8_Mn6i8}6&)%J!ugK`=! zwsny>u1e}Zb`;{+vAxnacBHk%T}bQ1+ico;`FhZ*gIZ7r^0PJW2-W4N0d2^3*Jiuz zmg9AnF3bXTmOo<8W15CF^1CL?f@e0m9GkUW@@5w?V#+1%)(r;kfyC9p&M%s%haRLU z>3oyRxtUE4zGtwInw=dKa?W5W6Bp8GT1c(Vj?j_{S7)%W&W(N`xyg(Vu#`SROKEvv zDb=j&I@#3j2=%;!qF=Cg$suc@B%X)pRNl^&9J%ELS5}828fuafZ&Yi!M=CL-HQia> z(Jto;nBcK~*b<|HwA@0How~COqU_{+L9F3#Q9~v>*<8*JNgP?qYJF0y_2b2g8bj8L z5n`=R5=#qZROe1Ps{DAd)@)fyRbE0>Hdx`uBAENuZQzy=Rn`+#__;)tO`yt2sengN zfZf?clAXcwlt`BAseqa$VNM@Co+5humXJT)M~|nw>ZvE5-B{NpcfGR=_-&D(#nbKF zJfg)@F5pG087+PrpoQ7F49(%q+`WtzmVvZbCW>?q`Dt-4kcd@vmm{Jq08D_26w;`^Ox82wJ*e?4Rrr}eh1{PNF@NPFDsL?;B~D1HReY<6^YmG-dooi7$?B2xrpC-)6V zyWim4+$THPVn%ZXP5Nc)wU*<7kvMVSNEDB9#{8mz2sV<|Flik6EHn<;99NSd%6NPj zjYFS8qt#mOX?HF;(&o{LGdzC;lX2zWFc64kf+xBxbLn9a9`0^;K76Fj>wI*tM;hR# zf5f+cRJi{QSI#+T)}$%ZBm=GmJc&WXt|FyO7Vr* zaBSlAQ{C9dU4A6kd1WJ&Y8b6q=214G*BEEm3NC%6=#mlNeZIj8zUw6W8{gTUc5arJ zo9X||HyM39)9$2wsWk8KiqYISjE18b+SbeE!Ol|*eF~xfYNJ1&%>)MiuZ1wLlFT1Q7j@o;?&P*@xJopCeFNp{JrB`*2y)nE4K25Z zX|>ViZKdUw80?wtP%*Tu2j?`mBciavg=0BXzxr$Lt@Y*H4T;z@-$yle52b0Ye53dt z6EyZv2n>oH^GI>W9EKTg5KOC@odQSbFoD|f4twP|Xdzi{TB<-T!^=&0;+vm)6%{WuQ8Xur=e_##m!nrc`u-zIT<4rO#`73q*MfDVUI&qICn z%pf`#sGj-&y}7BNf}W`G4e=n~oeV0-K!sKjdoZKIM%Tw)Ej2 zM|8+0{8<6~7@?=b)4IOgfbsn&w|FScZvr8*a0U%JjB^2xf6dAk*R03A@rdnS)s`5e z(tRRPAxxwKJF``xPL)c8@k3s5_4-#2_0u8kgS1PUSKe3}5hr&AOGtQmnF%kbqU*3uF!}3Pjux|ojJB;*D!x8hTrVbEYrWt3^)C^;=}v_E0+vg(Kj8oIBi4E?cSgcr zmpo+#&YL=4pncmHvV!H!qW>Gz+b)r`xEr9Bh> zTr${((#5khoENinca=X~JWs31k-lHyPv2m|nHnpd?Y*A$r#s9p?29Sh^LWDlOme=4 z;TO-h{N=js{_mwTE~#8z@TZGsTU2f^)&IB2?B?P_6Apm_ZMHovwwZqnZ9XFI3sEq; z09huH;}N`CE=j0j8+=dr%edDLnUc=PS(|N3{pnBDL9WEcNBaJ8fBNGl$d-XN+kWFu zx0xYdvNjVfe2@Cm%dm|h$ymbwh(G;)JLJqjxxO-gy4dEx8N2Oasd)?0hM6YFO{{D! z(c{77d^Mx%G&AI#K-v7_04^)-ulvqR@0aLd1+D_SGfpgjFx7WaawYMAtlv$MyHun% z%{ds|tV)bu7W(l|Fkvob^&uQ?n;%CTyRV{eZ43P0jx%Ed57g0T^`~c8F_!!2WAUep zall!5^ZaSzp!WBb`yPL~IIyX&u0xsp++>j~BTX3F7#fu5yGu$GV~!JE_Z)xzfo#Li z7RluMbrSAN4BT4=x>ad4SU#%U^)1txrq~L1H&Sec0=M5B=GbX&RwKn$nB=C|3cl%d zQ3m~LzwM5%eFE9;*xDzM?T)W)Lcn$>h9nTI0*8O7W(G~@1+pn}z@|v<)ylRZfUuog zu_yLab=Y(Jss?A(2m5VzWXyO6&b%c(L&O0{a3csefaO^Q2R<#ffs; z9!>ta*a~G%DYn9cPVkI=vD2z0J2x}j!W-v3)=IGzO7V%pwlEpCIJQrdu4QX$^G+aJ z9G_>x09%|7Xyx(S;_%x*t0s$k>x2QeILGLlezrLLEy@3fwzi<@d+R~dch~oet?)7S zru}0p{Mj`iwt{eIJ*>-wWssufQ(U|Wc5l8=GO$9w;@k$dpI*lZdeBpU7~!*0JP;fu z6Lz62&G0F7cEV4A+MwN}$uWV)%-A2;O)Z6*>E1!c`ULD?oX5vQmN6}_Ycy+yK=ffu_`}6pD)xC!CD!+#Ds{55bUR_MI z|6k!%;c2uKfZ7#IR}wI=J^7Jy>3TlnJZzjETkjuO&d1ifv(GypU2mJ81{H{e3pY|9+<0{##H{xXqdti2>x0Pk zwz%`oi1qvw>Vu){{dQ(X_*o(=em;mX5qMG10H-t`30W@EY!+>E2d(dWR;}0h(~((_ z({S;-vt4fz8z-3g0_1PGg0? z5~fYzKQ!NcJFO^Zl2Sv2KQvZ{v^#F_bTzt;sDy_c_@eE-M$dbV;`s9Ru8)Sa`^}Vs(&nNu%u_y~nz_2b_tkL!6H`a#hZx5dLqp zh+=IN${H~lxe$NR@o03j!t+3*t1Z+vb%I@?i#{yFvv2AImqS*0Jx+>YHmswRJ0?`p z0fItzwu+?*c1N{*O81$%j}`6Vp)FeH+(u#U0J6n79wm!k8ZX7vchnn2^o4zEZ*_Q+ zR@)w$hyVe{#eRej2H{uqnY}&KZdG{WWulT^jmv$u+hh)%T}KOr&Thiz)Ky|YC_(>AyuL{J|;Njan!Uf$v|vE{n`-kvo#(CJrV9 z$WJ_&O7&q-;odLN7~jLrUaSK~4J~u4@c$rHi}K+;_?g~YaOK9hdio{d#zwnEDU4{W z=3sptR;6ts%duEkj;%c-T8`mUW;r(HlaJ*!ZAU$DnYHC2>?^iil<-R9($**w27wa^ zXh9Kcyg1^%?d{_mkF*MUMZR%L_KR2BSlsF)71#+*rUDz}yzJutk@WL676zxJ{~E%j zIRiEpDNfPGBH8(GZ7dGywQ$g(aKE3_-^Ri>EeUq4onSI$P8pe4Kwa`)eT}J50VnL? zCR1>Zb#}S&!*xX;th-9KJAl=tHUIT|!%Ku}%`uty_`#P48=l@rF&jW2) zd1um9b{4!N=@-~pq*f^_pj8e#3y2QG>!t)(-=FiO@cg>kmFupyw4l2P(U8&i_daV@ zaUM~gE$}*4?)6qJFVPLoo@B7bW7ka^ijo+Ff#k`Z0K3YNoKL{Z0?U`%5l2_vh}`jg zFUATRQOsI3Ky%d!%tp-(GODmay@C79LRi_>S@ZXJoZQ z!B!Un4smSjak)bIedqh?BT@o%z$mvD$wEDG_`<#aeDo7CZy3qWKTzcv{$qi%eCN*D zwV@Tw6)tNi=)DS*WVF<_N1{0Q&`Uy?i{_P3K!ql|Z(@Nsy!IP$VxdXss+q;T5HkyZ zKV}xx$3L%p{LN8qlIWnEPx{u}5h^M!@VE3@J7De7?yz{SIH5^EB0&A+@{Tbqo~Z5F z?}dLl;TiEuXZ08Yc?6~tcpozz`IZ;{^k|&o7sN^SAu(a znHZyV;rGOfOi#x5tIO<}jym|bUV3g~YQWch%hK7AljCM_4xI0w@2ZqD3)CqM4SG$; zCZllFSsk{CYbe_!R2OZs5X+A&DB3uEaI?KCIJcm})X`g@YMF$?JUGKfYydSPDa3Hu zWrOBxs)Xn8&G_uWnTB}8)oFG?(_s^Q@gRKQw>YuN$IKP*%i~+87=HgeK)(q;8#PjSgrC($`c3%7Fuh`VsC%G37MaUIUI+QFbW=mR?s2x= z;n zOmS&5i93I|8EBw5V!}-lo8!C=ImVk!2o-Jyd1b0FCz?YRURH`bPK>2cavX6m}V+H9MbNfR?O@szl;k;w|QMiOFHF*kFezCa%Itb_dbPw6+tQqgPw<5=>K z*>8OAoBitkzL&_v8jaVhXcBvp>WKwIQ>vH13G%~0IKPzYx=WeQJPtf-8Y zS=&oRt;dN!kjSbPi*~zuDu?4pK0&*34i0eG=?%-(`2Aw?d9_%}m&EZe2lrsk($9AwrZs>T z#9TQCVQC^Tv%A_$ZEL>Q-{Z(>2QXNN7}^o5dW>b!lO7f7SKYw1hraa_T810JO)~YbA@PFEo^d9`6KZM6H@b6#yY9(a0)pp<9u z5Qzuh@^j)(^dNuXJIGY-KV1?=ud7yQwaU%*WstRkZG)1o<-b^s)T{j$g9cYXgL(Mh z?H{XUnKio-23Kp~CJ?9o`}3B1_3P7M{kgqB}pHWymvHUllW) z31p}o!M^{DB~0~yKTP_5(`(}QC;Pq+W#9jmCH%_&{a~p=A4=aJ@B1Dbd#cd8ETvG& zU@+w(1gf+1HAkb%;rc+B{3_;-xgiDJ!}DNWx`Tcl2!T55K6=`8A~&=B@afir?V5cq zE&SY_L*B#P!;iK0OAhxfx>j<8t@P?-uLDOUk-n01`Zw0*oQ6X9B#31S@VCUzh+$n4 z8FE00iFNV0$`EW15}=%gkjxL9qlh#3J%)ZK`|dx7OF;Uar^fH`zWdHG{@kK4n2cUU zF8?VycA)GV=hK{Dd%IUI15<-lu#FCrZQGH6kwX#4MBWa_*)*dHj}QSqi;cn#1dFlo zTiS|!c)M(>661cVA`m~NA~W8uFh$o#*%ii_X9jIs9+pwnda5dHeD?9iF#RL1WsLWZ zAD?jx=h&)uG`0$N5!UPz2b988dYT&Ix&JlTM%+$64yc3~QsP2N%%dNUIdPufy^7Nt z!AL*f4MOjz+?RR{is{0i)=~;Zfl}~id-KPFAi7x}u9mkeW_lem64}4(ZCA|ls9(hg zU-rTR2ET~pJR;pHW_n~ucc1Ca(9df*y)D!3x$U)W+r4|YZ+nx=xK-b-n4Yn(mE@4m zdMif)b&SqvdM$TSg+V(s7a!-+$J@47YVOZ<2<`|Xhoh~FuYdr zjJM*Mii+QHxxd!u7PNjiA2o#IT6}#9^_W5A=1=wJQ}t$u73cqs7t`s5n+}oCKhnAr z|C%S^HGDqXzwSusH@@Z>iFEfYG2MdMoID~zMHG>SXKODYX7S|Xd11Ft%9!3dt&uAm z{_4Xl7JSXCqAzV``+EWkrr;hLy~gv(H}k zor{KpF;LD$w8PoaeslC^bdN_xG!3fB67NiHn{?%G$B@HY!n{Za=v$#9vqLpAJG5M3*2?F(4x7c z-7*M%cp$1c+o?$3XZ?$e7*M3`Wh#s94S&()%|#9!CLnbsu< zoN--JnZ}!Bsp9L$rUtLLfn4{*B;tG)eyVDC_~s8g-4;V$szx9L7$zoamqqjd!P_oy znd_2ZWxOuQ2pn8L9MQ!=0UX$})!@Xly)D$+7Af|~TnU5af*Kcqj94Y#`6W5u)Ju3Q zQmvh>Y8t}^tJ`s&<+II44`GcVpQp>N@_AaTTDz**UaZ9>uIQTyfRAi(ZEmCr3NKNM>dh>k!W6M(P`V+v_pOB=4jrr4g&=J%54SWV{h>Au&hVplm5A0Ty!IV4hW;^;YA{0Q;tZm>{LLa!+=fKY zq*q;z!wzP5>;6M8rV&pbrHHz5@Ag9TnaebDhH2W?xOmPoVGXVV5+PGREMx9mj>DbH z3~Sr6no(NIO)Wu(qx$&$&WmO)j#Ivp2t(fquM(%+tAldj_*Y$Rq=e7^ru!>pSN5w= zaeQr!tBv-PzS!nh28`Fb%44K5oTZt<{oP8g8|Wnys)pWLB%(IvHkjqqrjt z)I=p~Vi7e_1p-Re5(`-Yo5cb~mxk2L2^6pcdZwcZKRZCTT1J;9TP>+nK+0?d#Gi1p zDcI}S>{ns;rFOfx(Pqcy{`&nlp-XwzfIj{Hkw{~-5Xb+G_<55V^yEdx;790&XJU_( zU)`(a3??o!F*3SI0Y84?+{4Gx+@mHR|L6#bU1D_L`7N^2WmhccESp}LS7l#eP^cHm zF4)78tBzNNJ(p=Ep9ErTOPGu^g-=`h#nLi;Spyl?Hq!xv?Pj9!rnQnjXi@i9imnZ7 zNk??Znoy`-;)}}{A(HlUQQ*@^{H{zqQ+7D)$SogPP}0I1Ri#Wv^m5iN-4c0?W#KSY zDPyS1S>uKs4qK^tHwdyW&4li0-4mJKC^SdqV!{0FNV2wNFKVlz$=2^fAzRp0k3OY) zrRugN@k>8h60x)kcD%vW5%zppX_x*M){-UE5_cN4WC>cr&gLdFgww2+aA*v}D?>Qi zG;dd&t8JR#B%H*DaQZ;z!I4WM$}JHA^I&gVfp~m57(62Cim^H8$HXR4`;5&VKP1A@ zfj?xY`}@TH1o|#<3+Se~uOwpV5s&aD z44{AxAb;7PeuJUy0ZHAgCsEBlWqb&IMAAWXe=R2ZCm-MIxND%!`(l*84WviMUreOo zjMIFvB}{$oJj*H?CI;gp-?>B*$}NVrIB5m_DpBOfPes3;xzc^q1>O-jbN6g*z`0-^ zog}dHWY6mj*uGAZe&gIFZ%w~*!IRfGcd^jjknP8x+zBIZ)_CDN&d_)QH>vnT7Hs~7ZEGs_;fSlpTO^}$2A%V4UgOdx z3kz98N(ef(?EsG zZPLqKc`pke(SUQ|wzGak#GNCStcZAe6uwQ8wZ_TJ)={|`kK(~#<#V&ehs4~B$5@Ik zw}2i@)+G9=*o=ubI%1GUNxw{+H4*Hi)XHJ9Y4wkqO-rU#JXU8~I<5FIyXgtD=}ANT zvfScFO{QhJagUiN$$?8dNnx6#tQ;&89%Z!~@p9LQmxXO&?X(8RGvl?>3V=yAE#onh zNe)Z~lfrCL5(8lGJ@2zXj%!FyXnT-0@W5?Xw5%-EtO2L_5Y|MCu+}4mA=D|7jt6n* zj{zPMWMJ_s#^Rfw!z>xbiYclt%aLm)3De0Ddr>8^{e z_B`8%Ke`Y;qpHW%jvLKx+$g*MnuF6%-)r9rXY2c7x4CyGUgPZZ|FQP%@l6$J|4Evr zr5v<3YJmdDY10%aYkMlRMbtJerR64s0_CPnB~3xy^@7Tso&)qk3(}OkxSIwerQ%BC z4R5~A{ynUA|j$d^ZPz?PFg^C-+lf3^2g+yIrGfrd7gP@=6Rl(vAu-Y zt9741ju^g`EJPG)#jvfY5CYsDx)l}DAuy)ItZQX4_R7z$>Hziz>$ zkqEXTkmf~+zig3>-#;(`z+g^`-w_OgF%--YzaZmZ0Umk zSb<;J4S!gyWAGGe#nl9lK%&4Q)-w3oF8KEq_{whhBjREPPoY*^MDPeC3LN4>2EVWi z{yhbLNjLl{@m>Z`p;nwn@CYOd9O7ICKer41paQ=j2v0m)%-|{1ijxT*fkc5rv@m#! z3~zi36jOM1G|?Er7z$>H1w>;6TM-bAN68TW{XrT-R*NGD2EiB#W{BAYgJ3HHg2|F$ zaGzNh|D6(t5)6Vd6wDA)2?oJd1OzighOzy<8|H|pCl~}{D3~D*Bp3u+5fID(r8Td2 z!yFc42nN9z3TBAW1cP8J0)mNB+OsDJBZd*rA~1_OLKy;y0(5hRvQQZS=yks+ zlHmPa@Xspn)4So1h#xU{3bo>g1dl+Xz#)FX;6Lbs->JY)>4rZgzRBPz)Qar{k3gcp zA?|1J`@7)X3jCyQ_|xLc44y(Q=GK5mAW`5DUu5tvcEL9(@Z)9pCi0=dy$oh+kKm}n z)zn%^z1%&nJl{R87&Y>^A~N3_s~R3N81m$?sId03v1lQtFv_s!?>rsk!KSmKgT^_G z@e)5YIb~pS$(`l#G#!w5V|o02+^u-@u#0)&gm;rlPKYnPJx+4}gYSgo45j2{Lkr+h zsQ}hwa3yl2E&>(P%Fn@%am<$}3d(mjhH?pSa1eT@1M>IetaRm}FhzM-Fc1$8-iQ;r z`@|}76+PvL@)`jPC-#i`Cvyy@Y$$V)P-06Wm50JGu8QUx{mu;&)&e53hW!?dyKwQ{TY}T*r3o(IqU#K)Wa)<}k2t)^{9RFD?m!C0+#< zcB-ee_noAcJS}1bw#kz4buJ zLIow|H98KxT`rT&n@ATj*gTsH~aXwx-U|l|roZU#ECta@sA42fD@6$U79zg>ab4BsVDZjam zKu@<3DBywb0IvByeIJ4Ey#{#J<>$Mzn+WuD69Iu^31GWG@SWeKzeMmaUR8b4Qx{$i zucdnk^ss+)mH#ur|N32eGr{k;M)@U|!-vux1c1jK1WNsXB=~dRrEewpEd-xA?yGHp76nJ>=3C^(W2N^u>9boVp+sW_1&)>ny3SPgS zfA8pxa-cnvS#@mG5J(h;^cK3ZXN0YDe@%4X^{9pL^rc1D;*CbxU z&$qF$!O?hqKVR(D;(0aiqg!jh7g&RZlxJzG5>@94dzHs)3I?fON|{Yg{i@`WxN{|% zsvK2$g{EM*s(gW_Y7$g=x;d(>6<87Lrm0##*x=#~V$B9%V`nZbUC(N5yS{_Z<4ie8 z)txE`SDsHEln!Tgt-pf46~psqo$OWI175}Eb%?uDm5icCVM~Mm2XZSOt5OMx;V7gk zSI)u6TQweUIE?6XD33gqN=ghbkH3i)YF}r$a&;cB4j;|)^CX7)CuECjC|T=@O=2-i zUxp(wptsLjQ64jz$7V$b-_Lzp)bk*rwJ6h_~G>$6M8G-^!$6 z8=!~|TgZF756I?RzvFwHdJFqy7&GCx4d39RM&rU^56<4OFlfK*cTN|o$_?KTv|pxt z<1)nTnpcI|!_*NoU4RACQl{a3k-Sy01-kd1SIn25WTv(=?lkiIVtK0pgBR%ltFaGe*zNEB&>3z;stz$Rg+C@rm=ts-Ge94jp>P)xx}3f7AQHqmYEsT-s@{l&jKq&Wkm zIRkO6J?^x}q+$*)%}JE<*FirbGa{SMo#|!3_q-Sk&v)wz&oHNJWdY)8{E$7#v zsID}|?lk5^RS};RQz(p%$*P~s^{dyypAhri$vmjj%oh=Ax^yFxPZ>rxb@D0h08llX=Z5PULl_-@d)@{UE6M-2MaV(V( zK)tXLN7RV52=y$4u*UOHj`Ktxaq)k$y?@fh*GaC_)lywebze-)lU@7@$;AcV7bZ(h zr&deN;qvDNcsCMq}GWj_u|1P@5U2{0Oy~ zu{jHBGxvSx#R2P?+D!HtG_FJ|2S5!7BTZM^U499%SL@9njeiJuT+kQ#{|L>#g8R#T z21&Ol;{rOVF268P3;OI}e5LF~PHTndmR#jS4w85Ikkfa1m(^T^U+5;>$Gh@5Q;KwpvOX;Tj)4BHM9D?dYM~ zR>bwFtZn0BtFzVPdvFoB6*p%@+xYNiUK*^bp>wz1jo^BL@8QNLo~4=!(es_#B~*-$ z&S>E)`9z|p;&&b8{?mC{F9CJA+O)JeLaype)7hiG9B%ffUeZrTxhu}|>M%jA6LL90 z9nR51xo+M3bAx(OIkw~Up{f_v zha33>B92i+0XD&u^l|Z;xDje1Tp1wflF#P`qLAVC;;J&^NE3=2Myk?pCD1`j{I6CB$LS>8!-x44s2m zv|)9N_yF;lhtCw>r-#-7jp8AC5IZPT@{lwK~3$+lZQ{2GxfJ8dw4-kDek9F-a#8W25k`c(zBcxIefid z{wPOy`3)WX1~HMaCB8rjG2UrZh7If5K{mtsbwS!!?oE^rR{*hOo)g)9)YA=f(--Cn zkRjvHIrbKcJsOaSPaWpV(@AQrLmhg0OB9qF4z=!x@(})>^3bD_J>(pWYCE7$oe5GO z4IEIXf<=r+15#9|swT=8rK4|85IK$n1a)M6Bf70(1-o0Jm!Q5?I;0i~b-t+HQdAtk zDi7+p&ye5e0bkTjQdGPWbrMmB5!FYE>Z?Ru0D(UX91zEW(*95JEqWwU8R;oTii(v} zjYHl1A0sM3is~mvjii%8K`ozpL{3JO_+SHUj%|DOBVR*)9|q+#u(At&YOc3uiF?-p zEt1;cQzx~dy)}BDny*wI5(xu{iHcDjjB4<%Uw1GPjUIU`YTJQAW2LCxN}*$s`+JCj zuIDBt3RS~>$iAp}DXNbWMbg3e55)EBYNyAUN%cL5$b^w~Xy8Gmj)+Zbo9{C-5wuN% zeHycd*xtrR@Hn<|dYm+Kh7$f=Z7MM2K0sj9N5BuN{HUCRNI{Arbh4yH3mK0S7z~*-NpFvm4J`Gi+IFM+IY0{Sxs8oS)XOY ztj}yO2XUgrZJc$|_>vrFr3+=oNeiqEs5w5ZEq|6 z-^uoyt5ipwst!wo$B7L~wsY`+%=(g1nOxw}l@4MUVZr!k=G$7bp33w{XyYj`14ohCn6aR%1(~&L7H$7!u@L?EsY_EICu^) zx{2Y;s`5EiZLjN*1T#Y}WPHfT_{=Q${Q1`ecH&-~mb%sX++gv~w6K!>rG5?=4m9eM zJh<7Jyy4Al=6&~Y(Soym^z*pdL$B8c+mLn^M-)8pzgK+WdH+oSqBQ56M|LG;Js{6^zRGMiGZNgw|HRrRj~og|7t+C z9Js&1{)DfDy`A8kbBTH`#Ozp|nG_Ew@CT;8+zfF#q2!qipj&r7kpAy=U8_@Cv(ow$ zD_6R;HcH^{N;Bu0t&s_%rvEV77;f?A6wo?MW$})z2>m))WAWyfgj(k}PAor!OZ~nm z>Oey8Xk(b!dkb5z!Qr|@`?XPH_GT4oC*9kaGvyE$ThvC&QJNf!hGjC58#! zB$iz8>I*{>^04tGyXjnFsNhY-j8yQZJd_Ae45dvkC59i#jCvrW^7O+|4_W(j1CKiA zd>%$iAl$Kg5f5IA$IEu2D+cVB+FmgJ%`k&`q;*VgPwO5z!6T2 zeH?{?7it5j?)GEySteki4+7v9@f(^paNM|lplf-6{)}V6JPE96hYbCdruh#AxI}1l z$`hlm$i>zX;4Jtb@cB!#!ED; zxHHMw425L}c1GcESYT-clb}F0)nQq%e?J%O&n=K`m-k`Yv($$xi1@)ijC}n`f%_v! z2+j@>M?GFylbD%u3&iH4=PO@>pt9jAfEIBk!I<}{A!Mm0hJd|A+iyzcltf|g%@79n zM^v2-kWgjvWk+BS7Dl#5SR>U)Z*&E6?g$-|k(O>i;%VD^@tFH*bX;ssMw(#Op^>|v zfgc~uENDF2s~0TG%$DVo+8>9o|2&j?0*vbja5sD&`mUC2bVf(lm_cbP%@%ReYe*X@ zr!}q%By^>6ULNwRvnseOGBJaiV9PbJwqtDIp9%8 z(nsc+Mr&Cp=$vzG|2~P~@n39s+!+OC(Aef;F!0|L*l9rSeF8hvxqo!{)=&6Z`svju8+z&SDfF?Wv|b-x9b)rnYodz_D=n!12#p0;0kY3W&+F_wnv zP6bq5Ey{{&c^>40qdgmmY%2qMs8tqJcTYMVmS4^%W~bVos4+WLm=$onEAXP`LMN8a z$I8W$cc&0CVRTGS+KAxFYUmif21Ao~)S8Y_YwE;W*sN=#q}=egte-q6=B#PBV@<8NrGa+Nr*Qof zF1|r7!M4wSz=~D!;&h%tcy$*Tu}7zC$qb?)(pVK}$JDjFBuswY8<3#1E5wn}4k&&} zX$K!xQC3E~%|^Ly*-4O#=mX$;OmZ+I@;Lhvx7B6wtleCF1^&4RS0P9Fr1ai4$bpP|Ot0UWoJGKN zaYUbTJffoDeW$UJ=_Dvu9|Q&Dy&GHZ_Ty+cl6Isq1yoD$Bl3VE3G{4^hsU zi+udR;5fqISDoXEt1uN$F4h!GP&M;$V`9vMRpuluj>m{w9gR$}=cPD<(-x>ysc^OM zztg!iqR5bNL&sFeDB22&u@NRCjsILH@rgesz-dP(_EElJAHfK5A1#QUA=yIPga~CH zc47mq%PEL&&?ip&q;`!q;a{UOBKlc)x*R3<%6CO^JYdLTSupJo*tsCneto{`eev#o$f?{>&?{aaj+%e#>lKZEI#X3 zgU*)88L%RtBSv;4t(~}E?=j$LO7~#Qkp1N`p%%+dbnf6(S!1#Mo%KWau}&KL+@U1U zFh;utc{~?oImCF(<6I&OgcmyN^90_W))MI|D_8VO82dcVr9`%OJLPq@$oz2mrnm|k z`rVlaN0d*dUSsxqc)QqE4|iy^2%)FU4)k7N-4v%FO-JW)gzFrIcRC6i%VYi^@Rqn0 zn3gvr=P|oM9_$9h#=OPHPd>yKALXZ<;isa?pLdc0wLEcXS6acTMPd(iZdjbSEkR-OM|)kk>3ZFL0+W3Rz9&n)&)g3iS>yLcZmR z{R_fWlm&#(bSkLZT0&%n|F(mpQh6ka5zNySInj;(7Zv=o`3Pu+I{2UOjw4wyzrL7D zV5<-Go1@MiTz?vqZQ+6X`52z-K#kp<_1n=!B&;^|Dk=YNMN<44aDj^khfQsSlTno# z0ea7xkjhG}ezRKQ?YU#>b7~@?b&$&J$$dfXv@~r_254o^9l9>(waB#B^fI*%%W~L? zA@Zy&A0N{AlW~7jI_TnF&xC<(BYGrt7F4Yl7LcqLLUpNoLu+S?I~^+8lltLNS~EOT zURj=4a30GB?ynFJ_IdK>F35YrgHa4jI{u3}S09dQs7!W-JWXHos{u~gpnHaH$?r)`C_?79JUK-GeJ zB;yC2LxX+U48>TT&Ihwxv&M!Yi3QkKTlm8PE3 zEVwewe3l014=DMpq%?oJGR?g#4R*pRX_io$@+;Gnu{3mMy5LMjo##`U*%XP6of|8D zyt?+m?%GddA7oa?jc9z9|BGLJ(f50z?*s3@8=MPZ9kBcC?sZxckcHzMh2tHC6LvRf z9fgXp-0i^g#NCYua~y?cM`8Z%^?1&86z1*TfQORZ8xe**c9N!Im`OMa!*_2+%-y@U zAiQh$R)opBU(1C$1-xrT%~irAHTeXB^#3^#_cr9{S0e;hlnM%J*JKqfoF}V2lQp|{ z|Kyn*GNhGQBA@P4^Px@Rb=gKOlI&X zIn`3$JUG<2q7S*>KT&_e6GiKQQ>?>4Sl$CpSNQD=2_QUURPNf!5!a%H%rk56ii_2n z9W_2jE!Q)xsnk6x)A>}J8IyFau6`dB#mOZ-I4u*O7~xsp#op=Aw6s!W_HIb!tC;QB zd~EJ>J>Rv>_2j#@psq*>rXE+bx2si#RrRvt+KT$?Ij+ThpiW;>56?A!OH!F3^z`tk(u~Ts?NLTk z53`=Q*l)st>seGQK_%atB$!E-5}UJcg9h!FHE z>s=dH6pA~Tdza;_3hya-xCm!m7Op5^K5*uc6f=%G-J1q?HTadmhp5|a?V5M8KTeq! zbUDQRw?1odh`Y*X&HY%?RX%I(N0a^wpS2;ls+S9=b17^4Nn^va@2y=d9;Y zYB*t}%Ui9>hq275o*R`Foubhvwm~ENU|jtJy!2p9z5cN_`^%wC_pSAB1|Qgk%g?q3 zBUI$3=oCiiL5z?xDN~3rgw|(G%4Doy4XK|y3HULn>v?NvnL%5YteR_0$xVqK)Q}~X zCbm^f+IBAempc3N8r$kCIKizWP9VC;%uw;*;N(JW&hm6@5C2m-afGs`x2SC4L)p?? z2QE)Hr_@*%h$#=!cJGN-aziiXznJ(td7=Hk;RWvh7ccmyD!d>Vba9haaD(wrNy>V# zA~#z%D6VkQ>06@4WV9tl-KHU&ROAkl_hjyguHmcu4cQ#B?w{#3T5@6>ThtThF!pne z4c~I_TZ?P!vY!Bs#-`8->uI&om~7{~ugB~R!#b?XDbZd2i`SD44Rs_N*48Pq;hQyJ z+W*EF!sbr|Pw2oCLGd80S6#!W z)l@slRC^5xG2vP~@#os#Cm+BQmq~{^uXJj?A%P>G*6OSB{%_?&)o*-S-NSw(A8z?? zAl+O|Tji=Jn2_C@}@Z|KKb-B+ybDEDQr+EcDoWOgYN>3v2L;{cL zu0HdbH^|7W6SzY1n2%>3^D?we3uu4XDem@;xHa6Y*4}F{IsZC<3o&EYaQC&w(yKpk z)VZP~dJQhMi>CLl0!SUXhO2Llq}MO$^@cUvw$>Z)YGkjy)^NL8IeI-xUwf|MUT^J5 zuV2t>k2T!E)*kceD0W(|&@ z(9r8)`l?!!fV;f#>i&$?va>!RwY9Sz@1N4=OZ5pOTQAbMa2}@dktS5o?K9cY5_@N$7 zT|dzGe^K)9>l3!MevenO!VKv6t{%t3e@m}0zTDn%svgJnoTAtF=mpYVF?F}xbt>o@fYr&_1ZQG4c0Yg@u=|1W0?rhJLdy6JG#{8B zuF=d6&y2rwsCH?1-BUZYo5SlW>{}prN?+28b8CV!4IYrSNi{dsM$Zkyfk!v-1*#g` z=0|GhY?)uPYV(HLm0Q;Hp(%WRinKq?ye|!MM>)^Us@6ZG=QUsPzD_I~U*eXxe*1P3m%PB+YHb3E6Gfw)+ySrSh}C3C{*4emkCdEa+h$Rve7`LUW}& z9=iksjso=7C7h44Y4vG7DtAkLj@hHXIbEOBmdPhzff*v7dmPsZDiZ?igy39^ z4ma}8se^QA-$&~MrwXJP$pbW#*HmSw&`rvtQ}~ZyjJs1Y#@*>LS$w9+hfKvsO;gU8 zrph(jcBvo|MfzmVfMm}{YQAS97n8=P@!dww$Et&DivC1OwsK1^0VzYs(1(s%!mMoy*Jo#HXD~kcirvZfc8MuLBK1VjKUd5Zjh^1+i_2rqABCUmxjXP-fxNp7JqB>>>dReDMN7K=jR~+^cbQI(yoVyV zm@-4u)R$CKpH`JQ?~x3V)h{tE>3pmVr&g<`KBjuedC%yV)tn9{qtXoh;fK>ZR?mR_ zximMcE;A`2{oU0q^p^S5y>;o>rP|AzQP-ML$Bz5)|KU`^MsCS1BL;^5mZmllrf7sGVr$O2As%t${P9R$;bA(ztgt24LiVG0mJ_H29DfA)Cl z3Zbl@$5#voTjScFlzNolpG;qk9)6;apM<>EToU*VQ}1E+{j^JQt5u{=-+<*a^_{W} za3LX&D|gPNqnhszX}y3#l*pW=_r5u zA^tXB^g>Vc0+crffXm^ljyx}2@m zRQz+yFf1s09o+kmRT&8bvPuvfZLB9Wqgip!!-j^8mUp(3aiBG&Dm}hR*0Lj+t-RA5 zT?YlM>a{2z>0{TbTA~g`KkkTL2QBc?=msI$p~{Fl6Wy5enjXGy?|q^_@}(YaxNylM zo1RL8){GgVhV{Xnt2%bB5^pDo=_>n+5Hf?b)0ihoH-cA4kuKLGoiI3J>AE~ zZO3;-1eJESCys9$5lk{$#tXIQ)Fqa|?6?I$p}XPI)L&Jerc~7AmB8Uwt3s&pNI=%$ zSy)l4csyg1^vw3&DxXs=+hC2)ALAAyFZWHIEU)PYn>`vo@IS3n2R$s)({WQPH2BCW zIM=~9!iZemhuMvhAxy+z4?ozs#h4h*)>U4W1MTWDr)RUPXwJ4mBzHJ_Y>Foc4!igp z+leg9^xn%md3EdL)vP1h&YeuCsI|ffl$#)VQ);j7i0=DO!3ZBTq1z@T%H7icZWszk zMh%gB1#)i|*&xBr1s|7)jdeKJKL}+UtH|$MzF9;eiaEYWk=8lXl~lvM)NI1qdNQ6+ z=gaGa&F;-bp`^x)5C3LSFFcf|$d-Qp!cK`V&s;E*T12Cres!yUwR}36@%TyZQ*+`222gRxHbH(;lE-H{j^38pfuDnN}RsMPAd0*|VKsO#HdwN={0 z&zf%%w4`pd3z1)Bx=SQGvhXc z%}}=g^NZLN1gl#-NBhMH7xCsQ4bBIUT^)4asK|$hYMFhB1Vaxn7~b63GQZw)v6+2! z|C5P5SpDWPaTna{(4Tuvh8_lJ<=H<>87BX9iIvxzab`*V0aUF`uad&=#x{v<$K-sE zUL;2;D)EL(K@twRQ~-V^!_qx4EJ2G)rX|U-y7Hf)s}FJm6w?ybc}!<;{KFWFB@;P>{cL7`I7y^bFDRcC7oPz`hlcsNY=d9zxbwJz^Ss>2+8M8vh;O?HdJezA~QZ!Q3?SH9Et{lL9q>om$C!Za2H% zBa}7Z;$CE`Oi%jDKCzW?%?3z1jPX)lCGXQ?jn=vp)nP;!ehL#D)Mw8X(=K=BDKWCP zp>m~UO>kKIi_2CvEXDHxhjk##ySbHIe{P(|TO#Aa*7E)3^Kf_XM7h&#)f7~ysL8R3 z7<{O{=tR>Qy@ty4+Es{p5xqhfy(-YUFPYzmY`K%OH};YFq%v+nn9}GTG^kW(U*M|TF>Fn(pjxL_OTiVLL;&{+~{|4eN_HnI$` z^+;@vdMD_g6P`#&E@3jI1Tuv+;YPNfgqo+jRPYDzPjJr+M_wM3W$2}X6l|l`v?r76 z#JnDgi8}6W9PX&}0l(%_!9Z2>4_&nOtL1*_K5w|3lr|N4-chDMBffi1>hI*)oV0tc z>A%FwynePTAZT~av z9M^4BRlTUz)RxjUTjXqt=G+4f$XI)-Em^_dyusCpaCQVVvl zWK#HygIN$t4>2bvdGtUf8|auxmVi#end`4Oj?riR#ls`g->gCX^TM$vFhT_Cp7I@s`3a;L8elUZCe=C0YvGAeCshybb!)j4X{niXphSxZ#Mse zi*YRxguX2S^q=6j1=M4Vk6nyA5iVfM*$Oz#+cpJsdh)z3L2C#Vmyi2Z*vzc(7iczJ z3cm}}BFds-*kL+bQHH_wH)W10bRpV&qQhrkg&LhfjTW4!UL&iUHEscMrT}AZp_N(d zZGRd@)_P+AgN**J2Gd8zv&u!x#iWIcQ?7BK9`1egt$_LUmO?0u-iChov?}wA>&r*$w(UBS!)>czDTTUSWw>?KpavK;(z91(BW+3O^ri~2B5rO+ig%)=EvsQyh(TDD>0zgj ze#}=ioPIiLUP~K0ncrQ^?`&nZ5Z&C?iq;uZ-!Tk|s^5oGEH|zI;ac`pp&mW=alPKq zGJ)=%da}+qC{z~b7!{Q>um5qW!@XUBbG4j|1(dv*k1xAP4zZ;r~Z>@Uxio%`&&uF zX=Bt3z6}R8Cz0h%)f`$kWtx2c!4rH%Ykj$Vmcv$_a+*P*d!aIVtTSAjpI%)_{IqAK z!cS3*pS=GYKRvs$i=TF`4D!>Cl`=oMS6+{w{=8D*r{}NYr<Siy8KkD@KY5X*(CE5Q}0yMPzqT=SSuxNN{XzKx#>D~{oT^<8{~ynyA-n!H_-Wy9_-Vm^#ZQl3#!rthej3L3saWBsd#}S!$&8|1o#QE5Uv*8;^_s5r#}*ITlN)PDV?~> zxasY8Sg&)hJZX4QOn*VVj%>2cxwdTbmt8KKj3cj*P1p1b=>I!;39<=Ge)x#|PrRi2 z|H(_nDOdB7vG~8?CHJImUh+>QE;2?jj=EXqC4a$nc&Tv($)<)CiflTuia-}IUV={$ z#(--LbyX`A(G+2jR@gmJ1sLj7hRPMPY+APBda~)U6|!t{&%KhD{C>lA#=6f$#!NI8 zT{hPFzd3P@vCe(>NrOR5*I$>P?zj#=-F5{(-SofnQ!Ul%_lOd)kG-#WH2zlfgC+TqDZho!R!_^g%*`wVw^!u1<>VVC{U}Y$m*A)=$i}c~AYYKCVqEF{Vjku*PBWlEy>7*c%7qn*# z2S0Ensmz8@dG=wviC1PFwm2s3KP0@`eloD+HqL-`rGHvcXnayK2h@C>KVhltf$hr%jJ82vNL#;GTjxw*PX!AM*3K7QGr7 zz19Wkb$lh!EA#sFst(d?MK`@fMz7_U(d)}eOM>(odvH2e?r`siwUT{Hygb%7}5evQ%VQ-xkX=&ztx zlB)aiEE?u)aY1tZy1e0+fX}NSIYO6`g@%u zeJ{JW^b^F<-3V^(WY@i=A0UqIEw%ML8N9c&15p)-GDe&P`?GsX|FP1wZ$6^@;U|Ol zmcG3*cyFmY?Br$lmhLD1Fy3(TihE0Aue!I?eaxWyB``HwXJ`^ljWg5ttdy@U?cE$L z_&8bBX8YQ}`x4mQ8HU{}m3vE#egpK)|Kq)-{-cz)+`Vj{Feh@zqC)bIi+zMN$n|@1 zZpgSw8aei|kz+4bNg+%_Zd`ey?v`w=Y@h;xNG=#Jo)8*Qbb+dBEroDl!Gyy5=q z)yaM6+I2E^$b9L4v%602;n%Cv^T3lvu2TU9ScWE9>+XK=w{>zKKOq(^Zz#N8oosJi zyH5T$n?H6RxQi}I;)4f?85j6=aHYd-dxC$ zVU4&b=szpqrL|sk$yeoVK4~%ZNk6qb6BqA%vVz$369cYN`Dlgm{q#w?n`Hsr>N5bB zsmz$RIZD3OrxKU$Odw)lRE8$IX0T+{*ThKHfzw0ao*QGy3+D#p_*Zo7rQ#N%xI(IB z!^xwDfv`W*L{R&bdAg?I3#pm z@?!i8RJ;Q$s1z<%A#H&1&sQjojZYeU&RuVNTN_r$Cbhqv%8C-7T#jpa&ZUGmttRXl z+YMZ5>uNcH`!FhI@#?2rWz4ZU!A>Bms8X26)mm!?*NQbUwGVb7&1VoihIHBZ=Kh$$ zXHEBB0iPwFUZ%i%0sjTyJG$TxbnNkxaBf0n-0z>f?A97%gWnpmn?tLN(v-Hb@@kwRq)a*_k)}Cdu3@|<- zk0D>EYeEwGkh;B8Q=Pq(bn-Se>Ewq&Pv5>5r@h`t>ibQqb153#B&K60WqpyK|Z<_nuJ>V{5MU)7W^0d6FC(O4oUchlB@*`pYRsN2dGrtOjW|aGV!5Jlkg?} zs|w+C-98{BwGWb1WvVu1#ib_F_c%>z2K~Q9^@_uM#nTk_s)w$D>sy$YH90L`kDa-p zIg=a5hgz^xWmFjR2Pp5&v9Z5jFej<@dU7tXE6 zjE!{ljkHZOru_hY6hh`5T)`jG@ESU11N-#&T*>2nYkaPvsyOO0>9OwMB%*E7~Oi7{{%s^ zhtrffH_LK4nYWtwT&G}7<*h>vw=GS-Z7Fa4gJ8XxcfO*5rZzsNnPil*LMwXD4$0g|7BigaEah%? zAg3+w)Jj&N7b7BGHsRQ-9>!zt|NIDg#AuR@ z8r?tCzL;?4pF}wCya0aTh+kN~?Qg2(h3&~-UDjle)#L-Q4tfr1<)+vUU93{eJRj|8 z8m#2*{{*>LT~Mo#efu7@CkBQq$BFXZVKVc7_#?_q z)Ku+)cTU|y@IZQuJdn!ZfmBW&NEUb?O(72?Gdz&;qjtZnZ)?}T_ksS%S9*9O$;Eh3 zjP9_SZ5*URdQc&2FdHF3LoTS)&VS0~v^W0la%{V(93RSwmt_$vr~b-vjBm;1=obI( za{O*8rxlzq=g&W-4bs z${BE3Igejij{8LgZ|T3g9Gip6nSpYWE-S}zWjV&@6uia%?sEKfR89`c;V&zv;mUH{ z%?jQEI4R`!@wIIgm6MEeO5}3-6y}voo`?ZF4g>hq-~e8t1EY8Gg)Q?e>rc8baR}#C z2Om(EeD_!68Ku^H^e*S-$d*dP8B+tSlaGB}5m(=KLIeru_Zj?NPPRL`zs9sd7oy09 zSAN~;r8CEw(B6sxs_LSOje{`6!y7tB2+2K-69jqvpjMAJRqp4TX&gI)F3E(%>#CBx zi4(DlR&B}8$;wMh*9!T$LB~t#u0K&dzhRm7m3#SyUtPH=9$ebNroa{&U;m>Fin5)` zc9>v3Rkd!cWNB=@Y25n|!Ur3KnOwkG{FYk8b*L~|&^@VA+Pj8i<3YIvQJ$vcpv>ek zi_o=Gec=pJn73#0ZZ>)9ne)6Ncq%Rw{4FSt>v0tSFmVkH4&O%XzM$`Tw9@As(K>LM z%S#tu8(%xYE)Sr`|D0&f<-PHWMYjW(t=c7*4w-sqP^R{!In2`xT?LP#c$xI_+>PPw z&TA6*xnB#mFto*ZnkFcD8RICgMj7`7b5s?1g_Cp2^;|3$Hao1exbc3P3YLwUP?1+N zxw2fpCaiR2qcmz<1z@ouTi@syHU65#HUD~>7M?Nkm~^Ky;dXp*jBI^Tfr=bK$_$JhD^q_7jRiU5Qz!mAaH{=NM9|{cY;#qvR_@3 zG-Kt`jjL#WH5zli7+nE^_Iyl;tMb^pnci~{RNcYeZ>RT>^nNRQA3^U~^q$GyhtvBl z^qzsBKtQ0w-J>z>xO+5)?jFVaPQ3%bARSywQ+2L|U8 zY`wILR_e6Ak|!0k3UfcBr)Aj}%#EKd@LKdbmd0d3mQ#fNic-(GQ_j=>7vyOw!?*Hx zhRgE~+YY6;KM!oq6}<6stnq0DVB5g1^CohX`$vzsla)Ieyy~SIsA6#IaH)QN?x#zN z!eDnav1zTMA6RnSY#2DU_zj(G(U16oi<_G?_ZF*W>0GUAA9WeG_B2lBvp&0Iu9l@S zMt_03e&xFsa0~UoqD=F?NV8odycsE2lVu%?f7cnvVFG?auBOc~P+a|>)#4*<_OjlpCRdx!Pwessgl`+GiGPyn9*kIAX(@Xo;EvI z()(bux5A9;3wn9Q<*g56T3U*1Y}v$qkd2If`S`y^Dq7-j3op@6C(f+V|G`9p5hb z%`&UF==9mRRB;{5azONHW5uHphq@n}nNp(Wp8$#1meueaFC zEq1%7c%oyw!BITPv7L7mTO8Yy9mSI!+f9z*V#oFrNAVQL_Cb!~sgCV~9mUfe+Y=qd zC64V$j^gQ#?M6rO49E5%j^de8{vOMmn=MYxQe0~GPBdGi!K&V0mbLzhVT5#b#+c)D z#>|d{`9;`?Wgeq7Hys=^y6zUiIy74gY8!QTsce$}#rdkT4KS)QllJX4{0_rl$@qO`57(tTCTUHFa;icEjui()EU$2rdaHCLEC_m z;iBW+zVEh~dbr}*8zgLx8|<<-IzG}!MH40XsTEly`v$3~L@Jst70r-}W=ch~q@vkU z(OqqNl~i=MR8%SzS*4;fspuYf^h?t-q@oI`Xs%Q=56;gnXNarlKFPjKn%iG0S|Hi~ zA{A9iMWSSH!o*53bp7~?#~0YWdp^?y8z01_7Fyok8(IW=9B;n~_8vQ3e`4?BaH$;j zz6rGsdq3Wuz}x%t_5qxWw-1y?Pjs202%&#E<9V}Kem357MtOMXjJ)Hk`GRZ+NB7O9h|zj% zh5#xjP3ucUbr#Mh5B~@!wN*)AUs^kWG~$ z48BDscAQcA@g*t$D*VzE8{4d@6O{4z4|?7g4?8w|H;yi8KE#@89I7|`dr9*{2GufU zKAEaFe7eNNs|}Zyn);H?)<0Meoh>#3cuUOQ$k8#qg}uju{&nBd_Y^(Z=hEL@()@7x zcS~h8uG>Tti1Y(XnqwgMlnNv(6N3IgE5qYP!s|)JHxqbu5MG1#iIDwTJiHV)wtT!0 z=S}ViWbV<86?ItGVxLDO49U^Z_w%&^?n)BhTUe`RDYUnd6N})D^dyoet1T91cNr6P za(TF3#l|boCH(87#BY|+EOYIGj>i{>iHq=je?*Be5jQjT-Z2~&A=Sj!+A4T1^krk{ zv?s!Lyabz{`$B+=m>p50t$uY(%kz$&D(S6r+yJiE{k+5?jI}K*3CZ{UO#>0i%!2Bas6@+oy)4`o!HQ-G7SzLYXDkQL)9r7paT0-RA~?EK|7lzDJvqI35iKbuvbNt zw(HpyTSw@Upxb&%2G!`z;j%Z;*igf3MJ_NUS(FBI$#3%*Ff?qXIk|1Y5~`$K~1 z;mi?XLes$!nP3&6>A;B0tPsKOJQ$XP$PL447p zMO8%`aRfJTk2{h`&zU8ar3C0a-9Z? zfzq7$bcW-6v%T5Uw0jX5FzxqwOzVB74R5}r#!+^p9Bta&vV&;Rc+6#5>oMI|hLouA z=qHBwO!sf&$`iuPJyqs0WV{k5ECAXqru!`@#I$xsKF+SpSGCh!cM>X4Q&%{;Zx6F+ zo!P#V_=$_~pgyMisY<3ln(gcAUv=5PGli|Og)&~&qDeVNHUFCX48sXvW=v4?rzduSpnPitv9KVzDCR6J5n zysYg+1-(p#8p^5GJSxR(+R$^3#k78&txTsHdA>SM(|#KkMuNS{XKHTCZ|j+V2v~Zf zXY2kYP3=nv{Ws{Prp>5WmS8Uj5|z(wOPc=1K6CoFUu%1}?add|IAob2b~8h)e<=`K z<1v*x0KKIyJChJ=-w*WC!F(i6ThW|&Q@&Gud0ZMU+vmr>in zQ>7NuE{nZP<|$SXs!2_^noXsw>7^1Xz_KD+pZo6vxfP&v`&}Bzc^i@bR;B4my9<{z z6;Z=%-w$*&RS?w#`;6{(=PhZ+dY%DdzRAf8D`V7X8SB^w*>_;^30%i&orB6uy)TVLwuKw)3|$J zx^@8?UZvM?+{n*@lEuFhGQ>?A3-3VG159t{ZicjRnB30V$mH;D-cS&vTGFIq82B8F zF>w)A{NaMVxEn*iE^a!%m=O3`5JMU)Z@?OVW&L077gHFHrYblpwwV5Eu}_tGgO!Ae5{{;rO~njH#S&`J#T))7 z2Qn$p{*gL4;?{m+U}A*Z-M;s@E!D3K8?t)!Di`+2y!~Evc8}DLt7C_x?x~Lb0~%@^ zp0w*z$+@69Rv-!UbbC)Gv!141q*zMXpRNj1{zH|29OXs{tEl(06lRkbV&KUDP>JrL>R$7p?*KS zUBnBILPE9d2Q&8E4;QLXS5uZO;BG#aa08ZxrU|V}7dI^+N?@f=)dO`nAHvb3N4J`8u@}~p-_chr@4b{a^E+}i>16>j_!u3E>N6F#mM?Cy zQ{Ki$yYeoyH0d9Z-4A5%4QU{T24A~cFs*OPsI-K@b!ABfn*{?4Xi22hAboLDIsw>r4lHw?29Bp%>?6C&AF{Y<5X*1J zZ~Bh&v~x!#)jb|)Va1PZ)h})`u;g=;;zvrzw1y#^gq{BZh7=5H!`qY!L@jQLVijl_ zC^w%T6T7I{?##(4u-Y6f zBg5-S+$c^Wt^pJh#vJJkRraZlC9KNpArR zYKVAt-d!*n=iL%_tF+}xYd!>)pD^yHH12L?a0CDPK%((9-xC`=eL+<6F>(0 z(mZ#xR5yzo8(X$hsSI}NgBv`%sI2+U(Jk(K2pLON@?f9dzrk}aOLoeAI+y zoGmO>=8BYL)~Crb`qHs5FauSlE^JuM#U34jsViaf)GI5b{=2^vRNJcb zOm_4jgEt^Miqn^Pq?`cuqTkpc)cbz3!Sfpial8ysZ#g3CkO#5``iv*q!~})0I62sD z6E}D!Qb}+1Xxot@TUF#>myO-vNui`b<&a^B4|amTb%gQjPD))CX*p7q`QX^Dl&GJq zpB03aA)lMTj`gPny9FgBIZ)LDDPsz_VPleVg8EXDYOr7Dv8Daw`-$<|Hw zRf(OGHkxc(U_9Pkf^z`Q84?fAJa)8*-8!}umG!pnuzbvi?LSamQ%2|Q{eiQj{NbG> z7*P9SJ!Y41QIwVw?@b?BJl{^uY**?n?*7aKJD^e{Ogkr5ZHkT8K?nT(E|F^&4F{fq zFi-6FmuIC}s(xol+1(nIm6RDj0)Blx-yV2INRleQJ1g39C^b78$I&j&x5th^KR0DJ z0GeH&f#Fy6aQAwyJ>~9-zwu&sHKHAn|Fop+tVnrq4oQ_Xn7J%vnX>GqCzQP*u%jcx z7Cpx|>=DB01D2}WoGA~sr0j%4wtA;=f2zhAqAu|qUs&p#sYl6xgzMgC>KTaV&k?q3 z2Cz-&a;LU?jw4z4W(~lVr0mWD3neu>hT(gB2)=iGMjMldXHRZ838sLUvQy$lMUJ+m zJUl-+5;*O7G^1r*fwE9zQ!-BLv53O7j-;cLXzFGxR>#B-yTq(xM|}6Mm)Lq4=L+I7 zmfxCrZgagi-!Stw$_JWN8(%YIJLrh9$Tv0sS;ABIn%rT|k{e+a0!<1g+=N+8_9KRGt+H1ubmHlTCt>(<#C6 z%_v+xQW={K`vv+K%1ogo@7JTq`FhH<60GEO5I~u9N-%#lN==3m#FX?3EI71nMwzh; z+!v$raH*Fftr$2op-d7b1kR7b{zps+&T-Vu-p@v<6A6qoO;sYw@#(0%IK=FX-9Sz2 z_+(TAL@}k7XLh~*If`af@O7p3XZ+TWN2w`>^+6a)na_FAat!rLZnH`m+=Ty3FXvR%O%1 z&1{Yebc}+w2>9x)3TeK6MxtZypGTr?Q1`(1tR3uJfim_fRVrT+IYBgy79OGr5<>1K zgUBzImYRcV;YW&{pusLDlop?ZO5t+)J$<$oT5B3@xGau#gG!ymaRacnP^mdo>N|Qn zs1g)ZYKg`sLH|mQJu5*Ue+=WyF=x~q&FSL=7rt(25HD82sHh@US*U!+k{8O2m-S`# z$o27zgo@d%8HnZst)o&E%_3gbNa07NerI)@9FoPGlp7W?t@@;j$Z#=fv|y(%FM-=@ z)K)JXXZR?EE(xC})WRz)CPerP{hoeIt}VOh4iu@n&0GO*Qpqrr31;$W;Tp=jM1#_7 zu{jtb9F}5i$?Plp8BCSXO1~2{tk&kMsn*+At=rvf%AGSg}jx z^>plz>+Ntz;qCvS-ovQgNfJh^-qAt}!$>LIDPaW6?_x0_!hQ66`lg}vc36L2?_pH$ z%P23a_wVG=4N?rNceGH)VpYNl`jzYLok;bbH>BR)>AzfWbCcZPPU`RZQvWsv`+J@g zcKwHXhf=)*s~L{0-VwrFhLKXplQ2TPuVpbILMi>8zG7&-0~tTBcPQ2SxC9fWbL7$m z^b0Dd{*Dl)vRIWco_>SBR?iN9QL?`?8_ucrRam-=@@u)p)9@cM!Ijo&3C z(0BY?ZI5=HFoM1RSkrUuI$;=16+*0pg*+do9uf-IZE8x4;+lyHLfKp;T}_`+tqo@RWGHFt+#AbrvJv+9Az++GaLl2WSi2aENNat zPU`{}`j==-N=2oO?|y{4P!ci5WV~RRQ;Dn0HzBK@_cm8q!K?GR{S`HpwS4y~ihGk2 z;^~O%@2ctPsuAWBhNh$_!=BE9LnYf(rfnfbK*HqAF`Y=8r#=Lp_vI}6{+gcsHNxz{ z?51roFsfsUiZyldhCO`-qc!CKUsP0X+NMaG*ZmY%yGf1tM|8X$FyDhfxHA;76g zimkR99&y@VSX<7jQ(auXryk8DfxU@xh6?qR6Z8CL`keqcFvV~ii{pir>^DT%N05aT z6k4KD-yq>zL%}&3p_So#ZB0*6jc}$GaWcMEaa)W~O_|&q3>8ta!0HQ1W@}P~1PkBY zMX=k7fs0C5B7r=}1Ft#s?%TRnu+xh$i=JiHg?q|@e=os7omH*HLJ=h#HP`f*YlLSBgnO1jB0CcdvrgtsFd_L|r*J!!pQAAy8m}Ew zQOp7L3u6=$6k;SNbWthziC=uHa24gcNkf&KNUy%iwZas7k=t-2y#udh42!W8@?XEU z=lZomG>c(9XntYT1PyUaN3q;wiAlIz0zxFAmtkDTN@yrVV@*|5;xx`#YcRK`nGGq% zq8lJnSBLovvp3<)~NV==GsrfPig!!*TU~qKBYNLF`J{tTPk7%8_Rn2 zT8lU{Tk-i-QpRbNaXO{IdetOggrl=O_b|VGYr^4CcTM6)o3qzEzdL-=`Hi}%uJZ-D zyHvAmw&=Od%eFX4)23|hPZH+ORc$fZ+`DI)z#1sw8isS>leE!XR1SBi$7V{cpJA|7 zqz20{P=dR5b94knW#KVBY>O!YB3N@7E_QCcQF6m|eywiBy51CBT2gJxFjLEAV-*Qr z;TZLkPrFulo?hU?HB@+s-as=N(Agn+H}0=7q(!tUgcm7XF5eR4=w@gg;Ape9zTla6dhyz#e*kBwls<_c$16 zXxWx<=Uc$7dZ?? zlj7 zcBj&OBHcH;R@xE1wTBmzA_B+LeG^v+%L#$lEs89f%ksEyp*%V&V6A@{zQF(Z+J~ZtDWN{yf2qSvKeYk>uxQuwnhs{ z5=e;-H|w@+tld7$x&5+6D+ETI{cXiSA%U{2+SBRwl)J2trt;k`$U6u|ip31beg}!f zWp%d_k&K{Z_W=VfP;vBrhsHXrCTCVe2Z3bU!MAp*SU^w7LIgeipWf0_e5?7URO%VRUGWMmR@cQL}d>@GdLYnj{3|$iLCA@D@F# zHp|z|%59Gx{!T(Lc!w${x84@5ja{QaRQbC4-h3TQYa_BNTDx~|dEn;sARQE_E&Qg0 zN$!Wi{Wc1(vQLBvFVJthDM@&qMQG53fe(1h+mGHN9HuDY8G1IPaK1396Wb|l198m& zG6kvyiZ4uN>h{f2w+A}NO;G&EsoO#Gd0(P=!f#*o$y*8?d=_mdg!;9M_o7CQ%J z#R5fHbY@Ic0m#$bm`>A*>do_iiSsF_%l2gd7dim6tUIABYia9@5H_LH+ zzOH;;XYZA|shOPy4!38Ri(~IommRWCs;P>x#|A1Lw zjBl9CJ#$}3)d3Pa5q7X#Ik)uW+#>vya+M@SRUcY0Ppf`rOi0y(agRP7T>>^XzLCpy zxw&?w0#)*EOdtH}%rF|4-ueAEX-Y7AJ*|xax#@Gbc^=?YtJIcm=0mGewU?cQ6bsAw zB%FEIA4>u#Y+iqt?w_~Xw|liqx1rThnBJyyfiG*3ip5op4Hlzzd>s4SU^&Na>HhrH zzWY~aWh{I+Ff+X^gGtZiJ>73Jawce*>RNg4v*~5V{pPS#RYyi^WS}D*lQ7QlF4L=e z?>|PmEQAayOj9%2s0Uk{$?OH*zKE$2<~#KXo3k}g99Cy>-5>@wCnj^+XCKfPcH6LC z$tFAAzUcC&_OuvvFyr3l-_Zlh?b=qW%9DCYnk92(wANM;xciN+3ONMv3mC%=g z3vh^KVMm1RD8Lx#4FA?emyo(u1_Lk5G8hkm@emjvf$or6NDL2@NpA;Du;avH{KRw zWb?*g4CKy;Q|R5E$asQJs6aEXhG=76odw2<+hgrAjqdbYwFv=7I@||#xqlDC2_cTk zbTOBg`pwS4pCkBAC*Q|%1E=MkZ(BP%?`-c{2-^A{uEIe$f#P(^BY59>f7gZ~S#Cxa zIR6CC$Pl=eWpR4nyfh286W$B`+6Rf|=ddgr9Vg!3wPFYqSRZwK)lc4w9kbIRWL&I( zS3@wW-gv)x4?L*XZ@0GCHaTrBirwUW{r%Vjnjmk$KH#tQDG2+3l6@~BYb#l^{6*V?8!mWFU^hTD!P(Q{%bJdoq(+V$ko14t403|eS-UEl7XA<0k%TE2v14CIxi zlPg9VzpNb_V*H|ZYz#f_1GPG0Of$>iuxa>e@uh{rC~9xh%nitph2@2#%7QU`^I?O`jO-{#{#O=u?0-t?JRg5({HoSuF9b#=djTx1+VfMBw@R)%5rY z)6od^5B*Ka7~b}XK2PGg(t<<+7(u+itR3gTTT8%ra2| z#sOMZ;$wNxZFj<0S&I%Zx1eEE^X&i zT{#q(Np3SVS!$!F&;2F=19hAvVDB7&>@Zu8$H>se28_1>+GOJfJSUkvN|W6(6kPiN zZi!)yTl?Hc30!iQ`&9yWJlF$930fdIU|bG38SI%3Eq(hHL11v~ofw%CPvGEatSLEB z-zfCCpC@pJE_Ww^I~)X;D1#dvFqQ(`ME0COv^G8njyj=s05@%2V+|ay5wOHA_frI{ zLk1RzlYp5M0>(T58^@k$1Z->&7}1(@2(7sq?S1au1Wn)NZY5}s2cbpE&|(9|D*-Ll zI2q3rf;J`yZFs9m+maGb@K95t-R+IVeeUf9FS^UUi{QC~@P^CqbOB=$AeoIOJd+7t zQVuAjA zbJw3c4bOq@I|-aC2u>w~3k?{tGq;a6_Scx~qX^u{AUMKJ93zb@z&hG!?DMZ77lw81 z{@bMy)qjTlnw0_Ld6Z9N&jf-JAA|zh(4I(%Cpe5Y;8;V~=dYd(I4j%zt5|u*za%)` zi)W0zfHQ(U^#mtwD4f2O_+cF+Igr!qDPY+Yft@Sxpl9ey(`t3VgY=H_hH z2c-|TjGE*c&5{NxJ#1@{r22qZYXKn7*5`V<3nb1So93Ar$TX=zQ5=kFs zK7MA*WkqaJ5$s*!EsIlfB#Z?^EvUohCLt-K!w z+X~Y06)+q|VutoKUtJA+%!fBU*R8}-T5B9v{PxOK^Ew}WI?Uss1~A8r#F1Dn zLb{DCQaQ$bkT4_f(|&RsHZJ@*M7t#mXr=w?T>u}V-Cz0$58lf^Cs!E9S-V%3&*;36 z9OfxujTtc#wzZ1T-V!kHm=ETJoZqBd=sNELPo{7Cbv?6cV7q0>&63`)_t@Dkq4O6L{!0A-(#){;L`o8N=%|q8mMHA*LPhs>m^IT=U5K9rqhZNpcV&z# z)@e7dSHpNqY;7Klx0qq4Z2)=ms1IQL$_aQbIAUb?ypF#o!jxDGOo=U*Bi>Ftf2VF1 zVOqbjzHfU;&*+*isF7HJD+zA}UIea^nX^Ck#4}7O+B*`M*7~b!?Rp7IGQJiHvJ^hM zaS%s6;TTUiju?XDh<2lX0LMTSaMV}$gZLc>evuXaA{o0k2Czdr>gV=+y9(_XezRm3 zasSz)I%a_FcRgRP!s$ps{Tltj9N2|3EVtKbnpRXj5COc@IA;nk6J8Apj$3c9OIt70 zMX#?}u7W~#bj6IY>KbeD%KZDoJnya|TKF;%(nY6N$(o$Q7{ILMehDDS1_5A}@HDHa zf?MwgywiZ!GXPJrbtVx*AmoFg`l4NlvVtMa7+!y3tHS0F%V&M#?MzIVr&Tp9>&HPc zt;tr%Goj-n^qoC}LdT5m0~G=2SKHjJ5i{y7hnZcoaU9|Pd?GaG!6-E*R|M^bkqk`W zKqAdk=zAyn9%4!k`uwwr>Ntv&Y@3m2jH4rw=@d%F=W;0KnZ)klq$g?dqZnPWoA)O+ z02bmTi)Re}Qw+XjXp%+jlRuc&f>F(6PMN??dm_W%+$N2CtIDM$R_sGO87<2o=OQn_ zXjwRT4|!?a{?rw))BuBJYJm)vHGIy&U|F}ChrzOjJIJ6iO*fUc@Z&sPNU&#!Xb%!ZoNG$Ix53^pZ!_s+E}UA z(eas#F9a4tj;*^DbN-lAzG0a11iu&-80c(GEf9j^wbT!bu7K@=KcVax)B;+jCWG)A zjnYpvKZf@dubTF_uly#_b2Oe*qs&bhORE{h-rO@A6pI8l`vg8bJ3&+8>5V7d2zwny z)YMF7C{Wqt6%wfbA!~y%Uq|C<1Ds{s*JB=3GXL}KDE6FmCJD5i6^(TkM8IfCSc){@ zAH;yFwE=?}P03P}d1oSUu2oj*Big$b%g33%Gl;uMNtJzrKcS51ikvV|+Y1BUr{@E1Z`EB3fp8uj8ANonrr?QdGnQMpu7XTEBEMiP%DJ}HEsQ& zZD0Yp_m=c1_IS}e-aqxXd-h_3LG5pT2z6O$@$8MC)RZLQ?Y*1up25C81^(?&7Sokf zHt-;JUYxxrQBZi>7^QbMovb!p?8kZP{_Y z5Nya6_ai%Jz{0>>ZaWNwoQ+ofb8c)le}1QK&$H)eBfZ_d`@jrsc>7##Pq0AiJu~VN zPH=VOC>^AWJfe#cN@f$Jp&p&3?Z^b-{hlWP?b2P-cHPeBW=rxZUR{5}q`V9L5*Lpq#muM*tla^9+ zy8xDsfDJ1^Qt~=dPBc|PG-V$`Q!u?I5r+3pv}L))Q$K{J%xghYq%<^0Q-L+qzNb*T zh6C-DsTx?Bu{D5JPV(FsJ>PBPr9t%^sI1{HJjbf3c2YH)t`xy6=cHPa&6qoHA)7HR zay0nhDkOgpJw$mNcAj`>Pc&*5Z(KXjj^p3$nSBcutQ%H}?(@<}pLMp+cx{()`mw7|7=K&hA=u`Wk!q8%h~`x7 z7n$uAr;#KoS%N;qWV||$#arT0inT*{5SYUY{Gda_!Q|*P(sLoC1Ak;l7fDFZb&={c z+e1k0QYM5Bw#!@>?C8%YoaX^&C?h*_VH>6>*!O{D==-@APtlORcg#oMPqP8P8K%SX z9Aln`IH@wGm*lv>b%-mlcnm|zdP@*TVx)s~<0vM&xfXP@X@G7vfktLAy2;xwSwM7b zC%Ty#q?GTN;eIjGv6XWmhdGThD%!wPNEny%91e1a7A`wh_iJRbp zU_ckpLSXbV(J9%6HK!1@khuX+kwuRpGPSU+rPm3xWLg;|LRW+l`0WhWlM=2|BwC>j z4y`+P3?e#(=p>5}%^ai?_sQV8&7UOVZ*K&CxnL||JxRxf8x!6L}Y0m3lV2docLtt=ePpX88MIku| zm#!B;3(?fXQ3HUrzwMbuv~cZ>($+QD3**7r|cW+A{*a&b(XQ4 zwr1KdQzpgAyJKl%?5zM6?7p-^A=^<|%oJ)kEK0Q;2EIT9WBGlYXp9)2)~f60e!Y^|W;KCq&wE!q>!+aAPzT$q~ez8*fJwfgFPpQ6nmB6o#@FOp8 zz$Gv*h@Ty9H0Abtd}2fIFTm4F_)RDLPD=39^CbMP8U$D*18jf~LEIoweIK3J01I@| zHr%}{XrU~N;J^YMdNmIx=4I_}PE^vJpZDpBqHP!KiM`0mT;ATbfqmI=B}%9duo73n z5i%7>qCKUfT0n)LIZuQg6kh`wtYp zo2B1H=}nY>3kx?=cr%3?@W|VQ2hm4Vx0B)omZzS=zoYOSEPOkKH{eiLS0WuKB&X(q zzbB}HfAB0oXGZCK@V0c2F&~e|e!B$wZPJpK(!76@(j2cNt@S%93)A0tH5hLz!5(9<9_Cl1PY3+?^b{M`DOQF)?RZ)KbkMvy z!{?|RnQp^+I|#o!2tQ5ULr8d()RUc@N3$cL3j&&e#lm7Xv?dRyOHoumT?<@S6k{%1DmesE}H8+5Qo^J%p<-u%o(>CPD8bJbdn zdH+DTGBVK`sLtOOE3~b&fk9b~e??)c@W{%-utI%d>JPQXQ`ITkEE(cTi31qfYO-`e zWrp`YonY!R;4TmRzXw-}CN4IOb3Bz`d|+iO-y9#T+`!j+XSZ-K?eClSw3>t# zdZYah-tVDz`3^3l5%8;{mhe@P$yvO$VP&eSAUY*F(xr5z(qRvJX}A*WGJxMW+uZPV zKWF^i$`TW2Gb!?85{n><7_AE;W_~<~nDN$?3nK9W;9;y?34H7%x011XB}66i_l0w~rrSZJuPYISoh6XgU z+`xZKs4()&`GSmDQ?et0669c}WRsQ-?z+cXvjy^Oloe?*afYN$gV|`$T4|7c8fC2n z)p%b@AeWFsd>Wa?7MEEihmcnqtBW(IXpG}l4*16!yK-!?8onS?R%Rm`d_mf)t?&i8 zHB}&Akj1+mHX2sS{;{4(s4m|72pPsPPflp7!8Qa9R8tK;Y^rK(h)Ja~%~Vyq6tUdg zk$iMA)AeJA{wL35+3SSVnkCA0QMu3xgrfRBa%7w;Mo9U7^JXtBXp~;opIK~y- z7@IBe@X&@Y89*HkpY-FICDd*GRqzh~3V2!evaRsO*qUTJ6p_#7OmhSEi5h*9Wp7?8 z+3mSAp}MBbx;$iTOjhyUBwgB=v1TG{X<=KEP*C&gy{+a)V`=1?oe5<%LH|5L0`-cO zo{n;Px>y$vdC83K8=XmsYkO|8+yDCp4ra3 z6q|QMweTvZLbZ8^rqyWjN2Kf=4g+VcMq=;p4DaSS?}mhic%obLh6Lv!L*7Riw_L{K zH%j`bW3%{djfGcPW~nU4BC^vuOh+wDm;P9onff%u&$xGnMqK$vAEW$zG7UrpA-@7kCdyTK|Mw!L& zSRfA#gIvdhxo)9cffmYj%Z<{eqAj4-QJy2gJe8Ekdk^KQlJb}z4X7a~9&LPq<*|f$ zmawngouHlOyFtnm*d6dKxv}YCg1_bLJ6oEJV-+VLKd|6N-@+RuTYu(|GqCx|QA$uP zmhJxox1W)B^rLzTZVd9fy=W_o-;iyG>gv?Y`{Xl=K^RW$Me{7QU+vId70MzP)C?Rg zj@ES8Wv1bSfgyWwM_GcjhbJ9#Z)pN_%!hdD@XnVhbWZ#b-miJykoAmh0Oqo_O4T$& z9xvBP#R7%#6QBSIj4%(2hYXmzAHA1Xr*e*jiAx|l7>J(;wF0Fhej@ZC*o0iDC{Ub@ z=HWWKAf!-j3sKqfR82FR*0zpm>5k|yJ70>}{NR|Q(M+G*2|H$z{ioL*s5&NxxVq3S z>maz0&vnBI(gX48?I}BS*ivXU&xUS~-VwJwc}Gg~29TMqF&WW~Don@G1*+N^T**s@ zif%)JB78=nqT$_sT4hQO_m)&BL^E$Yd_F&(56n!EmR(v~w8K1qU*U@H(CmeF^(N!y zXXE!aLTj(y8Lp~KRhvKWr+!G-anC(#)L)Yt9o0YM+3GB1>~$d)esb)WDll^u*}80_ zbkK|Z3_i;r&)8cH18}l$BiBdh5XX12<{tTmbsh@aEKNt#F&&X_*j7dCLb?PnPfNh! zdW6ot4S}EQ`kALveypRiuAezu?w%{+Q5WbunlH#Yk6Pu{-78!pI)=&h+Ly9-qi)M{ z+rwS_<{go!osC!JZy)A)a7Cc6Kac82qMPb3`Z?=;E3&YDXJS!Sx-`-X4s>Qu!b(Q- z0@_Tu^{z``XYRdDxA3`w@RWPaPYes3>bGxNQJ}7l&5w<3RS}4-&J`WQw22F!BRDhi zz5~M)AM5@^h9@3U6+}K7J<9ye1fnJHg$YDU<_i;w4tM6qgklESKnpNVvPXYtj2!H{ z7B`d<6zwLC>gc4t>up3GUE%Po0KvBJ2m3Bh?mH_)28C5C8hOv+6@hOQ@KUvNDTikxZ~pD)z?U@-wU6*3 zEBiuqVf?M*4RV=A$iS<7$jj9scIZbJ^R>1|8e*wGX3d;Eqp-NuYJLa()oSof-{1(+nF zSez&vUKbN zV;^nZZ2WPVVNZmhp>+R;OM4z&F7WjJn&wov=gd;y?&X0y5`>F1Q!ml9Rtf#|=D%`z zk8Qbdj@VD$$^_v8Mc$+_g>sb^zF{qQ!8RPaeJ~Zi-xS|H2W?YX!Z(B}E|l=x3_f*BUN%j5Gx<$LhHhwr0@-;0I9wz!B5c>fQIkQr9vx%lrlEc z_m3(VqhdvX$Hy!?FSuE9{y~-JFIB=G3ILi?cwbK5&XP+TqFMVqMxp5%-^*3PKO{t; ze5V%vK+zx#LgTM88l6?bpD3Ez4DcEloVeoEOBbXRA6QnjC%rAx_e>SkLhhmzAaq3j z;_Va`t5gS-o+qn>O;RfIy(p)zVUSAI=V`(~<1ol*oZVHze#(O8Ae>fEHu>XwWt<+Y z5;jmYYOtO9WDkp13O7*X5^Ap&ims+0FcRhDQVO@FEXZujhlLPNW0g=Y_HiogHd@Pc(zU6VmZP-c4+^ee9YajYqZWo!$`Xyw zS|waZndQC+rD(Zoa~KefC#y<`qiCv{=gKM}ngUP>{b3n?QMdFe!6s!z-E!nFe@%(# zrm?JU7b!$_%aW5np$NHdhAQC#1zFwF~{4ZAYytzzxmlZQUo55)B6&A7zC+U}L z$HkK$XYX zy`SD+<4-9Cfiekqv%;lnoF!SMk5X)#)p4za)oqmQd$LmKqIeJvReCK&qS9;Vmnsdq zI6&!`^SqV9ofM6|n_AdFZ~lbpo`h<_!2qLQ{PQY$RxFbW6H=_7?3A<3F5Jw1(VG(X z;JU0}F~YN3fqW&s5A>qYu~jG{sKR2FC(gI263xGe0+=)IV;HG~Su8Q-sPSZmXG(={ z_A=px;5yc-Xg4Z^63Q^xT30cU1*}x2uZB7=PX<{pgS?U@=3~<{9nD0GQihbn{U@q=PE-kr6y0Whw%D(&=rJx6#<7Ay zTGmr&f<`d2Sh8DlqWs1SnR4cTvdo!2LnRN(*wD_yECk!d!8 zptc#!$r4ttQ!*+SL(z<6)xyiHh=P&o86b4g0D*R~7>Ph%27(d1G#Q9D+0Ae2nK%k#c z66Oh!C8iv;pRDoRZTCIAREVJz2Ey7=EbO85fzNGY0Nt!i=D`ra`;!dsZ5iI(EG@s) zXpWK^WQ&vm-E^>y)J;)NuoCr)f{>-?-Gb+>VWwSmoZbAlE%n)n4dHcuZn1GC6`^ev%bhsjETT*(Uw+n5^j8B+vcj-7^flPgiQ)lB zh^xZUDzM=^eM*jF=dWoFuU<&6<693`PcC>hwF&kXXsV0b<*tiDRS2g+RD!gE81Mme0q9)ZiR!GelyR~^m zo31beOjhm{<5(Sx*BG;Ord+O-fXCO;asD^)#GkT-U7mSwWJh27D~X~%k>cw<$cqLr zTi9ip_eON|&3;ab_pbdQ?=p_tpPOzw^rNdU^+VUmdQ+z%$+&gqQW)OIH|Z^hemI|` zn`$|}Pj{e0D&$!G0Z`EL4I{b4FHBa2NntQqxy1KV_Nfw#d1Q6Hi|0BGUunaRbR@La zY}u2r{rt1KtTnVRQf!XGDGamzQmFKeml-qOb=tp`N6Z5V?w-eZ)GE& z-BoyEK_AElNwTkN?>Gsz!!%-wLV*Tz^uPCsrsu9oup)0;Doa}H&d_O%C2XdIHAo;4 z0Y}7{4||T*9J~6YpPYS+R27^?D`>yUd@99dx4}b%UR5ASF_U8;OrXV|EJhHx@yv9M zTFG_TgZKXk+n6U4xB$-0XTBI!BHjNJq2L$_(Y{FX0US+k6r&22PVgj_Dx5QjndAM2 zkS-_O<}79GA+P$3BnJSA5a!cjWQa|Y0|0huE=7|yU#~8ZlD&1Mz(%oxBTlA}YnrG1L6cP{ioK2qNW{ZKu31rm(-$ju*VeDPt zYtd4V;tj!F({kRpE3i zC}`^uO*w%FL-92K6;CUyNHZL@VZmgpIfb~xz&kzttMS#Fw?!NbBNrv_HLuV{A(O@S zrd*eF7+iZYXOHM0LvF|~y6+_VYDQ1uH}E{)pyJ@`R^ptT8zEi7^yww$Gs~Yv`5Wc@ z*C`2CwwyQbXY35jE#8m?3t9|#QPQOnVuowJ%c?h=$^p-@flMq zEENk$B2#pH{+VbhYhDO@pix*fES~78mI7s2RDMiEtEzb+7@K;Gv)MD$&;|li7X)nX z9bxf=rCaK4N!%~zYgOWU_0$M7;s9JU>%hCy-Mhuv8rJ06jO)XHAlsyYB+-3zj~Li@ zQFI@M?IQD>@uIsEFWy@}65Y?^C2;&>(G8QTju6TO{$_+rQSZ0j{$={)bWWMm*|{}K z$3^FG+dTK|GSEwh=lFx$w94cAwmD4&^+&B1Q(^r^1q6)K>3vrHBt_>W!(Nx+o=Jul zk8x*wG@W#nSq<@+4IzY z>qk2~7N~3x?oLx!+}uMdvJg8Sw(JcHOA=PETD1n6yXp07#$;V{pusqfU^+;Vi5rF?=b*a29b{ccc`&oCPCb zjamxpSU8gWM>BX)EF7&4mBTSC9E;UQN{?e<{p`Z&q3WLgTGPxDQ{J3VG=29J7{sj8 z)aCbvaxTru_}9WVKSbs`U12RJ6fMK9dtegWdUGNqVa0@f@ZeOxJ?U4}@t*pxoU?}2 zyR6Pxtd1-k&cae1S;TM_L3L!o%UO`sk%e_E%<9O(Q7p{r$igu!%<9O(aV)H#-8?|*JUA1EQfCgUn4!qibI-9rI0EVzj{64-4M(U!$Nh^1E~k8#akiIqR7!^s z)SnQHqF@x2L)Rc2L*W<-qiYb3qi`I;2^;HS?cz=7e89uFvLs<{Np49A|E%vSY6oAuZ5WBL4Py#`_(h@dzvbp*%n5$#M6k0u-i%&S2*$b!bFD&r{ zP2^=E?EOv3coIt}%lIbE_wUo5B9UN$5Hmx zIvP!flz9rax6$@WYe2m4d9)?ChmH1~<>h;vyUqza${+2ZeUHO4qIZSP{Yd@sJtSt~9XaAAW~xQKJY-g0p?Quv zGjh61*{E_x@y@w?`=K!LFcTg6y1)I9UOddUwF}*U! z;j{k2n33nvVgMc}9#Iv2In#Fx%p70Qo!WqIp*{;t8A0EkH-h$LA(H>CTctHKaHC#o z4;So=nC~0mvM?(n!FA$)Z>K=zuWF|vxt*?++bMAOCGBJm|KHmwkoPOw$?=4%peuxGw+(?;B3RBk)(io#N`d8V0i>G?3CA;VL ze@Q|&<)3BwEhT%To3YX3p#=IRYz1{&xGaDEU-<5cTu#wf6{h)F>bh91dR^4*7#unp z9L1`peWG7W{d1U&4R)UPro5Mj(OOIn?c z-75?w3whVpW*1p@NrbHz+%gw8*AWmF!01x<_H*xyX~4=bo9SpojF(QbS^DJ)7-;c? z?Gb5EeM(pI!L4EfZ9uw@^|$sQ$uaM8ey9_^Fdxcg`0h6_c#3|ruhXS)iK)8s^~k)R0}dnqWO~Iu%@RZRbd>4Bs*c=$g+?x^EG>!wVz~a z#1IbBPMfH02ucV?GtGPb@8m*{xrD{?mdwH%~ zg~Ma>iY(aQrR|~A>&ciQFbDtgnBjwC=1&#o`Tu#$e6x69%zV9=#>|(Ce==r1TZ}Pd zE~GJI?*9LA%tZWj%y6-#94y;=M~|==RsZX_ar}yLY1}+K zIBv{ez5C1KCZM@w+?c<9cW~V7{lAPG$9oc;I6~xc6aC-DO~CjIT+4f%Z&UOWd12FiMzmxft z?EH(P{E)fIDQ&g?>v$~v592Y=JY+ncVvYO@fdxcsweP$4}#= z@fa=+~}%xd&ELa6h@*PGyJpL36sqpGC$GvNlIY!}vjK zwDYibbNr`N8Ai^%U)@#S!NWi{Az5kvv2Y{fMs%^YprX17K2wql-A)df16J?I*GJ*_!K`L^)vUO6|S?S?z1=kO`kfRXm~3m?;-b{#n88O(GT! z+yrCwLQyEhLbiNru&noWea0lY><~K?=GXKCb|}UpZ|u}id?@gW9^$T0E<;yLos36* z6eL*Bv_{zuY4ce>dtYY#%+?WeXPm`f&T?I5Idqb3FC6!?mY33B_%G?tOLVVL7;FYy zCs^#!(mvYU_F0L$d0~mkKi*^?b^?rTjoqg~FVG_Etvs#$#9z3$-Ji@Pf*j{RZQY8T zxF?|07xgCh^J&Q@dj!M5@y0omyOY8?DSXR^Cih_qM@l$8^J#_q=!#`*({A4Pqw%fs zu_}5b<6&BsWc(|hSFmTg@lCvEu%`uQ!DW+7?&E36xT(;_)!G!bHr`^x2@#@@f3^6d zvoj#;^lrKD-Ey{Cn-`rH;bCjHHYrsPnr8nhZQ*C^(i#M~q?H(AUzi@9UO+!Qf4Rm>eL z=BA0ct?U(GiC9mJ-PH^P?SF5ghq5RsOJPAeUTepq#EGj3&Crfw*ey;{Vg_w+f%t_f9 z)g8k5O3Ed8TlL*==Q0Qno4HgCcKO)bF@S8cjD#+9G}oRa!T0h?X%^hn&wd^AIccOk z^{KjmYu#$`k0%#0R+>G%&3g53IEm!Ip-W>kYLgz!pNzK_Tb40?EL#$`e@e9!lgpa7 z$ZT1hWu3;@tgt$LX57!tFcxDhwrDhYc^mY1@J?e4IciCXf}PF5!;T4u9o+F5W8`@* zuu+d13^W+pW5|1!#TfNy6g$!j`U#=hj*zx_OLohEbQ9P*nAME4bDkR_$JfX5Z~uFkWn3$KQQXFmfvsV)oZz%__SP` z>ifQjKg{{`M9)Vz;G${YV(AjhT;5k#V+~)l3dU|Ghk4$=fspa&F-7}M-ymfPJkvJ^ zhG4SRlg=!x)sPNps0f|en-*dYr*%!%n2m1iS~Fs?+Lt8Xs>Uh8kUEk?KI;&xon; z7gg9J>r_q42JF8S35A3Y-L=&sDY@1jT^C(zkAle-$b8pAvCtURZj6)!FTH#E(C{{Y zQNAQ!U8NslrNvRHA8OCVp&x3`WtskhWmI*H^@W9GP38gCUU%zZP3D0cB+!A~eF-Lm zVNISJ?0)&)i*&*Q<$9vY!OOA04SG}=G5!I&E8gq%4HOl;8>4cLB~rPNrg^0dS;u0% zv>03$M^3eo8~iEXO^dYg-b#6YitzG9T8-Bshl4gU!Ezwgp;VqUhmwu<1B=R``}@oy z1L+Tic&cs)2zpW;8p?MVqCGU(XnbN3Y-CD0L~Vhpp2WU1};*y4&ntAk*d!-jxc*-LJKk z*?o@2cvrgXKiSHBl7Itg8TS9(R;KqixTq&vnH8{=IiI83m8QnPfdoy%0h-_h!6H#ZOW3NrT0~nHZCrg!0}0lFIXB&TTxIa|@OqMdgjX8ON#o z8&p2&(((mE%d5z&+;}o87c3u7*=EAWH_ou^K2FGXUWg83OcA zVhO;A-i&<&0FR$lplm1tA+WjUA5Rwd7zQXGN#$dDGrUwDk4q7t7N{NyKn)9k1R#0< zz-0tL*PHPG0l?#DEl@YKd?+juQhEJAc@32x-kZ@#CL!_Agm+^`v$v!AZ))B z!Z6s6BnZg^5H7w42#Vf}>j?rLKkJ0;ave-vVHUS0+r!GI%H?_U_wV88{^NT@%L+P+ z!VcfKg}-2jui$!#dF4$$Jz$4#2I6QnaU`7{u)~*wII_cMHk}UI;hTo2YMQ(ar-OF* zvagrUqTbP`gLe3?ygqmqb&NVa#13CN`CttspJqqVe`kl!(I2Ol?eP7ufb8&@uK$@G zKJ$-p1LvQF>t#E9fq%vQH#>aZ?-*=pxi}xo&h37MO9>lW>Jf?ybt$mM1#j~g3Wmp~ zhE=BWc~_8pP#j-b0E1i1Dwj&3<@2I=?R};2l5kG3`N~LG=E^&+sC2;r52b0b0F2qL z)jo|34R0f+JCrZmSQ!&Z_CY1X2;QgqA#TOP-iw*fJFoa9^HJp&Cny`fQ;;pC5Uol5 zjY7UfiM!Z56>9OcI#{elnDtEjajdP)Hq(>+CTmfOR!xJ1zpBcNLt)G(Qy=_0#zjTvWGm)qI8BP!Y*j6x*8GE8O+4 zRQL#yL;1z_E}YZq<4rgMQ~5)uup74+lba9O5IbYwo`pg8z8z~a8ce);j7d`&d6n_T zg(jZ+!BnBMncV#;v1uk_YV!#OUF?<))I2to$+2BV`IGEi4PK~|t6z1e$SjK&8mJqnL4BQ$9?uR&l8-Nvv>~kW+&1EUu!#p>#4Bt}*-|`{f zi|9ieR`Za4<;t+uQh|yxh`2r49_G28$~eA>du@nuNAP>ldJ&cNXj7`e&Oov+Q+TE=Od=kIJp$Mc#S0`6RW;azV=i80Z@b+zt zIrfS~GVk{~jqxqUgvN>;wN7JVi*aP5b4N>sqOQW0ni`D_!`Nu-QoH;`(2kX~E*-j$ zMPszdUnU*(1D$cOgo7RhlBRCigp&8gI3fqaJ-(8W12IMzJv4V+N)9GmG0dZJl&%<} znCX2kjy9F}ki}mnox&Xl=tDM;&Y&p=^RsaleCPd3zk^#r(C}YuK^mrS2k%3kh%MMS zCYe`<4vZS_I*gizh=DN%7OHe!tEMrgp)uC|0~Z`)-ur2ch0qu)9UNni{<|^enD&z~ zX1?n`jWNf=myWTP1;1{LIe$9Fg#R|i+UIZe&Hu$Q_WWXuG5Z2Ze$m@>`lt>kl%RRg zGpDi7m+j;@JZylqm(l=xlGp=|R~W%RfdhXlv(n@}ecCiQzXa+p8DH+jY<&G`u{6GR zEvE7HM+R*dLGyNBI=QW{^|Scb#wb6e?iZpnPs-%kg}S8Ay|zP6OgV#B8APmBlf8f)LD+=AB1=6zx&%{%eAB%6rIj1^7N# zDEaPW64N4j(uRok$aa5$v>0BFOnGDEwb1>YJWkDBRMx;%nj_kk5fbatasEfdC2*CS zt|ja7Y)54N*F`w-kiHjio*trjA6OHI_KCn^90}XW_{MNNpx>yC#6ARZ!#J%e4}vEq z5{N;VWjD!9k_6*FbTGwVUv9qQlbFX@Q)y zcHUXPN9TS{@+6z?*nSE&4Cad}Q&MDR#SC1X9Q?yNNyYlz!@t7uhs=%`&Lr&)9=i_K~3%CeXuGndZSs^E0f z)u`Cw+-}<6sJKhv3fsIdr+1@n>!w9p8ej2=dd~ZYQ;#pC7y&VlAx7c7`_!(fuG-^J z+;z2VRqqIM)lSWFZVhRSVi-1@BebM8|5nyN|95U-Ch3s#Q*`B%>N_9Sna{=!@!dge3GgI- z%8(d0uQ+YqFEAv2(RYV9?-HKDujNPlD&HMVm%{%4z>k;`_fvkvm2tmuAL%|){CL@^kk@$aRg#hRBb;z*VIxlu6Fn;ELDz^j0iHB$PI-=dL*k!$5A+Fd zFKqM)Cl@w);7?+!%iMY;*d6gWEk9l-u{*dXGsL`bR!?SU_0)F1B*o!~!cmi!+SvDm zV+$Jv;pK%;9LZ|54}mcz^*V{?0T`*oFwirGL1Me#z{;90mtZ*lA8p?r*VK{re{ymm zLR7*fqS!S#5D03!5NrXjNk9mo6@nmI-B)6Rm#(|&+U?El=ADXev^D z3(>Yz+g(6QTdTWhw>NL4m-M>ZwJo*Xw%h!^&zuC&UUuK#XJ7sZ=ggUT=9!siW}bQG zneW8Cu7a0{R0Kaq>Kn7y8bfcfX9e;x7KYD3Bt*u_dU=x_Ru$kh2fUuc0U)TEMi2t4 z2gC4at~M$AfWmk)26zm|<`i{pyeZ%nvGI*z+#d`fj%sMM&2@do;l}YkDC-!m>4&-{ z^~=b3{2ne2LJT&Zgzr=N??s)z0Wh-v$=AbuI2>NTsIvj##x5yb{7x)_mib{Ug4-4a zr;YhREQ0@@KZHflG`|j51da2r!Xl_c3_f_>7+?_uAJKgm7J>h*48kHXzLjwW7Qv@g z=ztL{0@oWEghdb>$hZQF-~);?42!_89P=Gm1la!*yqtg&G{-c#Q>qIV#LMyuk~>?o zt6V=jk9)#oGKUr0lH$}U9)-9dS2jcDK=+U@IGB&|vazmP4;S2SfU&y=w>(t6vmOph z3Qtb;_y~G|b{Kj=Zw~{T_#k9HD#M;5;Ops5tz)^a7&d=lK74=Su=()fch850MKmAk z@yoMc1z?wquf7HMhSdzLh0PlmnX7$xeD+7?LG&R2UjCNlkI?o^uXla&0~BphYuTe&_42Z6{X#_Z zQnbxw>)HIGdLtz8>=*@=A;4$)e6TT|5AaibK84G0p5{b0Z%ZDhm>(lPABZQMZ(0SP z57bCc2E0Ig3Z!hh)1&3wZiUA-Whia9UcPVMdZ&`+t+y(}^VV|^0?CIa=hR<96JHIt=C46h%`c*~whKhf8-WsgdP`P|FR4?!ZJ|y{W9=bmYrn4c;N~ea4jMk0LS=?Y^qcP^oZT8 zAtA~aI~T6Ae&s0t%JCgy@SXc9>!_)S=KsZh%1!?%IqPA4$qLIW4*%{-t8-ZOwDl0Z15rZM*X+=|QsJ#a?VmV36&g;)KQ zYvq77t4&%W@Kc)1h>2Y(%sDk&Y^VN!!e4#?^x`?wJ+*Px&PS_3?sN!x0RGtQZx-)$ zY`Mqbso|Sz`+Hwy66#k2(4?vq0;D(?Pen1bn==vnA2lT*?T~cDy3R`ci zX5{4Yom;D=HKPCVx5S2OsA2WhP{Z(J3wT3M_F{0gO_9?4G=u#Qwn-cOM&V8I-s*;r z=?8}PhEw&aZBs}QqkX3pk|Ku7gw(WWGllkSasxed?_V z1c}xN3EKHcN)PK{bO)K?%^nl&98JcWR-)nmr9>lm#|3eDb;B01wz@$CjkUvRm@d;W z(eBmwQqa)DiASR0S|`!)-+Do;u5PFi7gRTt(}lLfY2+3S@sUeAXtA_|mJsNn%EG*&R6vfO+xfXPA#p#_8zrp~_$CHk15djs$;L z2M4@!@DN-t&G}NY_%}W(e>L!&56E>brBg zl&$J&SBtJ#mNP|hzYHpBsaUUWEU4$*jN*bR(dA3#7dcpKOU@i!;P!sy^5ju%svSCb zzb9>~-BgbqFCPOs4Z3s=CwFHyEoiULi|fbTy|as%vV230?Ea`<@wo5WtzYkvb$DtF z&9XzyHG1*Y1r1S<{33;?Na-n3<)s(r_@0gR)d&#hqJ{+x%YDy|Z@8^tW5f0>{R>+5 zZaG-d@qiuWUK7pyl0$&ppdyJeF*XuVcF1BbloR!`J z21CbY#Q*}1L;~~(=tsa^Qh=w(-fYls_UQN9pBqKEx^XdEHxAuc=ARTEudb75^FonAw@qOv21%X4j^9MOX)nQKTtS3&Bkh8r zU_p99xRjhrDBG-L6b@$qy%AY%&GiYa06`QN|C;Sc=_kxo&BcM#+Bv8Gl zZJig0i1z>yQP&Sd#CEZLenYhQ#r%eG3{ekmy`=%7FibyDS}L~ARXS`6?AI>f(h~W? zB1~PbpS%whrGjM_CAHhG&`sZ$R*AHBN?VtQwEjhuwt(Yyq*P}=_`pb?zF$jW@Y|=}UY}oG z@v!o?TBWk}@%ejmfA&t|niI3jm(AaJ{R&1sE+A1q;zqe$L_3kIJz5?iJOavtxj=Suf$Zc0*~x{g?BoLM-hSjF+@-$?xK(`d0U*Fx0H039Kqq8j-%Z<$x87-`jX2ps$dY^bcV+!#HHUHh2xpM z)1h5z6q}sY@wdxyZ6ClW7s_rIZhK6&T2`$BNO#{HZiO88{4J_&Q|;3_d8O_h`3lGF zwl%eMSWZl=a@Ts6ADUF+!W2|eJ5Z->yiI(&9QgslCo1^Sm*PwGvQN9B_x97vbL7dj zD|=UXZeK^qml>8%A$Jicuh@TSMQ{D-<+%!E@Yfsqw`q6YR=T`xOucqvyNsYRSB_b+ zZfEVUR6A7L+hnC{Z;!e|b^E&ho3*Q!x9M(It!eLGcl;ai)5_a&SFht$Rkx|cqY?|P zf3Fme(_`10Vu1d`B&8LfVjrW#|Dpfl0s7r%-KT9+iI0W9?4|#mJ>}v@^YO46b}T6} zz_eI&(aRD6%dDbv)ciKB@n#c>C|^{yPnlfM_edy$wJz?dfX&C(t2Tb{u6VMN>^~__0hzt57lt5RPsjV4+1b<1)b=ueE zBz*fklTT1l`qPQSbVz15BBk5kj}a1bY!*~DAu1<90~7>*8zD%4jX4#*6>T=-KR%ai zEQo{Nn-dp9asC;O6Xpj7FuT*Vt|%@BaFq&Uj|rFx5j)lJbirPeL_3^r_&4Lx@BXF< zc)2#YzYAacBKtnMyge=_;akqaVeRnfP+6_FRTlF&z2S%^+qJsE*|4s`W9vop2tO%RZ6~I!R`wYUoUxO#MjIJ#|y)Jy}p@GN&7C?e(26szc(@B>m`*in~QI_eTz6rYHrF1{2QJL~c{p9gz&fB7|&@_(ruCexNU}q6RxRO;+U_pP1T?Rf_4P2q_VU&rT_;@aaDrt*w@8z zgk=&w8$i&?=+D;OH|88twsy~V{UOmK@U)I&vrCP7mzD3lk?i#c6Uknm znJ6vE95go>SF*XuzvA-Tq!dTc=tgss@xK!n@~d1cB^+RYvas@qgyTiW@2Tb1nf6(A zjT-J?3QoZKc5_q*y=S@n6h~b2f;}l^W$R-q@zdoTs08Y?Z+w6!gd=FzAmm8;1z}|1m=L2!kzFu0hxc(ZdW5--0r+}r>{4!w(H|V?Y zob5M;_fVG1_wP;o!9A22sPQnK^Rye$@3Zo5l=e{Im<>SZT@~fmQ=33jK%4l-ptQ7R z;W)I)WjyD3CfY*@-e|gF55-r8=85c~80Vq^f#*EObj2P@3&k0>hvHg`mb?njIpW7W zGFP7Wt#jeQE6=yqa2QI>!U%^Dos%fb^J(Pu>vlLdGfCTcaPj~k#anzCT<|BRT{|JW zcI3V*y56~{x=m@U0`4bTPk{|B+`ag5NP-6<8%JmBJlvUbBt&Ax-XEHWJ5vsa-?6Wv znLlh_!FRg88-{k9I{toAc_I!N3=lp|IYe=*U2i+h zI}2ic1zIs?9@<6xHU0K1nR{y8Jn>ibJU6=ICWhH8XWwsQ-z(>yS~X9+m7W=>p#o5i zx3KT**XO)X%Fp5*+&@wL>_)@`uISbZ@n;kXDryyrTuHyU5Dn)O)|W@x8RLiNw9KPk ze4@T-8LM5t<4a(tm~D$u_Eye%!`$NX;u_pKv9?%TxMh6dN!6-OpLd|$Cw9&SJ+pIK zZ(J=6K|x@2Ij0r_-D9DlIBTKxqzW5H-a$m%OA*mhz+sy6C2Kj?oG*z1)KRZDA>bXr z>0=i2p({!HKOvFm3kWu7@)`7n!n+_8{#jpk z-aJnnLt#E=uGwjW&#ZGg%NP5yLPEXVV@hBWfQ{TKCZEGLzA4~!!#zkQ-bswWrdR%V zpsUDTXVQUnhX>=bzXN!yeYgLn55xQejK(KV5VupMcNSHMTFTBlRqu=2BsR0axOt+6 z@g)}rDZu9}45vN7qw3A}1V1TQs0-jdm>ru)zXU{@kgzKnXArY1IPx*(e3!lq;b5MkZ{h_CyOyRt zqp9h5mg>e#AyY-;o#DuwFki^~#GNkhSydVhgJxPqj)1Y8pg@gZgyu$@<5qa$YB$Cw z5QZE&$EzW4>ryeMVy}reJ8DTBn2rDC(2QuWxO7Y7jT`rAK(jvDV^SmY6JLsdDQnCY zLynxT49w#A!&jD#64?ZIoHg@{tFmR9cnG1dcd6@l{|FnAY>Op$8`n7ow@Ld9diQ#7 zy>`p%Wt;0`^``lH6SnC|uFP@cd_sKipCNE4cR9|YodWU%kNg@pHkyy1d9E>*pAE!q z^2PngUv@UoT-(v{wnsibmVKr9Gj4)$-r0aW)gxaVNum$VJ?lBm2h>`RIyMq$@XtXs zhc8Yr&N(aP-LtE#^TRTqFxB|mbcO^2e((lxF0Xf~_-kqa0vTRI4?^=+edbc}XY{P^ zCI7GBc9w|A1NBX_J@v#rxSrQOxsdF;2*IIJ`($jLPpu!oeY*7*+q6A*mT6*K+tPiG zt>^}WB)pwZmW$Oyweu%8iod6(_c==>{+96AsSuaY$7`d3CN2Jyrl4h=6RIKnbL`xzS5vd~vnnW5k7Mq7OY$b55-)7vrh)LnG%VDy%r3zR!(zfPdvR zVjHCi2zD`sf&)UKID#IKK@|PJHaZgx7t4Zpk*ELCZ;zp@b8T60L%MrnN+IkU(8^Fx z_03Qel6P2&eFODci`ClY>p!M#^pwoWQy-99io3;W#1Py88a-WV@oUO^pRbtiEvVsE z%gsW5X4X<`jw5Fe?_YV=CKURFeDPyew+itsRyy{J3UubvXz{mDdp6bOd*L`|=s(O9 za`EkO$OP8)U!phQa>lLH9g2H+oYJbI8+ffhas-4tU+Yp#tb?J>@eZHh6rX1aF-nNf z($f-3gqNR$gO%c+q+o=-AceKQI#)bB^!Z78nu`{`LF}W)eSl;}+@tInK+HY3npf@+ z4^UgMhC`Jfp^rn&^e}y*H-^{D8r6m|^?z6-1y-l77cn{9AO#SKo{QzDJ}zfq$lD`n z4=6~EAEq>-mwq3Pk2xvV#Q6V=VXRKhS^MT#+A}q{tIYo=grLT@Q!KRmgDg!0{i4Q% z>vajZFvZ$&%xyW+joHY*BprjijbGf9VD@r0;Fu}K>ryd-FU1rCKH)lK=~#-0#+xXj zAXvRZH;^`!`s%G^`t{!Or3<$4!P2vNDu_CbW7x(-Q(_ZU*C=CSWqm!eZaR!-vVaD* za2V=YT*w!&rd%H-T!l5&N^614rGwQzyYlwLC5oc$d?7q*6b&*@0`Px$g1lkaSU-7( zLEf-)0ejCth0nDTS{KZ!Lx?x7Kg8R#e9?mw>|)csgkzJ0Zwk(CZCW3W2=iQmY6SNK z-)+yB`p!e=*C=$UaVQ210?ve@aVlJzl>k~poNAP}C2Y9NX5g4w;?p(%GM~nW&J;2} zfm5_;I8IUUV7y1mZ+s9qMN}T+@6Is16)GpX1#ZJ|0;fonaEho5z-q7(P7y0C)beAd zJ^=F=ReEeV$|%u4)1>9c|HOl3hZCA%m|wGP4nlBv`XJ`06Vxtv-W}+jk5|mo#pRvF z<7{cJz*B1 zb!()yrl{1sgy4)QdvNyod*YypRp(%|)G4&64^-44B&?bmYqrgWE>j&KK?DlbHoDCU z*h>b6y)?wEEBZ3C#$SIAv%v@A9auBG?qq!yL|02?``_g`oPX@c( zo(fe@5l%xNT))EyH9kyXA5iXh`n6;GtAA!+a?(uwHFNLS}b zV~;Oxod4(<-Eq#R<^1qfX99o)`0KQn7(82QWv z37NT8bHO!%9tE^1;~&rH?&Jbm_(udCp+JucnpE)VGrCnWvsRwj@piybn>owM(v1h@ z)#>Dw9j6Z-IkRhGKt}gZtqBFRijFQTjfdBRLbNZ=5Pb4X)A4{-+3}ne$ebWOF$Ayc zIKkD0++sX34fH!tzQfkqRATcyXUDwT=y3>@zN9EIWe$z#N^uhVPxoXQzjVkQj%Vy- z2XXk6vEmr|)bmyGDfK)tk)FkbNaUd5OGj&0nZpgBO53qH0|i=(&Fuu`geXzNBB_QV zD}xihG|L_4qjTu8Cc($YiX5XHEI#$|jp8?@M4A1f0hGq^(i-Ud{HDgtA>X-#Ibq+q z>1D>21oE5u?u#sO6g_%Q*mX{Ah?=uJ5Zfo?42n6D;~YQd`yA(VL0Q&UrSO{aHzo}C zoC~TFuJZV9Q;369gkgSD`=mQa%aC&7chv5^fZ7y9Sc-H==R4?6S_Qbs4ZIFrO=HYjl z0iR%n)uhq`8xcZrUnzvbRGwpwHi)IbFl1s;^Xt-;gim1jRjE*;bQ%=SG;U>$$!dzr z*pe$QP3c}yzUxxft)xxR9YPIs%yFB`MVl6<^2|QGz3imnXvptFC7W^ z_he!ulZohvOql2StNw*daF=D`>yjav__9Qji7!fiKqfAhKqid#D`diT?AtQoBrjW= zk{d#-Sn14LSM16Ax8x!D|56?l|1o(;_?A4x|GV;#-b{YBW(7ym(2(pYRkz@XNZzJ~ z@0A4{4>83Fz2seq@xdS~6|%Z+G}k1t-!bH+qZty^;j^9bScb z0T-ESa*-hlv@rTRNccZFn{_`U(Zoo^4=VINd~`PJZbpHA zuVjMCVNaDPNaaw_Ih1PVPb3v2_zq~eJ~*58b4G)HzfGh13K~i}4+9#&t_auXo1kHQ z=WNzeMuUF8O=HCsG*pCN0UE%s2v_I@&>~NQbYor8ZK~xIi z0md4JAz6Tv)Cd*VDJsoh&Ss@DDk+T0a1o`_?6`uH=!RCt3D7LUr8!BZ=|7v5$Y>;d ziw1E5P>w4&iD~F$R3=BL1fQewoH(1MWK`()+ilZ!1t*$@9!4WGLc@OyG>p%k&AK#~ zIvo98StkVU@#+vKtV%{UoMJ?#NJL29yQw@!&t|>LsJ!znDpa2LhNwt2JF4LgMg|a) zQmq7!Pzs zVm&pnjD{sb!+4O&^Z41UCmD?{qA_{6q>O4ajZfM*E-fr6Qh~Id8ZDz!5TWD$AJ8!d z&SvdnblMm%!)1l&*srFO;HimYbnFp2#&+W8z}c)8MrQ}3b2UGvtLel6!;jIK6QSex z6F>XTW;HN6_1{S+_i8%0#3q5!DT~lCwh}*k&Su@g=-mFDbgGBZ5eU>J5fFfk&ipVP z-oNWCmPuYREt6e4?DOoJ+gUpoM%eOmSBC%B+|H#4Ht44e;-ZAMy>zm>c#DaQ!y^U-zkDyOTC0UnI1lmkLc;e1$n zRlJ-7^h@7;+@<Fa^N-zCz?;`KTucs!gu)bd5Blni6jZ* z$Kp{{$&b&#|2`jmzrc?#rWYqaexVc(dA`Q8Q}1zsR$T-EX68HanS7C9&(!^qj~uo) zR^zaJ;Yr$H2zXB%4wSz-$5BJ~JJiHEY7(3^iRAXEw;2+a&yQZ^s8*KDhR;AMoz?dl z65D;SO~8d3g-(o;j@;)Wm%1nUKIetDtyhO4rH*WutC0Z-f>Y5o8^Sq;;dN9V@{w!<4=Ka&b%3G*TvrKS$8d0VR$#VJ zGY)RyLd|&dFnpaG=K%Y|X%FM;l%Br|UuVzx;rKc=@n+z01Par)s#`5=T-g*)csDcW zT*l*QJwL4cLCb3=+NnGbj_k~ugLdf5oTH*G%g$@(5UJlYQr8iwU>%VfqGNpMJVn^f zA{beOds$v7m-7nlJdgUz-aYfm=bbRbAYX5&bu00$G`gAcVX+8Ww&D~ z{oU9){@=bc9Q(sq8V|@Dv?iDoj)t*yg2nL+TPGq7n(0Hr(A0aSEC{gcGVG7%hhysm zXUD5h>BF%<`~{Q|hVj?}xA~Yl47igY@AJC73|J+2hf(T6d2zLgxTl1AZdRJlIwR0C zLmDX_JA*#-sElD@`LIVTb-CbX>O=cvQXlFLABMP=^&#KoKJ@VS^&x-%+gJ7>#*{by;Z)PI(gQup{Xqx2J^6m(qKe^#>o z^DK){$s&}otSTt0a1+-K^`C{a!~N&T>~R0FQyTx=EBnu~EBg=s|IvRI{80bN```DU zF)+pg+AvES8yMqlnfuKD5qqa5l##(x@p*6BC` zDD@wa`p@gYoWn%;A{XgD##z*VUSqTWb=U%^j;?+Hn<4w*6hZrm&DgZSKL`Hpdn3@3 z%SgGN2C2dmoc*5KRPxubN83_IU}irt6+ZZVw2?s2#UrGo&0YC;K>ThLDd0UVP4fi7 z;8=+{{xBV9=<9%6BZ=n|LGbuE#rwSqfJgZB|* z)4!X-npmBs3a5E9uT9R*J0)K8NTl>t}8$0$a5(%I4iK8 z?M+ZO@)C~F9a2f4UpOM!P9^=~7t~m)pL3eY*v4+lr}&Q>;otyG#aG~Pv;sTbeB4fZ z2xi)8h>sFKp^gQY_b=IhY#tzuvE>W10-Fs|M4voX4AR%0TD$&2ZnQ@ZQ59FWvgI#=q(PzIA`oH-CWt$#8x|2(n zd$G+B#-GsJ8~b6nn*WU2tHXsg1lgp@9EvC9b2*;zB*il!UTu5~gb&-Tw2O731ZPK5 z(2a&dXdjj~x@r;-rrRky4aE6iapyb5D70&DTGO5Q?_X)ecfccJi;e2yX-FC_G(&sG z;)m{iZ|0vFQM%XPAD#r=C6+&gQuv;lqju&=E36<9fyLqAVN5XEoP&q-^ ztrjZv!tQ9H(je>}Ayf*0OPoDNm_2uVG}XzsOkc zeX%PA!IfZkC7NA2^X$c&3>mCFUMTK7Q7lbuuG+JOa@sNtc1s}s{wK7!f7g{nOx`R! zvnJzX!8NW+23UP<_X}>{Gi;rC?(DD$@aeemo|AlMS25`sez*9WdjuF)FwwY@Um2GttTgA@ z1n=PRSEtKncIBI01;R@*&!Z=Bo2qATzo&AO(^a_6Hs0g?wa0eNmhHvOF1yE;x~_7o z(^az0HhG;lcFnW;&gNoY?{#7=F+s1t!Rt)XNv~_~FZQte06dQLbq*8VMBrF^!|Zjg z%C#p8*SV_3zq`)1*z8O)JI9)x&C4Nj*Zv^GsNX1oE2 z@w+M3b>pt7;bAlmOaK$b5q;I$G9pPUqAD_-4QL4ZV^LsPl#b_-4O3^e(P0WPa9a*RHq4 zkR8wBH+DMP+!i8qFgUz5jzzB79~I*z@>O=l*xBuiHy(MY?p`vHk0g zS#&ue^D%qQn7`pp?rOPiHsGj&rN+rG;D~eZZs<%QX#4(8QYHT25Aao}GgJhd)~_Pt ztmpK|yd7|+8j;y>5AH_Xz9aV3v-Y0n?31UpK5G}T`wzM6RnP|A>_Or5$N)dFt=afv zBioP5>|d|%)kB`)^J2`BNf-5@9&D#ab{@9-Qd30(iR+AL@kUw<_IzeN^_f*{B$>G% z`p|Ssd$%TGZ>~(EfFsyD$up{M;%vL$+dE}S=U=VJQGA4Q*Dd1dIf^Ke3tMZ@+Bu;> z{D!EAdnm@W(S6k5i*bW?>qB<pC4HRJpF>SKJ}H;k`0C>7)DkJiqAn(P2#C|X01D`o)^QPxaS=@YjEBHvZ_oN~I-&>RIVlu^tI_`X${JAAQY;u|BSP0VH>`mYJ zBo9z(+p!Rkqe>e3cWB)$-2*tE>?!SPRx3Sne&g7&SAT-T@Cx`M!Q<8^kGcAz&#rEk z^UZdpPaf4QSG!xZ-WGjx^~7fR$Yy)2Pd=(yJ~|RO0-9yB99c?nqMPM%Ts#WzXW%fL ztG}A^meKhwuR>AcxQlzVmYweYecH|K-2*%6@Uds*$Y%Knk33pROi5e%i?y2zx(9H~ zwAr&T&31aEO)>EVU{YM~h5~Y6zPVltx2H!gi`fBu|H*ga05niSeRBTsiG; z;OK~U^AY{Up!XOKhZsKPJ5%k=N0^@(}^B8jd_FARlR#k8)>gJF@lWg|X(bqt3}M?$oxo4CHCg zWKT6)C(O20jEaO@+@WoJvj4Z*4m8x0+Qay6jvtK#Q?>282c}YhkQG1M>=@0)B(!x* z0WNr$e0xzBubRS{<$P`SIFkJ<`Uks!HeUh2oZ6JJrJmM?qITcjq|urYa3yQDbDrns z*AXVe)#>onELUKhPm3QzP73$*=W5;A-2(@;hXbYXgx3dO3W;${wLfe*b=V@_NL?N= zE%&%7>cQ@T-%|4h#-@26ar;&#NH>!gD}1L%QeO4_cCC9#_rO-|j1fDV=gO}oetJhJ zupA1EP`2&#pQE9i`qr>EoH`?>o+^n+^OM~Jk7(O{);|^47Abq>Fr>=E^}a~ulPk<} zCFWmfc<+&po8t6E5&Pa9TEM>lAuNLeU{HG5a4?XH5r;P> zU`7J%2v*~$_Y5gT8H&>R*D5Ff;=y=+@f=MI>Q@|NMTKfqs1~P<+i=NgQwY8DW3uZm zg@nJ1gbv;eF3q-ylk1$eERD`-Gi%b<*|Ifz*V(2lE;`8$hv$kaB4T!?hz^U#naV=F z+b}?LQutJH4#KCw``GN9;j~T4iY=NtWwP3n&uLy+moIbLCL6vgoS5pdak%V?Eq9HI z_f(_MQTJb;^?BjcDGTDqn~&~aca)}Q=TZCeF@JOReJv?#7ef#%Da{?JOgC|T9n0F( z0E3-mn+wKbft7S(Cslol**3ZG*T9)UiGlnF$KP_XiO`Z6*dDVA$A7r#NIBv09)YsKR`iLo0Js$Q&|-BVXar${iJQhgRXJz_Zd(p>)VJ zj(m+n8|A2o!Zn1B3N?-m^~Cv(>BQ9*w2?!b=%`3^XmySXU3JgGqYmvjN5wdYHrY{; zT>Zwvx2iv0_^It!fuqNw(LMM}9%oIX?oi&YXi)5mV!EaA2niRh!HgeiOAAUXV`>XO z3vo7Ws;wf`W{9^fgZr+@R$;Qq95!5>eR`zXkYt`c$(EQvxPODT%7n@HShBlO%#!?m zbYt}OY^}Aau*60Oj0!mBtx2bAW={?naHtJA1wC{R9;&M_niG2L<{k^~RmM%Uxx6ku zBrQxki7Pqc`rp>7a0(WYZ9SHvc-$TLwG`uIh;TL}`8XrV2l~8_X#H7X@^hB`J{(r@ z&gr&Tt+;2tz)C}hJs{}%@5R_}v3KWr@z>s-5FLb=oAeG6{UEJk-L4?sXJzgBU#2b&j^oQG)CV``QEWH@8>ZE9=X~l zAK`A-y4&@b(YSi8w_cB#*4-}a*_E$JMzFsnm8Q7j{%u+dMz~FwuYJ`?0Xgpj3+>tt z+`U%&IcWqa+3`m^+GbJ+|2)VJx@O80IXG756eO=gm4bZ9xyvplVnnOif`NHeC!Yt0_9a$8=#OBpv zXBnU)IB0<}_zxi@=D6`GC*_?V&z{sOw#`DW=5qK7$Bs|PIi#2&tAmZaN8cL{pIl!L zFM27$B*3xx*M#UUdM=>EydT^Xs>6+??AgB}1V|AM@oB71t&591-yNzWh&Kuk219ik zJWG+zhYI1l&f@O~Ve3qKb~S|RFi0q6ffqtJ;e|oLUll$_#xTAFfp`5W73VZa)#EU( z*YhRS&j|~jn&3XsoxHg0=I%E0N#)b>#&+W~sV7y74z`*7$5KHOwkWxUVRtvS`;Vp~ zS-oAb)WWGV%imtLNdY1n1y z(X`3|EnF1h`l(EJnDeieRkZ_2v`+>*mb#FBc2#@)YutiIyl?hIlfWyYoY64P(s!P| z53NT)!{=Y)6+EqX&GtInLT{|4dl2)lzTIv73d@=UO6mA3m7DT&vHWUfRfjoEyb9|B zjT{1iUtPS~>|c{<_Lz_Att)1iWSfs_;FR*mRNWbs&A-Ot@mQU+8w&yo9&25{Om{{} zp*D}lpV?R--h@wcGz8z`{}hX&UB`)gkH=6syHPkFeNqwNDcNJP{fB`Y2%rGuBzUrJ zoINo``tk@})q+C~yb7l``0py`()AAinzAa7;~BldGFxBiexXt-DOMUEv4R>OOP&7N z-p7=vKXanlh*=$W!+J5~N zcMBS8ohtrBtsz)2aRnBV?9MpdC;!x_bSkwV=Y>Kf8F*(@B8SW|2IaD~(jS3dsQci_QmuUw7L^KmXvo@u`a7Tg8tU%9|$aVzuNVHx` z1k*aoiP0H#OYA}dz%v$%mrd*8ap$sWot^VtLv^iD)RAeA3C+zO!=i$TwLtC6i5n@X z_?);XNrDyMRNWgx!OcGx`eHL=W!-~;Jr@r@jsf2l;0zT7PX#^qvkfY#Q6-I>(0~@I|C{u&MX!YPzW~dCHktAE)DtDx zPw;oV!t`XLKSXRaTMg3-n#>0j#?Ds`DEyCxzAo|gO?ItI{d(e4X#V+MR|Zr(E;|n{ zfc0tr2e_YdBmxdZ8CS{b)Ko8#D!~o|#&T2wrhAuXp#GwAgFK zvtdGkTX36snQ@e?snI`LcA`;A#v>Ux*YPr4itK63Ai-N?Cq9tq($EKwBcdyvUDYq8 zdWMt&Kx$P)KQd`WbpfthP|JKccR5FC$M$YU!-@LHt!OU>^76v$flfKO}qBp(w8Mc>peK19*2ALSEGq2H<1*O6i9 ze)-fXtJUX~n~uUD0N$zA!eec(w+UnEoB84&DWx>mVt!;$+-xi!8h6YDK#L0|`l-sg z0W|J;F3znVOh3fep_8dqID`%*uyJ4iPFOj*2UcpWC-PSn*c7r8_gatVm(&(wlE(sD zhAytN|I>-e>{DTEvE;j((S?&Xwk)&N3IPR|d}-1JRc!)> zqbOUU9G6xXVmZs33prbw;88@G6>3S@GgrwW1p6mpGVik=P`Zj`WJG~1&>Yo=jaI&n z8mjQBcEZ@>))(Nex4tL2fI^{(?u+6E7HQib=wGPaT-iPFfOg3ULreZns5V@Ba&&S# z9iu+}PoFnbb$HT}Xu-QwUmsg#imJ+4T-O+EF}7%>)&U z8rjBiRc{#t1s_n5nLv*#MA)G)V%Z#)tY)oYasI@XO?e)LN>D^$Y;z0xIim*NT&5Z4 zf$g_(D_ZiBz(bt(sz)Wi7${c9d^9jHh(SokWxl8vu-~Ed-}y?r&)Yw!Tb&G`1Mu#@ zmv~=~{EwopRHs4syjFi6u`RCzc$JHTX~$dP?!R6O=d%I2rc&s&^i0h^-n;1pOYf%i zH?wiKZxPD=T}2T3&00N6j>K6LQFoh8I~6K3GRwwg98`+8mT7bvMMvRQ*o-cv$TX_u z3XQ<4pJ-MkpJ&Cubhhdhl%*K$!&Utv7Bw}zcF>>+pc}9$4e|LpA1B7qGU3{2-J;Q$ zA)w9#*P^@1fJRs)A9+=ovQ+t*g8Q^rc2h5F&R?KNu)-mm-O*P^{mLdqEoD*tYY??A zH9Th^7ym7kR*$r`DeEy?T*cyFZN#F&@K>8IpcFqKqZHEPTnX;qBRRk_0Ponov&~vbGh3(bd zf@LO;W#;A`c#ym6WiY?OX3@ZxX5SwT%cys-O8rkucfzj4IK(Q0LXOQ=v3NQvEmwwk zc}#%^-IEGW9ACDhd-LW&_kG=sSkeOOLcY1g0}ajH=y9(ZtmnOzo<-G7g=xAhz)L=x z7wbLY!CFc#UQgsylQ|G!?^*PIGOCL&*)3kjz8Mi++kM>A-97lUlGZ4nhE!-^=c8XQ zy*YMiAE8QMY4b^FRi(v}pu1P8v7>gu!kYSJ?#=7bC*2#n!GcD$5~zn5_Hcs8R>&pS z&Q&K@vgLQxp>Nwd>ZR>t#y_(ZoNP zvgTbqmKRtALa+KTM8Fu(mD`+6t2TTk7L!Jggs0=8@cU+*`kzV+DgFI}s4fW~XGXL{ z_hd9E6NPYYAka>Fz|$keJR-vI#2^DcD?}GbfK+4S+->W9HksRdtIx)}>u>eg23=$-y>W>}20k&=^P%Zgr$_4TcJTbh*vRIIA>%8gBE{?`w1LzCs>pb`@uveO0r= zIU4eS4<-13MsJTaI$GNoX>H&*{1vT@Hd)QvlmnTri483s4A7~{SY1y>OaYvwJNHuYS%bw*8ZpU;!)xOtRf}j58VxNxOC}y z@t?E6OHuBMFtc40F)UG!(Q_VebMOv_+-Xx^EYQ+M!Jy5d+VH6*Khx&GHW|tTgE;$D zgGNM<_DXbiEeZ?PZhF705aN;XaoWp;7x2)IUknZDR>Q+d(jz(-a=0__Xh{6;VQg+< zq}lcaW5nZ7W01L6uO&;}AU2^l{KMUl>2WBBvG?n6`UtysN$fG6pn!%e<5npa@DwP| zwe)7TQ7I2c>UH&1bXE?dBQCX?lT@~(D40ND`kbS%lu0&Bp; zw^mxkZ>EBF!0X1epo!^2JK?#hqUS~{J!9O)p-hYb!ACJoG9EGDSnXC#6Ixy&{+$xz zxNoXwew1fEX@*=u%Emp4r`2e25JF}4pJeGD8J50m&0tLqBZ0|Kyg)RH)3Enh6$LY( z17^TVt-DgcrgE91O0`a|aw4QwyQWs(bg4g)%DnW75`+ZSMRLH;+5mv_h9l` zkGj2Iekpgfw4Vhqsy++8gO<9Ut~NlCm^DP~u|W7H?a0~ph@snpZ67`lC!`~rKfT^q zTociX?YAaY=O^f`%Ocj(Tew|d{=5_lzQv|9YR9&0*W01o?2`(%&4ij@vMF-rqanb} z8IQiKJ3hu7EdzWa7W#!tI9qg7O{>Lny`sB9YgTdV9{u~8?AWqwy`@mmQJ{4^`g&P* ztOeVfh1_pf*`j%KG?~f~FtvSORv!w4;a8?Pn6ph|Q^|`BpfI(z555TW;GXZ?o<*to>3yZ)ju7&%`Abi`{K5 zkSE`^X3cuLp(y7sugWmJt6CRY{2Nmi)nWpWZkn<>EH)T=j=`s&%fZdA&xNw)lF&`c zw#$lACgx`4JkA$B5i;Cp*&pYB>Sah)6q6?S3=SEQNFR-?;)Z-#BKrZ@G4R$~u|A^% zMrpW$Iyx2PZ`(v5@rt->QGlU7F;U`QZiiqp}YL>*lzfJlW}Mst)!S1lJuGMeM$QY2#5*gQ*als%oopzXW92x*>|Ju+&MsE1}Xe+570}WgA+ZJA6Soik5|p(w%pCKhnub4-KeK8#hkbiM9;}_*Yu+yT)`^d%GMPH z=t~k^F--T((V+WlMt3?V{&X1KJZ{U1Fx?GZ?nWvW(Or8b-Tz{AuN$I!ON8HO&|S&s zrg36bm~J}wP2sjUzD*axgTwI+21t`&<>-oSnW1V{8^3X@7B>1vL%{VC3s_$9l-CPA zPs_wImRHtOK4f(xJ;Mr#)x#C#&S7a(L;U4S{53g|#!AoOHYUL+mjT|(Q!e+ED?H^& zuwk~!EP(56)dN9dGxe7mOhznPI07B_l;vp=>3|HgUzirg$8fYOsA*OpGHE)ct>U3v zWQ~Qt%y@?M>>4JRl)oI=dc0AHX~sT(4ezijmlAdVE0R8rK8FRY+kNw;6+bd?v#<+8 z%fpNm?272Jnh$R%51AIgsf0Qk69Ykg>D zV?l(E4TmgtD#v9$yu`;g$_GLM9`xSzaDgl9AM99qbH{Dgi)mW1X(r6jYwF!pe)WW? zH>)q!X}4^g>27e}N%h&Ro}h`-XHfA3`-GDUmlEy#bqQ`Mu--j$)y;SQ5+9sV6E?hU zDP&m}O$Y}ZLcnSTUTj4Z);$}0Wj(lAyFndWJ#**qb|X=~%x6d0%^gM7iw|hUWr$Wn zE`*@xQI8(;UZyctcCk_`E}6Lq9}wyQpQ)DFtQqXY0|g`?UFK7qA?RQ}6!=Uzp3 zMP~3vjBe2|x*Pstna#=;p^L)Kj=h2|we3)ua)+165T3)(hofDyLu3;2E(O{tbRRrm zpGXztYF|wjXUx2dzrL|Dq-#OTjxXqgjHbb`<#~1z**h?u*`)Vih zb-O^a?p@g5o?C2_D>Prg{jBaUoR|d3v?=6b+|2z+V!}*}*BcMTLsUxa{qwcfQ8T*~ zi;mQ(P|xmyZgkJbr|^Z(F}E{&{~@h4YG%n1-CbEHin2^fIz0z=V`VTC6z(N>4Qx^x~_*Hc8odj1LR3-`Gm{*WCjTX zjCX=QRX>-UxNt3lb(<2QI(v zNzR;sBj07w_!jk z(3TbG(-Qc;2Cn?M8L(SiF=JDu0T_fAn*ALG0!DV=2BH z@SKm$@W9G%vpT$dSwXD(J_X349JX@qw3mv9Ga4H_ zYPq#_#;!rDZw9p2qAu-4ySDLOIP8hLW^}1bs1=z3ZvEqEiT~p8)qStKze{U97jVJk5MFDy5ys=N2GD}3zIova z@M}UouS_+E^v90PH+Sr{KE9Gy&E{~CSI>qSJ9q7DZ;p<)-b&2g#+ZFld$Cx%a~nKF zx2&D9V?;@DADAWc%=$}tqlZ4-GGiw#7;M;*`GV8!(EvFK(-UvJE(8}961t=e+-lQw zM_R4{G6mZu$fN;rxEh(gHtTplDWE`8(W(Mf5BjPs81$2HTe&Rbc3o$ z2Br|Yqb6jXOXA^E=)GP=)l|ta_{}$
nDuukJw1@m-t9}X8qlZy)8VV;i0vIOt6 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

_EAc%rsv zzZd@LglEJroi(EgX}ZGCdjJuP(D^I_lu#d+E7}sR3X2ElX!dPL7+!IdGnTzN=QwC{(AeUai-J zY%&T*oi$;bxYgyGgqq?_7Gn7kg~c1E4Q{qq2j>-5nmT$5RV@>7m-4ep&ou)nnszr)RSh zU99$x4Qb4{ot_Oyw*`sm7$YMc|KqxZD}mLJ%Lr%zumA`RfEf&C3NPbX&!EBm$n}2y z+nUqt5;>KPV_KsE<74Zi%`S0-i0`)M)}E8Ucku6vwq z_c%TGvh&{I*Mr~WF0IyN)h>T!SSPWEZ2VD?yNZ%U4l(Kw2qF@l3&Kz zHIrT1EaJ`|ZU!1Cj+k(h#O64!Lyqxg6GDZXL0*|Kj%YuQ(LR^ao*;Av0QC$S#IY>( zbB@!KOa1?ue=V2N4NgxM^}kO{Z=<-PRp~P0zt)jo##EV!{4$1`+Ctmgf;`DiS6d`% zXPeax`O64{Ta|}L8r=f$CEetg5vG|+4~Mins1-B1$uHylQEi?ZkyM5x_w;UZ%UC(Q z%@gYsy)xRSb(2@dweQM3(N4PVSx@Nh>@MOo54@v4La{-^7XI+(3j|!g*`jhLH;`{tuK%VJ?kL<{Zsmlu~hup|2UTX zWA+=L`)2=o#@u^(-Lo&Vv15@z(syYGyYIT}vdB%2ydwF=(W=Qp6|Sd+c;it=yG{nF z118T;58K2+EHA5VBiBJ#1f={DzqS* zj3$&fi<=_fXG7R0O*>XJW2lB>sEWVWub3_UPE+G3U5rKYcv^+--k4LSKOBqVo0FSHBGKS zF6>V}y(G=Em;=9q7_kClT5-*}5PJtUlaf5-=+B=Qt|v%Z6I&|B% z(c4-eH^jTgK(b)6!tsEtAQ&826#z_v-ci#IfYVioOkQo6OB?Ne_MBH6jt8C|A}Hk< zJVfHbx9ptw6FtaZ_zp6a`%jmIQR`|HTCH-keJNzEVB4VNYxyr$BlT+k#h}3z&|oh9 zcl*aGm?)c=u%csR!u_QP48zL` zri1lN3ggLX#yM+?PMfsDihEw0n(xY+c&MnmQ-^m*xyVuqj76uhtLQFBU>Wj_)>p+0 zX95|j;@J0}v4ko9?}tg>Z+cDq{$$_xq3rv=vV>pxzaK1B=tJrI<9**_V^0-&m!%X* z84RX8gg|w6zUF9jIb0tIlU~K#F(;(3dw4#qOLx$(10hgn-A7NGPUK~^A3oiBuwApy zrG=lnbI5zRd-$=|e#zm!h1W`su$5h%>~-LXB+^%UPXET*+|y79p9rx`0sfZw88NI& zBSQ`-F|jT>R~3ToK_Zm15R&la0y7i^VRr0&UfEA#-Cdh29wdN z$m2gn#}1Tz<9(X*Yj5|;WngNs3bxT)vZnaJA#Ih$rw;SnOhXR%S(fnYHf zeoI@i4{w)EQDWRrQ3T?rRAwdE6{hHhD7(TqcBuF2b5~;($_^LQhjdJomo_+lbrg#{rcvT}oU)iTU)yF+1M#yH{~~ zBN*xDyFut3mHSe!K`~AE(^^WQC{POiY;VCB5JWfg!&UNj#SE`QMk4!{z3qyb9`&pE z;LBcEz~C2=oJXWv#SD)O>FzVVnfkdcr?+L(X6#BFwRo;_TA2|0~^7A?Rpm#;9k{oHcUJpzO#I%&?N#a`xHF zzH`xVFb2xGh;}$D+Ha2jjPCKMh^9fc+2Wn4ZIiCz?HF=+OPCkx0Da4KWOk@#W{0+o zaMMn&)+K?b$681(>zpAoHmoJH!)Y>8TV{J(qUZBcPF5d(^}y-YqQ8Ucf6zjSdR8>X6-w5t-_Yavi7ZHh0W-`jx(-JVpg5bb(G0g*zJ6o9^AKe zJ6AF^w{@#WP_+yla{-w@;xkR6b1>$QxK>vX@JDP<0!a;r#1A732k~L;#Ep?uq^*V( zDaw<-P3nhm;CH^NzVB>LC9yftiK&L0zcyKg5Vwimwoq3a-P-%^xmco~Y$NBbRN=4A zXu^~EzVPfTn4pA|b=pFi$h+@tp3$xUl9%X~r3(n-fxn@vjMbUR5Yx6f`0DwwSoumsYD=f)B?q_aCfo z_KWs|G1g9+aB>ud;Z;$Rj|P5%E-ff2IDLXP8JdF6z>?~xC4)YwQFv+zyf46Ry;)&_Av{g65I$dmJ zUFRCsPK$Wb^6@zG%L>f=$K!BIPnE~hgpfSu4A!!5&D`mBxMIZ|PulVBx=W^x7?iu$q*t@0}i+kS-zpMm*$DAC22D$>O_ zF)82&R<0;fMh9iVV5D=Roa{=%yvk5*(;CqwGJA z6VUtQBcD~M$I6SByjm8O6NH$wnT=fWV{4{1+hJ{LF_u#>=t>(_m8ASA)O=$Y1CGW!D`7G2W|1udfThjunn7|Cg*!`wJ)v?3a}7s|2@P) z;+lmLvgkTrEr<$-%ZrhcKGg&r@!EIH82ZN~szIF2#Ti6%`I|+exD833 ziLbgGhaJrB*8PWGOe3CrN)dJA-tC3vGnZxN3{$nO@d=z|{2E*ZBtfQrSjODB9EUrX z>DIQTwIj8bn_7YnNA>ahofpkqJg0mm35LGoUnNetR|n<5aj&}CNC}_+P4`#IuAEn) z;`rJcR~zjoeX-r0FlTjfH<^HOG#Tn%p%LKA)D>Kko#x`(x!5E3oZxO^1t#4Xq~$nT z>bqxpM_IWWv~3S0MZ=(BI2(-JGY!%BeuV~qh~Gf?`A56cLeN4lcY|05-y$Sf9WOxT zW`^0qmtoU8^Ci;#3&LI&G!Z7Q#x$tL`M4EtwN^_~S-90Qa+bpCkXfyA>m-=XjpUB3 zrY0&`6N{;dDiBbzmRP_F*en(>sw|{-cA$VA&@&xH_}Kxv)iSCq#cD~T0#auwApV4# zO~GErX1@x%FRk0fjWRnn_t)>g30=yw2K4Fok3brug*g6i#Lt_|peHXf1|O#zo`pS9 zK~1lgGnlxnq{!$d1^oDla}OU&bB~&Q{G%fzc8Sq}=eNkpmX(eS_YV~}&cVVvHxdt* zMh_Qxa5cutTzDZ6g^%YjQmS5)$wpQwsvf}drQg1`IwD62DZ6qRXW8`1+-m!BgF?MP zcEKK&QhmHS?71u}`6LiyTf$_VDSX;8E|!(+%U6?OZ8IG(*ls2oZ(1wqgBEvxrRdtQ zmUKjitO}+llLparH35UioyfTEN zO>=j}yV|A-PQpoi2&WHZ9vrziqQVjpFc0>&6^h4~gTW)Bt{9theoSf-wa?h>@k0_E z9r#0by1!5APoVFThHGI4TkujZVu%|sroz>F(yh!O-O6;v3!|*yy?}0-`$`gq9`Oio z!T<{B0P>gp={FeK9+1?{dXm)aQ^tqTMgfAaCYj=Kixye~%j+dz7B{KX_1 z&N$5%Tf)@W&aY?g!|<2m^k%fd#h!{&@FB88?5mwAVh%_x4Mt@9rJv|IQ-QcdjuM%MgR0 z@whGQoLy0O@~}NjR=>4%Wp!xf${xF7Wo6~GmDTaM*fKPBbqPg(Eh~Y~|CO2f!rC>+ z**5XzuKbsUk7&TTaNF6iJmSu{#mgg}9*J+0Wv%ftvvp)%=A(EpSoyph@gXTM^D&m9 z%PXV@lQo%sDmG)HjgAq%x-$ZYK^pW;O&2&8jmul(fPjJxmZG=a|2?6`)+Wu!%3qpva6 zSZc~Ox|(839%_ z{utmPK?WADW-Pw>In0t_teE1O@?5!QVy-1nkmetm)&8PzEh)9HvN1upM8xnYR|`MV zgBOh8qX#iez3c-w6PR8-uxtaou2(I}Uyh=?fA z{JzhelNJ!(cV9oh{4qIa&OCE_o_S{Gd7hca{aH4opZ+I*5j!{c_-=E*Y`Ti<)AkZ# zuateFbHwniq#>eED~4@Fg%H4d=vGunhsY>|OqI<9pXgVY?{)F~mf8pk#!@gt{B;YQ zMk3gXK$;gN{<1|@e*eH20E0Oten&6}#!@gt{FY!4Y(+pYoidF7+a8!s@e6`MFqVQD z;t_&DuoVHp{6~g?kV?S=h)aB*U=WO@V21cFf4AS;e4N2k2;ad3k3gcp0R~DH*wPLE zu>!xc2mY{F$KWZ{imM48fkc5rtYz@E-SF=#@RdFAN5sVpoZ`p;nwn@CYOd9O7ICKerqHpaQ=j2v0Oy%-|{1ijxT*fkc5rv@m#! z3~zi37*l9=4B;5TSPEu{1%zV+TM-bBN6QfY{XraqSBoPF2EkYgW{BAYgJ3HHg2|F$ z;Log^{yN2B1cP8K1vA7{fpEk2m4;gB!0zbP4{;>EBgQrj{eogQQBnlj0 zlmzeZhJRLppWXw1MEr=sQ>Ya`BzOc81rG582LC}f{7waaN)LRe_$Gs=P%E|*JOYUV zhq#}?@9&0pEAW$g;7^G!Gk6NMSX%=gfkc5re38Mw*bU#Lz~3RmH<1ku{4$uWy@Im} zS5s>x@p8|+@_f&{V${gn!yNO%2=4}!oDg67dYokb2ipnB8A{2^h84h~ zQUR>V;7a63T?8tom7jwjM{4HG9&tGz$1@AzrGxOyX= zpNNonz7lSZ;&)&g53hW!?dt*IQ{TZ2T*r3o(IqU#M7t;;<}k2t)^{9RFD?m!C0+p* z*4bOy@!pO-17t5eWk$J7t{?s#=3SC`kFF^|cBXrDPh!t1^Bg0aWbjOupwG6xw;o-x zQ0bEL8Xbr2s++V&EIk1~%Nhk#=*YquW^nAA2>4oDu^%KJ2^q#;LI(-Gdg#FRZb{Wk7Z4c5U^!_|@+R{h12d7W!}M?pBPXKC#lhxKt~l^lJjN`n<` zhfa-e_@|ZEB(=w@anJe|;zw7M+IxO8*W2lQ!PcBDzDzyV`rT&n@ATj*gTa6ITG!32 zZbrbK)0aei<*%pHzuPQWBhtS`*o(`^7K2x{LsGjdMX~>4clk4)1bPy*LTU|XvbM1A zq12A}KK(zNvzsVrxD=djoR3#7SeLIOXC4XkBDMBWU1aE-OAcSCyS__*iQr$nqWUDKF1!?8 zOMVFS(0_E7|1-h=`dxZ6!SA?A`6ZXahmsEhz{3ZDQvV+b{_J<@TM2#(!6#nc{EAEA zb>w{j@bEsMz<*Ehr@u?DC-}Onl)pfMhxMM|49k9y!NczWgV)$jeg}H~4q8^w`nBwP z$803~-u#VhL$y*F=^?R$ld*R=1&A)k$8-y3JT%)Zx= z4Ex>!qWhqQub@?_G7_(1<$Jhs(&5IdSoz*@xs@+nL5nrYdW;R^`+vMT8`lr>Jy&N> zTzc8n*SBHW~G+l1t*wmT0PS zROJ<#f)T3n1)8c!5asFSsIpdIN35HsYW-k?i#Lch8_*j&bD`;aR%_e!9c&(F%1Ns3 zRDrqjeDdIQ7^`dj74)qbmOtxctKwd;DmJe}+?}dq6g?V88vH+yS@AfPN=OVxAyv6@ z4o2Rp@p!|bM4v->O5+S3jmm!?j58tV!q1GNaNKw>9<)MYXmTtr)l^O0+Y2+rlO*UgDW&nP z4Vy^VFrL~gj-$|J4TVk$sxe~eb$Qdm@zzd%Vy+{D)3GfZ>5O?J@_aZ0dqHwuj+*W(*?)Fkb>h$K8|;~?+S(# zcnAIAodkA4{vVWe9kw0qmUSt*_NALzmu&(+C=J>hEAfwRYF&VM+ud@!Rn5+=Od7rc zg6Ob?yvO^1tj_g2zQ?V%&|iiz6^`5R4IDKZ7Y=`L_J)N){pEnOx=>Ya_=ceVGUXeW zA!gUSD%2jTj+p5JG?3}~TMd}JNCp^pwrrnX zu_Jr&hllnhrTt6qbFOL~w+U_+DZ!y1E&K8%Z&od?>Gh7;)H-I9EK{{vzUSWXs@HqG zX|kg9@P%eC(p59rxb2x_QNIZ`$%8h*92r3|1uH37FAm&9-r7?)NONu!|L&0H43y>! zf?Ip|w8x@i4lm6~l=9a>J|g5dfWDe?(J^O;ykN=SCFMUW z%}Hh7he`Q+U}l;BO8vuNb>%S{NB%lljJp_W7r}aiV2#J(#M)1=M#BpTe2T!0jbM$F zto^w!1?vDI>BiK068);hevKe7SsiEo!8IqF;?0`Yzb|#|mShl`O8&b4f(~>l+g?BVBSfw;PHfdb z;Mrv`P?>LKv1dYyf6X~OPujk|TL)`9@U4vCI(PtpEKdCQ#!F&G~ zoi;;u&wEbWe27w?8{gvc^Vw|fq%=AH=g#2;>q?Tdwz=8q9c9g#<8D1iBFC0<>rhm8 z8e>lybE2w<&x$D&#>8aRPv!>HZFf793x!=Pmz=MA%HNn=-hWVywkG9SY6<9L$@zDl zayt;)3$awGzd~|x!S{v9Qd8$@ zsX1K!ya2BUdtM(+b~#^gx+-lGd%K(~@!;Nm!G$|(T*hb|yU4MVrJrkSBHiO2MXyrhN0imSnZoA7bA@)kS8MyHe0gnsvLjNBj*;o4hQky}N zEy{fXnN+u37_bF-b}+tDwj!s!!gEWmuptM^yKTtnJH5+dF6v*%Cfvun^Xc>->wZ3B z?0IgJgl)c{e#JM2-(KkcSH(|Z|@$PTT zzOg(ejz6SMsBgVtqs!@Raf$sL=r~+)tSV28#}}V7zBP(I0UcsA`-XX=JR@3$;uI*3 zzA!nBCwiGH`eV1F(-Xbi75$0P(a9guNKUn6p3TUa+HPEXL40OIW#yS2d!qeoE*P3O z==EoIcsNXX7-U!!RI!ld*UXavY#7uI&L`8;r!=N*k9!Sd zy((+lxY+7!^&P#q2zbTK8QFG6crz~zQPt4BTkl42eL(jx;}g$NO@-+B&g~K^#z$wg z@RfWb;Zw1@j&lF$9PO9Tb-CKKv^heq>U7hYqrMz&_NQKwPe-{c&hhFnL9G*VIYAxH z=l0>%y@cHB_}uH8)5QZcF$epf&hbvh)B_vc${~rt@$7M05a&M^a5|I9`$zDauO(-% zYKQt+&82YnAPvqr;Jl=CNaHG;sGP<@|9PDE68ssOl!JK^-|4!94-rp`zb^F|%*Cgv zY~6L})AGVTqbTMFiotq7B^~I(`S7zT*N?v!1L1p$ZU>LXT2NE-G0H$w5GB(Y(QGG9 zNd)VNTyIIqlf|=f+^|e;x%z2)r7c06kfj?GF$OE>A6bic+gWq z;e3}tC6>$UDV5}UWVPhnJl|EQa;81vC{#O}9}x=CpCLz^&Kv?(XL(%nU-b}L3y38X zeL~MafJ(!ewBD%QzXOKi4MT?DWORhSc?C6}(j;)VAPx4%+)xN14g*hTB>pDI9K@mx zt6Rheh|WBGruaTRv<_?(57C3zL7|d|q&XOu)r(8zZb1$|vW(mxQAT1mvXFCoy8l9f zPZR$^U~fb;Wo!^XlpmpYdxM@le7c;ezdhN*3vx-F+S zIl{|t=-@YqiPT%-3zQJ^oknHYu&y0wGpt`1#C_%7MEPk@=&ZZkU_CFjoK% z8IQrSw@~cWfJ}VqFkhZdQfnRR&{JEYAlz`Mbw`wk@b{F5UX|=2=U`OZ0e$LBp!#Uw zfI1Zw9BkLP6Y!xlo-3q=0^)1pNwNR+@MfH`U;s928 zP|tma{5}u(qHd6);+3eAh&qg@eo|C_CF(p7{8`|DI3Aexe~NF>qmar-Pcc$dtek2* z>gN9#Q3+Di06A(D-4qIJ`P3tGGNQx>8=!M++p8b-8uI%v$fwaOyI`m0dV7|*cOAM# zQX72gq&Bp-M(^;4pVI~f0gxB=bm^f)uAz6TMRFscp>JgC$Wv1x7deP+gjwrOxq zW7bgH+xQ3?$5Bp?lUB|U!oRCcMGv_T5EzXS(1R*JD(4_lkYLF8CQ${!q|JXfWELAf z`vZdcM38@B*$6FfwoXK(QI+0t(%`#GyhZ#4TiLJkH~`|E$fl@GNRJ5^cX5_ zAX{)SbtoxAGq5YUZTv~%&-0qzc#m=&?Y~VoCtn>J|D{!Ed9g?u&$?I`i@$s3!0h|yj1HdzsIpMGEOc1Y+Ai>UH znZRFZ>3q5CY-<9lxE$50Cf{>`%7bangONE?A}MA{Q`>$$c`7kJeFKbvfSP-qzT{FV8MNQR5DljD1lBpirv{~Ab3W1T}3JO@3x ziS?OP<#VdqUe_ZDR)$>29U-IQGqYgx=U)@p3BNckb*uBaA>v5{TwhHXw)Zp z;Mtk1;mvL4efMzDg0p?h^Kk8<*K31qNIQco3Lbc1zNGo8JU%onzY?!;o7COUmHQGa zpT}u@_nANe?Wa%^H8cPy8dEv@MwQx+<4d+j1Gh%dOyzF_Ii=o$hq}PKI^5Q*v3cb_ zvjSA*a{g+(r4vx@M_`OHkO8`^g9Ob9!K!EQUN$NE_XX%iKw#=yJh1$#*aYT(H6Uva z+~44Q!dJrHE>O~gO5pEGGv}JEkqM)x|1icFZt><6&^}CM@s6qp{W@7=@#dF=TIV-TEI$ONeqR)I zAfb1RG0g0}nQho$a9yJP+Nd#mvkJA7?rqGOa)^s9YAtHWo!O9+;Y!PZH^8%rVS+b_ zB^SK}OX^>EZf)*HD&N1by% z52LLR)+{)EVve3q%}Btpp{n^R5WqlcEv>8A-Xv$0w4*ux@MH$}z&yNuD0Z44GcKe-^B7{zo@R++L!2b}AEa#;I;dAO|clA%AY;e0s@ zvbsWAt9ZN>dwP4U(H=w0%OfF18xNGhfRN&1Jcihpvk^Cu#m%9(SdXD^^(~>2w-|Af zx1t7@?)Q}paXYdQSHj{-DK1Vj+^~t_X0fH9(Vu+rRmy?@ows&2 zNW1y6uD2LsCgxSL1bZpLKjZ||^%g^{0=l2RyeWTar{1$=1_VQbb9RYkF}ph$HnGkqzPsn8oB!! z*zwWIg66Zmdcm^HY*{|3{V_26&qKM#LAZ_pcircq?`la$XLNMW8I-otY!N5DhP07# zTI0GvLU&r*W5KkT>tWGEtIp*53@!o0feIgW4mv~}_#S_~obJ*!GR@klSR=n3C*}|a zeNieYN-Zzh=r$(5fH_+uMTIC)_X3gWh|S2b0PPG46@ae_O1nLGmQnr z_-(fk0enUv1M-1?wm}UKI47n*>h4ve9NbryWpUT6#t%jHRKv z&VZ`BMOjfT&jWohv}ZGsZDn8&waS9(?n%eP@=NK&>{QzmHD;#@s{*cX1zyx#=)}_b z*tuBp?i4~MjEU(D-)Cv%X^1JyyAwoZHjg)(?=YJu$Ry={OCCo}OkNm&mttdzyFjq* z#1eHv{Q~bt4IN|FU~2M?UehspO`TW^opo)LlpFq*^^*t1oHY%%uc;NcG|Fb`tm^#sKIZiyW+oJkI_^E%y*{A|5LohZ6^O??PVXS%a{6;Cj>d z`xqVAF0e9ESQ#m-3>k*5*0{?NKBImvYd2S4fqyQ-Rmf33DSfvMb|B+k)9ZI8XAv+t zE-7my>O9i5Z3wp*s5u)z+2C5?79h|T98(xk7ea{pU_FB@fexXN!?04Mi)&8^01B7# z?g9x+W<-tSIw}>*@dtvytX0S%TUi=e$I{2k>F;6b<#wZ-i4$o#o6~kCp$T`}GOn6}DfxkR>YZKi8$K%g_{bCKhFAAF3yPs`eFXk2=GF(PwOT zpf$vIu;0l!15rE(PcVOFw#--UjcVJ6l_T0`JkwR4(HE@|LP`$q1;KejaLf_16`h8d zH1fw*CAgpJUUG&}{HZROlFvjIc+hd+5w`EUxEN0Ru6E+yMsC9AA#Y&M?O*L6$Z!o()iDI5uNyB0-SbCVn5{@&Jm0h_tA#v8R9K;Oo+Pd z!!8`4bvXs`4f@1spU|$+Cj4tmM#O+@^g`M8Z#F&MH`upccsP3azj6|%oPCe&itiNPIQDr+p3zq4`ZKGsE3pF5No z8s=!XAkXKb%!e3_d7MjRhVVjHeV)ME(^?{3W#x)|33H#vxs=fMZkN2z7MUF`-xOCt zQ@=a&;K=gHG-}L#4{sOS>R}Fz79sSM+0nh{*)YW|NYgR69N{`g;hm1c#`2gy2)re3 z1(xLv$$3m~kO#d1kuh)a@skhn#Yg!mr}?R<^5Vv$!4|2`c)stLi2Qm{Yp};01-H$4+3JgDpvs`cF#gBV9UTa^-nrD19QUOd`^A z?;xw6L3ixoTo-W;aDwR`XDIr^cB%^%fNbWSS;%XaoENB57lo`QbIpAHA_aQ~7a`yB z#2X93RFnmn&vYuNM_WQ@h5fdJqf&V!i4n}x6+Y3^|1T>2&(4-9Y^SAZb25M{d+xAxIj=>gy{4D3eOTth zR!os+Wd8V&#-9xTP3gdkdp%c}9WZY{hQ34_gpj+zCJ)E-zWM3cJ{U_M5Xc=NIFqM3jk)Nr z@;-y;_xix45CmZ&(M(vu^_yz&3gvb{~lZAZEu6(A$HnUNZYRKPzO{k zC`U4W&^0V*7cch(j=U4@;|Ve5y9xcoW1-bRn>L4M#cwfWr|Z*_gxqj_2EtzYWCK4* zx7kpWzD|E;-MXJtTcRq{;1BmFEb7+rdWe7IJTON)zakBpCq?a_mnpLgA^IsaMu>j> zjR;rtd;aq$Ais+4u$@2QiLP};|I7dD2_gCrx?x@>7W5(hUl70675%=A9@e;`Kk%Q! zL%l2dLmNHRxuQRE|BQ!cT+yEze`W$Z%wKfQLB2PniFihGM(No+4%1n?NtK+qK!*#@ zlk)~APu5hWs#5p98`8S%F^r_ub?7BrMxb^%_du6aMX&8?=nOf{dP*Z+o@OaaW9m** zPiYoho@PEvgZl@Rd{$DLKV6>YUX}(YVU;vXC{6k0Y06j{x-(sHrlQXCDa~w(#K6vt z6+d2G`(RJ)r?C$*sly{0pXLAJS6}q~p6L5P`|k$V0@w%aKC^qBmKbE=ct_zKj=~AM z8?=r>g<0-);CbTiMua(zLbIbVfA@Mk=Q;}Wc5lE#$?lB^!yY|JOEIh@9EIV#HzVfm z-CGdewRQ9d!80j&7x2KYb;!`}wQS?Xt zPfm;j{HL~0P6$yN!Sal!DVjd~%lPpLkMjac|FP|(6Wiu_oL%(c6ZeNF>bEMqb~Y9> zc$A!KDQ_MOYFyEW-0z>LKjDd@eLyE0FkqJVfYKFuJ53A-&zP0Fk8;GdXu*bU8}3#2SIUiNiR;z*e7OqHg<7#Iy5b<6q&snQu!)Y zJ2oF%`&`R*ZF4=@u5GB-m$yxODYWUkwYFE1$aYOTTMb4rZc<6;41VyWlH8O~eMVc_ z%?3kVy<)l+Zm6k;YxZ`v%CM?lHe6d#e=WncxDV9nOX^{{=5I+VGlZTR5mlN|*|t5( zXi9?RT05AOaxw3CC7NW!VqqrmdcI)9R~OtXVF%SPR}u`Sfa#HfDsN zU0L7SxS~+_VD4L%uPVH!KggxSEELsHDR>U3`!%++951{_A^Pi%uk_QAON2YBhhn0o!AZT6Q#o9LQ zSen>YF=^Y`^k3@i&ueU}FQWvvjwpffCKE%&gF})FwK>bvaXkD_=|mCAnckwZg%4#* za~*J=ZceGOE)Y{5q~qQbFQpOTdI zU`1}WZg5=Tq*FIXjm>CFjJj1reNvG-SU!`vC%T5O9x!xs$hv=~*J#Ozaa>Vv+`~A) zH7Bh9t;>EKeKanGZdgyNjmBa-=Y2g^XPDNZT~3MawqLv!Z)m6^-mtb#;SJxc z0nz@?cC9Pxy6svk>$>e)m(|JaVM*P!*u%CD(&yKKI~eC)MiVxFB4|Pfnh3H7VZG`q zHm#=GNv7JXaEJ+4qlv%N{yzQynz)2J+vahn*|b()k@tVgAF6(1)9N1n8~$+f zf5RV!NsX$j7_|PUrs3Z;SF#99RWdoiV85F~U=0WPUGDTraBk16AE9G+pe|=|qI>95j44#bMvo5u{XGZh!WHhgUhZA`3&h#V#PbBb| zp6WB3d4t@UbplsN7W47UVqS)}X#wpI>*Q|hh+D(WYVErQi}SAoaEKYZhP$sdmR|jV zqs|o_(QDw;E}GuI3Ltgl8m_)Il3u^0*X!1B+gh)~tC79-S;OsW<>>V&eeJ!5d%d+c zy?#Njz1DCCTYJ$fG%>ezgstHYw}#;rhR%4^t>HRbL+SP3^r~IMU1-(P>*rK0%^F-m zp`q8q^i{Pc0lvKO>i&$?va3EJwY94r@1N4=i}eYkS})M+C-i#0K4E<8d3yagaI{I) z@k@Qetkz%X{UZkbbA7^ntv}QIhm`!M`h*LuKhf(4RP>Ma2}@dktS5%{K9cY5_@N#~ zT|dzGe^K)9>l3!Mevenu!VK*At{&IJe@m}WzTDQ)S&!>_I_dR2`g)>1VOQ%3dWFZ< zfgQ){6JBpUhF2qd{iZ&lv-N9w{RhGN>r=T_KfU6LkJ~!FtoJz$tzS}2U}FI2FX~fM zTfd<1?*!71)PoNGQ-3)faLp;Hu%jhHEMUV*a3*Hs^aE%Crym$C;7&0Jq>SG{@`3f? z8qMtR%=kNpX_toAJ+)K2IlQjIz6E@z^d-GGww`_jO7l>6MQYW+icUh@_2>%zA2B|eG|^Ct_v zllk!E=+I<~roA`Bq%P-1lH69Dkj;i|yDvgpDnI+1@N7`xx8s?|h93H1#lW~fBv;Dg zv5QdPC_ryr#Qi9nR-fXda<|mym_7QN()CGgnS25^m?84L$8lZIB61|G5cbAmSzCa8 zw+5G`@YWmC*VH4zv|XdiRO!>sK< z9~0hZz*yiwa;C%Ac+OM$Onx|y`^nT&{l6e%5vzw*_chk~&}CkNi5;G3bp3!t84;X| z;o*AzIdu>Z?fYnd;8Xz>qj-R3@|vm)6^2Q9bPE3wlyP?|%D6i{CX3HB`H-pjsAg4`Dk^JOfjBF$X0IYBOql6Df*ByPse*x z28)F4Lv^kHs&jdno%poQ1c|V%>A|+h^*PWGM7Hst3bJi!mm%BsT%yYmjw}^y|22{0 zy2q^l-xs;wG7fEzVayuj;j`-^>0NpD;{O;lsIM>MKawSYq<95M*^*CG#bM0`BL;mz ztO|FW#0lO~#h+Glq7Gf({Dt7nQEtrHfn6$5B#hb|=F4y;+0-f}GC%l$_4? zIFCYU&#YEM$7fAhNzY)k~Pv|je2z@ z*LMuXEt+yi_|i~l+y_jtK;v(Xp53beGi3-IetsSTK#w6xv{#{;vm;p4v)VVp#Cs@$ zizzciO?^o<^=Va^^B&0%S^W}|lFrA@aB8(`>Z7WMocD}*SN1le(%)U(LT{N*-CLKAQ>uNu8Fj50b?mwy{~tONCTCkg0J#akU$(dh-w#CL z@<^FrjZcFfzndsQNdRJ7*2S%M)}0CqLH{wmc(+*@K<<|He$ z@26diTdg8_`UY&DY3!73 zfC&j%T)A^D9@Tt*Nb{X9`p-<|l-K2pp6}6og|Z8~JesfZ|Io!tMEm80Uo0H#7mxC{ z9pZ2GMKAP3FF<)?0k|Bt>d5orMPIb&iLL}}0E5Nf_hRQ(k8>lwybVF{;ZMzw_s`Lo zA>yB7hGIcE>)_shtjb6|Ad3V+(Z+h}W;84AdFarP(ekc#QVz7HRHesP$x?PCvz2$5 zqw64mRlOGFBYEsvRZG;N=*Jw<>mUU_8r>j7J5(7_r=uHlUem+&?Y&R*N50gf4d*X< zWYtq?P@6GB)UYwQb5+OARpM>LF6o_r!L^|;BZ2;I1^GklGA-` z+;)6dL{MmFd;Ivek-;Rh<$j^|bY5f`%#K@tE_63socgQE)0B#uyb?J4YE=j|9tlVr zJPSK&6^~~elAhV#SLJi6WgV>X`D5H-Xj(m@W(EpBfZCbymni#EVd(isLW9%+?*5O9(H5qz1pp|F;aAlhO(?wQZUq+cF^#@S3HoZy;zZ=ISwjGo6 zJ$iu*rKrT~E(TFJ^kM<%nG{R+K(PcVE~%Cz$Lh*|hOR!y4OCQ1ROhi>!TAq|JUx!p za^?NHF1b=!fvF>-8G+wBpaz9XMfW;--B+EhDL^$aAYkDz-RI3}b=JDPqo@wIcP8Y) zRFY~(Sx{YyiW~Ufg z+fcbuvL-mJH;T(vHY~;SK!P9LkGDkb3tP+gm#)LzeG}zDw^dV6p`s?o zCSvlT`eG1GXZRW><7-zT>P7epVfd;*>%L@mAF}37&feHhrjyFJ1z}2~D+Qk(?kMi4 z9oK~XZ9_UGZz)Xw_C#T>HUyWBwxOLOghzJ<+i=IqhVj8Rkc$hY4Ukz<*ZzgtfNW$Q zVC$9G9Q97nJ|{epI=O`LloIe1)`aWXc@k=#?qb0oKtI7VGaOlYP?n(=3sP{5TGO6P zrW5meDJtr?w{f|nG6wvbiv@#J%|CSG+OL+!rTd)WQdHVhWO+xK{*3sZHL1UgXKT`) zwWj|fFVp&&?u4`^wO!z8O&ax%vPQ9;my;P!8)&_t%sn#>9%JOxt3IAaZ@5&}fF75^ z=hlO5H_{u1d7-ADQFXQ?j*g2PRIfLer)YFbaecU^YMM&-x~jZJg9F`MZFTl_hk`RTZ?N&yc&r35%w=Xq|2Eg{fnSx6qiGL58Zg(kiuJ z2TLY}&p4O`rt}aqa*}5cRI-7tnPds*CY-tcisP7l)?YY0GX2dO)IX05q{X2upA9Ni zgRe&Fg+o+oHc(YBRa^)96GCQAovcCy{MBSWff<+#q-a}EIF}aM2?X7~O0Fv=Wh;`p z*ItSl2z7NU{k5Y)3j#rZduW>Wrg^MRd|;8 zZCG|YcDz^pN|~!{8#34c>-az8f9-{z=V{6#Xela$+_h~x7&}>1^uZW=nC1_#5SJ^7 zPHoNz6Ep{Rf$sdR;FK=@X0eru`7lCFrV|e{8;yryE#aPY9M{8&AJfP7TY_AFI-gr*=9PjsrsZyr%OYzw z|AGr~EfLgxTLKtA!EXy_#26pF5O*S6z>%{RFq*e*3h4A?d0m3mP*+?!?^oe4vqE3M z*>oxVE-Z^Ei;Cfg>25_C2JUal990-XbofMv&*&9ubOtqAaH4vRtZvr01w@$wl(~gg zrmeUAX*g-?jR8zD{`)UXZP8Ns@%auirJM2kE9`&f>p1eLarK($Dw+fox|+ViIncZ? zc`N5Wb%BAfrIS-Fk2QHTu0s$xsc|1O1EF=w zU@_YZbNUB+lw;Njt4I+tewCh-q~T5OYVZR8*YM&sO$eQ>rg_ieO<Evp7MK%tSIy($}NOG2kNRfrXFb30PJ6D@674ZA`N!m3OU zyM6RyzM|pO(^2zU+Stwf?qYstE7OJO=C)R}&Y1d+VQ^IaKHOrtaRm_9vabsD=)I5Y z^M;l&bobPgb;iM=GCRktsN8w|kCTSOQu=4oU|aShgI8s5z~v63Q^3*;vJAlZSH7lc zfj6a<4v(m0R>%Bt@8T(coL*r`zd%$!K~HsNY|mVBxe39bH*mxm~N7H?L^ z9VG9(s-9)o6?y+(^ejy!9hrS&U5P65-tZl<)YJaI@T80VB~a2-Z<9~zKY{jDh(*x9 zl{B0(M$O>ca8Yv-Y2H-Lp>T&R4Y7m&00!pxjH?iUzMJA zo`m=J^fA|@r&@)cs_4olnVy(tBVQ7XF5w7W`NA^yf?H=@CXx!x=plEA({lHRvgs(Nj{8o|aZo zr*8Y-=&4^1J@xITrx=BvqOML)+kP}e1nFt*74#GVdIB$msYMTadI9X|kJPs<`wA}S zPTVEb^!7V!)VWulG`uLLzaUAt zf=v+SfU8Y)RVx(M6k(87*ga7NnCeu9$`vwiTDIa^yy?*uGH-Ivy_}Z(e#159y3a&L zOf(l=GS~UPIdPS_&VBbugF#H!Uz470zXmaryWZORr=l8R(wN*jvZYyNeWlhK3KOB_fTqU ztlj<~w5Wso){`|4*3Q9UmHFbP%7%@Vq#mUkg;GA(yc@bFS61AlbbYSzy`A=kt~nPl zHdEKm+&jX?#6^?qAbFqb@6SV*3SSZRc3CabkDsn7%q@yOl@~Sg=C+KekyECVfJolZ zo;Do(z?Gyj8$#vPhw%npS#{Xr7`OkB`qlQ6fhD(b2J9;>JZzdZ)qOKy9w+S&uMG{} z@&9b7E&XJX4dyYAh~-{hF_Lni7pZ_%T`)f!58ye3MonMd*T?}9e0J_($81QeoOs* zPLjNr`7QkfapW7p&7JJ_TlxXw$Zx5w_sO8&(hfvbAj%kV66DYPmi}X=k}X-Ny{NUjkF3b%rL<)HpMJ&q~>CY2W5( z!NHrv+*-j~39XBc*`RQ#42{RYUJ|3|;2{-cz)JiKh5Fe7sCqC&Edi+zM7$n|@1 zZ^-ycnmP8enPV?@Ng+%^Zd`ey?&fT*te^sdh%Xo~oDdtAH#nA)*#zSW8A5(ak7~;Q znIS70JBI9NTr5YD@Sr$Ip#fuu9tPDow?qBczk9@8rB0@+*2%x1~mBd%4a=h06ZxlRR`U|BcGQg`=*zpaz|_zAISc|+l~>STNC z>UHwJDc8yUx1KuLEZ3?N;qLS0SHm3)QGSCv+mRDu((;DHYt_lvcJ(^B|E7%J7kldD zzrUwWYK_IYuK$#}(PnE>Yp<1DpWW~15(poRNWLJ5cPg)H9(mR)D!tu^0D~L=#F~F6|M=O-?r%sY@mIdV1XCR!Z z%$T-0O7`kg38y;~2pO1_A<3>8B3bn{F_LxA^bnZm##r*gxq&(U6&-u2xP>ULkZRd* z@~B}D^v~pcXdvY~BJ;*j!Jr3?aX(IbeuyMHU5Q6rtQ?mShvfC>SFX2`M<=R;$qc98 zUCh6q@oTE1bD!)QC>>5@CQsXZJ=L=AY0*ekBzcQuO|QYx+6=0rfI5;Jnb9yv=yCF5 zd=etwffiH>j#Wq+VEoHvLSy3-2A^}++uqiO6|ze0Z=G3D;uFi^hUaWbc++a?J!89p zOKn{(CvYD|#VlU^bgSHRtWMAqh$^ZS)^WAgnjy7fO-${B-JRw$2p&VatbB8S%;2-8 zdoP2}5>G8t;JtwV0`MK(@CQ2fc!@bTp)&6GPhR4+#@JxE#IK8OouOGRcoknow#N)b zSJ0o!#~9$Q6D~tK>s+B-N$jpiw+uDIhYj;pLp^1yLsSyRMdXx9VShOMO%`*v z)HNXq{Yczis;SOiN-}wynq>0BA*XNOi`!nWC-MCz)!7t{ZW5ERldwKZjVoVue^BGf z*DA9bg8TMyu&mIH4=Hcf6uhqH-m1=yg!H~@m73cL6<5_QN7}@Uhy=Az3L%1aQzGOvL>hH>v1wSG-q<- z9ibMSR2dz{>;cOAa%^t3J%5Iap)sClreiE&^|9@tAv_&B53dSAX4Y4vGHVhS_K?*$ zX^&*F@()=P%dEovh=5DDMFT8c$QW+=%!q8-dEf zlUTuF4gB2|G-~7(!Va{e>LZQ3=5Tjq!1mGOTp9Fn_Z{W=o(4(-vOk#vSva>ID>jnV zH_|c9nDztoQ3#oLa0P!z!)xf84V=^Cb0v>cQgZPhxqveMrct56eQzMs%v{mq6qT>W z160P#nsQ@2ojV>KO(r3V7e1Q_kh|SKl z{uFp+xiK=BgMU1g{iM=9G7~Oi7{{%s^htrff zH_LoEnYWtwT&G}7<*maEw=PY;bt!NCgJ8Xhq7j4c?h*^{?>cfKNlrZzsRnPil*LM!^t4$0g|8Z+GJEah%?Ag3+w z)Jj&l_$#nj z#RIYO)zqJ!HJ$0$&pbX%BYI)^w!f*CH?}8#by<@=R+A6NI_Np5m78KabfHQu(|ok2X^4`$ z{}be1bzZGP_U(Jro){>ukhgyX<^2-`1nhVpafYzMJhNpj3@6I_hRMYL;g2XcQB$=G z);V<#!2;<~vOp?>1yVU#AX#96G=(gX%&I%dzdEa(pN!UgkxtochblF}@|2qg(vD%kjIZ zoMzT-IP=)uZFTL`ZNc{VlzuDw-R0Pxq;l4xJ^D-9v+j!aysq?H(eEzDznRLJk8%cH zQqE(Sm*aj>>9_RXU5?E`<;*}iNtcx4xV#+Wb4tI(|L$`9byQ9c%Hc05r{VH)+|5eA z1#nZy@1twmDk>)#<&?y_~FfbbpOygDynj4X^yV z(@S@bGp4;26I9g&6`Kd4h=(l`bH`6?J8bgvXiPu#nc@rn% z6s_8lpOclBmaY}@bAyJLG+ck8dVa$)?Jtk=4Zpf_Q#^3m!Ir=lnqU8;Op3Ce%66Dw zK2^1DtYm3yeQDnN5X=V$gc)DJUHq0>#C52!SkOJ8Qrf$Qc;i921yP=+Yekv!gwmq7yK>AkLz(2|8Q{)O%C5i>^`sW_;aPtIkI)oGMASe zUmIUL!JG%s<9|*x=knfoMWfq+o~_y?mkyqKXOO4%r!~yeth);SjN)a~%WF5*Z+Bi5 z!O#6#u!VJ7jHhXWl9w@#@@ka(zF>~3BCl|APPv|o<-%r%l@>SNPfNkF(Gx22Y9?2f z>(_*pu56S>kFTKh;t2f{VQK3d9i#8KYH`iK-l~OVj65gZsVul2|5Y4I9pN3kxMO&w zIGjWla=4RuNuW-6^jN{4sGctl2(D_35B|E-PzNUp_-rdC{MuE?`!%{-R0r}rA^lrjxIN6L**t4PgtDX%7qkB+rJ;C-% zyJ)9Q`zv`-LAx;bGkV&VeZkE5*#@sguVZOU7Gya^*slonj63B#{ZArKQyIRMzcWH! zci46)#rBCUqxc^^;!ak4GI-TXH4w$X>u{-le(tABio&3G zG_hr^A|F_C+-w*$uJ{d|tkIA7f{UA*H1}q!X6an5Yab06xAqin=CeM#WUiK_F-Cs@ zU%#@i1$dzzRFrAn7iqR@gf}AvYqBh3@$WhfK1{%m%hj|w1_^J%vw%qv`vkr(dsBZ1 zrif0lwPAVb)3i1)zW%N7W{zM@@fng18H}xcmMWQ@KW%najvZsR4wjit;VH9oCA|+Z zdn?RvU(m-ZE^mDp%hFP0W6M4cZw%`fWkq4RA1*5zL%}o(su7s&k+~lr)U(i#`#zqBnVoyaj)8>?Xfbqpthi!XZIXD; z^4gmz$amz&ie(V0iPq({^V6-%h2lsSMv0}%;q3vzQ;z&pNB%I#p7Z~}jdc~rJMslb zen$EXr0vhOxbjE1@-tno^{(OsSJMPn@r_MWmhU{{niJVnyxdjHyNVNCO)6J$lB?J# z<&T&0?{MT#aOB_V$e(EbNbShy9Ql1L)2c1gYAn-g({ETVOk3^QuJ;u8cDP=56my>9 zK91t+EXCKmiX$z>Q4VLNqc~d1Pi~mJ%uyVh?p-Vt_jMGNZhbH zXP~9{HcRpCmf{>oajvV_>?qE273Zg)SSl2cb`=*`ipN-r3oZE$OMaszf4#+CZn4`v z#S?oe>*luzZ7dy76IEtq@whwj`Pjze`;wYZx*q-PpE^%y6 zauiQ@Y&SZJXE?SGbrjE(^7mNg++=a~u@sk@y%WvWXppM+mu0QLVj3Y?oiXM(-7&Kx zVSW)#VwuNk%}oc#j;Xs@unx=CLik2<${6gY-eydo7W=kn;GROBT9#+0oM$Q|?_QXjcD(&gT?>uYN6%reIZ4#$MN5q}%tV(diaPX9r#){L%g@GpPAd-&ot960HJ_Iiq5K71K1~9Q z;Xu}xN^H%exTeF4PzFg->xHbWHzjiWsBEVF+~P zq-p&LsqO-OJB%q!bvyd9>14D#otReah^0Bmov+M6rp1CGbw>=v+46#M4-1B2J7NgV zXjZ17q4Y8!l!*!%ul(a#_B@yX4}Z^|Q|MVdjc3`0Yv8vm+CH9QbPI#nnUmJeMO=UXXm&jg*%Y=OTf{~9^ObkCIyqX?K1h{&bkUf` zg+6beR^zfJ<1*x9oP7KpZ)?)BrX==eIev?`l~)#Ywn@i_dRz5)W7V8^e1Nz0M!e+! z&VA=`EcRZB;zs$bn#c})Q#^yO{Mqa+k-U-Bu`%X-$4EiFBmR4;ZkoOd3bLtEgu%DS z#I7?+KfWa8Uxi%b%HV<|3T0D;-SZe?Z(k1&4*Z1jl=YYe=li%$e>!LtS3|T zhEJEcc(vi;Qd57D+4_g*A+yCn0B?!e8#yMXudw%6(7x_l`ktaE{apIHOPU`}|8A)) z#&w%$0g--SNplSNo>GBiWkGNwx|Q|gM(Wp-iftzP)j|Cl^e004Yw_?>cx?H2A?};p z5y;%58z<_puf;i!NGOt{A@ApF1^7x5-&eW z9WO!W=RP0cB4$U_Xscfx+w#1lw@P~JEH{wrbAO-8KdvaPobP+<$F_g3c&}|f?!G@< zBfYhs9=PmZ^atoz4`wsrFlhSyIY^`d2*it9U|r3$xys#ImZNUSB?*XevosvWNn z!jTK8P#J{r&mM`ciUm%`ehzjl$XuB4-iW=uX9Y7t2g|a)8YJ|;v^(&z%(Yi2< zVz^)KrE^);yb}jnRi+`K;|%B))i8C623>*kDXO%G^}wCYl9UyZ&6vcbBl{PjhTRv}MyD*TAv9#Uu4@AHDLTrjA=V04dmv}dA z0P45uhqP6xRZC7|g}UT4Rb4gWi_oQN!TwbGb8nS-EGe(V2@BBe7SsI}6k=LCBOiBH=BwJt*PVn4)YKJ@>EFw2T4%QJ zBzoc^JgAT9eyWn`k7oP2`d3}{`z)rN7W*F|q*Ey+p-z~}?e|+u_nD!dGu#h;m8Z2dotrVuJUSjJCtlKa z!h&ALLJj3qYaX3qHf`uV$6{JP&sL^WjXGByr)j?x8zaG9yUh zF|D@PTR`g6uQh_nE>O}6!M?iwZI`{g%+yk5uYeQ}$mp&ojtAK7W>dM4kB;rG0p@Qf ziBOII9HjdT0i?#G{RN{`a?_zxQ5sjZq^X)3=kFS1X{rPP4HxXCJ#814G%aVJFATb} z?Uk(U#y`q!FD(N!soJRR*59=K6}j!!%i6BH9z><5{>GePHmx?>clEU0%GzE=Z3j)2 zT1>kv_A;5KSV5>JHQj19m9nOnN~i$KigbPMzYpS80MqSvX(Z>Zg!)^QrYr3(T+&oT z4YPee$k9|mSQG3sdfJ`0q$!tu{`NQRu3+tU-zB$uh8fU=J!<#N-?V$N-0qoIw%dYs zqp#8KGSg~YOWM=!nXKKjly=WBo1Qh>XHmN?D2SnF2DN*p*))T-dqx@JyLFt#-GkD# z3()W?y@umPeHP>_{+-|vO)Ay{pMx+a zF2asKT(B4S^w6)1o6aq!4*V?GLmF)3V<~QTzE-Eeqe2_GzFn%Ks3*t9|+(A<= z>mY^SD5L>?1Jd{_8~^gSn8NyKs?tZr7SmrX_Ng*$u#!+w>Z2)UQ!(qKVhJ_qrValS zgBTZR|45x2am#?QP%*;nZr^*{mg-lA4_&=_l?&%&-hQt-yI1PR)v-fU_f*IJ0Sz^d zNZR$OAHudLWXTsY07xDOQt#N+x1E}qr`Ts#V3_oH(0X4BKv zD>dzJfX%q=xcK-rRZc-$PDAO}gEduqR0Bqg9PWSy4Q<#`I<$P=l(MEQLX2RaP`@A6 zF5>w=gG06K2Ql{E4-=|TS5ubE;BGpWa2>XWrU|V}7dI^175oN6F#mM?Cy zQ{KiucjsMbY0^I+n;*#58`2<54Ze1@U|QdnQE3T*>DYoy2xAQ@U%N&j-ARj^ER>JC zb&%8)E>}&kn|o+?+~TGpmf*QTZLd`_mfl0W;qv&_A3N%dJOn|H$=sT=xG9IFzb|k{ z#O&TKliAS}&N%&6O_iRlZ59kFpe>P7gY?Bs=>%ZgIjGEe3Vl4)VjtC0{?NrugIRt% zelvEQr<^+~sqXRU7FPVIR{i2814}+fDSniMOlw$&6SMO_z&Zt!+K4u#0#S>bqF4o* z21(A37uA#<2rsjb=&3>P#ZA4~_a~JaJgsIc;7Brz^`-fv?N6F$94!f7{)|-!j?Q#SF{%`iL^q?lOB8ja^nxntDgj*v%|6 zjSvbjrFB<8#(&wMRD7*6+a-&A1<2iE+F`a=TI^!MSLlAw{nK&l2N0n{Rcv-H7_)iC z&=Di2NX|BST2HHg@!3!@_y4i>E^twm`TzKtGsA$xSPa(@P?>=d#7i)mfN9GN$VGDl zQo&m>=m46Ppeb7Ia>m3#s7>tRt=*ZElVG(uSVneZ(9rBAZrj>!7LsFSTiMMnYw~-4 zo^uAU+THg1|MTK?<~+C0`8?0_d2XNQbNMNqyE<8=?FyOB)4Ye)yAQ9Y9-J+=d0Go# zP(#GB^X~l7IPaFYTBR*lYV#qm{Dg5orEym)gX{m-hZ2padY@SD{xiY#=gV+Y9RM=e zm!>(RrMj72*x0h2N@bu^cdmEuqOzvDN4L1{BV^1~NrQd*;ClD{EZHIV>A(gMqC;#0 z_|=DOpF(0W0~508XMOs{?W|8tKkd`OwVJJ2WNBF5r{r6xg#yfT4%uPfaqnckv z|Md`tdtYuqhszVykvi%t|A*2vHOf0@y?YMpzI3^nN3|TOqt2U4Z9jdqI)bwvskT<> zne6Bb4Bmk3C{ADEk#Yjqi+*E+Q1AWCdiQS_#PKpjz4?f&L+;NU=rf*Z6B881;^aWL zOTJDHOFl|zOhKG+HV))B_9J1KQpxcNv?#?G-_$q_$U zKPw1zYjVWp>*sLpCmW0>2RjQJU~DQQsk8QqRpHj;C#_YHSc>rlb5%I?Q-n1+oUNPe zs}egWbu`(wz<9j31m^&pGbA3KY3yhbyLD_SD(kJ?A^DgO+b>dGlSk+6yU3Z7|L|@i z45)p%4zo+BC`!wT=a!Gn?(e2%v@7*ySARyF4N$2OrkxY3Hb%$lpacFvm&mn?2?w8n zFi-RkS7xS~tA1xr-rX9JnV1nf0)Bnn-yM8LNR%qSJ2TRJC?zWr$I&kLcgK!EKR0zwu&sHKOg||1>A>s7T&9o1{t_OkAe2Oj-8w6UyEo*wK+; zi|*s=_X?r(0dv(Ij^v#!$vfbXt=?fckfLz}sY~3)7nC|?=uy%y;kxgcdIsY8bA;{c z0c_*CTq*7D<46|1T@7$0$-6VbLP^PrV)z~(g700Q)5heHS(6)1f+-*-?~u4r;iIj| zkIYL72TptUq_?asP!?*eO2%nD7FL+rk$7|xP2G&eYM=N~mza6%i1)#D5?e3*TtQ6w zvfDH64{^7wquOVUhOEn$b<&w0oTlj->yd=QsxnQ}%=`x+2b%p9s6Rn?jc}!{%lxXc z)cKprBId6xRN^EXVTGVea$|e4Q#`^Ld$SmX%)%oQ1d@kTiAOSp=LrDlYW@+F{}AQ> zHcfw6vL&qg#WUGGzV{KN?LSuaAOK zOkPs~AeH9sFgcwAF*?~aHLy0C`bUAf*x}jyiY)8s{}FNbTXE(a(0UH7{r>Mp6?5m9bi}U!aen%oIxUd^3uiuP09{!AedC0hCFj1k=}})MO|@Oiru7fP|NJQIf5ha#97o;k`FxZ*p1?@cR3)P9pN-0kLClWm_0+WX zPe(OC6jN$>X4mVVqi9A2Usq~>#&7*(l$v5#AB3Qk>D(w{oF=caqUm$Zhtc%JtMz;| z3Ns9S&p9yjP`v5GK>WQGm^aW@0eMqD^??I@Ft9JTpMm2iD)Wz##^gsNUXO(n-mPoo z!Oyb)6&~ckdj;PMo~7GeDfH4yydxX@WY-PxlYP;DiQGW7+Z6DtIz5!}y8!3yx<)fS zWLqxHb1EZ;tFiA$KT!umBpjE;lO=udmn?V{UZ>o6Q0_q7F^a=uXxtHs!((XNVT!|J zAZ|>gu6b!?Qd9%B>sk(`Gh~#IVw2e z;kw3fJRB9n#I7XM_g~Kv?T>u|69R|M%k3g|y=xluAAJ$dbdUhN8C$g7bsl+-phsAH ziCq`ryH`p_xdFe^?co#l)!*IDFIxGWuKu}%eK4CN*84A>aq#Oa_4)1L87y_>kW|6+ zuQTo8SuB3_ka&mZd&FPE;_HUQZ!mp#rk!^>_>J{&I{93JDBkVAfS3jczbO#2(fA%;(*e~zzfM>}FWndWGZh88jL7g-Wz&XD zY>x7GjDofZ`0A|+X}*3&qGQjWN1|;|cmEHp9c)~IGWsc13SSaFK{SpQ9;OKrLhi-{ zkzXV&H7}@zA1QW%2D_YKT6|tm3Rlwa>9e)aTGMDlWpT8dRO&>I8-TT$O3kKH-_zR* zDnUV|7Hg~$^snXEvl8_2$1u+9vq#O=oIXx);p>J5@gfzBiYii+h01r$dBNOxSzl(a zTp#yHsF>ZJj%eQBIx0ocEaGLg6y78CJFDa5piJJVT)&WM)hAYjhl+`#1si>NG2C9G zwtC?>!$&D}N%%aW7G7mBLBe0?_w-|OZP`V)zev?>;tF`9N`{$4Fq1|L*HPZZ8kA;> z%?m-oVJXI%#J+NX!Bh#Y^gBVrYHhlfYQ2@!y4}U5+}VTGw&D;UsJCZ_JRh~7-u6mX zPy2Sc-gdhb-u55rJ&fv|C}G6v9VxUhjFiIN5=OxM9u^ZMJV3vvZy8!|yXEKg9!B-P zg7UI@|4uI5AjPnHM+$W;RwXQ_U%B3%iB#{oL+b6B{>$|?HOc+$p#GjG^>1UKzvoI} z=YOboFxA_?is8uW9VX0S7%7E32_w||dKMETl+y3%tA^IwpZ@cD2UER|OE6J7TP|Hd zzo2sJ?=WF1i&Y8Z={L~dqp998L+Wjs_{;V7+#&b(D(dfiseji8`a4ewuN#=(_+3IA zeaFYu_Gs4%BiQ?oH9gm_6^7ANAw)}9$n#;!A)#>XW`{ObJ8br_`k5ONc5Jxs?u6z7 z<5p!u42LHSqzddf?K@Y~^GS{Hd#bD>F`|A)XU7N6JR8>hkFJQ=PGjeNM#nzmiG=a> zM-O#;weQ`|hhB)057hJ=s1arj zW;bq)f>9k~M6|JsPuSa+V6Y?~nxeIxPD^&HL-=w{{a(@aXUkqOdvBjGgHk7GdObSfDSE}^`aJ#4 z)_5OZ1sZyUMe@Q+G*iBPOz{GbYa3xNi&Y6b=~sHHRUBs2CwSpO7RR3J7pR-C-2t8t zQ{*ig;VlB=yQ8M(&Ke<%;;3a(JI5BIvSTf%D>f`b0wu6{`VaKBSOcVvQAOdXG6*;| zNwL+Ighw5=7uS@t>Qon(@2y8ONnmfGoWVjp<-|O{iGC*l4oorJ!Qyyf1^W#W_7h}b zIfWK$)Hg}^)=+S^MrdXDUSHEwR3n_JMVySUMcf)CR8uC``h<#zXkhgvC9^fDLV|_w z?jqQ2#lS@+ES5mNzyq(@^zPlVMzGO~Fq58TmIZsufqyT-L7i2t#X=D!yub^k?3Y%q z->^58(7>=}T@KCRbWXU7VQQ-BG1Umq5(wAK1c~g7Fw8ocH^PMEbDhGSRDQO`cxb$K zOhqvV)Gv%tOi+m7oX|z3;3t03?ZP#b>lO`Faw5HYE7u59=tXYBk@OC{mNG2HQpk7X znw}fi2$3v?^`Pm+Q4=)8F&)KnlO-nMN(l&&gkFYm11q7S5REleQHjGadrg9=J=K(u zY$&=Z9m9o&Cha+d*_7E^uqI3A5Uq`hFJ`RyH1w3lH)9R_PUTaY!x*(GV!XK`O0cr5 z*RC;(GqMz4Tq9+iMj5A53anR60!BDG%X1I&+qXIt9(7m8f4nJc_4B(!C!OD*o9aAY zpu0yk(`t>J)4X)EgEVc*=KLgK-W=6tqt&&0rV*@x60Tu57dlBB$wg#yce|~|l=|ER zYeh<+3T<6#5mapwi)}~$l*oy zgrZ6H<_K+ecDb;TGLRCn=i#&l^uJakG%+YX;V$}>p5*Zkv>|Zi<*RE=TyKM}er>&& z%z1{Vw^^J~WkE${okb;vBJ!nO_S!bBo{P%n%G^cut4oyHO+i6oL@=0eujSD%#(qSI zz%k&f=;>>_y{GMV;YMi|SlP4hcA=W3vgd1SO!tjitjWXq$iHb+@gHi67NlKB=8KzG zJFsLp`R+u?dfga(66Z{uSElanL^a52UBhGC1t_IG#A%<(hZ-63X>biu(H+)Xsaw9J zRUN%UX*!YSomDIC2;bhzi-}?W<7wWBD}`l*K=fusCQWAf6hLDuJ;6#LivqJX*{*WI z!s3I3N%Sk2>6!NkzHK$A#3WwISz_b9G$wtjKEXGW;M8|K({MWY1*BaZ&IVxSO5Syt}tbh*o&mOUwaw-fRXf`MW&1G3*i zB5_(=twbavDA{!|fflG3dcR9!8CH`$GpvI^vhCp8J5(&7r(_|Fp1#j+>nXlnSi^P^ z*4ulozg_r_-UStsN^}q3kd^G)5zwBe1C1yApV(g=q=vR1~ zo>H6T>t^P(M-G2CE)cv+m7P;>jnqc3Rv@Z;ZGCUP4yLsc*%hhXx3AoPYg&K~3e;wP zW85UyBjA1;gxA<7f`k|8x80a1JkKIDXhQ#oJm&2^w+V+SN_d8z4auB0MC!zL3R^*3 zxj?2sHDB?i(L~+8N$PfgN5Am&04&o71nd6W1TTZNo570Yycbr3AXLJiehzC4!E#El zOi%R-;s7lBdjw0kP52{&wS~dLJ)DLn42_Y$+a)>-CsrqAgJmfiJb`BS+4Bcda`d5{z|z@5+kY)EuX7ZKQkt%YG=%zrz1O{t zxGophu2i5(o(*Y(U!4&`c%q+t@qIo@e} zP4D@~NT->QL4|2*CL8r&YcrWW|2vm3HNt$SK5kQ%28zS#Os*Tmz~;mxPW$Xb`oeB2 z)+^a$$2*stzLcI8gAQig+kD%5VENr>(>YHY4dG)?J8a?3FW}>T`xg#dM0CmUE|(Je zGH?M7u`KL}upI@M1UkdNeaR`LY>~mh3$qNyO<>#v#!Fzl1g0-UJFOiKctQarFF}Gq za`N`STyk1cie(sQ_+!6#Ipnb(4}QLoJeW5wmRbQwo?`YhTwzda4^1G`+mhv9@E zdu5uK!%O{Uj8s!l@Z-20B{g5oT zA`6^+%)q@C$RF8aUGK2& zV(oD>Jngkkh|y{F+F?%&>ulYc-MC?sCa!~@`q<1#Q=fyt*MMA7^L2uf<}PP=c0p$$ z9LCMGLc};1dZOUT?p)+~R~_Tc)Qd_oi_dS_zj)bHLIg(@E1-IYac1KoIO^!w$`YIDwd9PXp^1k9M+nWx=r>ydhMZ9Ua6z}HY{6(Z0%D?vWSymJ@ zuJRgJNz6-A;{i~sJ<2$v3=W%yuM%HgAdI5+HqKb@c;q98eY7xamBXF@ zdCM7h-}#Ya#>VSH95WgmGd4L~zjw@V3MXqE_C!H}JW0YgwGO*UXuQQ?9}~aLF=Ll; z#=}N?N|&o3fs`G0yFB$mh6MYck~+^P9~r)`HQEY+k|sFT2Z~aLh+4rd~FM*>m7IfTprMzo8Fc^4N0*0m}^l z)1(;3giio8IA{Yjed8B>?l1y2t;-!r!1OXO&$SXTzr}Cp0k9nQv})UpHcJ3lD1ezK zO29Zk%WOQ|=T;H4%r18bLDL4HWy#Q{`3;?bmThvS1L~a7ZLHsL2cS(htjBYb(XBMv%tOJo z58xIb(zvD1b(Fv*b-7+6aK{5ZV345sll+EdfRoOiY0%QQT@?TZ$KLVb$*}|uj>ejj z;`NO}pX+%7m(bn-{pFW zfOW{g{4o+RQ=H$B2VmpaGnIgi4FDrra}1$1XQQppwVR;nyIidV?ePG#a2Z;(-*7dc zr5GmTnM}~e1fUIXHELUuV+kH=O0>JJvAEB*jo?LgxpomeR{-8{8J^B>NCYI4!H8!P z!AlIlqpG$HR<#wb`drNfEUe3Q9|5~x24)&20kdoU1|7f{+0#J45(2;ox%Po>Amln4 z)B9ZY=T5_OpzCe|=L~>T$>4(h2JFmjqYeEvM%yR?H!=W@a1+N!;|j2jHX8bTtI367 zUAym2X+-s(VZSD&-*6t~f6mZiy}m1ojdYHVL6{YC%B?48~7|M;1VO&nWSm@PbLw_1kZ3d<_cMY6g$ zt0fHjCtbcEZ%2(}3(qw2?0_x2rx3@q(c;by*uvX~II@N3i9H*zh4(n3m=MT_vjJOp zZ8efDJd^%xz!sjTMuf~1$&mP?&knJL=c+-JedJlItF^=zHgg8_P&P$I{lpfY|J!)d zhnWYV#P@DV-nt;YrA9J_7ug#As;rCS`6eE=@aU+=8JcQjTX^=bE={Q0|e zGYQlB4fTE7N_s}ubU}^83|vWg%l{H^mCT&|u_u;cO3|K?z_iv^U2D@zSd#IzV34Ko z`OSkk>IuhK!g0h997nVp^aD8hBY>m6!WY2rIPeRv@D<6}y*Yp#+EG8J=ew0?$Ix3P zyNCzQ9@Q}eY`^RIW+hHX3hLL$4`;(JT*5M2ou+Ad)k9&xOO11;@CxD8py0T5wz|}H zLS5v#nq?{|WJgxyhE&&BidW=67~+0!CDFoH@sKV$y;9cX9L4}tp&Bk$r`}6V8oCl-SkW>+{8%8oP z{)6!}PoeJ}=zEAM+3EAo#;ao}QnGDEqA~W4c&1Y*8K29hm}la z*J+e~s`)Xrr+DSG$GzpZi0-4Yq#9*v!dP0xDE8K#S)f=Xu-V7|(b)-_5_fMb=|4y+xzVKSx9ep?LL^R4Q-#p?F|%Yy)U;O z;W%eEj?w|T$RoNKp=35e8tTzm+Kvnm-tTz=&@SCIZPV>|Zk8mU;?ecTP0G8_FVWEi z4)rp>TrC=tgscH-dhs?;(`zdvyA1Y9(9%MgmO5@DTB>HWWID`fsa~R`a*38=Fli}8 zmlI&=2-vV3BqgsS1q+oa!#rx*^IgSHnJJh zBG=PjihAbA_52;HXNY7&@@ZDj1gV|{Qax#{R062@a*8mUDk+Vd0;=Oos$(J5(OjW0 zTSLr-Lc?3j(~O-L93k%V+dy;vt?}?rU3r@bwGP@8nD(+yWJ;g-t@Me=flp)$lZUk9 zZ|NIF^o{EW+Hw4QJ+p4Zf_2jh(RE%L=`+vv8Lsa#Oh0z*3Bzwo+yvW{JW_2m6w#cj z{W7E7>@bi-B}>o;84cIwv3PSVO0jkb4FI!yfFE>dIG7xrMtUxUwEvF`=^_c~IZjfY zW_t*!UCM;u!FHMBgdP2Pg!5eB3}s|T4s62|1^V8<6n#I(>@FJ8_x5?{`)O9-muoyM z&oQRCh?6Q~d|8h3-+;IRvpZo(Sx*V#NQ`ulZtTTGH`jx1HV)9uM$pJiMmKo}Bnyc4 zZA3RS0(5g16lU%djV7U%rbaf?etRp%M$n2hwrTTB2F;jcn)%{Zj16pr*)Fez+6L

d*1iHsc2R(`*=L$PbXrUv5QfdsgC18RC8C*1-);1N%m& z^o=y>8{yLU!)!0?p>IfJnMR*5(OR{3`GPqt{MKsc_Xm@QIppi|W2On(h>G_6<5{;PF5RE*qa3qFcrBScI zQanaDN3_leS7#c! zX=|qaDtS`0ygQaQ#-0jb!R|{t6tW$aMGPiAUk2Qi?NmPHae15V-iE;3B2{M=?IvodR%d1Gs}@d!npCfJoceB#UCV%OOa~@5g!~L1 z?wTY7^Qk>JslMH7CHyi50WJvOR||mBHq1M*;cM>a?H4Vz-xJh+|CH*RR|)*;2tV@j z23-7e1NhnCMpJIT$0s)Q{sKI`gx_?+@1z7zJy*i-nn8d?GQbA-5X21<)pyUt23Vkz zw&AW_0SjfB1P2!A(5rbkF)wX*aiWs${5(%j6s@~pPwXXD=F0Z2_3X>`t5HIIkd?Rw zj*zK9BJC;dUKCg_f6eZiD0?gQ{ApsKh42;u;SUB68{B`&IM~fN3^aJ;wn+v|Oc@LZ z`x7XkRk?DuTCM+tH9hq4PCOcRUTO=%4&mO7pyi zv~sB3(;5O*N$SV9;@BBd-XTtwfca0z1ztgcIZ(%z3NQtY9vL@jUjY>pNgw0QI4ov_ zv%FD~ZDX6>2*ejyvAqwLrYJ1bd9ZdYE37KJEA6(^ITir&t;KwEY$N(*g79 z441zbBo# zsSW)vQ5(vKZWi$(i;zy-bdvVJAh+jJu|KmxchRnqZP39U&1coxSkp6?q&s7T&sA$R zrUL`v%J6uLzdCc{;n$yJ<5(hB6)oAX5 z$_(#$I?mXYfV({K|8}krja+mpXMZZr@X(4@zB$&vA18d=xq+|u?r!0J+TSmO@EK|cb z{hZ-4}UJhGk z>PR{|nd$nmL;sU!GHrE2O3h;B+K3!z1@fd7XjL^;H03~Js?B&=8=kC`+tBDtY8>N? zY>dv5cz9^TmlHr84WIVonJLt5`Bm@^{|b1Swz4ho#@L!@JrtJD=1fxq^@$pNl4)yR zBH8V^JFdE>%(5(KY*c3PzC>N>n6V}zY-wRz99K~D+WoDjJ<&9BO^&#-nt*>EA&z>* zLQi|SJYB4fg}h{X_sxz(&YGyG&rHbI&)XKVUC|PvYO$zVd|}%S9VtgMSsS`NJI`$6 zor+D{BU*TsL!sKVUDIkX`ofZT42OZURs*s3cZ7EHoM(MpLoCs)X?>jIP(t3v7`I&d z<2OtCsADtvERC60nP;lZ$HKBwJB&xoOqc#xi2Z>xmPc+T{nUi4$OfK!orh$g`;RyK zPxf1O-JGc+2{5P<6=({Qv|8LV!@fO}03(__&CTZf61kooH)9|BaJ9T4Uf?h5F?>^* z4A&m_-%K^%1qD-uw*}jCwqb7fPtY{&Zc|}fT2i`gIEGJr$aPvO+}kut)o0g0(mMakLNzhQzhjw?eVK2C?08ek>xRm zxEHgp-5aN!=DkVEbcp?XS#ytk!#WOyY?7v<>6nhlH*BjSdI4Pmn5M;H zaXmt3-vXxS=Y~$C3nwNv8W4l9!(cyoky*5%kJgQ5go(idhJi%w?Vi0 zxox3Ne$)1F)XvJQ^0y6h?_BQh>(8S)lIW)T%YM%C!17G2-x*kxl}?Scf&-mdldzJ} zynr@SZn@_&*ctn7&@FhbAT;@Y(-Xt|r}}Lhmlvq3qw}MqTU7*Ni(`4mFm3#T=Lk-2 z-uGab;$z*P%J9TPs)F!6k)urCP9R$HT$n(#WV$e+=x}F#R4`_c^|S!vBzyE?WB6d- zwYZ>^plCO8R7VH(U2h}m=nREt1qinNFxYo_a^G1XGAOKE-pIQbE%*OR>QAQA3tiE6 zmkNg?d!n@W5#FdKoz&OyRLl}hMPER;D4=hOZ04?u!pqf;B^;iOyy>@J0AJQT)IP#X ztn7=`jp6WkW4{h%?ceoJYr@Y+`R;^7Yzf9w)s0Ge@TQpn;s1tmDNS*NgMj^x3c_y{ zHbq%DwmDam2yV^uOVieNRy`Bsp0wPP8<(Z1niO=4q5V;rvG(=N>%$zYRx3IX?VtN<-)oSznV?y@& z&OMQ#uLm;?o8gqlY6x1nx;{D&*YEZ#;^y<~!h3P@znblO*jUPf*KB8a2OsFd2-bzI zp;#7Uli0X}IB^F#TZ_{=6^)_J@LA5ra16>YUjo|D^J)KmfP*&w*Rn=l`!qFniPNRpH)I0+i(wkewp|DGHHPN+dk0V6pQ&mriq7(n-By!1KDfWgfG}<`^*yW?q&YF;)F{yQ!m!ERtf#|=DT`X zk9C=Fj@VD0$~fTyMc$$@26L5WzF`e_!8#neeJ~Y%z!=*-8*Nir!Z!pfE|l=<`#Grk zH9VteSj2N3YlW|_Gnq`FPllpjcdJb~~wrA&Y>4(?HecuIl{d~mgJmg3=& z>>yEu@H&O2YuaL7&d=f9wG|D{UUO94Pr3LnVH+gNgGLnLdT$0#&i<9(${_=kiD zl<(BSA1E56L1_F{Mx(Pz_!C7_n*m+}gA-G{YRUZM;)6?z_NKLEc%P|)TF5<=0)&pp zU%ZpTVwLIzrTfV$VWX6ad@srAs~MzH^?8~w&^Qb-8fSNvaDcL)IS8laluiElJ{hN- zRl<6TMh&)6pX_DvO5rAoTuklNLebR}1V*BqTuR}#u#gq@2KHEqhZ6JpdhPTnw-JU%A;;YPbauzs)Wx5awN++-eoyT8_E>k zqZR1Kt0?}#%~7DJGnC}pSlzR+T6mKdm^Q_;I^RnbJ#Q@)-ebiK&!#ildzFPO!b$oi zTe38LwHi9og|{e%O=jK-Z}(E+7=@+hY*KCw5`yT1Z3h31all+HbWjHGrP;Sc{$O;5|djE{xS)?OUmGWt5SGO zdPS)-a_;RccZT=%%2tc;h?Fdxk`r54V*XLZNuB$572d5&g&<0yx;??ejFhm0GEn6) zb|0ko*ZEUQL7+^+y{vGl8fQsX>7x|eX0cx{VRZ*3d!MWnx+ormLzP}bk*M@)`lU*P zE)G&U<~&cOa5qI`@1_>k)0;1@x+kt$urt8u7vJ29p5;rW!h{s-CmZE#vkAAdU-YJg zJ-9B*S&Z=P79d|q?*qLkbZild2&%A%<%#ibtVHuKqyXlO2N*^wVJ1sVK596b?w(TN zowZbWF|dxcD%uSSp@cFFw$?QaWC1Ic;jN+0%acLY%OJ02iTT*{Oh+@(qLeP>aN8?| zYou2cyIszn%CeWLAH_83jUrmUlTsekM52vYN#BX8o)c9kH4 zfR^A(5mRr37=B>1kJu{m;&R+6k;^{7WJHPnw2h9m&i05 zM^M`grX&fgHz*mEi=t>ovTETKRz$%_^-6|>#{Gj`c!#1HsUD{{-^!|;SQ!0wB;& zDGBq0$P$x}+D_KE@3na!St3MH3Ik#3C>Hio`oQP5GJq~tCgX)5!26R7?;RQ5y(}%i z)nJN{8f3GS0o`=J^oBxj$eA~=%%$?46zi0^BG`TtZKr8Z>f5lyyUd1-mi_K8#0oW% zJ&Ob9C7j)ScP#PRhz;R!d||e7B^AMK70Vnr;4Gv}C|`ayq3Ev(Fv$wT+R)cvM<j-9`zA<;f7mhF2fU-1%SHD|9G$mi+Rn4Z^YW2OC4-o^eYJ`T9N z_CZRYIh-@_Rpt_fHXOb(9loT-8VE%Q4oi}=t0g2XWBVP@O5XsvBcBR->8&*#x>pU! z;6E4`|G^l_872P1pKWOIOhXp9np2GsH?eXJu&-+L(N%cCmImMp4n$3gVXTmnK6Xp< z@-|&zI+(27tA?>U7_Tv8>Wn#DD*=zKrQ`f>Vu?Rx4Y@Mo{_u{z_E+OYUp&RveV7*s zVAhZ;GVTxS=$rMN6z^H{Vcr!ScOWOtdgw=IU&=?$ll8{Vgha!Z8B1VzBj2buAG&xx zQ8(3me828shg8VE>O-KQ_WPHD+Iue19}4HqHUTQsX0Bf-}MJc%=in z_uy;|$zo5JJX@Jkq;rkH(b0++V(v<>yHaL|z10^#(${ap*^235EIvPYwz5x%74ENW za8Pt5NF*&s>wb?q4^!L}PmOi?J%VD|p^Kh^ZyQwdh&9ZO_MYuy<-t+9knl&~5J zBqCrBJM&S`(VAn|p7fEkkCCc^(`W_l*BMVGJ8f2Yh|sGF1Sw{66od)1_>;v5{5PMO zu2Czw4qM>Nqe`Uvex{`Um&pN`Du7Kl zWc9G;R!D*Xo`r~N65DtQJ>s{jgdfX+TLY-Fk#{CG8sW<~9JcSsIdLx9c+f#_6z;YL zT@~9S1Drv23XX`CxrgL~!4n<}9}H6eu=_e)EEGM^(avgsv2>%Z5|XEYUM7B*ntu1oA^sQ?zlvl)w2-D&fB_ zpOd6?o8kud=KV>d94bE zOF=GBw6EtSLLP*Jwy;0<{pRJ`2xKx_ z-;(Q+27_x)X73g4WXKKqMc3U#U(M)A{Q95g8&n*8-AbI3b0efnm_EJ4d}jGGDSxA! z{{|%lqrfvG83E6Sa`yM-?1WGH-F9;Ym%af{o;?%O9eBTrJ=4+!yr;9L8QtSQFg|^1 zg}Gt@No0!lFFqHIWz7p<4>SUchS?oC)m)$~i^z`(YgIKb0Ao{+aW*Sg4Q(JWbwR+U z-VtVZNSe9cn#lcfzE&l!S5J*VBM!htvktsFT)mqetsza$O}IY%2eM5XND^H~_lo}g zmqgcL*e){79xu8&@#4AtW6||IUi`;D5nV8;Y7c@PJkuKkHuH?xo*RQDQy$bF)QRtm z4}@)8g}Zurh4wM*WIZ2#9=&|3*=?9g2F7zZYmUO2!&`Hd)*J+btT}3HPH?R;r^IMo z`vBjN!j(S6tCKmQ{H858-P8!J>((H&RL%bS^kZmavgT?zEk;Xb}|t5$h@|5k^wp#G@EY%Hwbpn!mJ8oke~pQPxVl(5g4aNnea z7PnzXi=&XM?^xYlsHop}dwU^Y@4Vens5BR<(wwUug+Xcc2&&UEZ#NeP@6E-{^{lz- zzxAV??ekSu2zRF{%r5R>6sUCP{6{l*5iA_34wl1FEF6v1M@o-jVg0Pa>A~ur{#xUV5@X)%U^IRA6d1&; z)70hn26Il$$=KIJHa$${JDnjdCloEiZg^-C+d3-TEX?Z2!Z9qYpVd4) zwBhxBZr1AVZa7P_cYV&hGT)FtiO)-sdMhn^^_ag4mTM1QQ6_m6ou%lg-tC!CW03qR{HuTzta8%w9OGe_@Fy zXd*8QV()KJ#*)DKf)x7q_IdyuwiP=Hl|Tou$tN zxmPT)H;!1QD4i0tSV4Q`S;b{R3!o!a#LVkJekc9#;;Q}9ot>>~F|yb?7uVs=F0**$ zhR#M?bfrRgesNU?Z@+Uy@1r{V_xd&q?elTE8MfWhYT0Jnv3Z+Ht7%hh54k6}z_Kag zifthXhHO{Oa^m9kU;VRkVgDlP3a1hUKyI@pvDoRs8BteaYcjB>nS?z}bH)?FrW;0N zTC(`UaArMgnLMVX>o}bZM81;C!&xZp5Di+mkun+;#;%Lf7^5@#*I7QZyLzfQtLM&t zNkTW}pJ)2aC3~ftvC-q91o{ZN!}z96A~t z#j2+LqEAcxbC`_{cAoYmzn_QET1+NdUiFfpn!s?qR`1ZMHa!q4pRZHik3PrA<$L;F z%M(f#@XjsGPO|J04_hy|WiD>6BOok*(WUO~=iVLDfR$ku)6obUFP&sF^~)46(Bcl+ zE7G9)jIQJZTg5oqfOH@0Z|y;neeUNYaq1mNl!{Mgbi~@mVYHSQb7qH>>SJ3&=(-$O zwCSETz&3HlPF_8Qn{{{hxBVr?c?Ch$QQF|D$h*o^^V3Tr`I6$0rl%xTWc&X9_QQJ7 zmxK@cqgi9rNcL(XQIUYXnhE+6T}bKH5puqm_oM%g#)u>&J7M0)ynrwBHhY-0pCoF; zAP&+_8>wvwN)Sgg&HMfD=0ltYf3LFk=pQFlC|SQwY7b1Y)F7wOAiIvGXi~y^d9GW9 z!(;REOxWM0?V;4`Nthup2mkVz;R9plPZg$l|9Q-OyJ%p{e6xtg%vXzkGG;zsgfU|( zq%mXa{{L~zg#C2PaM7h4EZciVk1!il|LeH1|B7*ApTNeABz5ioY}~&YH>Lk++&nTk zZcJal_siqPueog8n7(;$aNO+szlbN02@ z<9O@IeIMssOgr{xp+`86wp+YC2XSa4&nNxZ$rO{ZCAZRKeZP4}yMn_37<|g#$$Uz7 z{>4##$Xw-+w%Y%7JeK~4@#t?JG9FK{M*fBI_;t)>EwSMkDtV_@o3=3g!2unILjN0$@W1FXLZ5$K_*yfhLcLERZZ+cknm)39(EOOsyU!*brfi;JePsKfb*In!1fnRH zJ*1Cp9~^G-dE0EH^0k}51@{&Dv@2fefz=e;Pwuu++2MWAoG$TYlJSGgO_7l>e$X1} zIIP_i`xzDaNDt{E@_SW$C0t%v60Eu~qJWjw;u?}>jCX}Q-{ zc-pGydw#W5hkVd{%EXRQ^koB{e}2E$iJBDAr++wIxJ4WPiW|mAfX)U&2mEqcf9dVQ zX{)cC;dF)d@JYsyF#UO4HzZ97n-Kb5I1Z4u%$yPw3NU$OV&rR&ZOBh=l_; z!C1Xe6biABEuR`F>v=<;K1nV+#14h&b^U-Hit)%BJvA5~^1rHwxGR*)&=pfBG^TvwP6on+e!`-80IrSup7OZxK?-76Fc)&yK9 zm~D~LKHAjwd5NoeL5a~f-e?R2VQaTGF-ubmabZ{Q)X7a7z_@Nyb|B|bi|oe5-*dV(9mQq+ zk9NIwL`&{>7)iO`n_WLlom}&3xfsLM#wcLkuJ#77yDw>jYnNu%F13LFH5Kl>qs(t9 z)+Mf+v@S<&i#b!ooD4B1vn3}>%$X|YTqEX8 z6LY4EIoFCgzY%k?#T<*6V-<69#GG6)XNH)QC+6gfIR#=)p_nsM%$X(TTqowt7IS_p z=3Fo46p1;-j+_$hrrMkv(1vsN*3NmXrm4JkNp#IUc*dCBcg;%|LF*S8?AOYLr~E6+ASO+! zT{tB;ksk2PE4|(*^_rf zstM4vQ0emz>buhjj=cYB)ip1WU)z%=p$L8Jj&bgV<%IZT2`~Te)c~jvlhBXZ$vYyt zgE((Vxdd;Ez8mgb2H{~dm!iQgAA36nkWH51(1niV+7l)C9$qQUf*bqUuYDdTjg+T8 zQx|ZpTg<-k37djV3QogZ?hwVTd9}Ey)qEvpIO!G2*a;J3d2{JkR+z z=uv}#219!cdCxQ(BKAbEBfX%X5R9**CkOZT%*fFEA1T@pPM`fUmA%uU-yi!CdnW}r z)0%U5m)c`5tCPgSuKk?N2mac?oV1v4GL8-MesL2S)nWPlqfTr216E$WhP#DN&9SO} z=zHX&?9Wd0e0&own)WY}F2PLYeRVaK(1j~u>}GO^`-7VZ8Mhu&wD%)(j?>5vAC(3!nu0p@U8*JO>^$i|*^Ou)J}H{?@w&ztm}?Te%ft@(YpYttS3 z?MYvSFSBZ-s`_*uPNNs{4&MQnUW4s+rp~$a5~O&hCK_%7Gq*p6=Kflm1zeSs9wx&3lO40O?wfdsVO=U`Vo89we+T6}NQ^2_UwYD<5 z&(Rp~N^|}vTbWN1a3C$i{=eJG^!x@F^<*ov0=6>eadf+Ktv;Y-lu~@&tV^BLnW&r2 zOUg#*XNjjm28vsh9JCE!C-a)DfSt_7I|TLoEkW}eo3`&nBh5-7QTQ|_1}(8%?C(n~ zm*e0~`nV=z`X@JL_0YqojmYvkjhW_$LpDVWbEZ6=;&_FuLYlimz$tE2^!di0Qgr#! z+I^Er*2CBfA#_u3`g>IT6cwK|SR9;*F)4{C-~BJCJWuA_#*;a>K>1Nr-q4$VoXWpR zs9~ z0F3BO-%kMW_*n(Yh9VFIn|r?TWO0vSfb!u~KB_m}L*?Y)JCumDH^A_o9m zK>&2U=?@VAJbu;!bwkSs!y+M-*AJA}Q2F7#>5Wt#kIOoczV94bJ_Pm=seJrEc{P0SG1@5IjM>>9-Js6$D}bU>6XCZI?qB z2K$i&A!z`@rS}0r(VKoFLBQi@ov=-=gRv{b?9ybpS@{&XJa78peH`6?e4l7pL1$6e z;TyN$7wqsA+$b@xJV~bq?C|9xj#d+U;^_f9eA$R2JA5YN>3|)+X^5(($vfe6zz$#5 zjnY}vGx~JE4&T)`2F{}PQKyI4;Y%YQtp23a>?r#0?C{z9W7M)8zKiq84xjPHpV{Fv z{TMTF{)xL$w!`QDSImF2!{_;)!IqYb^U>_w?o&9Gu(72cp}0_&3~OBQHh-aDcyvlg zWeT5n70Cz1@RbEHxW%k;sT5j1FM`)TPzo;z=MWCmirlnidPdm~C3^ z)5y^94r024`LYd_QQ>4CR5Fa<^XYF}lF}7Y)7s&fJ-|3?@ z?FRLr{hHXmq56P-i-EmE!{)MN>}NJDM*Tbr?Ee(rjf)3Lcw+j7)Zc&L)NAMD`aF1r zw{CGaXu!L6v08y;+`D=)fm_4Ct!CgZ#sJ&^EPr^P0~u~DOWqdZzL{nCfiifP4f$SJ zAKI{*hx98~hP9RoRFpx)ZBKiM`$j5b|2F3JA;ulS??LN@Ro0_TsRoaq+VyvB30eba zYSFFnFj`t+NT`U$d#uqG2UKA?)Kb^(YCY(c_$79QKP9p{sVY3HsbU%5Zis}pZ$s3v zS0$49px0rDZ85|(R&1|z7~)$DBO4vtTPhTF71orLNNgC!Mq-!R>8S7TjDaN_^eB)tcFQJ|JTJu%IS}r#m5dySF~aDfx$AOrFye|~E{&r!#Sq0z z&vP-fslArk?y?dI5%Y&sfzP7RqhuP=0(C1u|`K-U64vepqQh9uBE|teu14Gew z`S|+VY0%E#_*(OS7+@J#eXvmS z-AN>-Mf9W%5^dq_z5;16yb_u6#>i`->w9^en!2cL0#|7YYgdLztV{d(9}ySFRc^eV ztjDt*k?CKT;KW1vp5JkLh~m9}bqv}k42y9%Y$xLz!|{NAqc$A-5X24Rw8lILo)}3W z24Ru|md>`s2l20rk=Dm5X&(YP4M=^%hk4g`o;8EEOr9%+&SS;kIrf=S=seowu#FS; zEga%Q_apx=`p`YO5Hg^`-xf9+=}|$CKCvs){~1)D^_~J)fHvKyz-9>NDY5IQKcKG* zOdv=Si2umW6n~KiyUZV;_Io#ESC&Zp7Ap50WKRe3LS;1Y9)M@V(Ib-%OV_6Qvs2r7 zNBv%%>p97jY?^)BDcCTWC#sBz;TaXVxH`#BO3-ecuUK${Gx2~^u~<Q`SDP(CF4yg%MctvJcA1=N)~U=^hp{WuYz)s>GEb|5(@kfi zVzXnL@j#>E9)&Yx)Bfz<4Z1BG7jABR)hp^b&mT@bzJOu`#5{%=h3DQ=yQVs8k3(_S z*|J5wJ;Yf%HPf*rs4;?J*l>=}lG^+`S^xas@g?prCBDR#(gcYwv23BeF6KY;B{r1~ z@Fg~s4)7&}Qi(6|`_g~Qm-uV?+EVZ({L3&&2c4gyE1y)~`H0SRHhPHf4q{7yC-GB; z#JIV|sdImUA@Pg8JH)w{@eF<~KjK&U?r6Fk_WuWd#FUty@*}Q}`4#+#ab_iX?AWDgsFFc6+w1wX7kc~H%C!|Yo_7X#d|+pAhYU<&Qx?| zYDayn`&jYg6{mt;=e5^JM%s^X!l?y~JUxu`sGtX3BU%M`(y%(@Iqpq~f9g5dC%m(u z(JP!>(CCIgi7if3>(yX)#NxF4c%8)V;F?Sj^TJs@iJjF`+I^A~hdlyEO<%v(YvL#+a10H`!rT0Zwzk>p2_%f|_XrA+UNd z43Fk&ld=ygjLR{=V>mXasB7a*0k4RSZw%x9UoX2Fj`u-X$8b$Q)GeuB zM#kg!aB&b~u<;~(pUQtP>ii9Wk^N7;9`3{8@cKoa4G1@ON#WvmViB~=4`UJBx+pkp z%nxD_{P+AJEP|%_b-*HMoPQM-K^1tj4QARKCMCr zj9?MC-pC*up;LXK@aS+lNo?|Z0eHVx3KAOem z>n|1D>Jc)kEIg;VMx*vNY)q9bXvK{EK<(waTG+>!r$} zdF#bWY2G?j`2+LTbCqOI|3)`_-tyo6t$8cqTl3bhD#|xi{JZnkV8#F4ymkIc+W-Ia zmhlDMRr8kddELJ;Z%r`Ae{0^lp<>(h6<5z&bu!$83h5Y#Ts1{=mcZ^EyLwN>#bm%4 zk%6Lx{=e#eSa+$uECcf|%YY*+17+VY1D|EtNd}A;u8;xOB9Z}cjK9dHN;N=_*v%Re zqI|J);d<*=j`FV@-ysIyxu3G0nu=)tU+kyc^q-OgfBV&P;NSo6$ic*V`ObLSNP!b< zvaeL#aC6HY;vvVjLyoHjLB)sl7u-6=yjQW6Lb`UnJS{1^X=j@0=Cs^9U>2Q(JJDfL z9T`ZW0X%p|2Ik;&0y8c&aDm>;gNoo?16U#nghOf?W6#g6$SlwUXH;#uYx`Vy)lZo$ z2dr6b(h`B6(qu+V>`GzIso`Qf^#>LH@(Z9B&zbJ3jk9(>QVnvaL(l{8$7X-Cc(-HQ zT@FtT-&`ZdiProro$%9^bNHw5UnG41+N=OK1dZ?mdcZ~3Ct*FH2Y&355~EhwdSf*s zCyndeUM;N={g1yTHdI3mtFML{h96tN8+x)AgR5LL+Tb?|Z;E$UH+)P# zFtj(Es!wg3LW&seJFS!yFf<*Gp3*s+v^Okr^bweje zv_?qK&PP&uSP!E+$P91xm}uu{64ta54gW7C8o}Exh%2fawu!aX4I*f)8&1P?nTCmW zuf~;vh8|8l5)IdSiH84{3u1M3Lyfqgx}lsdv>i?(w`houT-rg4r5&_{Ko1>PBpvT( z5)Ie73$$KlOZVVUBps0X27QVFhjh*j@YrYiSy<>Dt!g;F*7;F4tyvEcJ($(wz7_tIY&ss#%W zrYa0M*C#c7S{`4uP*LuzT9^=%y@d|K6~_VM2g;(%A;@e3XPMn?$v22A=UeK#bGnqR z>S|Yuu2_~cMRBhTDr%`%uWl@;=iQ9rf+^AEOXe3nHVST-j~y=`13C@5bPgwXdp0d-ug{C?$KAcNi4mEFV68j8|$kPAkIY%3mR7Vo*mb4Ys2P-2e$PuXxX>z zP({lV4bL{W4z|3q?ZkqXcN;$Utcdlj&@SDo@I=`+eb`YSyR}}g->SISqTO^>dJ7l~ z9a|Lx2sjc6&?BH90e46No+5j*LBG|bKVUyTw*FT4Q{5eh3^_+)Iu3P1pj^Qadj1oK zM)Ysj?yBEBPS!A1_jtmA{F;LcavmqigS0@qp4xt~9X|u?Ad2y`L|;uxA*~ry&YBdb zA!Sv~z%uRB+KaXLf!Dvskg|H^Dshdv=`G1Qt?H|oY-isj)Fz7FnOoFQl()@i5O$7j zX>F_7U(`?&FbJ!X8`iB9S4~_qqHmOB@A8{;4nxWUomN`=xvt4@JTu8rlhXH(5HU7V zN57MFZ8f@G%)72sry{A%(T$Y+`E(@Hbzg; zZYPsdm1bRF`TM6_V09!n7b$gSC1@KKFQCiHZo+T)vNB`+_1JJq8og35ELf1P11^4; zwlS+>K_1qPi`lwy=*BYt#PE1^okW`#iWIsX3PUkS>I`=LCelrN2`1x8BCH!}7Yqdp z(i74p!dr}4mIZkjO3P>{oge7I5J4QLg-5WA3r>SUcfZ1r3rg{Z9Q?)+J?(IM*;ml} z@q#=Is|7Ty+F_cM=o!NFQZX}Wx$6qicF331VKRqC`Ekb4#imL?~Q@9?tR2oJu$iQ%$8gW>V zyr;%oVE_GNFVH+%q>}vT0wJsHvP3Un|Tw*)%~0!8?aGbpTDzWYqkD_-vQ%ZaH3`elnI;!s`s?5 z_W}{|E+8W6`hkepF1F8ah!($?-!PUT>cOqIG(Z%F=_g7{#rC;MhfRU~+67!%B41d9 zsmt|~_o1RxuHE?uk=9OW>++D+zlhQnaNN$6>g@aP8|f2xkC$7}MmKqH zzEAl8yUDvg-;ttxN6Eumu~w;W?VrD|i|?DvJp>w)xf8dP-!XslV{|zaX+MV&1R)Be z;nrq%>60*v-27M2pTwmdx{|rJRlcG;Dn-F>~KJp zXZYQpC+6{svh##G9TM~kvZW`wyFG?|xhW{wE2bGp8GM-tsPHcn7@P6gfLm&%C2;VJhOK? zv`dX*i?cd@qa4@v0gQ5?Y@=}NW3n}}Y861b`{r;f<+$f>QEi)Qzt+hsb??YmIyTzY z*3w})F|o>B>sj&W#1a>#ppx2wI&I^v;@joO4-h_4!H>QaU!s@&+LgTJh1S1U ziqF$y*PCL1{=+1t6`x`sqs0HA|KdUV-EZBmZBvPlg}>~h|D8SM;z#rGum*N4DKfyc zSai|L5&_GsqI1;zHmz~F2}P7Is@ktiF6etW6v0{-_g28>SxNSv6O|35 zhL{^{4br(_@mUt$hhM(UOzPpwiCcWZKfN2)Hvz;2{Siu_tIO2Z3_yavB<4Ep>vIym zeV)lDs3`sE#9=xlvm24p?eE732{|?kDw`0Mlb`_#g1?Orq`$_T3g3!0oADo?OEwn7 zLGR6pi=jCG495xc0|S`dX|u!d7R#GM3e1W-Qa9k-{7(JrZzM;Bs8RI z$gS8|leoWme?sY^g{hjvruz^nI;OI;z3%JK{)9Zh*GnF|UxX1r9|Pvk_ndbtoEJVL zdQ+wG-NYX}BRXx~(0RAKc^L9*a^_ui-Yt6`op(EvNax)aB>v~lh<;l>!sp#O68{6| z-6FnT9cAC)>!qude7%CtTp058ibELrdijrC81nTRg)s8Ig@~`0yfWhJ<^SV_VZL79%%`M%7i>Rt=c?bE81eNI%gER3;{0#M?qakhZERaYSoVDqX7W^* zXj($}+ir~Kv?=K}NdVk7!$S$z)+=$$SJ|d&Pys%;mS{qY<1Eshs!FKHH;yA)G z37-uhXl3+g>z*5P9#yt>&v*SH(IfD*j$^Y@PFMMQL1tOet;?5{@4Auf^@kG4UZ0sL zEy)}-HyKy4xyirs^4z2pN6_d-bCdDE6BqKUU8^Ja^s#75peIn?v5m`((Q7@Zgo_TWdHBrDkD-Lx|2vl;!y}^7{2Vom-itZ9X(<0FdG>z6>t-6VvWpkX<`+ z-yL1=TvXkrG*$uk6RoGfh8FH#{5T}R1CfoRvvnTsOgR!Fv10EJ&cmH4hr{pKSJBKL zwy*N_ckZi5I1|0+P!X&@h)>O9bFL!z(o2zfP<$dB=Ksq}<4R$*`7dcM6g$J=#ur(* z>nBop*551gc>0OU`luqW+1L6uATlyczSgdGpWlT4O*p3+OgH(2vF6^jxT3{%Q=(bW z9k^@LSH7d_;LVA(V}C^Vsj$0LAbFdjb3=PRzTR{GL8>*RL!98J;b$OeSe9SwSJ!XD|((A-EkAcY?ia{x3ce*b5E_FC*DHO4Af8osK%Sw zclPUZ-XrB_@eUrCAbxfu;sIB5dxiKjiUbw4ibbxZUtEZW^9dWuBkhdw!*g2ZQ7=AG z-?WU?uHW$`uv5&o#VC6#XT4!=ad~kKZkkTd$$((N2rZh4{m!4y69swz^lmgGX|#UnbLIsE8#MV0`a+Tb(moX+yazO0Z?FZY-dm;_)WcZ$j9u#Ia9c-?Rhl8LtyBe3a}{~hQm za@U!3VBO)txa{u$-fG|Dzv;s;{{W-$$rHo}sM0%&Dnu=1=bfte#cdIrSzz2eQN#F> zi-Q#4a~6iv9^_H=W_yC46fD#Q@E**LEu>!pB27rx9gQ=H*%h7H6~Qk}O;ewi`)xSc zjd*i;om^>>o19>t=fuAe)t(b`P7U5DzDn~2g8}uW-Xp$CUxsinPtiB=0)$(AT@v^}BzBjYzh|61e7yO|w1q#9g?a*FLF`?7IlTp;G%KY@JW7AHaRO^%vW;J-3%>Vq81YeU9zu z27@HLollmF)kL-PCpU_}r>6HgOC?|!Yt&8FH^o$-{MqC84xsLj`UE~@d6yZ zmO^@{cVY++D!-f&?Gp;bX%qoN6iXparZ=CnAY6|nWt}ZlkN$7cvE!}#V1!MW#!XE@ z1}q{V8^==u>1}TGk{DlHt@s#mA)4qzPt=@KtINfBD*e#NxrGWVj-&5$qaEO1d9Bz+ zX##>>jG^FwP$-U|2V@XM|0hRhqTym$5HIrdU;6Daly$Bv3vNnxPe>_*eFIt<>Z!gN zibC>EOR;aDUTd*hyL|n}w2hvUIeF@Xa!YZyIE@&BJ3ynSOD%p)dGGfX)4c^X+#0!A z$j{7LYRz%v?B)Hd&f0`RpO7zp%<5JlzQsz%eo=wWd>SqO7HZF?x_mDj=M4RanL;kU z9S#}Ky8cV_23*d#Rk}yx9vZ8(s^|t@>yI1(A5lAh+Gg>Ml1=y5+FnGyFWdj=4553c5wJH&(3 z7Ode=ovlxt%Af5tFYC+Dnvb1dzd8r)Up{}Vz`V-HX)wEO)mO#}U+#)Rv2 z3AixD+HuTnIns^U$iE~VgS?Gj+>~JUayHRDXK7qF&WA0=FcHPuRMfy||Y)jqp&W8xA;(F1%TJZlsUGEV~Ve|UntVc1wdd51yX zuyXQSeZ_N6T-%A2>x+9^>!MFuWBiC%Of0!*BwpNR)7js0_eruo6xYD=gIVW2Qa; z^B7fnY&gm&(Ld9q<;VZTgJp*knqinP}>?<+p4H-+41Paw^`d#-oXY|XEhR>QS9Z$^?T8{s-pV^du!7UYFs^G7NK=( zq_(E0)Vze?j3|3?_W66_povxIV6@aJw5ShM)T2mPH8s|3n+sj0IzWO56sjF`n-#E^ z3=Df|h*?+kWoC`P{vKw7_r*K3Du%s8g)+L&Fsv6=*uRC=Fzlt)F!mBF2e6lFB8^=zw=OxUD*2I*y6Dj#inuOw%;%w{NWySJU`HtTC8U-#ll-Zn#HbG~0 z>Z+zFRO7fUt%&S8Dal2!DW1uP@#hImzci^#aR*gsIz;f#PoQYO07ee|Z;XesX`93KVzNLNGB;7z0x(UXy`&XL9* zU))&#(KEW|IiHsE-vd1&tq+_Vxw8Qt`&n2A2Kc!&)w~j#*a_V!*iHS+td(KpGZ!Re z=331K*9LkN(58%kJfpjv3uxgV5p;wCJt}BY!KcsYR?EyzYqTWeE^&2!w2dAHHy5Gs91QDVv*8qt;FME0NV$ufTFkUJdD*vSs! z@F`=(G4!eDtKw7Yd14|xiwTj)LBp4h)~+$gw|V{--yv=*D&3CamkqJ~9M4MkQ4 z$A4*-JIqJt&}B`6kB=2OMmbn~>f;;5Z%T(D(UGjhREfa|v_8zH`&d zj4cV|H}%~YS>h;q^qjEkoZ1jIXGI{kPsSM(b0o((e$Mwf&gp`(tglMpHRW$i816Y2 zR3%*H@!h5n2dN0d{HFFhpImP}h=H&8-}ReHEdhl(tT+BI{H8_{@xuSWIdtO!0OwqB z4&7Ll@PqzycDiaS;m2)q@nKd+Q*cYCx*@?+px(dy$qnN2;%&=|8}4q|fn(?eCbA%A z7RU85-zjNH<9~g~lMeboL?)bG*{Wgq1nZhh*9x9>zbVyAcbPpgFL*0tVugA5U1q>1 zSZOt>^uR`hP~1}rp)i%_n4=A1DKHF~Sk(NwbQR$f7=BeM)F_<>g)@y?Rb#T6;xe}7 zic3?vSC;R-lyyt#3iwm5x`f6+@v0{%Uj z7|CQJIwBM1dH$+@ArstXnfSV7NG854k!0eFk{^(XizSc=qx}k*a2@-$OgPEQ)~4i! z5Gz(W^VS!8^8PJ(NdCW+2gQF(9umGK5Apx5Jft_1pRHNJku)?UdrH+UI3kj_rQv&J z0mnm3aY8S7S7LlH$V!E*t{crYN$htFdFkke!&M|nZiAQMDyqr*V{xEoDIDECKMPCN zS7)=@83Fpek^tdFB!!^{aA*sN7c%nst0qSjiG=G@kO*EnoAp~pf_|?gL4Jo;a7kL@GHUzWB`-wfi>s)NZupo{nI56ydID5}htFmmXH=g378UAyUEx|`<%#vw z#4;L|2o2*QD$nC*vz}x$x`@W4;gT|{%``4)^Vqboq(}wQdTO+cPC^+-x8>6%FJLyypqazTgOClfu8J+oII=p}P zSuB&hWLhS>cG&0HHMg^NE{w3{=dKL@ueqH|5pFD#!iR8R_S?V1H=t^+t02gipKzC|$JXME!c#Walj(4`zZ$$nYv$+|KeA$>kuH!Jej7292~FZ5&sg;)pqfP%lL64Hs_f& zui=BLiicyQ=1Dvj=JK^|e4hm|>ptW)Z{@&k5>7Oq(tn_?@`dm4DgCXEOaX3)^>KsQ6-S1En=cq|=)+CbKquypnSTR3(wWC^DG8;YvsdQG~XGm=K z!8QRGY7{y#N;-0%i(KlS50s6%t-KvRie;+_@!6^%~DFHhJY|FtF$QdNG8k0Wkzx9wn@1FA15_jJHc*Ht=mFW3peqA1(0H| zKx|drP+cyLAPD?{cyP12>{~Y!-9Lfq@^+y9q}{{U^+=A~E@zs1bxLl)B;Odngin8f zuiFPlKy!`W9LJjpU&jo*i)_j0^Xi-M0TV9cU%2MJ_CxqO%i;us{xZJKcDq3MI)c`g zuA59~DxWaYzM}+UI0UDnYc_;)48!ZFJme$U1|Cv|;p+fJ1-PyfzK-G0?5x0Sp=K=H z!iAb~=3)3cH_id}htnR$*C{=J6~4~i^TY9VYU0hn;|LU{Z&$Zk*toJKp73sF&bf@o z(RzMZ`Gc0%PP9{b?jPBiH3#j`nK?&ATb7-Z=MbsiGg8+PsbC$E8lq!-@H|C$fJHE} z2zRr*QZDBe+;txHm&?QWpHYalB7uc$DGXzOBr*QPcwp~u z8(s@z>zD~!2cGl5)`7FPIUdj7gRSG=GGI!`P0azejydmYY#slabKixnW0q~iQu@2G zb^O15XE^qUu{0i#H)u^TDI5)B>jaDA8MaPD8Z^^~grTYTN?8zK*Jaor&ke`c3C@mJ zq0)zAfA|Y1BMjrQ1#a^(br^6bKi=ncdl|4w@HV5=h4SKR6LC)o_1vs9pLIr{XNELV zJaz_s=n)yi!t!B{R_b!Wt<;D1%cMTk9X<>(ne`#x%ba)Q4O@ zzOoPX7X4d&$Z>Ta68~i%db1$wje_s&L%%9TAKF$F#?KjZ{tEn@wdaT7=S*fD=*`*G zf!3CyN?i+>Q2(qnBBK5^SM(pv<^EGsO8sYPsnma#lv4NjGo$nqq7-yo*?(5C{_`x0 zP{|^cv8*a6t8f$74)vdfv%~%8$n0?cu~QoV+$;OfvMc)!|Nqf{7W`2E$@|~;pA}d3 zpE2Lne-i&!{bvE4P58$!?3|#MaNDlxKe6A{e@0*KKd$-V{-Ye~KgNF=eb(tX0x0z# zk^0Z;z?{QG_#zkSKgL{`sE)3_51Sze;1ogoiOtxwz&{86?SCWClgmiC zo(8GH6P*37+Ent_ut(ccM_^_@F%>@eeYBB4(8VL9q|IIVctHGa6e-|6Elu+T!Qfbh zIsPymXXxvITO*0*6G8C!IK}(R<#^8P5wC+CnP`*Z1>ZV{cn8=i4dCUqCPZ|WBARC( zlFInIp)v+v09a3S2~a5euKwO5A=YY4JQl{pko%I9)C<4KBVLcH4e7ziJ>TWJ^TL@!!A7i0^<$#1&j>Qk% zHHUYCZz?4CCc0}5@4#=0BmAZ~I**EXoJ(N`h2u!q#XHWWFuhb-KUjzGLcTUm3!f8y z1LuToHL*i-t^G>nDQ^CH^4T0heCH*AQ&wYmT zg&BUs#=?xp4F4$3ct+SG6Dr3FdlW)tvam-fRE`t&LY`N0>c#Tr}0ms<_F~S-+T_7mbsu6R@=_sckOb*_G=`e+gGU zhNzzPA|PE?fBzF&+`sEeA|{s$&#cY( zSa6N)k^xp<+x>#u_Y7NSo;y2i0(?4doaZFp*;Pz>hTkpzCLh9%fX5K9v)wOl3BL#M z-hubc^j;&+ce>2Np1Xz0TA^}AFGi4oonv;#bAx39HO`Ho-eM z{MG5QnO*s2SAp=7%=5?z+@|W;*YBy^;&c_Rw~h07f9 zZ=1B<8@u*deP?sAulG8!mYAT|-{5to=%m+m_ZEBDeE=Ru`g(_nZX$53yJ7ZvSLM2s zh3j2a`-2;A`eJ=+*X_4}V}W-Fy>kWtnPsF+$+HE>x~JQA>O_(F8a+$D@F$9k5?`T! zfU#11ogUtJ+;(1mG8*HQglAv*g|qeT)h^KV@nzz3jL<>bsl!F$vy4z5{Z5YdCGHcS zp+_Qgf*#)3Ydg=M#NNB)I$r*T02qF3k;7L&oAq{~*>#U?_Bp{2E4bnod(wAYi<^ZF z8rD-&$4%-iD)v1yMNFhBtBb{;y|_7j$BzO~^jVzXVTv=0#nG@h=&#&jU;2(Qnu%>a zpyKbM7&$D4f?}`{(G~j*`)ZsJJ`36S4FCvm{82{tDbJ*I_Y#HZKrc;0erLH`i5S~2Gn^&0erLH9C{bm6f!?+wQKj=V#tnX z@f$mxZSpvndl97(|G~n)z%TFXRr|%aK@H*X85(B-#Pzt`&GG3lBgAr97wuC8GtYT= znRzy6=6TO-2$0-78{Jc`vGt*Dv)=!~C=ouh2JCr!sq?@)*4OPNwjy1+?bw0!$1J*> zkolNBXUyMlCwH}6HydzN!BXR-7jVQmcqeqG5VU>&C#e#D@CW!R)EO#*P3u>Yan^Hs zWZq6VQ;o=MxC?h9KCmQT3w>z1 zrM+8|urF7pQNR)Go#Yu+H*vN-@9mp1rSq>=$?YXq3USH1?0-M7FXt=M(M~`YUJ;vWM8~nTe1^JadEm~YQ5)pEc5HwnY6oj z$q1a#NqQhr1EDdRAUp?^#+%}jvsIr&^6G%^?8vsA*t=@>a^A3@Xiv?s_Y~R#-nUz4 z*u@!CVgUkTn_wIX@u2>IL52KIzG4o>?44Y>aM9IPsXcHE1}WN_ucn)B@5ut)4_edh zVmi?cs)-`&uPlKk=?XrQ?LBFT|M%8pyO>Pzp^iJBCVy^;51U-(ITnI+H+$1JKgk1> z+IB1ir=_9a9gE0^sO~ zcIy%S#pku{CkA$FFCN!6p6frU?LW~ya87%sWmj`xVN9<)DuB`+(RLrv2NsU(l`8{s zRpeFED~}1tHIdiRz48$Ouo{j$Dj*+emXC60Ydf;_=7q85Yet=uU)-f_ZyCtbp2?nS zwvL}|s~8mtxwuo?_+_Y6#>03j=Ww%IY7jY(+hm;zkz zFll2^7q6PandN+K_E?hrEBXh!fHq$Nz?|BYYf3$>4Mpv~eMzG=Bj8HbZ09`3=hqP? z!`12V)ht(FoKK4%Lrw~J_2+8c+1&$&w1)$w@PyX~UkZtFOtn93Id#|~-bh^@F)eqw zDe9r_f!|W|1+Gc+KJ50bN|0_QFIM*mrk*N^N%NE40}pH4ebzq}*cK^!EtBiV=r5CSXPa z?Fd%msP_yhMH!0H`ID8CesO<1zj%%&2K6hBv7$mXDpZTp#%;Rfv?+w%`7zmbmqNl{ zMnVT~2A5{rgh_QyTb4%Ww3#*O>uuSZed}#g78jjlhr@G46%jEzQ$&Zw<4k3t-W?dA zIVpUqI0xa=;C*a%&T!f$X2ljwoia)7$>%h$tk0J@ZIcXN6;4R?*f?Bv#g@B9#e1qz z=%@#-&-%P@>XZfXYzdAzMW!B(DVE7xf>&-K{K$J)x1pL^rEk9%uk zdutFSwYLUQOb)99j0CJQGsfoaq&%JYv{1&Gd+e$z++v7EASG*G$Yw~2nQ}2-J9SEb zzBUsB4=`+OrBfU-j#w>F5L98l%%PPzDr63s!jZ3VXyuLyxkIaPRNz_Zs8Bj&8b`jy zp^b7>MBy4jM}->4hI-|TwIA1{`WbPC*adgNN!WjOK(MySc}LdzEn$Z7#2i4@nEt zPU1?Axc;}bDx88vWLuA=C?0pmeJ#Z}86unwNj}a<@_{}tBwBw~n0(xFz=y*s-Z|YC zs}=Xm7g%ZNum=QP|J@k-E%xrb5jYz$r&}B(r3q&v+#Ywp#EFlRc>A0>IOVdwMq@}l zro~AcHcwe521#@2X_|GaX_jc9n3zF4sWaT|-C{Pq;uu_UA+EjtJk*SIEto3J7+bP2 z{^>6^Xm^EG3pz!EK}9K^V&pc?I(6GDaU{!l`Hb+`MPu~cl<#eu^L`#v;E}6+@)7QK zt-D>18I7ygdh7L=Y2EFzp56JHWCZ(LQfZ1S?%$!cV1(O(`Px^V6p-^iu+XmUz}=hG zn1B>M3~%9A%&aQ2JW68Fg1Ms@*JGO<62@{XjbYC6SaBcYa^9>{^Ja-}k;Jh=Pp3j( zqE{4p1PX0@FC;$5q87|LRWM6@jlTJoPp@6Z16BLdFO0RgC4Mq@DPNNoe`9S54>-2I zY1qQcC)|+Q3LX$4eL1jk?OCHamNO-`b~^!#XdW%v&KF+C6=rcZ@5rL~B{r`XJIeqa z!9fd*!G8!LF~^NdIVtb_c=p6rv27M|HJ8IzICfk@&ZCMMvO3tvd-T2W@X7V{@S>L@ zOavU8e{G2FqUQoi%=^K;p*q}H%AWlzLx2?F5TC~C)VjFH^PQnOf_S6wU@%mt!Ltn#4x5Vp>wXIDe04ugbJ7I-0q6J8h;{8iy|WDMg=5O~+0QgKd$R6P#kdOcrK zeOy@Z)Oh!a?&QT~%e&jmCzVgj8{3V~q@GkQI@D(NA4>&E*rMbXhTYxR?mwD}Wc7Bz zQXktuT0GH-yMj(ZcNge19)Wv6cj`$+mwC}1V>hD{V07F-Qjr_~h*=Y9&AqXR_hRbw zFZOj^AK>MhXxF2%1J7f5;X0i<{nLGi;^&O)-Fxx*UiK~cj7+5f^U}*xG7Y;dJ(^ZI zpoNP;d6l5F!)4V+T`n5sLYvia9qJRYlac4I+6!DFrKm+8(ZDb(ii z_%j;|#GCMGj)ve{{GVb`wEKDD-s3S;&TbUWN1s##cuMw|?7(5*1_CGmISHPu8)r{Q zk-j`kSGC}f1FypA4gR~zxpafWzqYK(<9J4Iu*}w1x?iZ2N{W@nN35X6$5N+%w(l_| zZmBYUOez8^vv-5wU#oBT_@2j+)!FWLx9@q4|A|x_sbg)>>tBl{-=x`M9Lc>#L8eTu z#x$D}rFtgZu<0{e`zXcK*`5kHyfQHBI_*(h2lhR^#A)tY*}Cb+3=xa-{ts z*IAsuw;T>oxPEoJ=11p8u`e&Q`(NV%d)b#QM>JzaEopdg&fUI$Phjs>O7H>&$560u zS*&B3mZ8S#5&%4cwnH|oM6DrMFkvMYlI+en-6#LlsB|i|Am@cbB;+P!Ynt)VgC~<-KiHU!`CtHS{PZVMbyJ+IXH#1iiA0s7z zYdN&k*T_sgnXCGglU&V>e?fpM1LY$c0%un6fVw3#S=redyKU!xG_C;(d)lNq?n zcVbeok2Bm=pmV5jq#G41x}}^7lP=Q?{)uQNfM#tXz7r1b}BO7%!XF!{g3n(>gonyO!!&qo^a(9uu179>b!73AI4&%!wN*sQ8?? zC`p19-&EZlL&42I7y4o|WM$ohfxQIyKcxq)M>EfUz7^gdvKCV!VR~jkM;#*gD|R>g(P8CN1__@oX4h z;1=9wUS=F6Yijh5mYrynlJQ6e&UL&@mm+%_Gf41e*@+J%x-|5`Dr?Xyg2<6edV%&H0^575DTrL{Kj^3i5jz{;D{!+8MKLxVZJ>b(CKFP<#LD9Fgiw{zC>qq%SQ|NbU^>t(zx>r7R z${O`~<(8u`2!MC0weVQm>uthV`ewfPM@lKpwU{3n6gL}-hsGT<0np-tiGHfGZUBvY zj>oz6gXxd*b?9Vj6%L_832fZozYA85?txWW>xul;1vZ83#NF2C^Gj+AG09_rEkhUA z+5hQ8W%j8swpjAr&FI2On_HIIYK4G;OTIMm9nFkhx#IAnFluJYbYCiLa#d{thNCE3 zp&XZ17h*Zfn+rKxn&44HnH6eD*)vzkAp{2|VlwZuA5^-EWn@HwEYKX)hmBUgjvA`) zs&>NI_Js%iyL#}>R|bqYS9Lk@ZAg4N4!a1H z(6yB$dJocJsNBI(mS$eHmb+vdnHHB;i+RCGCEmr=HuH6596WsSe?5oG^O^}N7&Wqk z)&tvb z<94*A(GzcAvL@P`4%-KnLL6e>d^I z0r?+AU8zok@OiENJYrj33GgZx2h)zX!rgzp7S3k_bWNquYw4Mq|9tP36D+-((l2M@ zZr>u5{kw`F^qaMMmK=$*CZO)NoOUWyW@MI)%Q&bMZ!Od4G>VSG?XVeLN|9+)D-;@m zS3l9LNrV# z=q1>`k^EI#^E9C|a}Nf#X&Q6=4t&F9vWHuNK`K^wL0cC1c; z;WS{^ipOn@1s(;TlQhz!kmV#rNqwpDpH*Wq!^?7Z#v6KVI-k6(d&8DTT??u9v2jER zc22@>N7=w2#H!cJJ*{z-y)tLyNjVA(O(`0qSwscB%nit!&%!e-IHmuBA^4a=x^uuA<;OLxNV#W=(&gF=qYRO#J?!~+e@-RNuJ`JhRz|Kd$Ub;MX zX&<3VU}^J7XmzE;uKv!;aHm%boV6!VA(!xMuH_^c3JBmq*5jdQnc@Y!T;?=3zX@2y- zy0;Ei?V_e{`WjIIAVmmIy!iYy5TZHpf7#~Q=*`dOjixpyU9XGC)J>o!mt(I*e3af# zMwJ|DBKk+GvwCyl%>fHV12%w`PK8&|j~MTDx`-4<4NwLMN!y z=ROg1y!!#_4V#}(dvr>enDM+-=*B`opSlc+DI%m#8LcnqUaym{E%20N|B4pOlNC=# zVKRF$6M6GXBurAcsqrZV>-D z3%nHNt_(BVMG?ai^%yb0LS@1CNBn{~pHX7Dk$F zPcTM24mAdui}hNv)D2=2decAL4VfN?au|ER4yTW>dxyjx;|U6AxH4{$VgXNq@?1x6 zW*e3AaHL+>Tt#QqFgoH=t2s$!ONxRC6sFHP3QPVnX!u-x%F8=tyN|+TBQCH8Onhsl zRs3cuXa~G*ObeQrKC}~_n<{#4w9+%iZ5+zP2oQV((DD zdge!Y=96Z~6{Kw5t9V+C76&0zX8%c+{^4Qi%hnFoh7IT_uy4AIj6e zmsB;H3`uP!y;vM;#qKHK-CIm8xh-~99x>6HfhYDoD~`A#hv+B61xxW%M0qMCtP(4S zvS;QfM76bE?cS`&Nt#sU(qyTuO?x^OR#cKGC(~(`%$l3ubXQNvqM69Coe#q>`X zWS3=qXaU_rJ?~pI$;b>I6$eVP-U*X@IUhA}Rh|2Hp!`^Nq9HgM#5-hY5j+?>WY7YX zFj%|Jqay2|4={~^{jxgI$f0CxJ7wAZ{^mpRv|U|~y;t#1mU{KBdMsW!&V3gquMMc% zd*zpMM@#!z0Hf-&;5%rk>*;C(6p2|w#2yQTZ{p6J{SOsn4@KYPsBpMa0zFNj;d+3Sgu!eS7^;DZv7*FUz;6UmaVrGDmn_Zjz?ZE%Z{~R zd$W-H?P^;zZ;mEYIRd7(@6YN(fiV2aG>39_XlyEZu>lmO*7l*8@p*WbrNM4Io3m#v z-O#-@b1+Y`Yal6pgJQldR| z-5SfSJLGNF{f2cvr6;=;M~lE$W)s*z>}m$tD?^N$CT#^Zz$vzztha=|?>pZ3OipVu?p;xI{YHYfhrqFWg) zUPq>W-PGvQ(YyzZSg-AWRgaitriI*hQCt&OYf=ZjFV~SS#fgGNw=+sKuyl)}zB3(e ztwy@BaKBY}~IrxoOqUff0cb#8;;G=o#tBmW=Y8my|ebi)2)zpEw=q( zmNb^603M_ti_{~t>iJ#ub>BUfw7~JjaA?+qdUOcLy~o524JD7v3J)a*4l0Zp*v~b( zWr&JBJsYjuSUuFxei59wAWy2fkSArDX;htyOS5mJ(`sg}&dG_tql)sWxlQkH-4%;R zSa`z$uMGu+{R>F7rkGC-eGqrgYEz$hQ8RMW*H81}7Fq!GEd&^4IH_|h#kXSujQSKa;Mm5A=zQkXX6KSmU9ByM0jB**^y*%Y|Pr1TVt^^xq ztIPtp-c~&jB(_q2sljB#qJ<;SaZg#E7Lg9hF#CmRVSEfnyMmf#B_fliQ`#y%nv1Nl z@Ru3Ske*$`1e5ZYBU_I*3Ng*t=da-%R^?Kn?tewn$I<7ofOWfdhpCUJ;azy|iI_OmVxLFBWLUKh7+%Pn|+^<>NI47CUtSTmE=SUC-*B2`HJzDUU+P z0BQtd-Vndw;V|)VkiH|7*m!st|50y_rJ#qdb%ccWbQ=&TK4* z@UiJpi=E1GnGY}Vv4iq~kbnohcLQAD%K8U8mM-tO)p{{aD>lu98G3EKo64^qAN6MS z#X9Y_%`@E%?%Sz8o7Lkrarz7@o?st;QsGjfoxd)@Ed|y)XRcm;`!Dgq88v>>+m=F> zbV`UdBwc?VQi|_%V4)B?3na!HPPCQUR0s@vW9_-^;8%X89On2^8bXR5u zf5hk(4Wqm1FP7P?Y!SLB?CjVp=u+Dbl___4nGE4M41GA-H9JHmA@5S4okI7)1NMnj zL9X`IWO2q!T~gjbwJ|?L2#1*H#avno)%~4=4T;;jwO}iBi?Mv+FM++3(zL&J0$;Zq zBofV!Yn`Xgoxv#NIz&YaKPSOR?xk zoeK5rF6c)0e0&ODc$~SN+4~>WTBBx`9MRp8by5x%{FR})Kf^ZoY9_jFTJ_{hE631r z>X`tROVLe_q+|}{nCsqCwi~q>y?t<9M@vouyZoqRs zHp2rezs>6K@?{0F?t2s&1~)&-t8V7D?VHiq;8Dx1 ztuuBHT75I1y%u$8FWR+@cf(;%+&!a9T|%wM3~(DBM@#${hp+Ct-Thr!>wPmUN8GyP zF{~9`d7-MtF{pTtcPV&8=2bg%*if|{yYzS}U$}q^CWr7^yMr(uhc$o}O!dtRSAbs= z@_A*dIix>!Y+c^5&-(Z(UNxJ;MP5CdX6)L%tGzio-g*l$dn;r1N$tgA?XDg05Z$(J z#?BEX#eHCw%rhG<<&7Ttbn}c|v|zAdOXdqsw^sw?Bur1d@wyOPR7mKOGH|O+(`{+F z2FMg_mn2Kig?y~`BtbDo5uZlLvky+OXiMhmdhVLqB=`#kyF-04$*IYIIHVg?MKUmj z&>b})>s%5KpF;2TDypVRhQV*X0X-ZpiiUL>zbcrgqx*2UD4JYU@DB5IG?pcJr~STY zXu){LLLr*%_c42LG&03Ic+!jw+g@t8U@E6uodRu3W|%Pp!+!y7d+AC9k4y%%0_-SU zs*X)FVW9arLCNC30-OlffZow09XRmZJg?(kWa=1$A;>)hHGbdY<%DQ zh{_?%e@)0RafZ(GkKjiy)xAxDh6E~MGmqAnxf^JBgfWuJ%#VlKDCt)=8ZQF~=yE?y z1D%fr_;`I-S+r5IW?t`ZVB@b%U)o9e2SQa}eu9%8GS2k`*-0quajvf~6p)2w1YTN( z!Rd`cJ1lZsFEV(~-6WBq94Vk{D4^JLL^&;;PhS1?JdHHc=5vY z-Cu|U(`h&hXfJv&M`JtCaAvxRZxpaQfL&Cm8;_fL+_OYq>#?mVzKFbaf<_P&rwzPB zdw}N(e;48mf1VBw1fGj}lP}yAGQ2)rXMV@<+;kt$dG{#9v!uTj?mz_^j$MwZ+8Q#v zJYDB|%YXJI)xDu@ho@sb5Qx9ntewyTS84Hy>GkUNux-H;u`Tdn+XAfzwmmXEVq2(g zvZJKPg?UO8LQ;5fKb3Cc&h-6z>uw2|w7R(SZ3g0Z{m?x)*g^dt-sBb& zYIC#2+YvzS$vf4Xp7*v8Yc0g;PIc=qkyLsYH%y;1qPbepTpbO)oDk;%5`XPdjg!h9`V2CDSS!AJp&W z9zCED^T8&iu2+B1?kVBMtWmmmTANE`k55;+-Jf|%cu$XTr{nm{^WFVqz@;>qEz7n-N4c1A@%?UWYrM)gp1)N5 z<3bI$>!2r^3q;GX7u!*<4@C1Ed+@>V&@O}93+OdyN?Xu`B%>qV%{!_4F+XtLEsW&G zEK{3D%3x%I=gH%<@XT}U8A&qe=4$XIjwHbenA9EO^&MNMbUeB@&7PdLGOyM&7h}WL zoo;lBRyCX->6i)a0d}-#MQ-}8ouXoTV+)ejk7)H@A7*0&Mp;h$VwzM_ZR%}bPuox8 zP_J&dCfshY)*#c1h(e9rY*Kl8V>iFX*G=O*z11DiH{YMOm8rTO6Q8qECkChOs^9kE zG{kK&X~uQFI}NkK#85cy1$-p(CR*=K&UZra6e>ioaYd1t&;pa1{w#}6}e&T~7@dCqfg z&pD^3S-*BIdvzi&{PB>peR|98LvQGNUzzy&Zda@tGq6K$LvM68>I#C?hmfeaEwuqZ z&^?Jm#S)ZSnk?$<*oLKY@Y0AAo7bQd# z2$Q{2(Zi%eTg9ZUXD@4@Hl3}d;>QE{-=@A7oCpgN;;$%S4P~N0*mR=;jfD<5g+B%; zJW@NUw_)Pi-L7^g37%pYSh`hJ{f>mw6!TO+p15)k@tU5X#NC-AeVN*ODiN4Z}Ol$+g4b!)1{A9uOI z6#lo$QP$dttTh`6^|x6UOvF@rl3@I+?~x@Vtd$elTQsAqzs^{$C*vJ7e5 z;oIA;!12d03^#UEMp}*= zOs)#i#pzNy+6ANFV-8Bpi^;7Nt&=9|$kys_!yk9m@<%a4pr2KMCY!u?X=-JHHJ{+! zRrh=!$Q?f5<0)|={{7^t+$D4fvHC4v@)9MU_hxB?BAJ3bSreNiG8w5hh_5N2nI!p} zNo&mV6Mpv@<8McpC$ad6a!=4dMwf~sH^ALV#@`>JTkikKeX*&po%#K^3E8do-smj< zZ#(_g_PvnX<9E*Jf-q*HKB!;MG!uJxQ$(Ej;I=H8WM8d7-Ox$q%jXMMZMDn42a&cI z`cv*vQEA$0anXIiPh;JnV1aXGp|fY{_NB_9+XSa!X>(wS6H0M9H=OOzTe@+E2}hcy zbsu_N|9YGE^Zd1IU31lCPX+ZP>OEiNuch%x7mXg9GNMjpDteQ$aZsqKDJp49kS8*m zd%lc3gL8#JUg@e=T94)X4fMgHSHpD?LR5iJb~YNEa@B-*`tn`>peAEqCX3VOMH%orZyEl$+h{LuKc<~u z_jv{9?tzIDdnRtnu!&*a89iUOKkw>x!+9gHzUbAP=~xBz-g!}V7{)3|iojYfYs-gg z^|a^GsL?(pY~Gzedq)Ie@2`*(RZi%@S6M+)G_VU zeb(JKFEkdN-_q^N{>Glnr?)Ty`y-e`V^1}siIxViNP(Y+S^n95`ur`3p(I2Q*thDmA5+I z-}R`XpA53j^H+KK+qxL)09w6GPm_Y%-mV)du+sdVx%zqK-Z}Z*bCB2-l##KZJ=5TQ zJ`;bDkQdYpyT@ytG$yi#%=%S6_HHO0PizL(hgc!07m3X#VIWiAvFU7kW;hHFDYjZn z7zG3ywmZ={jsHOl@UJQHbq8F=fc?3~PkZco&yD$ON&Z$<%T5LTwg-ND?SUbX3KJTE z!+77x^!FLG72TS!y3E<}wW+2tB_2{xL%SmGwAfLR9 zeE4eQR&7)BsOAHgX!>iuK}NoKxsb9PAHI`^8XkN*udjV%M|(!~?~cgn{0u?$TOyqy z$dKdTdoFF6N6RaJ7q2}-fT;P-!AlO>3*~^F{dCG4c4FI`x>Ys&niy?CY`p^A&+zk? zqIao^Iy0sf$$|LBA1@*hKVOc%N2I24&!sJ)c-c0X)1uJLZI`h0$oHH0awvATkmX<7 z+{@$QH|DV!nV8aG=QR#_5)VH|)O*EqdH%q6-MGDS;Ctui7yGUbd>`r0d86C-*~O~^ z-*3wUL5l;S@2fX&xE%V9wFowYayk}-moC6XFQ}F1x4+|U0=qR~e%^&FkMGP1mJQ5E zk|F55dqedX7dsI1-u*`R@P9A*L)|Zso+<>x<(Sh5rQ=2(>y9rI@g=xy@5oA(FyHX? zqD&|lUoA<9GFg;asZyj)eWZm#*pnPeO(wE}lA@Iw8@_0ga+DgYumspsb@v8ix8H?> z4lOSB>pU7WDo&c%T>edY&|OiMw`k!)3Y&_pL10OA4r1pQDryzc=e3n8rCU*1e1&P1 zhW6-O8my<==5IG3vZwP&)ThR!k(bd!nnmoIC>#nwpapfMOXCl?ui-nTSAJr}Y2^-x zzO74vv${ig&sT}KHTeAhkNrKv19EKl5SdCKwmTt@V!IL5JyOyrawD`wuPU(c6#ezZ zvMddyHyZW!|z-aWAYj+5sMK`m5LyoL%4s5+CE`7=OE#2--UbxVt2NQ7skFk zg#xjs^l21ZgRB2=331pbXx*Lv?(|%O@F)V-n-G%czw`xPp;-JzhjuwK9qr;yr7ohm zU1+K|%yg|lw~=99g;fMRL$#*)rZrK8eeK!;M2K$_>FNtKIJVjb@ecpj&SS!YswwP} zeQsa5HW=HC_8jD~Pwgus{sse)ynLM=AFwRGhvVAo!_oScdAV4L(S<%6n$fxf?qDVl zX5G}5ys0%&D%2JghLp|v^D>qVpTtxTPiyT4-`IMaVw;B zNg1sSVxhSx!QgH?j-c(fvsY-dXz@#S$4ELIM(*ZW@plwGr@_GeFJgCA?X295%7Tpp z7Zsl}_>JH`rO;JqT8tHo--X7`(I|w)opxOiy`TPoZ3P-=v7&a?-HAB50~IH$#^NG& zDFHWBC@Lcqh#(=dovX#og*BmnF89RFnd`Y4vr-JYf_j79gZSK9uO-P`VN#Zz{_@d6 zu~9DPO~GfTTI=lPi*rW>S)a1!K0Ug&H@PJ!xblV}T$t~)n+qYA_4oF?LS00u7&Nt0 zDYXj+zdX$BgUo)%Q(k6g$y9E7y%IV`+`{JMod{JpsXe^hKDgZ8=MC~Mv$J@DIV9=X zqjrnQ9br;ZT)L}JHzpT1--MhDIlR<9xER11@4a>wNx-M*J}9aRJT@xZ!;%VNi6~H@ zPBkvgP#3fpcrd{Ts&~Rd-K!Qbc63b^bWb#`9+=rSsaUyoa)}ag|rsdEZMCG{2K#@)-VYr#*wN-mTN zBL69zzuGQpIR72sua@}>#Q%S;=U;gh|E<0ziG~k9;jg})|7W?sRDL7p|52{^aW0kr zHSizK<>fz>3;ywb{`~9?JC*C!wrb3%q(-8XG z9qymd{DU3NZIGZrmscPh+({d-Q@PHLWnEWidwx;o@3N`RcI1jXxyLrbpM%4K*5GCU*8 z&`dHMx<-cPpUQxRX};L~6B(YqUWUhiK?V!k&SiKgSA2xa@E6GNxGckBlHmov3>v(q zwa~GnKV*p5?h>73X;nJbxqeoJ~BN{XB&W(V@~U#tzVGGorsj(2 zkaxxsh?^~oYtE%HUQIw=EaeK;jNIivBCK%&byL*XS6Z($cJJlJPW^N;fm|a~^7S%} z_(hqDxlF;iVhCiqqXRNU$ugHW;>+WEd;kjcU>WdXT&PmcHsm&pm44$3m^B$>XYY-;B^!8(|O2w+s*KtQCJt3-O? z4*@?(F{=TI_?QWE$ma0E{!FahT<$Nrz3eXmnHs3SC@AnWJ+Z0-!?NjbGg>! zh;3Z1Rgi0wEY~WM>#$!gK1bPJmC6os_@{Ej=Q#Xoz&|6yuO#@_0`TFm!NNb2Zq@8h zBqm#tbDi^YM`UCqHf8FYxBLNXbQyL799muk;8$EJ7R@u{cH+8T*R_FiUDpp<*m^Ew zWsbOj%h(ASm&!8UMKZ4Q%LrXB|Iu5M!)myEzs_;nYwQ{}HwO{JcJKTgR>9Nfigi}K2K1vww zK?1g!=<6FCVkhAQ3oMuOKcAhaxTxgKbVLsMGn=39wfoU1JY-=ar+X?} zJe>_c;Eh1JzRl(gPG+NAWBv4p68$FDdOhhwKSR2&UD$ElIJ!z=A)NeyY`@SS63w6h z%^RRAA-IYjd8_i(DY#-f7vVOZP?X>JH0`wWwCcsUnTMw>C-o?~zE=1!&~73MxT{}S zrmT&apKECR%KCbCMs9fF^O=YiD!9$C?p^jN&C`3k7$1fEE`gp924 zEniu8bHtakWyCG-vJ)I}EfCvJ7zTG_lfIAoQL%xJgH^1=TjD7hEa`h{!&?!kJ! zySH9(QN``)x7oUUR!6Y?r69k_=PqqrhA%JjswY`rrp<>UBZ4z+h$~_9vk}@tDbOHjr-M8k6YN?yfTxrMHAV1#~j#siEI)Q znZ$g`hDqSck?oVx*{?ZtY_?d)q00dMKYlwH&Fx@z0G@2PFg~06@NraC)IUBn;qo}b zW6MToG(S`CnU#UXzYH6dD34YFSOkId@?h^ID4RULZQpE~*U{9oHPZFMAN*s+ND#Fe zvMrfzT2r<2nTWg;8i;Ym448eLMI@TNv)4AC!m_X%pj|P|&->({3%=#i8dFf9o;g|X z9f{rZ@PZN&rX&W`3q!Iki6)xo6f2pY)L)Q^9hGb+MCO%HLV7P0&NKtqqsX?H-AWS< zG@Nqd7=&wo)FG^KyMW!Q6@SQ*qx;MO+0cQU&tgZZ)z?HN6SH$!#H^UuNr|@UD|C@G zZ_D&z-(tn6B?$3Ymcim0LY;|kXURUZP)LdxUn2#JZxNtYNV1KtDH_ZE&cWXx zaGWm`|CTj`>HdDcgTe>X(1o!`N8zfw1mjd48_4RY6|kSo-lP?s-dD1?&Hrx}Z0i7} z5Hs{si+5+agO`o2S3IiM;eIC^RXwy)-@x24&en$zLtn7ZHQ+7m?$X7s%vZvT>PD>H z&3Z``*9o6zYu3uVutSgNA6vW(cSLCvLhK{apZr*Q!nb&1*6lm)DQniu>dbQEk=1%d zx$#l`T=7mNpU0hgNdLfM=aOWhY=^*FU?m6b*ztNfe`_$7{oCCSIV}Ok9lF@$d~8eV zMl|eZtB7w}C~^K1I0rqdaVP{F;NPCTb*s`JGm<2YPAVFc6cKZ{K5MW(tIzwtUUwGz zoLYyx(IcVY&=oeBGTcsBi^r9PNKgc?uZvm!V^=K`_+TF|E~68m$YX+)5Drfk|45c4 z!K7^5E62cgRkHILT$9jabhDBz;hL__lG^`<_IWQfPJa5HEb`O$P#)T|W+v)-`y?~F zn*+}U@O}VS$kkKHVJiZ#QvfUA>I6?oRztm8Th^~+b2-|KEOBlY4U-Q5w^YWR&2dX* zT$Lt=#z{A2u{cs1(XfL?m$o(3J@)E0QqS8+uOq8xa`?bxeeV=(Z3fz*wiv)QIoY^+ zAiu5oK-3{L;?;n6hg95{HNTdc#zNX5btPskADZtD*G0>Tl?$>kLI4+NSZ<1l+5=pr zNhq936kppV+F!VPhWe-vjuFEcbp6`?mnB zQ8-nkXD?(*&z)3PnIX)T!!k8Hy49{i?7q&8mc;SSv7#~!$<%!~s2gy;fczt>fv zc2xwO%sM?7g*Wmj26lLx?{}eeyT8h0XiI_vbSoobPhax! zkN0Toijn+euCm6*&2R923Ve1#nUkM><@kz&bQ`3}*5jg95%|fF-g`t!mw; zQ~MKG9H%rgvqW(*hzn^VI~*OL5eqGBcVGa+!PJ=-H5|x5KqLJ?2zV8+jqW*blihvU zamC=6Ox;reiQ#HS-WRrp`pw#P-FsBSTS5vxGR%C}uvb#^b^Bvj0M;nLNAmLHOVoFg z|M`Y`fZk7SQb!%i80;_O7dG+>Ur-)=zQa!PtuV9CIPggTuLSUqHZJ9l9QLCCY#LyB zb3AB6lzXCZW&=CP(cZF&Cv7xTbOZN{jC+jZzANLZaH_paj*xs&hFmT4a?ads+->PGPTH^pjv z9^4Ap(7+q6-E-LHZ@6~upI#vj;PfPY(nyDoCrBqfT;BC4Cap}o22sgBb#qy}1Lee# zDXx=F4!j1yv@EexF4uMr`&+Xih$m>^QIrS+X(4;1)u9*Bb+<=UjobI-s&H8Q`sx=F% z_x=%d7T4i@T!%p{>nA$=4KH6Q)Up#rd)TkTxx8$11A5{8+6oh!&4C>N-UZ-AvL32A z?5_f_)UQ>%IpOy07FNu`Cjxvoz<=YXeHVu>3c!!yIu zb^bDtZfm_ zd}t-#aVh|7BiQ65_8y0Q6R?$l{aS|omczaqfTeRO#v2r;a4_s;D~%XW9v?hqr>gW~r*)B3 z%eS30i?^mfAcZe}*7^XS9ho(K?Q~eq_~9k-9qY6rTr-8IF$#CRgtgAY#1-pBD8qGr za}zJlS`_E+D9(NqhkS&Wm&fZbk0X)F4!^mM1OExYZvpsax$IjxY)b%EwpaLw-&?tl zcuC3r#L<{l{F9aZ<{{v2m2vAi?n)W=DtrAOAHp#7bJ5F?z2YPT4a8ch5ZqpG<~ORy ze(toAJ0URtN<#dl;vMKu%V7Em*Kf# z^0E~AE9-++yQ*vzb+}*o8pqcCN@=~%YD*Bxwkg>pPN&u?-pA#k#GzjW$*${+=z9HOmlvrT>=7|7@mou~HAm zD;%(X^KCj5(i>|PlQ{XiK|Wq4Kbpv&rR)y${og3uMd$5mcj(G_y$ zF}8-&-JQYqa=I^o?tYo>4x-z{>7wD$T)UCO_GGaB%ku7#VYd+MlN=W7;6l%HBCf>$ zcA<&=fun55U@vl%K9KK`QQ9-eKQH4r!9r{3bz%#+OS2tURq-K%nYCnK-rlNYzvc9r zGLS$jTLpU0$@CgIy#pZxA=X2mEGFT%AUJc;YGO&~6oy%bn5a8CH#qg!=6pb)Q zXIjrw8G*?ZTL#W&ma$tnt;HGm9bn94TK5ty$JKm`IBZP@yYDjJhh^AGf}QxweDAx$ zcb?350;hFb2Ag-8ZD~p&~M6$IOxl6lj{gN1) zv0r(nk)0*H-ccF&ZPwqo*Q_{a237KJdS2K6rhLaEa$OH|;E)VaPrye2JWdut!(l(* zu-F&ao|Mc^aM()|#9)Fw4cG`7_QwfilW%j_3Iu?i&y$0YbmPwBDVPz5aG*yrY+1ry z=LG&cLHvFK2z&_wN}0fC6HwlL9Df$t0VrV3N?}a6N$>nbB_^+a8kKA(r}6#-@!y~^ zLkSw^CU8IT52De>X%q;;e%*zS&nD2q*%3@PV4cTxO@^pblEU-xgr@hy4QbRs%sTvO z)j17O0#>O%oZxH=H<;xNoJtgaia#It$A1prDWUvV|_rL_KH~V>Mc}9;RsvQ}xUY&shJ3mD)0MzcQp@44E8g?r@J0@Uh9p^@nuv06p znIK6*8RkRxaE*B;AO#w0hQ|I1jgdY4k=I)_<<~8e?V*ALKRH4CBLQ~+xIZX;lhMR(=CrFOh))o0GGeDpdp@Ua=d>}91?|=0EZ9#wF+lsj z>F7B9w0RGDnRcRynK~SeGPlJ$pu` zLdl+s>49B-T`ZzQtMe}L&@$5{)E91Ng*8pao`+7?uf!7CZ0hS(SF>jxfEQ=jk#tyS|#Ba>3P%|A7#^$P1(=@|d* zQ?mV>+?jMJgJk_J$c>cAeM;m8Wpb*av2X?3i&ELk9R5_g_$i0K2k=T6{v^S_6kv}K zalv{q9dm6J`#t$qb~N3s?V|SOvc#N~^s$m|3>lxqYVl7{212oAZ~Vo)KQb#OTrrHhNX9JE^Vm@G#P$#Jt^ zZq036ZVSuj@OPz)H5{H6Z0?ic=Mj9SUvAA*VMa*q&QzAp;Y-uS$2mML(p1XuzaseL z0Q^MIPug}zTu3f18lR+3S~0q za(N@Dyq{Ax=Kq2Hi;Rq~*+{Ai%m@F)QMKvfNRCRI88#VJ&F%N3jH`k^$cEC`F^=$K znyBIkw4veW^=%q>9rE)UqL(Rz=JmeZofh^w$N6uXIF94cM#dPK$!CPKFMxyHb1j|u zHe>(7-)&}Otd_P2hBp6Uoje|KO1AVtnrutkIPGI;;sFw};n>hR%Y2w=ij{tAX3uO4e5q!H}7Dw>)vLNmi2V9!TU$+I~UMY?_?%&Bfl`h0O ze|Pxnw85{_;$ppDr^$Yo{e!4A{(z-X@7XkVJM}LbI_QCA_A_bhHu}vzPqW~}f>2go z5Vt?s9v-)BWnZ*w?|;tQV=%CS7;(o}MvP)_rP1E)p)`)p4yI`oAj95FgEU=Q-FLVJ z8mr*R_JuV|_$wx^G+ZBz_22VF5j7FYx?EwqM1R??v?27%HKoeBY+-w)awr>{98T;F z%J)fUfgvgr%fnprg>p zO~Q=?^f&HP!Z%W+hf!djw4nr)ePx;k%}Zc6rD+U68p0egRqGfhFn8Jz7Utu$Xj`rh zVF=HqxV(a9w7^ED)r}H#CP$PoLyKdIkWP7ls=RN>~OM~ zETP7w)MNv^?pLxk48a;u-B|(~NUbZ8tJ?vyu-{nIM=#CAX_M`vbi8J8SIu{zMt9a| zhj57vUd{yVG+uk@LR>B7rBhxm^7`o)*^0m3Q+T~wgd2-B8UA`V6XuZFU+-or1OAv` zB5~RwjL%Tdu!CT32fAX1M3pgQfM!C|G}PBoeN+4yyOgS-JsS2ys)p9-U=FYYd~Ogl zqaY;(>oQ*|roS_^L+GH{kEt~L7ubbV4V}Ma->2eI5`30fG$FzrrXkG9;@l1m*+3A{ zLEE9`>}p>sx5qD1`IWBM8N+DL=c&|?xiNf_O2+U>Dj|A4N_9{>rZ4^**%k`y<5Y{u zrKD^1T}1-hZy^`5%X=!76;r=O>ucWkQ`r>yA;%P4St{*+Eb7U z!zCNUIo8xVm4Lq*rQpd+g-h||`|Yp(dS^760Zi_MqL@9^fr_`M4#5Ulw!hwUsNP$( zyo=y{5P2gFx*qP0P|@dYBcu0{H^O(FR&zW0pCaD>lv4l0-b`_n`rk1Kcq3E_d7~_9 z_|s)?M4kuT13IK z)M6~T!-_TNi`XYA4mzs|Uxbbbzu!y2!!Q^=4MmeJIB0wX>&3VP6_52d_I`>ZoE!@r z5XKD-?tf0CIJo~gmg1o6T3MDEeq@G8gA;h}AvK^;kHio4F{7BehQFu8y`G#O9{1R$ zzGw*@k!`eiFok^2iA@os#QiB~xcE!$Uwkb^_C5IGKNW(D^UUxDjoseYQdk-}4-oe5 zO<@-L{xiO&KV@)FO6ud@{uI_jUWPr(@lVir=fxBjOK5dU8twF?uo(Jv*{zJ`L!2>e zLrR@&UC|hNp-2Mra`C91Wxw*56xpvFN1gsZ*w@hY_LZH2d4&8<#v$hGDTVtNc5@2)I9M3QIHqFua#F7A-eNqN+{AeIHi1mclOflkCsnRP zPg)AD@kKRm4*3agVhW9QfE(#Cq-cOErod$2lE$TwV~VHRZw*=ggR>#yI@693M_n;( zJvZ$LnXfof<}2Qif|WfGXj8KDCYT@k(UB?aVX{>R^`l`a>_PfLwvDp`|4L5K<5?Hx zvgF(Cs-yc5%M36ax>i6STZ1W>T3Irbx5ilxY@Clgw6Ag1OLin|YS0%3NW3 z(7f1$UCh)Oz3*8DyiRt4<7zR6rI#H&tsyOK0wONDz4o=aZQ-$QUH!_PAIV2~l@63Q z5Z~FF(DJAPb6|`6tQ&uTf>B8~CM|g2=h4>8pJWd5%-EG}m93M0lDUg##;t6dhHDh+ zuOqw7g6n-;+A%BJtnSDblM0n#$vm5>wecZy15-3f^b9jsyu2yrk3mAEo#V8IxY8iYKB=>k9vanwh zT!w2v7u(S;v@bF^f4gEnF1OrwvFZ60?M+K@*#cqQZyCH77=zwg3%ivtKny_%HJKy& z2E%cFp!!Q6*}8yVI0t;rXDT$S%|T_mlkt+bwzu$%+_^SE}_k;C| z?JM$_=SItA6Y9c?IQe-Us?g0IybZe9+o95J?(I;G*gV*wQfxllq0(&rutOER`J7Fq zwN-?}bS?_XgJAiU&>NF6wj?6VSf2di+H!`K?Ia z_AaTJ;%++bF#Bf9aLCKD%{w3u_HmzV!0kt zXPGy-PaQTq$`7cn9&@oPQ--rlzD!WX!F1J;BuOHk2-giqU+kLn6S(iqL~g`zcmOWE zItq-HExVMnzBFHvay30?{v_TPs~&mLl8E#)^|Uz<_2~|k)~LV8ZRi>s*ZFOHnAf@d z!+4f1JBAKi?TTw`?E~}R$L2ooMDGbR8zA>ZuIgWAymKiDk`%!Xo3SpDd|ZF{pXR}n z04($#GPCUjh?a7|%o3>X<)y%#Uhj~3`FB0;jJ{A@*Ab4J*1}wYW-?$N957Qe88EYr zgzj%9dk8c*ntb#s4cG7Gd|EIRo#qC3 ztN5SIdz9ym=Xn)Bsh4Hu!Ie<=DDP4;n@6&d4;ZV{Jgaf8UC|_afJG}12jTYs_n8MD z2mBast(pCb;OTygh5Eks`RJD(Ch6}D-O^1l!S|Tgx;Jahq0yy^4qxW*xG3azbsO5_*U~^1$dTwr<<9Tc+yF&fB4#` z8A!*aW@n}o7bKSx1zet>BJDy)9||Rda}T?3JNR93)k$^ngD0bkSFFp@smX<_$%XUi zQZ*c>po==Wprut&T-CB#QT$-b21W6TqZ<^uVD9sS2_cveg1OHRCKolj$U9gm>JaB$ z>K(X!bvoOU1n#|NvzTY5rM?f~-?GiT$2OYDzjabRy6mC9kbg{y$S(2@)D9Mug-?Cj z!k*>GW6WX-NB$>}lVs#kW~j9#fcs}<@nGi?&aY7~ zJcr{^AL>RSqd1{ufv)z$=x7b!yQ&CII=fN7{F|AxGlWsJFE;CD6ZWoV&h*TjM^^>R z$KlJ_t18hH9INi0qtqk{Y#Ep7Ym*qvWum}pg)Gxq6J%N(kZF-p=1^d~jzjVnJq&U$+9urbHqrIEMFI3 znGlD(c6$p$O@f9Fk`_!i;0xVAP_n8I$Ku{eVUsz(L6i6n=XU}8-jw<6;rwn2@XJva zPilE5C^f9-#c=N~UxM>}!t-amTvv!qE_qTtfu~g2W z%OvjM3}|imC7Hnn(w8wnKc4D8dS%QKukgSeL(6_DOINt(IbN1;A6S+ljHa?IK9($K z3m$mD^T6W>Q5$dJ+bNt1*^3(s|H?{&;T#oI8)Wq<>#>S zdJg~lGaPgwYmQmi_eL`2hfHE4XD|W`Trz{jRF*F(n{4@Wh5NzF^zXl({>ERV{~@P; zr%8N>(+>yzMKb-lME^v9{@V)oik9Pk`ES3T{-1tZRbFX~e9K$)TT9JL*-^NH}qcH`MK`sL^6FUX*{`8IFG z17rmA=X>TaBqKl_Iq#;baXz|gO&LAr$~JTUMw56m=N||DHkrRj{5t~tpHavh7AIcE zq5PLQ98F;!PJX0GOukHhtV}+X*MCER{t88HWJ`-KE*O^P62qgv3qo6M31?~}cFC8# z;CtX<^kN~B%*Z?BL~`~~vI(%4;`_K9Tin`AO(qWy_X`zX=AJwSUVuPe2RdmHw) zpTp8X0m4XQ{sO44Eg1j)Ex6+^2 zNqNDPgbUwM=lIg=^yfWCc>*P^MBc?j%6rtG*G75YQxf}@XFQlJKFup<1}cXr;Nq71 z3Fs_k!x7Z|8!oLsaLM?cKF4W9B81eQD@zjYuAb1>u0l*fb=rk1W6jB@7U&C0lNU|q z7f`5`=)QgAw8+uZ^VRB33qc1)y8xa_z&ebdF}P-9Xna+SYX*{VxQY(+VES6CrmJFV z4EVZGU9936wUkk;VY?D=wvug5l8?5QXu2X>EFnc=_`#^Vt5b9nhZC-2mU>w-6-Man zIEhwA_NR+Air2x4e!;@JBH332SRBb#{i0lv*T}V+%eCT)T$wE=G>~WPg;SM!OAVf9 zu+*G#=qt3z(;Tv<)LohEMdCub(Ba2X&N7od{EI9zuVMKJXL2 zJw}`yP`oa-GvqqReFO<5C;;~~mneZ{|Dr?**GQDZB{E)?sM0&&9-Lnses`1-yM@KK zl@u0NmCP!Bu%rUB$;B02NtbQ$30&ZrpM$sIT!jXgYNV@BbgA&*isVw=;3`aTskE-b zM3*YuRcN%SLTrUem`K_5`AL#eFBK*a(b}1CDp}t=7;Z6}lr2YsbQ5vD>&G9@=IVlMugx5Yuo0F)o4AMqvHv2l7uty!rwC?;lUTbGaU9ekv9vO~ZlXKIJIDMsfXQ z>ctn0`8K<(kDutLUn2c%x~!kTd=W!USw(P2^&zTgh?MNn$F;p)qfwj3aOyX8>mb$=#cfgts?t@SR!99_5JN z7)1jiu2%vvT1Gr)gxg;ttHHX_OQuXdG8nZN=NjoP*Wsg=ZV4@WJ*>E}s5+u}R#A1NdsU%2X_^~<3X1!t z6&Fkwoqx7ECl4G{>!Ri_)fYT6aB8LARUJ+bp}0>i)9aqXBkuBWwWE5J?y0T8>^w2+ zsE%G@92I?cQ1q^#qUxw2tiWM%KV|ZaCyJ`W@vS<_T+z6sCL&a~fVv!h#;~YeRa6n& zHyG3L+8xFU%=yn)_OCzk+KqUB=3wA~k6Z9Qv!)Jjpn4S=>JRYwa$QGKRO2+npm$aX z9Z3;vI~O1+l5L|ewsyf*6M@;TO=FLKn`W-Y|6U5<;#a5d5>4RSjR^niz)))=A<@Hx ze2t)xnmlVWTHGdWqw)<2#)3wnUs|ME?XXO}Y?lu@x z-@G%l%o2RCh?NsYO@wyVQ$`1=Q<$~VIJa=_tWveTv@oVLF=FoG(!|KQto-Y_p3=nd zx!$5geMv8wkFsN*VfZT?jy1mO18rvPWya1mac`=6okr5?e|%6<;qQ%>#5~L&>w-bM z)T@a8mjFEDYjGV3!O<4MXnYf%V;TuTuLxt!ph7rP#CCF3)EXsk2vl)DRDto%ePsAA zQVs<;CJJ_C*oy6@3iekHd?$c)0AAqN+XAk)E*V^jy*T`%b@1laFHBwH4wN_&Zwl;PQVc zgq3bL8d*K(kP8mk;7}wNp^%EOf^sS`Wb2{Bh85Yyd1W{N{xsRnd%k-^%XV2PIRnYd zf=ZL6+S^)c{Il(EMcKL61n? z2SHf)n#L@g^*_Lx4!(SpD7U*$5~TE zeUN*Wt2$bz)4kF4AbXEeH;?lFI?jI?-J_z*M?_mjq~bleH#*rLWK6wlj<)}{mgj{d zV#Hn%Lx^hT13g;KPM#aFq7;n!CkQuUqVv4-&rXgD}E<=brOz{!P{Pofu&zOtsralV1Af%^hjXs zTNgVcl-)$Mw$N4BI*L)P55*t)99#c-Yh>FAy#4*zW@Va|3yY;qYL?`)vDJ#L1abi~ zd@J30y)NW%P2ylpVqb^ZTa(CsAp3}QZ>&9v2A)WKWPcwWq3u`o^x#RQ>T|xELX$$5 z4X#hn#p+&X>&cUo6=`%g;te0z*sX-NIq2kP&{8CQr>kvj;H9H6qd60QAXVKBf_YTn zVwZtd#r!Tk?hnTjZ@rP=B|zc-T*8~H#$FftPYGu_X|YxV9bQ$gR;kH3A6s6|?=b0} z@7h1UHKOZ4@>;bnv}2(T#?e3rt97*f*hUxZbSc-24Pwb87|QrLi7%HCX}C?{cyh?{ z)=v#>hPF?2W7<^qNA0m_>!V=jx1cC7}#(o&^nDy9q z2u@@{L|zndbI!p&uPRaQI~@&z;8i46{~-={(X9ClhyD-}o`9Hcl&~ma9Qu3&ow4r{ zcocK1Y30HaDte5r#DBOv1|`SM5qYuh`n>7A-un`UzwlAubjN}4Q{DRRE`lbz+FXG@ z{S{BmTHD!&C)eA^{b8Z0qg}VA`v|f+Op2>$9tA%Po)u5^cD1rrDoxGUD0Y}Q?F6Tt z(|dK(@IbmINMHvOa5ufntVFUI0?bSp{=^rXIdz+I=&*0oX(jgmtp}iQu%O2|!thd5 zyRvhbPzCFrgkqI_a%^VaIPAy^hx-x+UjW8{_pb>ok1&XIyQ2C7UvQ<~ z+D@v~C6=flYGQ)%ec$NHaO*RW56j&uK+Xt7-}Ol4C~HeX5$+;R)Zn?`yaf=tL#RIC zTUi-tZ6f(*$7b3V1W{R>!^iQSN67Tv)KEG*c-(h;WiV)arRrn8yD2@6(mRj(aw)yT zD-6Hun@Z`r27-Ur=XEA9C&?*3ipz4k_BQS^=^K~8b1+85Gbw5!g7HYGoj_#!?s7%X z6WBt^?P`W!tKr}F;@blHhQ?r`Pd-1pMLGC;>BcwETR%?((n4|Jd*eIk1Kz_r3cH=) zHoNF2MlZMVU(M9~0Y8)f+S{0DcQrm}lR~0#@O2UO?cd6Gc)<^0!db2Ukx#PP9|v1L z7{TXkLgmvk<&T(DAy36#A@=yZh3Cc60cib?si&vf#_J|RlwscwGM(R%Tz}@kYF0$R zQhWOCw((f`p>r!2{b0-Clb+;ILCj82(>nl#%fFp>AdUSof0p6Cg4&f$0c@Y0!R7cA>@3)y7$j{TJgbou-T4 z;DpXCL1)%FVETermB5q)yESO|v=3Z{l7+*;34_52ecpbrPht(^Q$BSaY5#=o7WaN9 zb-ozn{YGNnQ2Ozr{Ww6^718;L&-%Iasp|CK<8&hiznA)(Q-2tK>>#RnY|w}2rNduI zd%dsp5|&&RCGPI#{?|dV!tlemdqDogky8F!Lw|?V{5xDV|5V@;Uw8)u9iyOcOY8{A zAs&=mIJEVt;C)MCf2R~GNep7p`3%%-z=Io5IHsR#l;PeXiM}7)Lda(0;lw@7#hkug}{|yB>(YsG#`?$_joo~r^O+l6(iM_yO z*(^zr<+R}4BC+TFd=NBkS7NO0-6XM%fiyhe7uMCxwh<|DorG5BYFwlXV=qvGb&cd! z!EN`OyZTHD?^?+i69@MYQ?K&2NbG5%;kPyL_DHOW^KJl@gGao7l-M8rToABiQYJ-= z^tvVH4y1(&-a5$`7bpInbB%rsk}waK<1xvNvl-?tv(~#@Vvk?uQy~;SC>5)tpD;up zF%&7ax+kZ;6kM#F`jOJXs45mq?0y0+TnMQ0s2bx)?>!Rxt;|t_mjjE{Nl_!ccT23s zF9nWLc13{FGnK8%rkQFyziAG~5I}3_Ur{iS&!4UwVxaOC&`^ zFBu_@lWQSgcLW>6cxe*%agy9gW0iGjq}#C)iy>GsMlwa*e)=vkoakedo|y>_Z>5V7 z$eDV2c5vlfF$C0T`P;aF)fR_g1>^@hQCC=s6B8zdPc;>3%h4ZZ6b7Q8*flnnKv~vS3QhWTEi1xcopw)d%HEHT7net}_bBNZTbH7}AgicI z3!`wZDRe$rh$nS1K&wY}W$B*S^rAXW4Xx_b1sLur0faYJTE4^W_|IzV!iCtvhSi8d zA_It}&90_yL#&%*)M>YBHx~bs9VOkiuP@vjfrtk{Z|DdW@R%v>z;=5vh=i$_KEXqC zPI?)xu&LG1^z??d2niI55{^z5e%%Y+6MOGIt#d1!&MckF;I1NRw7QTXrLZ<(?en15 zQG~(HO7mF<)@B+KbRo_7?cV7AipO7(vqC!bc(v-NAF;NG7$e>m6x|$TZH1cXZe(74 ztOZIPKm^=(Z8m(kKdmofPa&zbt1Y6nzfEc_ZYymyA%x7{wxqSZ&4n9Rn(-bTF7;=1 zq*L(a6+0x?8&wtY6CV@Z%9q_H_se_5<=}^f2lgnzGi1czl~~Ubu{ETAGaOevdy@2q zes+mWP$T>4fUWQ=VmQre(WKn6*IMhBznZ|s`Cvru4eK5-hb5epiv5Q2WSRW$5flov zHuQjKad&B5mUN0qX|`mX+K=b5`a?2y59|zybvIiktiiZZi;RIbC+P!I_>sV;-T%=t zemLPL`SJa-iyTl3#nO>5oeZesvd7Ahh&v*|~Vyo3D|VGBg^KZXLyS+rLzU!nD8fNFIsQGFvom9K?Sna+;`pWY8ZHv*y# z%XHr(y5}edHsGgw>>9c|F4J8@bYJ=j-BXTc-O0(S7Y&y3b!j_o)Eg;ZN0|TmKWfFLJu;KsOC^H_CL|i0%v5 z(tY|Gx(@~Dc5=E8{)FydINdsfwBdfx^~iKr5ZyMJE}fyn3bE&LLqmO$Nhq7GWFC(5 zC~zJC&J$PRH2QI-BJSEr)_qGT;obw>a$G&wVZ4bwPRY0q5X*e5#z5ByVd21dGkb{A z(U@5^5^Jf!mOngm=F^fnv+%&>lmF zbQ8N(Zc~nAqE%69STtvP_3|I*Z#1w>D!ZE#wtITVH_b7C@?1Oi2hu2q%_hu(c>}j+ z2DiUBuxlG}2?LM98x8qcc5KAn%w`bC$sdSu?q$Y%Mi5^sWl7#(G#uy)`(zUgbdM=X zCIgsd!LIEy>5#S+YJQssp>MPYo7uvEjtV8u)c}R0kOxCk8kZfs=d{ zPKqA~hbabtqc=26o<5nopD(E;LN=x~kZ&}VnpLp1|5B>o?=tR(#&FsS(7qM4^)l^C z;~`I!OuKFg*$<>|$TsV< z@$)86pAFf9*c&8;ykDN<`s*d^m!oz{a*#7bIeiJC(eI%S^4S-JOwI$>fAaDa=_}qD zKh06AcCRQd!(L5ES^o;mG1m~p4qi5O$*PhSCF?P(#F8yY{F6vvt$A9|@*gojWLpAs z_JU5ax^{Ns!&u{;ts6}y5M0?VR8Ec~xb*}VogB&z$KgwF|9I}?ULQ|B|4mA&6H!JK z5}eogX|k>3rxmxV`wQK3@epwRT-qG?BZ<~T0DCkf&n}XuSY4@(i!M8dC(P7a`d&@# zNX%#3DT4-{)ctKxUy$MG zSj})en?fb>EE|t!FL6e&KBOi_Y5d;j{bxKYpd48S47yLm%e|-P-tq1b*M6UCp>VWS z>3ug|?mF25USqZ6&(+A4Rzuc2pAy+IB6Kjuc;4qd5YH@rvQ##>E{h3k&iQsMO0r*% z&$mk|c^-!~I`zHkJJk-%NQ0{~3woB~IYrw1YF~;0V+*c;*A48)^Gmw1bjNkK_u0^a zXI2b6zp8yBhNFgiL$S@8VJ$3rRZ;fUkLbar3Z>a|Fhs3W9!?oQm@>Z4`;|9kJo`O0%{;Ye1f-<2H%4G=>#uLoFdnm(=r4n9%Dm{W z6fBA>&;adGDA9Os^Wl^R6r91q64VJcmiUMfAchUZz1ADXn^ZUm(oDj1MaTxJg(T= zrPrl^hd`V+@3VdtZ&$hZMIR4$N1W={4}Kfpup1cPQ!zKIte;b^Dtg%sx<CTTY(d%ZH zh{cK$t0}@D7McsE_fGFN+GC+C&4swnhF4#Jq<;$1jqUtX&=5Kzn^)loh8@u-tsl`R zjr-|SflvBk{7??+#yHNp9*T&5Ft%Sx)$Zu8W@}=>zju9ns*=JEGU3-Z#mn`-F`j&0 z6XnB3HPu3^jSkneHHWB@HYc%qj<`Br>Y&c44M^C*T+4A*1aQhRr9!DN^V(q_a2GXi zeHM>sH^;Bsnam#L=uZOue}LZL=h?{d9}M6Z3$3ND0~hhMaahlEgLB4;jsxGfzr3R7 z3B#8CE=iMAJeS?g={yKJQ$c5$pU&f)PDOxDju6&%-kj>LOtL4lUvrEaV3Yvkx3b{( z$HOS*1mG=@*@Y9!lP0?(TvZWn$yF8UE^<|ka+kWQqW4P5QwR01l8%HZwv+73wf>X( zuUu8(I<0F`z6&ojhv0;o+s5w`>(DAsex@7SF)6?6tG@N0>{uIu6^i<=XhRhU+u|(*`y{SI&k})|}7dHH;24{sr1!I@5V&ypMVKwYyYT*QfNlVah z6abL;AtiQp`A*HzPn&|{AhS&I)22wX<#~u05$}TE=pP|z^P(js({PPO_;o*?>s~4A zQsr=Ym5z*5`bCLUA=`WsrD|=S*z;Zm$%Mtg+bLK-Spm`9Z^7YEcpIR+=K} zvBMJXHB|3b;}lItyHI^htxEyuQMLHtxGv@N?%|Z6E@j{Alc#rgey8qI_J>aIuAUKu z6FE58%npx*P`xL|al`)4IM~OVlw!>rhkjbcT1i9Vr{mBAHcQd$WSo5H)KwXcl-39g zqvVA82>JK_q3mtoqN>tA@O$TtVYt8y4CC-(%?yHrFD(ox1EupKAz*GmiDI7} zn#)$YRCC7`W@y>~-2`)YXV_3AT^0Qmnr?taWVk=IcGorgWwx}|+NNaX3wi(F=gxrE zzWo1xKHRzYJm)#*Jm>8>=RD^*&lw0IcuDK1Ni1{mP#4A^u=9};x&3u$fZ#LbBVnN` zzLxT_;6sd0 z^1Wf=eD)8h``CtT;@;5BhX%buxsll(>V8M0@2w( zvRUAKT@4*A-0`WW*ps-!GJNakd+SmDZ830oFmO2W1vU8u8S`*ehGOy(DWtEvy zm2g7stSp8-@o`fB$4Nc@Re_I__)%)I789AbCAKss(7H5mI*H#crWFC3QLTPqvtHH~ zySXjNgFA6WNAl*j=q{74i0wh0{E1L~P1b!m7K&-#Sm0<9KTJS!ZlH*(zotu|a-4)S zM?xpgKwIZO1*ny|ce72cT;eiS^8*`-o9mi3xG<-Nj-oeC9HJx`1gbBikQ&C9FCrVI zyis0WN$P(ksV9&T*q6k=MtLzi#kj#;AyR`>?kXm9zN0DMAO>7?{ZE1TRp`zW3cn+% ze+R-31%8JtQ+SUv+nTd<<7R8_kR|t!Tat1{|GxZK#9@pVyr1$U4yaucV;$;m>Xt-! z%J>(89%y|SqD8r`jCdI|8^(DVrS;3C{uZS5Nx+lDZyHZ4fzp~hWO-knJ1iz5julud zd`liJ9zPq;bA3`jk9e*J9!=ui|1qAQg=qTE@vKVfUz60+5fykSiC;4wkEsasB->s4 zFIdy@Q`>7fCd+*lnHO6c=)$Cv=7+R1C~J_=uh>LynVV2v?@Q``0CC?DSeV2w8IL=g z;?99p*A%qW#&&C$CAZt3e)+So_Z7$F>HA$qb0Xamn;E41Om~4IH!DZcAZ|ip#G$nE z#qJO)Sj=xJEo)N01Lg6XfF+5aJD%1Ls9b+Y^;a-nxE;`iw1i4UgHfE}rAfHJrm=mR z`jTP6hmh;YNstR8&ofmQRvnY~Y9LCPLp-}Hv{({ztw65DGC)%TMW|u4Mc$M&wKuWz zyxf!9Xi>BkZE4$V(KPmGb4_H|BPJi`{r=+_Z$1vifm2d?Rj+kwqTHiz7!w$u$81i+ z{v)*`BbO9xN7&2Zk~++cu8G7Y{I7|&!w>;ObR;HsXC-K=W2+A7 zhO4cb90eHy@+i!jslDN83ggv81uY`d6k>>h^BQW#p)kmn@ls;OXkwStcQ7P|<9|#^ zg?Pv^Nyhm^Y=tVGkSvt0h&6O2SBE8lYliV36BZZGDgE9JE`Qx{ku|pAD}m9?lS5Y` z)|Zu|MO&%-YhsSVcVD3tJsP?ne@-ln&3HGlAUPthkn}IYR?8PkAB#QOm7F4>SQY&7 zZ^v5p={0L?KB8q(=CCl;-T5F3m6;H2{OcHt%dRkBpGPPd-HizE$F$Nc*4*)YW|42GL67fq2YM2#uc3g0b$~sgmyr81DNEx>H{oY& zPuwBZ70EL?smv&m*#xf6q(pLA-+I*01@NIWO~)kSjP!p5U-wWE7g7BtpeS!kKyG|K zu}gB3j+hBLu$~L?%RqBt%9RN^Dj7qS!RSFKF~tN5kTQe)jD;{rTJf@Bqzt)(;itVD z5|fHj@)TQ_7O+^>ibI9tsYI&`>Nc{R+?7Q;uVwr!5w#=T5u33#u^_e(m!uRX?1;5W z=Wmo{{1{nttWLB}0s0hcZjKyG4*kKm%B(V(STr(>wv{UqyA(Iu%16`(L-k>Q*ML1o z)KZ`_afI0Tcs(sev4@)RKM0?{9~DQPAe=gZOzZ?Rm1k;=#mFI4u7grnewc6-*z=%U za(so}cwz{FyA!=CUqQQ)9K0j3UEg9)^rS^V6Ule3%j8??QQ=A*+A2%gtT5h5Mb;gP zuXlo0^sQM$(|QQ|_(1|w&q^5t&BI&7Zqh1tDSkS&r19`ww}ww4AFBRWCV~$j_?c;9 zaIpB`sDx0IPJzFo4; zy=+;NV((`O9sZ>1+pvW66_EyvH(}U`=AeLWGR)WX9Ro^!fbxZ@pmUO&RlA*fH#^hq z#7uF9+ZpeP^(oWzFuWi^;Fz>nk91g>CN(Ex-#gjO#+dt?OlkcM9|%l=R&hpDHUhtx z%}Q#Wx<_G0!Bm+wPMY6u>Q`SIJ?HQH^c*Ohu}jgpoh>^WWtNuuT{s1^^!^!;koNff z?tb;bUXuI1Nwd0RGTdKJv*Fi+E^)@2&i9SG(mVRq{NZtzx}(3CKQQhp?&weFUnUm^ z*4fi9;`Wexp=5DPJ~#GE56zwfl>alQTakGQ%#9?pvaxPSLSxRP4oQ5sr!h?{Dm%cA zj})aeh~^77e)Pjuvv%-;aHiv=n77j>NBzP%cHcAa53qZC*ge?%7&*r}*&dTItH^|s zXgDO)>hceS>b{=|_PZXFq+>wcxmbcZ&?=l!sEW^DB+m0r3(kQ&i(jex9TN1)A~F}` z<&Oze>GwxK)iCFh#V_P&a!~0>=lbvBBC!;U+$BqCWy`iG_x>ay>1c`|N1WrezQIxz z4?d+J+j*$@DbM_l&kI%jJXHMT8hyE&&2rDvdq#aYVBi_`N|9`A&Zy_yurlYI+XU+^ z?kaVwRNp+y0UgqKJ1z$lwM;vbaTUF*J>B0fs+BC)xY^sRmzpwjE;amFDA4A=Bh-J9 zZ$P`Sm^9AaH|&{er=PWF8Z1>Bi%HL4*5l$s{t|ux(IfwqIY&-OP9^pY&_7Pl?C>Bb z4be|ZQSiWowezn+hf$_mFEQh~HJ`IE1|5wlbolbpf-}2Hxyh(74G3wKBPEK4%K{_v z=m1ky84e2rc6(hFTkK}ZE48JSx~j@pL~5ywS!1X(RmNcr47sw!s4*Xju$cT$W%sai zBzuxI>83u&`*TfuEE{_$ZcVQWTDh#)Rda!X!;|!NpQrXY!e`{Q*&il z%wITZ3Otu>F+V=bw0msoBqfkK6C`CSJ;G4)NtKjsnFL+8L`n9RR|d;*E^}k_ihYVF zf{F$ej~dq0^qGt?t;V?Kr(G^s;HzZ533D5Dtw!~h^CykkduAMlHR+U@Y+@b?#&>k4 zD&wR0>;#Fz!ax&m?|bpw(UjA2-+B}gScrR-M5j_MndT|c>6smRw^8n1MRFu>!j2M; zV_3PvFstNuu~K5Un~Gg?xX5QZY@IHK-Ml$0G*Sd8j5nJ#3#Z1zPA zlqSE@(x2t)ssFNc9ur|o%tUi%Py(jPNT}RsUD?*oJ=jj5KHNkhkI2%ICt&~Bh(zzVcW5wXtg7JlTFau+Id;^Q< z!3;uU=7-o)P?uI}s4?uRLmTLSB7(F6G-)B${J!iSUx!iM`4^!~6>ef@m@1(nGt(L< zHO4O9xQ3IimV&s&`<6|+4`;#Le!+_iTY^j-A!i&8L)ejb1YeVJUHsI9Y#L*MDj6cAH? zje1T!c0Vw#d`RXukb0FVK2=9n4XdybSIME4mT0Q{!pfWU;1~j}M~~u~y0U4iZ{(jT zD0|rY=&#<$0`{rW!p^W%YF7M8UdqnIxNl)+F5dVfbnhyoX2rfW>D4aWFd*jgW~>8# zXODP_;SGUg%)gHCZ`}wn1V1mnsmXO{;~rXd-ofX@HzvfO_QWJ-AV61tijO_hE#|P{ z@93kC%#P280PJhZ-xD-{!|zOJTZw%(SVvm1!^UC&HC1v?NKN0ZC@fB?ybob~FT17^ z^AdxMSOY6!B`}yqwKY>N*4GdJTYdH73_03nB)U`1U_4crSgT@ntr{7xS8n7dY}tBQ zsJ>=>tG=jxl^V5+pHI%XuFr0ra?&qgI5WnAx|9)NP!!of)JJM%XplJE5NeQ^BOr_D z&u&wEjlP6BX#YL>#!WH7)f_FPQa%^>kM)gPvPH7Iil-izpkIC z`EbeLQw-w8d0iXhap;s`x8!6<&`rC| z(45oFX1FU!trFMtxn7{=<{ol|L>%D~*tQ)os%tJpa?vPaE_$?5J&5Ks>UXXAyP*O% z4`M}%Jf-|rsNAeyu|?8wFH^Q9uGX>jtCL1Gd17U8RH$qd{fd~f)3J1tN%1|zQRNq^ zD%mP@-hL_{+{qU#g3ezFv_f00^sn4lv8Jx_Y5l5I72b`min5tnOeWHbp8SX~hj8<6h7LL2LP6hW{~YdoD86c31sSV|>^yF6Cv+7*rTU zst4FIl?tISIIQ{FFkamqCVA)EOl5_BMZ(W~t6+VSLH@#O9lAk9?d((qdtn>0K{ufV zGZAbf7`MGt){FHg7Ne})dl&2Z*CwH?snbS>+L2?Vm^GH_r*0wBlYv5g0~Cv{VdpF` zPfsEYCNN(=2sX|HY-ECgqh1_viY*RvOrBZdK$9yhae`54CHZ!y;2ajF(q*#8v@w|4 z?Z=kMbgErnZh?02s!)~{Sx};TF@n=N9gLG@O8vrOZJd3P1N$jUOG{HU^B09B=D|;K zMIgUT64>7y^|R^^4cPmQ<|0tXn2RjL8a^D_ldeS$PDi`UH32b#))ZL2sCX>B`7!mH z8bejJrrZIY*S(SYq@$fR@?|s+Q>;|2Owd)!s#vD#Zas$^xSr|cKcYgx_<#2#>!?q< zB-T2bBgYajZpoM+fNTot9` zxH3u9E4ZcKQ-8dT`pl*nVjID>+8GcO35bZsYMf9r^>8 zh`sY4ZEz3ux?mQr!LU_%Gp>=Xf+(jL^$Qn2r)wp#fTA%VB{Re$-kBuw2nX>ZkrCnU z5ZO*s5dWl~x%j`p2d_zI`PU~kCdhTUHyWgjIR@ebE_g3x;D{In-93K`KU?@;i0`Zv zb{0{lr#t;Zp{8(xuMq=6fBd0vMr979k(kJ&(c+DkwIfKUmAE z^$+-kfl79Ox+<-)KsO?iSHFi#oy~J6&1c0|23m>sNYq^xFx8;ZLB_apl7i$m#wWz+DcsFKLu~PvhRM+8wwSLe6L?e>9?epZV9YG{$sUM%u+BksgNJr-J98v7d)kzSqQG)OjU2mf?B) zXK^V0kUR)H{fTir{u3RA` zh5tX@4kdp^XAbUzdSZ5ZvfGSSy~pg!^d{#sj29guhXf`$CaVQn?Ks(tieNnE!OMd2 ze{`Yr%}!3XOfo;l*kyvShz!CVmh!*V5&yg?&dfE(D834wH>pZys#2lMeOAF7Rw0wu zanO0Jdv6yAuC0!gKcU*WlsIzEw3S6Gm#$p3a{bDtm0MPJul#i7wW?L>s#WnTHsXqE zDga%Wa&_2X6ysTY$lADZ`r}0TeDBl-)QNYk&$^CCU*~?jHR7>#ty^Osmp(oTsfLNR zV?5^kQGr?OXNuo2U|(++ls2%QJv8;q>4H+9Y@N2Cv@i^^yh-ay3!KBUb#atVYUpl5)FsxyU-PNpnq(l4l_flXm`+or8RKv6g9! zB{}@8l<}W&z@Q_^p#Mn{&TAgF&Fu^KrnR}-_1L^Ld);kohr_*VJ*_wx*xDe6>~L!W zq|`Bxf>AwKuwep$hg4iYg9q(15L5bC@Gmv!a#g6#t~Tr9b|Vz_3u4;z1!iAiVH%Z9 zVT>=UU5lXYrUDB@#}*XEWuZXsiSuQ73GQ|Jtc6OTI;-SPlL9DJSSSAR29zGk|KLB-zN zbV)^>pUQTs$osIe_`rr`5j`v&`klt7pt0xV_7tg0i!`l8F^9HTO%Tr}TTM9$7{90O zFj-@Ur8{D*76@f6I}|iVI#G@^uW>}{@@Zb1#*>`%R%4FdwN!)2QgNOk z(VUh{=W{a)Gf^@z%-)eW9E_|!BO{v*)0oaC{h?$}j3@z$2}yurJQ7HNBpwr5hsm`l z6j5PjPzs2yN_oIqo02E~RgP;{b$?QD%7w~f%C_m7ait;-K)=u$&SkepoGs84EvUcR z3L`#R5!?~ZwPM3Ef#s6s`{uy-!KiS-w@Y50`2QkD?aY#+Vwv(p-Q_Ew;1;z>qG6Y; zx|-#W#JRfFy^=!_oJhx%aXMz=Hl7$d9`W|jU4282?OWv-1m0}t5645XSW- zEPlpPg?~23lw+yC;&s-4((2TD&v{2Jr3Oo>5rWz3NA;E}ZK&*%d<%LyuaTUc#IGl@9pVkQNs7s~+a$vseG)AcBzCdbC))7{^!32v z%4gtMZ!eu*f@DzXwI~!Md-R@j59m3s??CDLTK&oU_9q&;*nF>YubZK zeiiYwsoGY~h=hQ@=WYAhaK^V3t<%z=Vg~$@9#a&}G1lwP9JG$sNTt?uIir>s1Lm8h z2J1Pr+jA&kjZ%q`(iteMprW#v*X-GbtAqXVl1z} zM#m+e4Nu1|I3~2Oe-Jd7itCgh>KOj$p5$z;irdg?RBs0UQU>y_viopK-I={9+5P72 z4@=XnYReG=j%Ou$Qjcg`jhf+4?n&FMl?;}Mds>`+hP3Tm4OY7MGELrA|C2cVs<@X) zpH=5J#<(2@r^9G*m@G!vkWJsG6H;^~-DtKbqv`7$sVIJkifZLK4Bq#rf zuy(N!67JB5`!0V4ChW~Khr@O$M$*|GkE|`>>c6m*MDAioriGAjni|pND%aTaV8+L{ zogzaez9*EOR_I<=%#bc)ef#RKV67C9nYajDg2DP>&C4z*iSchzf|OGANkjtU1sNx_ zWK=%muvR=1oAJNeeA3y==+(lWLvWlz7hy8?Yq6P&_dabtngf%umj;_TDslYcnS#+8 z83}L-GHYaEP&jx)D9a5mAgRehx!eE5eB1#snXy0Zus3I2Y}XL8bbD%8jx5%fspLZ+(Yd`Nc_Z6}vS~6Jibn>8v8Ga1vh;qp|H;MH z8Hss|_v1J`DUVg(uNN_17tX9o%*%&z_0w7f1_i8gA;P%^Vs7?nOGFnJU;p0rS78#| z4gF>aDj=QMtWueswEZmdU*md_1u?J(1S+UoW3RyfL^d>R>`eO8j&yt0#Z8nMm)li9 zxv|4us$H8pg*1s4^wTt=dV#*|)5ryZlxViVI6ol`A_Fvs4gu#R;X9J8cg7eZ@|>jx zXN(am_k)as@F+#uKWEJ)!uGV28J8d?Ka^t-q z)^l3VXm(q~F_{SJoT@)ll$%v>E_*+hbBGC2+yJ4f~lTdsp^@DdeCG>IwrE zwj$?*lYOZ0!*Yw|4K=6b6wfNspWaZMztL_rK-|~xEJIxHmrQxTRURzXK_41Yc9z7R zWvD&3l6U%_T(O@mwUkv1ysIwD{SW-mP7aH zPt-XmG_CdMtwh7Z!IG3_^c9e}eZRJ$8m(T|AZ2QK^n+SnY$#Pm<%eg)$^!j__e z{EuYt2Tc0?j&xTRG*uzXC@yuCSHt!!SqU!28RbLhveY`#^pIk#Cx(p6E*P4hR$pM; zQ`znM7}5U}9kc)R3Ly*@G43$d%e#kO8<(n)QRyMRb@+n`!}7z{d(4enWzH~3Zl2pO zv>L-3YhJN85Pj8^{g^1QLDI#x-mkb^fh61g_y%hRcRr50&ToCi^W(SN>F5iT( z59v>S6Y1|25U4HC7@yq>b!KBGiEZ!=~ ze^D^^=SWIkiXBwJGBfl!AhRiXE;b)iKc?i_SaI%G@=R>LlqvDXqD_`~VyT~hNmi(4 z8zBeOuMWe$fjCSxx3Zfs);l~Fot0}BxA}r1)MKQ1`VPvvESGZ!2ctRRuG|gjt~$%> zAEbk*^g3*%UAT{Ux6y9Tnm!j{cSO#r%D;8B<2m9VV{aKK%&8C=!x1{Iw z<<=xVHh58B0*hmpsgU^|jSfkT{4ZnfpPDdvkmkHhNmVQ{<&uYD8;B>DIAhTvOXkN0 zO>i;)CI7Ymn*Sq||MmYh|56%rwTZz3XfQEr-h{TKSOOU_pgZ4$DFS7E-M`8D>`>NI zu1)e4EmP%B!k+2BDS+E4qVeoQ&J)Uabst83E<3^8W2Cy;23pS?Y(OEDm|`(e!8}vNue@tW}L=Is3vr_ zG268g6=$tIVGLz1qCKr@X}=;G>|Z*bDcmf8Y8L>b8ecq&>MA0 zIIA-AdxTV(mFAe0T8m$>RI;R}0KsYT$$skqo6&<)QJA7M3TLME7`5|XjYKWg38C^S zz%ZRZA0xJcxZ|~kJPcK`DnCXm=2^MDVZ%b@GnwgS8;Y$4-F?y4(O|{k)3IHGcFj z+kfm|sb-Z(-*O??ClsSXQ6?tEnS+BQ2uB&CEJAa`&^(4`boZ&T__XI~3Qg3JX-S%t z`2z3z9hTSyG^O@nN*(G*q1B`eXD~zKcs44~X=G|-HO?yO-g6pQPU`%t6obS12*;Qv zJ8OO@WUuLD2XjoG`zre2LY40{6%e!q?!;j{MJl5BljJ0BQ9mXMxa6IlN!zty_0(d~ z9xU%j@-N`vWr|gXqfr)P7@huz6t>5PS$1E{VFzV6A(vx;_M_9nDxLmsviIL@acaRu zUY{Qumz@<>|MdKg31BqDRphBsx_k+(Zx2Ly4K%DWl#T)|u?n&k6cOH`Ggg&HHDo3Z zvH)6thvhg!dYb*3aXCehE&jCMsKy#wiuD27UvQO8{1?;GQlopf;I4|sa^Hgb$88_9 zf)HgKSh%+i$p-u^7cnQI`L2!AJ=mOnWdm3L45-@#N&g^dF>aeks}&3V-8UNBxt`r) zXzQ)O!0>1MwTK|nn5w+J{;kb=gZZ4>Uq97)8&p%E1h&Vl;h)zunFc9u#!Z?*l5`;C zxPH0CB-dpsu%2yAmexaG7wZ{Z##5FnZ?ky9+v3Od&@7~fmQMmVPbpOHO8>otM^sT` z-Bps_Rz!0_u(m=5&^o>evrH3`16WIS^CDhOR-!L`Esz~xPU?{ylkQS(E$`7Auw>oS z-naRXbdK^^xF@0ED#k_KAteo6UqY8$Sst@cS`}-6k&e!Pz#Vp2PM@g)=4Dt_`=8l} z8CJ%8O>S;Fc4E`(vOVRyWZ31#`pOb#$bP=S3d1=N|Iy58iNtp|?h-+1omR`Rw9sNLMLtR!pzq+&C@^LPrfU#-Lf-+Z4bDo<6LQ`1oWiwB z+@{J{t7Pjt4pCI{1~4F;nwc zW-N?6ub+23OJmRMJNrA$g6=`k3!%zhg=VBf3K8noRqIp>mPTYRls|t2a;wf$H=M8E zACWnD9lBnM(kiTyB|A4+ULUhcFW#rgopt;^P5Pp~v$Hh0S=n07a%3m1IA6P=Al<;% z$3|G01@g_znVj@73|o0jR~3c2`}qADJ3`&9f$GJoBT$$q>P|UClc;IayQ+Hntwr6{ z@xso8@~Wb{%bh-XVO+Tr769c-P=@7}VR@mYpcLa~DR!-^OiZaU3Z^kD$_%7 zLdF#83Xz4Cgj8=!w@F-gikf>19UE%_p7jLvg@PEgtN!{gdcLi1c`O3;mLLotwXiX@{i`x_`ceC^T`H|X zX;sWNtk6zbn<}kuSTQMXlyn4RPxk8vSWOR0*JV{;+^dZxolquw)2u31WAPVcKR;tU zWb)&)DWG;DAokBFT*@-Rx2iT4n;jtDVq((g8FU-DwO|9lrn_(pVjj<`|9Hh_JNnOF z?Bzz(${yGHX0OwsA+MA8vRBfLHY;OlO%XLKO|?#+qEJ`sTq(mgH7dQCPU0>MeN3q_EqA7)`gp95dB86@V#RFh=7vHRcn22Cx{&PG=J0XHr8ZdW&Xq1wFktcn>{&4Hn+ zZSKGOVn-UV+X&pU`(Sh-FiFkFill~%^0VU`a0NPM5#dXHynwro7xu9>6S zvL!7%5TWLkBDy4BL9_I2-r;~k%}WUed4H%3Z=Ur@rZjFu^Vf(*oFyZ>brWe^jTY1B z`c4|7(fsEkx&-q{O5#}HvuOTP0wD=hcWBd-^4pa1y(`8~qPfZaA4hZ3VN~fnq2c{^ zdS`qDAFNIN1o=pebIvg-Zk3SmXG3@&gx>#0Z?Rl}9e6J~<+6~}G_vH)Bwx}4u-|x! zya8DJriimk0*krx7~?z9W{C&(m@sE)Q*Mt}wJK%9D_iF=`B1k~ddy{>8^L-p9*stt z1EI8rLhpWh8wbQV8Ze4uvrL9S<-xs56kLBazf;6HNWptT!QKqL_ljOU2#zyq15DuM zXg)xo2FPNJ`vKl+;TIF2e{VFMf!rI-9}t6V6NBXIP#12Atc(uT^83kWczwq2qVuUA zFd4s%2K4s?Nbu|B*XpeHIl6Mkxp$h)n=?l$M)!!a{SQgk4qQGn_b6zuU zL^8UQGJZB8o!AuJ-y9A5svD#EHUbQCOh!|*Rl0LIe+RkeUc_0C=Z#8>nI&3%O??1|D$4F>!bN6$r*SgntzZ! zxK%*_KPL(<@%1t0`Hd!OUkRe8gwLgS zMpCp#+NR$F+ijjcFoZ3hzM)`R5?(_HR%u)8Exvo;OKC~?@K7|$&^wIYfoSjmF^?hM zP>J&zf$(VlG66H#Xfj>{JCE{c{vtuhmcP3owyP;5J=nmMHC-Pal@$Ww%9+KSVm`J zQU-*;sVM#k!GorIXiph=I~m0vCLn(_iqs;MY~YVk{DB~8Qplf%E7_GUW#N$!54!$S>UxO{w4AY*#^S7?5o0rJ(xBp28)igSpH;`tI2?G_sZBd zO9=MIn=muswVGnxFEYV1@9wJD@pTVb9>;W?{~xgw=kay)u8EarY>x!L&3G{~$K;WD zEgr4c>6YPGh}M(km3Z>JF`oOqRi1~uYYXZM-JA>w6wbqsXSri{{5}) zf+Oyow}%W19SQnxpYX?OX;c8P;E1V%F-)Nxr4drWktE42PG|C+^#g_}l!j^fo%khy z1%QQ;AZR>qEiF-ow3CTXW1HEP=jz*};eQ$B^7gnIjLr9E*gbvDo$>Q`NU**$6r9|q zSwN8xsPw2$RceV&uYe=9%T-rQo`;yLa) zxlJEl`NOEjR3)uhI6dlye;~@W)xEbos`-VJ$J_gwTPhM)Vcir~@$(I9D(W`x#%grW zp1M6R8-{=M>*!~r5Q5poB@TZR{+Y4?aUhoH`Ww5VxEc2--rVN%|H555^i-8!SXEtI zy|1F&Smk#$rb$eMJL5TE7@m0TySE@*(1^Jl2@M-d@q{WG?`hGrZpSWra)H`b2^b%^GQ+AR)QG= z%n@+pz#NM~I);h0gRdYWQ*A7?OBu6d!tS|f9U`EP@_W>Q=qQ|88Sf#wGnXw#lE~L4rdE)hK-1MBm!86QNZ-}FG)RDyzuINuD$`A#GtZ4|7X?Q7GJH#k=$ z3I=Jx1(TvL&hmQ6EIV9=u zGcd=*CH5bQ+-M7gw{Q?S6i^cZ(isvL^#{T<10 zyiRYGcdgg$ZT&=YHD-tDq%(5B8Fiqv_dZW}z!T|eI_WxkGV!Ie2Q}TV_PwOxp9YAS zg#Wb+m<#LK8UIQ*Ol6`&x}BUS-s`kTr)<5C%gm_?8GI3!BQ!MyubAM|o*1J$a%!8} z=#89~iJ$Bq70^Lk@V@8?I~%}zT*P~d@SggQc-5mUjsqIZE==!T-7eEK(y&3>8@c!$ zcxXLP{rR`xBn$6$nWu?$|E#2?I|X9 zjEr8sV+zBoIkd)wDqKMnNnc!eeiGw`L4T+XYykG-l+|^P!&fb~4eQqKX27lO`G(cvNKf@qKtBiqg>t4wcNZL0!MO7OB`ys`qjCC$ zi3kW%KtG58md!(f0}K#?K>7_5!s4?Q+Yj(l2U90V0sSDbcnjK+d1m5fW(y7c;B75? zx%Bn{{*wp*yY$V?`|aQ(yEyR1@w*q`(Q2NAU$8pjY^}SQ=5952HRMmgHu`C;C8Q4X zq?X@sKxhpxL5!R5b}q>4Z$h3Y%$8pPPI(E!yOj5YhvJ6XE73T1j(?VezuQd3CANHz z(be#?vf3MbBYAXLTN*-f2XD==wrmK+*=`+#h713Mh&I$xKaMs;!G>DajiU_>5Abz_ z!tXjWboo)#Nr?@RLThfkVcI%MhR&SBUTdf>SY=S-^w*X82@k zY^?4+HZ?h(FQGO*{1F#R%y*8Y0W6$kJX~f-H#F50_mxGF8l!~|lWK`cT^q;eiTxr5 zAw+h_nP^_Zq2|EL(p}oE$`cE?{sml5peRtp@%Iq6-K9M=bfSRkzZ>8Mfx9_ApTHM) zXts2pZGu8hVmodn+Vbkzmo(iycK`m81g^N-umk5-515NGmvKZ=c*GlJZ;V~cA(2Lo z#&YCxF1AQ=Ou@P^{*YRVhl(M@g7`Z)aiov<7cN9dK};&JHZTJQm|S@Xk$~c8>Ai^< zY)yQk7-a$%!X&7Ne!P|zomeg50VRjgkwD=sF(Hr!AhCsnw?v0PQ4?64jf>)D;;KA9 z+u>iyb5tyWE0meG)4W1VPd=XBX@bVMcL^@97+*}#{nD*&4|OAN2Ki+0S0a!xcObN6 z5TPFO@=nNjdL=Dq#6I>{zCFU#Bu4w!n#2_TA0qU8>+TOi6QT16*LA+7D-x zGu_O8_#Y6@0Fk0=KabXkzm`9Xh@ar9Tyde}{iH*JgAQ_&M6qW}9~Qz`fi}Q*PPrbV0^&6&0^G|$DU@bA0E4H-}NvwYb)9{ zCk{s3;<5eJ+U-3S<35VB?Oc6ts3^~gPR0p`#DZ&{V3swlCb!Udr%2@hUp!9508ur->nGgAFO3-? zw7UQnGZRAlR5k7v#}8;?`B~bK{->aur6?<9OOh#|XI4f|1oSoWnKP?F#+2M9Lf^>9 z8i&4drZq!PK=Xa|Zxox<(5SPUh}g9I6d6oLjOg{zcf3BzpT=A$9bb;LC<&=N$K~38 z9JBAq5&nq_R&|+^vd+Wlu9{31~QrE5`okR$@+ z1ww=^fqqWQKScCT{y(6966BYTFDQgZ*7B9AImoYQRsi>YxW$qUxj+AofA@qxr#h@U zrpkYS!3}U{w_xSes+5$igA|o{yGq>W&|;8(W4`nhV;`wrd>2sx6y9OF`O+6DlUke*9uf{WlliPd}cgc6nF; zTxVO({*CJ7lQf7S5$qW;*lQEPj{Ls`OB}{A=+t-#>g_*KyQkH$$E7`fUZnP7h}w%2 z)IKpz?TLVD%ln|EZKk7>lC^`{%NO&~?O25dsfCiMS8VPxMY+)gy&Hq{o=Us4nF3z4 zx6})EA^qa}w*-7v449VqYCg;GH7X1ZiQS&$#)FcE4k5zV;-)$*2*>>kY6$%sRDKlE z(_)bDh-b#Vs1fqFdO4-cq07(GTA&~vKZP`a6|k*R8I)r?e$OJ3<~)id*00ZYD+Yhe5-BgC)yIUcWV+d z9pC@A;c)Hux0F-GZDH zKJFPrwoV}T&VwXtG0F0=&Mt*-Pg*=iE^!^zu`*r+b)@OWWp=v2?G&whnhw8SD$zF9 z9iui*Q5nX?5hBz~UZz!$3e1Mg!Ffh`VzR1#vZ^P*2N)I4QVqA6eLJ%Ck?qcQ+}PHD z`Ism;7Z;lprZj9hb|vPg}VaLD-d+0ZdWJyR|m@vE|W(cfRFt2-beSaXHOx zI?U=8RyB^N-W(Cl2g4y+qThD4A&rPL)e>#k){on&lx9o?`P56YHkLXNORGZ;b@K+CPUB%gV2Wv zDz@LVts4H(@GZE%7RM|aQknECj+Xl=n~=%mhfhp{i$kD$0itYTX8ipSXyF9bh1QD~ z>o60Z1FJV2C0<0yq>Hd8jztBNzMEjsIvPj4CkVFPf{F{&MA~x+hFuo^`{C2fIW*mE zo@Zki8I$xk#?~A=X1km_X1j<-e?Vf_u9MiOJSnkz@MOXLCE!=^6yGDUH{+RdQDT2E zX3X}tuNYhT*Njbt$AxDP;0C;B;E~-qX7gw`yY)I_Ys2$%JU%?Vc>3@R;dvL2b&Rq7 z?dObr58m82jP3Xh##Vv%5Z+e+yPsigF1YLQ?7t+j9~T(gMFVF)gtrH`yih3 z1kOGN_k$LR{b77(0p`Lt2U?VP_Q0)0IA;3vdGi*1e|~c!XJ`KO?x927dwbRJ-->eZ z<9Qe9B*Cr4GnT~J&3I42bJs4`UXSp8Ja)LPc=Sfjeg`V|dgoYny_n>m}Bnhxo<>!Ilm;qh^?5xC`*T7SDTljtvhF z3xe>}b>k|=`<`*A#gA)MS zbmndS8RNO@Z`n`Y2>MsdkNet|_rbC&5x>75#vG2@7Kwj%nGcgM%J&vUNbaN?cwa2Hqkpy3b4U;M+`mDx3OFa7A`8;--T&Ykgp zDZ5vdX>1!0|3UX~e~oRfn4dRn=NB{fiU!6$V=rsBqI_S+^Ygz-ke>bi>&C0LjOA4c zmHUs+VVH`9pRSSK(G>r?JFgpa92Lkp?%b5Z*10cr zPqW>7JZWy)FPG0nyP4}5n8i#%9hdK6?Y+EM*RKWZ^r;1ipUS=;arfc9@N2k_jut)= zM(v@y*S4nf!fg1>y%XO^T)_8&M_x~2{=9D)vq#F>oR{vKyU6nJT>0;wnfuD>_vUVC zk4JoVJ7ay+b}t_PkzddD1Fs7Y;-7a{w>1+=bMq>k9g)o144WR0?+>rdy64r5%&1S_ zpS5UTleIEp_MOPX+*-87kKWeVUD0pZj^mk^H!GXLqx{`3?r=pH&U^Z*$<|Bp!fnZT zb_!^lXO*$GGibj^i){{LZ78E@C|8tIAJRA2(QeUqY)moy1mSygr*0at>1XEMANHU& zGj$t(*SqUqt6MYe`rJ>pU$jK`#@K4r&nA4Gnr@5RaQ4Q~dy8!wpZ@VDAEml&HyryC z4+dVc{q(+y_ul^57k}8+n(g;IskO1yqob$t=Gn~BvbRjDSKG4xta$qJL(kjR-%x8b zntt1Iv>k?No(noMzW&MZWQDieYURL91GcHy&j;Yul+}ZF}(S$J2}F z5T0Xr4DiGLw|zxTO$}7KnXQ2N@cbX1vq--J{c{RG!gUa(MNKMS}rg0-3PC^>}3 z^Pl3j!f$^hYkLt7#n-IExP>z40*^&f%z1pj4!>bM$M75v;hn(yJf7&OC=-N3J>5V( z*(&G#Wcl{SleT?B70;NP25r~Z{kAwZ?;V>unzh-Z5C#v!OtIs12A;k#2Di}}z-M+M zJ&Lmg{@kdk6JzYLp$GD=33KJ6SuG6_uG%!`hxk z`PSiiSc|g4^Kcwsc+|1X1m0G-^^<^)=iQGOTb9di6NC?hyt7P&AY2y;@J4rI9&||T zn-Uq@lq8Jrc&m};%VA@-v-rM%$5RJ-pF}<2;U1OPPdzTNAF@mAfBPJDaf7kf!;NNe zJqhvK`UGJHrGtDWA-*Z}%?g59Oq;%#8^|**+_T%B4#F-W?@ILT8wPSe#Rz)VRSDK< zQ{q9f2uj9PS%BwEy4`lhV7Ju)k0IO-5SxKYP(YlFT0qP@%Tje z=Rz~ydbowx$OqvW2_(CA+dX*vm_v=FVBREQZ1>DzY+EspNo3-+#s_=y(bJf6a0TSGMYuT1Zm}SLWQ_o zEtK|QfhesUyMgl+_$-t-zax`%r%IJ@pU zWFVSL#k8U^-GAa(bV&BRP$Ua`>r4naG{2sI6L)-i?Icj`2|D^){gWFRuifC}j_1Ci zbA-c13#AQ+VwrU5<%=1Pi&q^j8AJ}&1c4h9Yl1sMpqawNnqWoxo3i(xUp1?sKRv5&>PL$7Oh-P8 zks{NPn0dw>Y^ekOuu#$bQ6!NB_1|ujLj(`)1r6WFCks@!*pU}E5Fn3mOPHNje#aOp ztV`L_H5L>Nw3G0qOF40=xiVkL@V^Zq{pQ<9^#?+WaoSZA&te!ikBRF`=wbb8jBgtQ z1^lKl+|zPli2oJ2aDfr&?QIl !mHV>=|!!}5;t%>;$y1bJy`9CL>=INDao1U8KE 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

e0W@n+Lx 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

b_D3 zBk;{v>fMz25D87g-S&{0Kl%F908b_*jgZu%BPyj8rXWh)aw_W(t$8Rwo+Az36>xrS zSY3^&<<#Z6u)10kcCj-{TTZPx@*h*P)aqM=9UAkS+4+LF4l@Znnan8lb;6;QJ z{*ZRXhDZO`oQ=_CSyS$oAE?)DH5G=JO_pe5!#ogN&1BKktkS}8E%R(Cv;966hRrD$q4WB?hNI2mmuGP3Y8Di?9O&As3i5lP=qL>AGyt7+Nu z)jIpqU}&mjL3cf4KR+kzXeQ24H)8DA)g-7Lelj(mDm!|il-Xt0cB>r*^Y;K#s(d2o zd|BAq$JEWIE<z*oHQ$RgVQ1_Egh4j^qUmzTPRz(20b)`~+2zJ0*RuGtou0k`ld#cEUZx(EtRv>;I zwDPq_*Hn!=l|*hdO-)e=DYP8pUF}ND&Fyr}fJ*knc|zz5TvQ#qki`C)CPpLVv1{DH zc(op{-UHo)KC3^V^^?M*ug}SkUcW|px4g`x+p1k2zA#3jn#AemiWt$<4ApWRdOZs` zbSE@23CrDV;i;Knsk3v0X^9;7kGvtq8_>`vsz}R54sz z7siREKB-+s+KOR?akvC+y-uot>gWErMD%!s<`}VKFD|^tXTcgHS80-l>luj?X;6LD2Xtu+>6;bNPiEE9 zl5G@MW&N`76|n&*9oy3swI)_H^}hafzJW#;3v=g6o+Ncx@iA@2HM;p(y?UeHMwyFy z>M=lXaAqNNCw1#9Dap_K)(!Z+fpvr45Lt2+^T~4XdKC+a2F)!I8H!>&D=6*_uOP__ znSBI#gVge8C=YF@0EroGc+|fQ!|Oh2L*e9ZeUI+*YQagE*Zn^7y@fUu6cF7XsSW5P zv~_Av-JwdU_ZAB=_^B(r9{dD)@Mi&@^Zh_V-f|TZU|NeE_lEK|Y?$vKK8Ht>J>v{dwKqpA_M!rk-3i&}?_~!xjH{s1>uYO}7vGB}# z^_w5Z!{|4}<)_~XoPKl3htMm3{az~3{Otz1;#-EZcl^n&m z8pWI|*~iZn{pVbX0nZ`1K69?2@4O~)APuVDXHLM_wT^wx-QXL${pZ~AlzBx@JqCC4`zTRSoBn9 zRo(8B16grMu;>{=6hsj4&mZ^NU(fDQ+uxa-Bf^TKe@1EpPPqcG3C9S=YJ&^! z=WG{Z{fxNg;nxDA7~dw}nn!XLtd$X}j(Oq~Qf+hJ%iKK$0(CDu=JrZp$ zi=QdCS+OTk$n8n+dxJBvzp!3a=-ZR9^x2cx_;g9&%Kws>2CXQa}^G&hmiL*u}^o!nLmv9Vu~J&CeCv>E;psWKLlR_tH5 z_`7>6HpKY!)~lGBFG!4CCcaISn*d{Li{8DHV7%r-NUyb_IqS} zVnd*>d`D`7&t4BMTfa7}mfSr+$@{lKNXmb$h}VHD=-(f4ZiR`LeKB<4jNr3CUafn! zBf|NiiTg5r>TJcEDcDOv_o>cub1$zK@ptvge)xK$x1kf8N&+#Oykm3l7dY7$3vqkr z-HM&3;a>zSi$rzdRX~|$iQ!tPGg726iQKlSns=fy!RDz@J7}cSIw&rwU7L}L@IYEJ zuq#wA{}z}PY^)+rU@(IcZQHgau5N$C%GkFgZN&i%j_9!R~5n71Zlc@TmK%em!90^4gX@11%6HYUvfhuNqT23fVOI8Ix5z=_q@s3ZA?Ufu4 zFzITH3gSnE0C1G(%7A}fBe%#zV#LCe%3WfreWCQZ6?X}a2WVdwELnn6m*~9K^@pU1 zWtG!RjH@0O93?dXgf2up9OGn?k%@6ynQCpIU`x3|hLFSf+A2U4`o_rP9dGxPDdLmX z!s5({KaxLR1NSl#+9}W~klF-P4<>_fzf`a9WhgPRQq*3I* z^9}ZH(L|Nb9#y3BEdOA2UY?Sjl{$5rSmh9Cx$M*qDsQ?$&)AVs!2|R4XK1}7b+bgi0O}x}{Ltf} zn**jSKp*QJ8w!d;^JT$SW>;Q$!=*@a({}W2ovulre15k3itcxWh}y~6K94=#)E@fP zxQnXk<6N0*@yyUWu9&`mXDJv<@Swh%J0M-x>j7BTRucy z?(g5sPn}%K-Kzc&xs=xycZv2)hNkSwE5X&dv}Y0(J^7p3K zR!qa4!Zvc7?or&Pt1k{gy6Up;BPk@=KyGR$l8l{jGruib&sB3Jj#q4C_+~a-!oAfo zxaHU6npElBxRI+L1VQRI&VFb8iV*#Z;A*q4emKRfK@|%_Ngtz%`E_@oeh_ROe>uGk zRW1E9-Ne{%k@Aa&56O!fD__fs-)mxQ4JMhBL@pYLw z$e`N%Bs!qer*#b{4P7_Bkp=%9csuAoW9U+vb$%ojCESbSXBCt*?>dsJeaYLJW551J zw(=W~7+k7nWZ?q*sP+BW@P;Aw`ZqXjw=&Q7A?ZJ_krF=7R{3*TZdy}wU9;yp|J_Z# ze|lZ>H+)wyc35_4M4S-k%QwwcH?glv4)MQ5JXYf|Q!ph@-T2y-5bAK)sW-!pw(mcF ztQU`U%=g(Jj}`jbe;Xd_%CTF=V}0cJ?*HMj{?+kVe>j3keMCIgL0_u>p?IvcWB(iR zSm0qyBG<~aFDj4N8wiZRNs+PpDEb?O^hxCrTs#h=7zia3vlG6STDPMIq_nw3S)6a8 z47Cd5dh!Od_9!OE$@T!2*{KZ2TAq3IDC{P8ndnY(nW!D)DyGygk&4yX>s^T|+|Kn1 zt`hBv!XB2r|5c)Tk~cEut`Zd~bMh|=>lxfpz0_z6eE6nyLW(Qw>;eQES zy5~LTNtU>NTK5dDuxE8!S)FwN$clAt!R(4zFp5pRXWdn{iGZ_!$-*4Q68zPU0r)F5 zvm*`otNCI*V>2~|=173UN|E_sSa3=SLgeX78{mD~HW42W@^+-g z1z@EW8T1?iE0OqHJOs=0lph2unLx1aX%0e{)*x7SCu!s_^a0rqkCLD{>qMrSZNK9zq+>{{%VIG{>r?1O!%wC0i}#} zaQxMb!SGkcUi{UL(crJFKob44@mKc(e?|JH2Yyv)Ch)5re()>L4*U+sWx9UgyW9SVOngW#`{n+D>q7L5^qW&LO1ukJN(7=XXB zj)=dSLGV{x8+|*S{8>a-&Mo4vtYbZk`1bHu=&u}dz0#=hSKhM-YyTbO-pw^HV$6rW zGC#>pVxaK0 zy4|zk89i$J6?Yafn!3YD04|TmUyb4{!i&G!J2d`kGJ%Jx87tsdW*kxsffX98e>nWr zZMT5GvJQh!!KQ#8K4on9D-M0-IlzyZ0cVKo@f@y`+SNO5CF}d)uPjK@giS+#%vddL z-p*k-_-YS|0_#_6Gsy>HCAi6<@mE91H1?|jm>+nRsrL+vY5Q(uO7K?&d|QB@l;A9J ztordO-xjjx?~imtTR8M^A4&5l#L@G!a!?eo)Z9(y&q`o{}_z534q*sFhFzU}m$Eshm?HRk#DAH`mIehdBUH*Phf z!(NfIMXY1SFy9i4rgz--$1RQT!CnnEZn^pP_!zNQqp|lr^X)BQuRPNRp8b2VS3|X- zaK9h+st4&Wi5h@xQ*uaJDJ9(6&LbR|DI|W3Sws z#)iG>)vlqjF}IApx)=1DUhLJCgV?Izu~%9idsV5)jH+b}w= z35UB<428QI+WyYv?AEApSEH#rye)pMGZx%c?m-{!it~(Y=?eMl>Y?!Oz$L82ES3}Bk>KHLs!|KL&i@6%A z4PD~Do9N&Yv^32BQ8UyC4IZVxCt}=TuSL$IfSE<}ufncu01ap-gDnYm&4s>!ZC41OS36eRBX}yHEGTwiC9CD& zR(XJ1i2?p@Cs~lU#MtLw7%3twUuWYVUg>z0FXBb=d;Y@f(ux&essFiKG6CzQQNydu zUU*d{53kz5!K*5FieY=Tjzk~6s?vwAs^suhl|$gG?(M}_ftAeTt16L`4_|ff(DeOV+)|3A#Q!$9A@O<9<`Aaf^u2KVCb()~70J_T4 z09^HF9$d9`?c?SUSgJnoDmj0+dy<1!i8y%Gk|5;nA``#5`7%tf^?iDO_$$5Ws^1TS zuHx!;GM2h2i~2jU3y@iVY}I1Go_y#kE_rk8H{Zx|3GR|2_^NgU!`I?aTlcg_L=x%= z(u!}h8!)V3)NR07{zLUJN%gKYa;Uz)OAhO0hahmWzwQe~oJf(e4YI&J2l$6YBnQZn zR9%<2uXA5HJl4n8HJ_kjhxt0;ih?K4<4)cu9P{Xtlqm)++Td#ccuJ>6fp3v zl-SR{>2p`gY?5rayHb=S0l;J=CT94T$s#GOp{Qs*A0sB6{KCa3Q__exW}#obqcwK%vK1C z7+9VBv7;(G--DKNnu~7Z_ssVj35~|pW%qdQ zU5jb6#nlukopn4I?YF2mKeox`{n96IUt8Gl+xX{1GRR-5^8Sr9sg0x%JY(t2T$%<2 zn*4o)xROqk1}e(elW>XT_r0`*wnXycCf_Y;OQ5SMv`7AFw&@nJympUx<9cg0mA0kN zHyh*k8$R0X;*A8F>@7RZ_A9^N)3#jm|7J_~`!?`ho2Zfma5Gp0tWZF)BrYN=qz&E= z!Z~)kNTF2lZMdbhVFpVKvYvvBa7_;{$DqAISw~~GSeSYtwa=1hYAL>q0Ba_bO>D4- zZLuXH)~&owo+SCQn?z9LB0-``sLJc1wu^Z8l%`myvR?mKyf$oL=q%hG&oVwIZz z+oKOnyiTjsQCxY7R~q&mV5;Rx8*LF)MCQ%0P7A6Hs)@{+vu9ecZDK{_y>lw@B&sIz zt~v1`$2?cN73&)eCMHf99+xaBioDY-qNrNDTNV{Dhb5VmO$?dCT4?noDl1aJg%u_3 zA?3V+a(-wHr%Vxu5B=a2l;oO7)kJEipeXA^K}BTt6qMzP64XTAI|YTAzX0=x6lX+n zvbf~HqDVmhiJfzCnUqD{F@@#xE1M{t!kWSi1bOGcSJ+orN4mD#gw zR?D_B`+YW-rK-%HVPh>ZWp*|0d>CJ5PqUR;9x1cm3#}BE*=O6dmicA&dn_j9`pJeHW1L_!RDKe=WQ7Yl|>BJXtu? zc1Ln>^7`X6gj37nV-oifHx>~Q6Il(DHe(EF=Z_)nvi@lmRSga|Qyqm&Y_w?-RbdNh z3a70Rbj42E!%&-cOba?Z<&Z6~X(C-8K_80#)utoULTvFBcp~SLMK9|~Q$LY5MbKsP zd7I)x>NKBpQ@dy~QrBaoM#UwIiz4sF{K(mb%0O2*mo_SPQ#@xN?15JP$;9!Sc#~ld zG=ZX3OY6(CXM7Qb^BZp}edws>3QqaRd$jj`?_Xd3{r@74F1!LY zCj0Dq0N%K!lXA%eP^yVAaI2GA?R$@<)3|!Dbo!w6@zo!(nYrKLUzpfwMcLyxMa{_WCk4%PiRKD1-saNN_&F@^ndvi?ca&|TC*KveRFp=(iOaT0 zCLa(krJoW`6VLYgD<*bpQF5fIlQsqZbmt=O?}Iy)Y$wgqi!qeBLcK$oNQ4`oCc5iO zC1TiU$=Elw+%yVFL0(*A zd-9B1BxO4VEFG}fIs_s1J6>DAZ-ZiUvFS9#xA?`tqKS&y7%~{p z`yb@0kfo3ak@OnrI#iLbAY{U_eEDbz(UW4IYSRcY zF=WO}ovXsaV9tcUg zoDr5f1OMf+D#z|r{70#@{5-9rkRJ69Ng`z)o|&wjz|w$?{eM-S9L zNNqHw)?GdtcGO~Oq0`a}@QQjKWwp=G+CzsL6{bkaSH^5nE^E@x?`cP%Nv|v$pCCO_ z79V@LHf&?vN%P5jBmGtC`TUhl-oKsvU!Ohl{T<048R>Ubr$Ec9)oe!A zgvi%nmP`A7BlaU^7!I!R?cV)tayQwJtxWKnD!JQfXrFj&}-0m${Gm4;apU^ydY70OUWJf03JZ(sf1Wcm6m(r1@uO}N7+f+gNrZBCNp zzq+Z#rMNU!i^UG#h7@nz(aD@bvJSzkF*NHyg~-V2Q=YVx&)@eKO26}Jw$Q{zX4xXu zLXnF8|HQq2T+~&*KYr#tKX}i~Tb&tjBB6ZGz`%gIJAmb2whUwnX1i#KXtftn5UuVt zqt(i4-lM^Qco(n|T-s*H$$`}k&@Iy3fXuXT6Z_?s7VA3YmOuEF^2_G)Jn#2_ws-IS zJ|6e}@#Vp5&N;91I_JF3Ij{5Ub$+~8&3a){y*a|JNsdur>V=dt9+bk^Z09;3Pd2Ph zUR#-r#S8z0TgLeY&Fkp+6%zH@<@xfc>>_FQj!1MsdG20;x7 zsDW?tz($!z9Z6xSmo!vR9P%onT~>uOu1Z8356MA>89W#1MBB6#qx;7va3uagQkRI> zG4O&2F000DR(3Ntq$A0i>U!EPPxttDXv~JKVqV12Ct8X#nM&hW-QVv@m-BJ=o6{MW zUgqSBoo@W=#bqw#SsZ`0$Tyw#-=NC4Q4Y-p={pSOlM~ELqLhY9F4NXjRu)fxYRBvi zT6eXaX_3pF2lbT)dGt8t4JUT$J;&O*E3jm4$&GH2t2*Ri&Q5((C*LAx$w7VFhp?!f z2W4#s`L_l;8u{ET7;^ku|08$wj&uEEk@fnG>oD zj-CEwX{y*I?L5(-uWW4V7CR=jvh&F(3_A1!6ZjqzS_`@)Dg?+k?~EADw0R;%r#LDj z>TceqQE3RFZ==_jZ+5d~ny6ZbE4?)Jf=WrstIJ=l*O2-w#6f#0j-7A+jwVgZs4iB! zrF2U;9&2W@3}xojWX>=)UW(#IY1~YWVTWB=X>EguTFvPF6Wvi(sT|#g*><$4Y%Mfhht#YOl7h`HGf~2*`Q3s_%I-jOk&!6H9tQ^>%erJA8_5Qif^6h zNNKa#2Fy1r@nJU%x}oyHoO_-OqZ{=r-FIbttqwV?H-zEyv#?YfSZFjV%iHiBl^q8# zaaV#(*v$qgT2@N@fXrnG0}>Xqfu&u`*qW4nwMOLWzsA{OV8)|G9uNdEIH$GUFc)AH zE=CEd#Hj3a?y6?wm6Z{33DH+llL~LnD2xd+?;~@C@L)0FAHWRyXE^a#>-n0rFhw zTqSCJDtDysbT7_?2Pse4o${4b7Gr%$XCJ4Tr=sB^W#x@gwMUQ|R7X^jTy8dJBR;?& z)@xkH1TK;o9K`L_TB#Im>Z6}cFndFFw2d)$Om$lK@fOnp^K83z`!;p*ySG+ue${!K zDgITN^e8rDqO+g>ZIW~A-Q{MUp9@7JGCD42+nCuM+D=Y8LyIMClsT%V(lfVW%KJ5J z)Ykb?nnNtdY*Ux+35?S(NHKfKEc0~@CgQhMpR}3#kOr-%-zv)!Li~;8H?=yeep!oD z2GmQGZ$pKByz8Lyp6s`xOS6u5j=+u4T9ZQ)XOYfoAH%zot(jx?F;i*_Wpwlr(;0Jr z=!))m=_J`Sg#M=%ACA$QOGd`}?QIOdy{+;E(`z@ItZX5(;2x_#vQV>RtJQBU)co>8 ztKVLzneRHP)7F+;2|8oic(WJL==*Kl^~bbB}TV@&a+=xlTOtTkCO3?R@-+?Q!yaFDxK*Uq$D2Ik3S&VsD zl*bh$-}CJJ+kZ2E-$w0R$NuzLu}|cK+1sC>%$}Ipb4E{YaS)$ZWVxt_OQp`gdN&Q7 zsKxhsb(K5$+M@a4VcJ!AcmgG{>B{OTv#TSL^5#bGN!fq^@gucUHkM)cnKr6W4F2P6#6*-(dxay%AGn`YmbWVM~23nB3%t#M4DJzqbPf|c|HhuBasK(=}ao@DYKnXE6D!2r6zh!cj4EbgyrcizoPpKSBBh3&G}Qi?)BP01Zu;1kN{b|wQp34#%!v-WFx>R7Xt=v(Ob3QJaT^p*Ox@nEf15FZ`H&#Ci%1DEM6)4hFk!^4Kv58vi`{VQjq zE}C^V;w_saYO?xaHT>alLz-?{tA6AZygBPU2z;wZcm#z;xXiFMO1eIE^Ea*0w(O#o zme;>V{ViQ%=c3Mfo_qfnGEUpnRE)1s!u9ztm<|tVYb(Y?sN%!vT99*F@s$QTvsp-8 zo1RA44TuvTZ*cRg*C?yKq|(hd7{ZEpvDjMGT%zX>H0KR!{Ise!9q{~tR#yEQa#NAc zPxIQU;cl+{ome`W4Ml9VnE+Xxz?BxMtkmDyGT!I4GMpMan>#nx<3*ce_}rqtd@Cp# z*r;0W=nnrkczS&PO7qf{T00X>3$H93qqI~HkHj6ldl5 zSVayqVRgtkiMJ|q4BUp58u(hzQ&+OKG~}t{f2PdI(Ci2~C-6U4pGvz707IJbbH_^T&M?6gl;ZrDC#X>rH&3Vzg!VxNExN!pS$s zT{9G?``#Z9_%lV0LH7AdPRz|sV5x*kku;aprrEIue>Gt zMfB0!#U!-Y8Pu9kCoAt}h3(WIw=wHb?4DCpR-jb&utiGsvDO@&tKex8&jW#ZF zhNj87U4>fR^bdRkJ)Fg$;110S5N52N#^x5|W@!HFPi{?dYo>kb&OENTaV{~ou9w1l zAu_I`gXh^Ad?x<8vt{XJi^^8uEAVWY;GPH%f8>rSN{%auf^2DUI=5DzAaifz9=Rqz zqd25QyhqcG7p}EF#;x^i)$NYVtXbh+g!YXtb9dw&T&}ZL7nk);&W9r}eM**hwso{| zhPL|cm|&4j=ea4RlP9_r7a6zvj2xd)-VTrRkc><8?K{KW+2N_YrB>Bfp35!cqV-SawBtk5>DJ`B=!k;KV$2H#f8k?sy2*+rsJ2f-3!u!o$Dd=0MAVxqD^8yA&yO&|hxS#Ytx-9iN1EwUAnih?>exJTvk5M2UgDA_@p@ zjzsf@h%j)fJ5`?K?O0lKzo683!J5YKN9yZ|+l&>U;HUQZ&9U?E`16}DFiU5*nqFzN zGH;l}h1aN0dtKupjkm5*c$K=qRy{RCd95mIM6+|NlYZZbcgtWcQ&-i6P|XcM@t!A~ zjWTE%ML&u5MM}$iiX}y*=;5sr18I^fsb6wEu}L8`RofcfYWvM)vQ&NZR!UQ&Z7*+Z zgQn8CY8@8qWn#p%Cv$60{*jz;>uO|`JGP*Ir}1h9Ck@lewjITJAvY+7p_8*Pif7R9K5kINbw?9JJsC;=m zV5_F*pzf6Z#EF7+XJjX&R5Xayt?91Ofz{IIiCf?1)`fW1R6Qi-S)KHbKa^W_)v^#O zmli`3{T1^;hb0v95{mUMX&5Wcszo94Y?1uXcs#qA-&8H6es($sND(I<>Z+^|<^xQM z;g_nex?93{=QfU`?vhaBfgJzjzVn~lvqSF9Qtj{PV@iHYgxf?cTNT+@DW!!DRLRd# zB0r~#grA3ee>Uz5T>`#Km=MAz08jIs$|m(T(5%a=ERB_te90yEF@LCvVl>tDbbbJ9nUq+y$2sN}R3R3Xx8fq6@%ESuar#;nN$i zVwJJN-Vkcfc<`jZQ51527TFMML0%=H)SJFTUk%SPbVR~84QRK*X-ZtfdcPdwPHG6= zo#SpPRgm=T4rL%ja%`MLOt1R&ugB8+e7#XPDlxX{$+27>!Qe)OcO^7eHkL2pkc6o^ zG@b)QhpZnMcUSGFBoKH%x)7-WRQ)l8Y8gj;yFMD9#WGFjwl;fL@Am!bjNeq4sKO^E zk04!qozmoQz#af1O3o*y*BX*v55k@*%?mhoUKl;Q8-!9yh8ioOX5Dh%97^D9 z=z10euI6VNglDNo;JYT!h*r!qRuEM<;_a!|4GBvReI#x3NAw#kNNNm3hn1z}802;JEYg{?|ieE~hzy z!+v;?QsLRM9ooYAHM6zlP9eSO-x^M!*vgJZw!NEvVu5}^ZRX96yVAMH`Ez6(3&Na8 zS=Z8r_N5Iip3~l?4Faipl?7AIiH55r!{0M=xpSH{rECl-)l&?b8WIh!A(}-yPk@iRX=v#mUPZgxytDCOYQuL7xjX*VW&HRMItsn){%iEqL!HA8TgQ3KuEfWN7d+ zIXi#km?W+tK`#AFY%s4R!UP&&Nh%@f>V{m!y-M_%hnNR;2?xcTEl-}Sm?tANW5qv? zu0lG!6d$$r;YQ&Qwc!inJB3j?|0uB{1IY^%$5n@z`VW-C1?s&_v8dmlrQ{SPx6pc4 zJIZK1@2E-DZlU!Y@Qd~AkMz8neqxKhytd}%%I&D<9kZo+KFxNW@wb2DZ}F6P&-ev5 zjS_TKpZm+p4l#aKsHT|HoPnO50R{V5aXmZ3cX0r~o;zg;}zV88h% zr&f>)Q(bh_6~L4ZuPo1)#SE+ru|p$<5{Fjtvi#&U(B*y%W^of zva_@D^0L;g%W^uiDl4-*o~)*(EHd+7g{8RT2(0EWs+*AhHMQd&b@l`5yoc56o>n`z zsw;n|_WVKJbnt)R0Gp zCH&{%&p-2j3Evg|UgrNT{PSo4|G)h?(h0|t^Wj*fk>vjc7kJB(w5mqang~fdZy8OC zj%OWqhNL|RU;%_@9Q@o-(N9N6Hidz((X>JMDdZ$g7dM(lI%LG{bfg7?Uk9zg)c_8w zNLp@?IOgn?Ih?6l$ErVQ9l3z+ABiJ2k#$5)V;yxltYcFG>v&|E%&}@Zi>HuTM^ho| zn6rj;1cSkA7NQ_M#JOSsKoCE&=Ntu(kASXiE$bNl8S6NY@t*#jLVDeR`_mn1xqt@& zd4N@bBESoI8EKhhVcL9>&Ypf#=9pd~a})tSVM$ugB$74&I6sqhMBK(YdV-3!?jUAB zys9lYl@Rbk({@}7UVSGR+(51cufbHn48oMb9DGNiBWV$USinqx{|_<;kFtpdY)Ozg za_>Mn5oMb$f~2)hCTaJ=97Q@0!gK;&01y&}Wc=g={Oht=#~{j?L;@87K*84mPsoq& z>;i2r?B`$>!Q=sR0ZxDo!0|e=?piR-kuiykA`WN#Xxc`=Ccuea;@GvEbsV08Fi|A! zGR*yOD_F{(BaPQBL>?A`w=^;uv?Jj#$4t2GB96Yw+mn_HJLx&pMtDMsV1I=%rz&K*4-Q8w&=tk4r_aN-Yrx4lJMGI1l$Z zm^~kpw7gG9T0CG5fbb5~5l>~AR^mEQszvz?%I`=1f&UtC6>yn|Wf=qsA-m8(f)vrDO(1iSh63-i*gr-7 zJ|k(b!6ZaR9+{241>w%QWN7mU1)BZvQ-J0KOz=b2f#wv-VIg4PuOw|9{2si2fw@1Kmi22=tF=sYkv$akKT_N~1r zorxqk6{LR;2HUnckYAP@h8Yb07%T=&G$5@_EJX#VM)sj^ulzR|iiPke5Z6&7`XfLj z__o5d0V-h6gV~0#buhQkwBAP?X!^l^nEMYacp6bpa1!qgBb&d9x~qcHKvH(~IMQm6 zkNZ!gr+w0yo>rOXNbAW)f7_Fu7Q74&UO{-2KYpF~eeMz9{nRfQe2zmLIzR*f{289Y z&;E;OLsw82FxgA!M*+Lw?u0r0GU^;q4EJG}b72m`jP5~w1BwBA0OtVHd(jWVKNF@K z&;y9=gRBDf10rcym;`2IKk657{s+_@%)>CxZDAY=H{+-i7)PFyaYSrm99${mxF6<) z=NN~xgmLsd%Q$vX0ItS}J_X@EgjtC=9|W`_u43RiVfFw{07nMUR|EC~e*$J5OgG>` zz#!anVYb4w!R$s|i~`Ohe#K3rX*$3hz)bXM`_UgROho;`-UNG*N{%sWB*>%Petd7f zuI(4#V=nRrJ;_Db2LZRhj*?D$@NVQ2d}hL={AL0-9rj0HRscEyoC9Hjv%=g6Xa%$( zzN0Yrz&sBV^NzGiz&U_319b*`F`&&abL7Ev1Gfvn-i7joI|Ak$nCoD6!;A%ZU>^W* zu>TzGeiZfxVNSP>rX5DU)`5qG=%Zx@d_e{FI>6`xk~RZ0=iwg#Ix8R-Fc)A05CFy5 zQ8wp5a|&=2a2l`+;pZT}emx;?ujL$Q8BP|oqwwnn9R9o3aSAj=pc#c3I}iK-HUc7m z%cbce{c4zRgML5kn>83};Qs>b#W;uG2K)!+se4A7Ss; zmI+$$c0bI4ERuE{Ww;6UzrZ{J_f#kxUVwWx%(r2-0ym6##tGPW!S093;2i7?Fb80M z1=9-rMVMP){?SOt>wr%IMR3o2m2s?t`3cg`Mf$CCzze|EEfc}s$~bI9NvAP{mmz+X z!7hOAPLuih6uO2`$VE;L-gAHgM8&NmGsVd?Ct{9LDAn5BboV5kz zJ;7r1V{-uusp*yHIRKde0$2#h1!M#62VhP&O1jZ2IWp2nc19X{X8PBI$S~MDgRjhi z?IRlZb0qlAfBYy;zR-oy5fU{;X&(eI&moS5Xp@zIM*vgpKL&%prNM$h?or~v@Qgni z?Q=8*me?}V9N>?j55#51G&-mC&OVh50vf6p(@aG0eh_^>`b^q;2EPRiAv}1925WLM z)03XI3u)~E><6rZ96P^e9G$QaQ1&A+9s!DP9Zh>4<_mxmfQ3^q4+P8v{%x2GVGi7k zu>~d%_bC7m`yfmmOl#C=ngV7W%+mmCH2iJ>Jzy;u8AS)e{^|7+)WO3={#fdLSR5TL_3oo^oM60zkR7V_HIn;lB>< z`vFeC7V3vQv(FO8(T%7STFjFOX5%DS#3OC^?*S}?{VdFxFb82q!aN)S9%0%qX&p5~ zC_BKOQO2RW#-LBYxCVb5p-XFv8BOanp*+k0*fU^02zVPX6ZK0z!r3~68w8xjx`fhF zebJs#7oMoL3yq_k8aE0#7TTUF|LD$|Hf&3)N!4qk<+b9myEok5LS3VIS;Ngpx?Fb5~ z`q{7RL!p)PGnN^udo_XmVY?%y0~4t`BnPfpRXD|rb3JI>1ZPI;&BXTtP?OCbGEaf4 ztJqhae)5QnL^DVZzvBL>aj^_%z=gG=PzvQ7Ae%<~Ou#T9Jw9d@%9PXFk2Z9QC%3W`?ei&; z(+Z0zPN(Msjf{{@?G+2)vP2QzyK>=PilCNzsKqpA6kBe|I^QvE`|MHdx6K|E=24H( z@7v!~uOwO06;)S9#A5Sa85!GSQw4*RCWG!~bi4Fby(8siZq{fe2x0CBepdZs zAUsUCKqG*DwOcf&_!OjIn1&O+4PADE-B-qRQUNIVtt8ihAA=}iM)*2JLV59B3(`w1 z!WZK>0jI$N`U%{G;}Z@|*emm;YkYljj!Ay?yk}p1d)G);*NEp;mvEW}K*h*>n>E~> z$K`lFP!?FL!BH_e5|I_a3d8aOC;~Ma;k}U>)*e}FN=@48^*J?Ly#=NCvFy+YZIVxV z>LR=h5O`LDU*Gfex8Ow$ZHBMWgt#ffE@}$zT>qWJCbiJgE0ZTRhs>ss`FhCwW60bq znZh1w*09OD@pVxiR&oC|ffaU3_@viHeu59g;fz4RMuIA#WwqyiuXjY^110t<<;45@ z5#beTfu30SJ+(l=PH1U(Ye$6j6oCy5gx$+jJTAN_63B?~h~xph+fZY!W$KUi_%#h? z*ULCA|}@e zU#k;;c|m~Wa0ah)r1XG{W3cn?eQu<&fs1T@c4YTK#^oWj<2LiB>rGFOBqj(?jl{Q3 zHa#_xSZi80vLksC4)Y=}(iv*$UOg^U8N`0-MtR1{*-`wBfjRO3S?nQ;R}dVjFfB`p z<_^iRcmFWW>7xqAe%`xkq~LPUFI(EEO}{yiycWcCvLEZxTe#{j32q0@$Gd^{}KL_XU`7G;cN zr|86AUPvD)-^@*}N*^hGOU5a2s<EKgZv8<}<6B*ahiA5M7)r##3{-(^p#yKiw!MD$&=UJU1yM@lcZ3(S9QYzQL#|D($hVq|=op_L zt@zU+g*SS{at7y5=g}_%F~*aGmR#gW;7c5q7dsJcy+Z_Xq_t!rhGV zUkDCkXOiy2ma$a)sSLO6(>o4!;2ec8G%N}>aKhDLH>*NdRkN|8tj4~>tfu73=-|6_R01KxiO`ySN_=ZDKvxX7x{ zhG(J9#O*wgoEvWM9-i~Cw|ltYVJ&ZqFn&zBx`*4p9G>&E_se0+TCK+uVf=!0ogQxg zY>oZ zXDk1j{oAH}x>>)Q7Pj(M_spt&!#mwozTwhT{XO}IRK9p^-IPNr2!9EtA%_^+Bgbh^ zH)d|hIXo1#XWuE&SlA0KY<7J2iB1NUgLtM^nOQcLS|1?g--NyyR49@VJuNLJ21LbE*_%=Zvg zDp~Zv@T@;E_zLBs6~oIHugDl)di$-5J4SPr%Vc|%zE6S=X)Y}9dT6-)*NDB`yI?qQ zH^xBA$H1RX>{>e9o(ueT@7&>nxmxZf)H0`9BYQ^Wf1++D$39BGJtN7%i;#mt$iZv& z9#jF6&ReB(6ymHMr_MvE=M_6kZL3-CxEzO|3JlsuH90#KlZl47Pn!T~Z1#`5v4{)hG?=@bCq$P)nxDXWUTQx(vSC)!g#O5_o~{|2D@kqJ)H}rIJ3^P)Kxeu% zWMza)Lr$|xoX-p24JELld|M5hM!e^T3igK03&RZ>5_W#F+fYFlIW?@y%#^BCxG|9d zHm!?u&R+AL87kNlMuXDyE&ed$2UrexPYo3`gweJ@T3!!&KN%|UhnYSZLSYIY4`CIB zxtb+Xn{;9bE!K4xv7JNh1U7Ltvrgy)C%#w1DxUJbKNNT^45HZokBthe^55?a$ zRPgHc_}?BXs1FnVJXCKb!a*8;4&paxh1Q{J4?$P$dx9(YeVDPFcw2^iUNO$5A&Xav z(^${?>S(l-)5f8|%V8KfMaS+T%gbR{y%;yjsv`*w?%;dtxtNc%vQ5p!4g%;~*bsG-Qfi(xY~#zx}(?NFd53`3TZ zcq@i1HDPop6$h-Jj9YXaHPKf+#-D2_uzj4r=Y}lXCH@rPkDEfTvQqwzvTg0f@hiJW-v9V&3s8jBahY=(+q04dWO|r2t z(@nuaF-bl)UATrsbmA{Bd>|$D_6^~3Cz|>t;`sb&rrzt}E{oylFE99|aQ^Eo+O?*@ zSg@IZJHIAhbR&J0$Xb<@>2u~rco%3wF*a8iE1z8=0N%yfDzyvg*1WZiU>vU*BN z?BnaikxEmSM8Rc(!st?%{&Hi8<5GzCuV?E5Ud^KKs~A*a5ciA{ysu(L%jI0OK|<>x zBh%@;a-lepL?7gq8{NhmCl1-7erNa1%#kH{H;9>cKPhxEsu_fH$J-~ynV*i!SQuxx z#q3p;{E`%QF!Rz$;YsF58Yyg{AC91SOeSkZCfTYQS|(M?@yjRBLyK0Hw<%H;xaVWm zB`DEk?j*v0hG_p&q}}%4(W^?PPspUA8AuH??nJ}J{ObvYM9!Qb=1?6B3T@|?!!np;~ zG{m(gTp)vP7U^9tQ>5b^zG~5_+efZ;J%3fGq~(qAq3I4@>A31Qqc@87QaCa4Q; zrTL|HlQb@Tebr(L^|@bNwKCG%a?+)V}ayu8rClq8e5>~G_A7j*wcJ*PI2W=T8Ov0@Tc&po3+ z7^;7hlyqHkb&_oFRp{O9Nw)SZd%6u;y=M8M#;Z`j(VK$GO8&eY|4FZ1wPkoR6_3hJ zD9GI|#h9+Hd0baVdHYvbNnsdD{s{%&EKlx`+nU4Qdq!dCk{Q}788ifbJL!^(+54%E zQBp99SrAz*xvG{;kddzN=O0kWHdDUACw~rC9Odc4RrEy;bliQ=P0H;ynjeRhpY-2c zbs#f}N^J%Ra@Gf<<}Q>w{vJL*JUY7HG2n2dw56}V>PTtIl;_3q`7tz;CI1ML8LB0} zR?Pc0NXtAI3)G(!a0ki}&6pPNNm`66CEiYkzqd#s%lj#BzaHbQMFHX)cx#cT|LXsn zw|o8tZzbo0R2J_375GBAcTmk~%UEsbl7GtUyCSdqD6bz=Uhj~2{Wx4XUm>&ql-JvD z;PvAhdEF;ZkNxj>jT$dU{=f2i1guhCpPwu7`Yz>l-#D)+gwsL5$s2Og98Nxyqk8X7 zXLPqipvDxypUQQ&!AU-s>t?|~Uqa_g+*RKkgky{?ITxO zJc4)dO2KkXK_WI4);YhCJDoF)t@6UTOhL8=Zy?By%MK~9fb&_DSg=jrrtmy3F4ykg zj49$$mI>Uefzr(QI91^L{v0g9{Gf2|L}}6IyG1E2`Xpz8j5-Sqlf-A)Zn(EEUG1g( zTFl^LG3LWL-;u6Que5&(1`l{oTmdg3){e>w?_zxOG-eMmuONqHfzK3HS>0Jq6L=$d zL`e$oVl8b73=sG`Dejn@IK=y261x|aCGzj`5LZ6oq6)sF7tDWxy9cXO2}jHP^(zG* zsq9SM$0+lyii$6~_FZY;2Yw#*?zvJhUjwy4j1u>l8L#h(?=N%_jkvgIao9UQc6Xgy zD7wELcjLJx42yJaEB4>qLeVX$)@WUO=$;XDJe8rk_s}XUxHRUz&i_3HUx~b~pv9o` z^1cCyMs$PB_f-%o^Q+fsXgp_t^9}t3XE_ZV_N65LJ3Kw29>41cj5ETMSKMrkwboo? zuT6GGmKlU6u9Vetk!AUKye9v4SN<$lKE3S^zTfT_{^JUQJgT`@EBrgPHRf=J)wmSK z9N2%re28g}zS8yS4||nDH;uVL-X3|Ss|)T!jIffDH-v}w;#QD?3T20fbgYygi)jcy zCO;-SrlmF~|4&W`CY3R+as+B*A+Z=VW7kF-c#c*WPE zdMQFiH$pP6ILn-LHRNY~U8ff`$%;>43R5*CsjuU z(ekP4Xce^-dFGABCbH!Z0D?!%F zUlDI(n67YBd@2o{>g__VPMRlY%rLv>mnGU>N^2PwY1b1Fw!}_2aZtbE;M4-8ruiEXH-q@^67jb`a8nAdinOaq zMvQX)K~sRyG@k)g3#7jH3yJJ2G5rCN?je$s!OVZtR6sP%AA@cmr8^ zNb7{cX|cPDOo40a=A)o|1L?je5q2Woz-5sxi%Jmga<}S?_b~B=rogbe`4Fhzr12h* z$lJyAFNpMal0DNdFup-`^FC0uBF-j>=q<$Q`%a{x8+}e^gwc)&`M?kA<_3@+pb`2c z+Sh6N--@&eWDk4#tgm0){0fK;BEC9_W;cy5Aky&ULRej(pjX}e5@=BC#L0+-ay6`*?$bY&9VOEkT1k!}L%jO}EeyzkpdamHH4xRwcguWlAV z-U;$&CGxFee$R;X5G--eJES*GHba>qbG;i~I)!z`gq2QV0xC_b+4U}88fEiu-W@fq zMjpRLfAqemJ8|)5FDv{ofW{9I?1)izbcO*Z^iWK_nJJKqV;K5W<+AW)F$wZqCcZ#f z-@!wU{VuF6l^BWUvr`xSV_^Am;md)gc7-Q9q9Wj5T>rsTZY%D46l}+9-^zxVTxM1Q zySO7-B}2iJ-1Stknq7vlGt$2pSa7${IWWJ~?9X@_mv31FvsC_gucog2XokpR_MdnV zfTf2N%g{$*IYZF%_Xcstigrfm`~2$zpZ_>+w;Ug{yUgZ#YlP*v43p}DW3ui>eWTfL zxW0#yY`zt_@y&j7$5)^@x|)lb zRVN9Ymik|L&kbNI=Id0~$3?(W@F!W$7xHAYsc@yf z>#KqGFG2Hw_e)As@PRr*Wv^J}zd{4-rDHI*yYOVk>|}d5wydp4SVmL z+_7)-acn^ZZX>gHGV}h1fpL}rs(D`=9mZ*yKrbJs;4Mw+=IR0StM#;Rd38YStY01A zrdaA}+npCr=x`LM!DJ+7in`u18Cx2=U)1DVCg-avD~x83EG*v~_ygKM;_%UKMT%nk z0PXKzMjY~aI08oyz#JE|)DVI7Zs}Pbl38ytS`2iZdW4~Z3a2lLI&X^*d3(8B*5nLe>JMV%8>;Tmz@|GroHjnTA#~_n`1qzw@AOucDw`E!6gx zmWQ|TBq8G4r&R7;e)@qnE`p}5=-h(Yh?x{DP*i*e3jAA@_z^p6@F(>vD~R0yq&+-7 zoG;j>ZjJ>TbQp}4*odL^T=1M&&v)RpQkmJzc;D&wn0577$R^tGhyVkTd{gbI*Lhxe zQHUIHmg}5}={$Y&>jmQ)MJ1mHtrg$r;Q2hfg@q|wVoZpcWSAG}10)IIRx^(JK&qLS z8RzflSup3c?Yy%Xey+P%?2UtI*v)sbd;Cs5&-t$-JWnH(Hk!jCps)@c&rtf)N*!>fwD_9khn=xLiVE8K*Nmo)T#*q>MB6 z#i}j+VA;7d9hcatJmo9+$~+uPsT^`tR_0Y7o7#=b!8@fe)>N1;i}Ex@k|!**W@w$$ zY5dN&w>L@hw1AyAE>8>CJ??)YPqgyzUn9zs6rm_hY9MRz_+0JyooX})iMBdMWE-yl z>YV%wb!RL27q|!WTJ(G67VMNixM_qxXv0{&MU5BLpca5$wI7dkBL6JEdAl=|e`y>` zULi~FP;|5_TFeu$(>cE%E&B9MxBe)5M%LE3zctd|VNuF+c3wk2nPHzd5G+hox1tdW zzo3*>nHD=AoSe6B7Ue3P+>^{rgRhtN zQ|`YR`@7HR_!*Qi!Oh*mO|*Tr8TP><{-Uf)*MH(`UH2I&7Htaf){S{7vMz1^3Gr$! z5UCsSlND)9P?%KxW=58{gi2y|%;q~;cw`pg{V+k+55dQS#y}*j)Gnd;$Fuq3#jg|G z*AC$yKY}ywrptZQGe7BvKHE$x;;H%6y*~MM5^}Gn0-cw2ZknAT=*pQa#po~TSw0-x37i?zUqo-?xVE?P zG|MZNYeABz7n=LfL3^A}&|R+A`|vuJhkkuSoF@x=`lzstw;y`e%}-Hd{=2CoFyY7M zU48W)5*VcljoWcH$kW$iCBR@xYcc#N{Qib8raGD*D^8_B!vk9BxFL@D)lwqigwx)S6?HQ*`w^Y7pB!8b8@$G6J|{C{A@Fds^!>V(^s2MG!-{B z@|~KBw}s#I?R0Zf>NDv_8N9`md-}kZ%lq68|2`B1((CMVd7NePh4NHI(xZK;%6T}{ z<1UjYdo}{G76`Gt&|hfZl2b}qwWqDt#Ff?R%aZ62MY^}@%Y_-Fa*0_pW13*Qqo->{3S0z~PMaUmbWGrdEz~dI}O%D@NhAmL~i;>fmeB_KuH- zKFo@tc)1(3>=Y%3V4f`UBak)eINzdlr>|jGZ)fIhAXWd-TUqVQ_sAYg>)Y`r1NB0l zv-q(;^**(pGkVwbZa5&K@`|~eyam0qFA2jIab6LB;r0tR(JA61qh zP%r$f&mCEMJEK}fqBfM^Ympu@Yu4%JM94}+VE?uH6-wg=H~GE{f}yQl_q0Q0q_BJ+ zEYi<9@E6 zi5dwliN*EDx`CY4O51v;(B6wx?FRc|p8w&o_j}i+IBm8cQf1G`n-BLw%~nu*?M{vH z13B&q$myB5N{okoXM;KlJ4TnKV+(kDQ*OZ-M<`q^41J(ZOp_{jY1*ik>*?Yg^1)zI zW3OF4?`Gh!3mspxye9kzZ3x1SUc4lN!_KF=cK5dL?rrfVdw<^>_>R@$rNm-yW$(^L z*8bpDP?palN=+@**+Bb zQ!oeXf$hER)ri&Mb@vtoSj>yB(iQ6ME6Ac%z1jJ?JagM@b9zbdf(-Q4cS75TmwarV z9~PDN&iWJGvc?|QV)5Lou9>%V;0Rg8cYYa;fiyqSD{Q0{JVUwY@!sVr3v!mG91;7v zg4Y#=i3+Ud+N8cuc%pawd=gG@oL-f_zBf~miL=L;yH=EU%JE{CT!piPekN1niq%)t zKxIi^OQSyNt0`>+T@ZqL{M=w}e$tBGOgZgJQVmJJM#DiAqKL^= zhjGMlHaElA7j9>aF%g+^ zTfT|-Cg*Xz zU6z_p3+rkRWn(ASntu;MBa!pPBq$^pOJ5MpZMn?%N4EJwk67Y8J&+(TtD1!ZH_MV^ zZpm3G^X*}q1CsYYdg?ul#)>6HHAfIP=5V=g;}g-@zd4(;Bf4h!thbmWkC6qBZ+MHD_ZZ3Eem3XH=itj%c9nzr>=8S$D464pcsm6xl2d$|k`8Tr$#Q>V zs&D!kpVOffPmi2Bb;6}DKQo(KT5PQPi;>9Wy}Gt_zs^c zwWz0kQBO<#qu%TuEG#UQEKTJ&>*?5zXH(Z%>5`J~LM)MTT~Yc*YF7uX^35zZy;=qrrDBT)o&8TDWKJudaII7`%MuWE_7j>@1m^yD7;lfP8 z%T#VcImr1yRWbii80`!W$*qg{f|XKH>dvT+(^rLNXzDaceV3))4PoS@1bG%NT^9Rh zS`PGXRK;!#0AW}KMqGw_?cBU2ZEI6Fqj)B_gl_U?ui1=)d#l|-X1YDre&3$E4u$z= zBC;bmBlNe+Vq5!f%gBqi#%8S0m`+?qbE5@CvrUCQJX0u&$dA(-KhxF47R8D!P1)*2 z%(SHiidf^jlZu4Q9JjZ0Kco5uS-x=t&fDKfj0KZu5n8M9%qBw`njckKy$My;=8F3C z=*y0IE1!|m$-#65tm3g?nef#mw0&_yuf|Qa^gB&OIU0k%Of>I@4RG#vNC~;m|1=*{(T!I`>xuXga(G-8_JmPOC zPkenO$IQBO zrp1@6OhupNS8V&gU&b29d0BXnmJY2yy2oWy8nPWTjl~m9^Fe52WCaV^_EnVfHhS3tDhT$3>}dgN3Z)=bk-PHQsBNgm}uz?i8(e_>vGy?HHu*NWp>46kYQVm!Qi3 zc61xlIa_PZ8654@U@Cz7lA-wNx;c^t38fL3h%T*t~Hn2HJ4jF8@!s!f!VC(#cM9J+*#gM{pz=+Z-v$8;{@V71sW{h*k)ob z?@pFJ&x$K!*!7175dA7;CG4jT zKO;1Vra}0A%oAmUG85{lyMYnD5lsUPM)907>Fg!U$(PB~|8@y#bVb3RsP59vP#j*P z;4N<`mflA%w!CyLekBTIRiI@w#Dl9Nh?Vb4=SkNmm)bwM)Z%%<`|+hfBAfovB^>pj zs>>z?UD$qfDbb4P+)U_YcV@35(4;UOzQlzCO&?*DcnqD>7#=&9A(k1T#|zpn(R-E^ zk;p=n`8C(~67QyimoR%O_(65jBkQQoML&d@*=ZGC^WD`%FH;7-RN;pS!`TpxsGQgR z4+q?&>!*{L|9Zadhl}DR(-#XyCxB6u*%-fLWjN*@GwaN5v-77)^?5iqEVWs_}%pNNr&(%`slw z=877WRIWc6VLKcBj5!gS9JKix?k$0))g4)}QKAu7Fpy(`L@#V!eF=R|!9|1>3m;`) zE}*SjiI7j_Cd&NA;_pzjTH)8!NfQUJRS!}La;v=$T*3>Z-sSk^fxzp(%R(Nx*HJkk z)X2p#Dwn6Q)bR6oJL~eSC^alSax-yx)|47HHPX(1tux$2~&)W5y#L&d0cn;hKwl?Rn^6v8J_MYPUhG zi@ml>zH1O8OK;GL)1PKOeL9T`?>Z(@)6MRH63{9}52e`?M1{SXs;DKK2FYXx$@OOE z+IWi&sr++#kY3A$P?TheJ{uAL{UVKiGK~vIOa%kApd;Kd*pI*{>l=Bcnw{GyyPN4G z6gp$YrS>rJyUnY(L>0&-U6HZJrG}O0>bt$AdY2($v+h{??e8L??84yNKdNjo5{gX%i-XM)|x?&wZ^pzm8 zO*PjLuyq$JrEH9iEhuZ_S-I&g$_X~7s#-5fFaP4CoA8EMbnI9NF1g;*<}vg2uMzxu z8!mWoSgb!-0)41Y%81$H#A)%_aBBQ!$uVak) zdQY;jjJEkB7)YyJl*+AM$nayW_*n^Kdgdbb6wN*|=23T_={MBN&bVHGF7!q;o#GSI zV68@c<9&S9S}8Jb+6B}%U;oqBz^ngA-)Js0(62JkJ`OiD_^O9!h%&lWfYdPX5V@LuS9Ym`0; z&&}W-l>83C4c+{sWA3*{&)^c36yY1Ac&`4Jc>jG8eJ#GwcVHA-k$S?558L0#MIYiQ zZ-{XscPCfz|5*DL@TRJ4|8tTweML;#Qd>Yx0x2otOqznEpkrG~X+dX7xlRiM^ zKEChY@9W7qYp?xUd+)W^UVH7e#q>V9xpkG|`by_e&D)m+N!I^fFfaWElC1O_+k2Nm zbB{Y%TgTd6_l~UiMALc2m?z3cwwuEE4zaF^ii>%$cxk>~BG)p|J(pgi036aRSvRzN z^s18Gb?GA*?i&FZhI_3LSP?dIL9m-^6WYejB{y_AxwiEirav97D3A-f$hg?w#l`1n z7GB0`sQ7!&JHdR*6_ly%itRNjles)tQ-Jbrl=#;L^QW$mUyCzWY%ft5tvS{Q!x2jC zw(x_PAdqP0LCB_SLcK1RB1y>8>$+Mbsf;Ms?lSiF3-#y}we<(EKd?vJmsDC&JxJsC z>5!EX@phL>cwWU*GXbdFp>Qg~9&Dhd-2xh>RT-MGLrFnqbqSIj^3 zU?wQ&mD?1n%$A#_8^naUa>D1h%f0xefBi*{V!|Z?;%GdD>_WkM{Qv0VJDAGm-LzRL zF2n`zR=QG@-YpECWg(~%-EX*VfDZ?F`CfLV*FQK{aM|Uy`@4EB8iyskM5*YdwnbMk z9&9r%qvrzc^2-DH@U>I0OM*83B@oL%8t2}i*6-IwYPa#WJ?MMXf zinJl0QgYw*%BPyil$S%2jALz2Tv0oec*6a|RJ^*a_7j>hm(_ZEfESgH7H~rcG>l@7 z?I!gGfj5oD!J_-rjyh{;inCsCEw5YeOnn%R+D8}3!-b9&Ep)6QcGuOF&R;3?iXj*a zSW>{EO?X976(-@WQd%BaG(0|0a0hXl9pPvy4C=v+5Xutv{7*Z|l(_Y>R=80PRVfN9 zb$3&hLfs$rpjW6lnHAG#puL3vE}D7)0$m0u-6{LT#4`y(Xk_d+V_emFiDrJNBqt;?)ddz9mTgZP~GMT#eXQ8k{JC zxZsgp?Uy6CvtqW#K!|r_JAfX@w)BJ$5bFL=pkACTSKt%KG@h%^jBDLQ9L^DD zwfc)I@Ma&yd})!4jQzds03kr|*?$>w3SN699iF}oS{3)jWtxa?6J7RR#*B_#rqbP) zF`(c?8}X7vu_mFg+aL@dt%b3DB4i#CZ23V`O|O_6N2ZAK))q17OUuKh9}2Iu=5V{L zy{!)}j|dP&|Lan?ZFAr~*wSe$r+1XM{IaW3X>h8l*Iy?7)qi;u!b)$iOLfWR9lMmw zS{3&n6cD&dDI{Nezpa>3!_hRo9&#FCj1BiVQQa7In&$}DB(c4CxmR=%zkllLm04_2G(0uALXHw! zdpEdIaibpmms>_n?EY1 zkaVX^zVTjCW+}8Z?8VjKv?wZ8PEb~k%!xaS;9lZ|uoBC17@LcyL|2AJm4*|FJsw*m zf!V=c0GdXW1`!;C-rYB8G^`U2Hsd&i9LL5!CPO>j@t%|i2oZZvw&A@*lciiEj$xA7 zKk5M;tysl8MZIsfEI&f^7^xEUvea!o$)a2%?bEl^xdO@hzw3clqHuUl=nGSSPGmbO zQ`~dR%^LQ=ph2X^V}gGPtQi93)s%Fp#gP}PVX0(#NdiN^ChTy5ZW|+xO3>uo&F<=( z-M01pL`7Ix9o*G_EH3k|b@SJ{z3xriwQk*m!DWpINfeE6{@Kl6MabW{Z@Xzhb$_tQ zo#ZuXN6wQ&RKsaft&mSyN6B9ka1H3m9ErnSqa#uj!{H{XZ|Y249n ze^jtj8@@gU2D9CK1AvpbL*1Up;FS}CoLb4qg2du9>xSC(V`8mUwUt2`jXCbD>9rXh zS!uPI9T|0*Zk$-n49e+9o>4oyBgZ{uQl`5woI#v-5$8VZp4weOWzGzD^6cI?o=l)WqpJ4Hs94C}`}%PM&Y zv%Y4KwFnyGaW#<7o_sH%0aB|eGWzq7z##wK6^RENv&)kbb z0=Edq5p$nqknh!Q{$+$L1jn*{28VLBV^w*%*HLGZjCUv^*P|CmA)ff=cJseS zs)bxux94ASJGz869OSs>kWvB_UdIq?NmTLdGH;E0XSs`dyaEJW z@z>q_V~9MA8`Z75B1aK6hWf^L^CrOV=X3%=yX6&i=mRx@K2Y2R!#nhmo&Ej!!ESyu zB0RvwcKbW!r$Y`!C}L0T4dBmAk~sCSB$XU1SCZLJNz2+}LQm3k^A8}_gPfwwTkOPkG2y}Wi>#aP>GHaN&0Xn& zma!gVD0~KnP)t^~Y_`-9($|<+uhI}YC8 zwD4Jgy92d-JXjoKySw-!Ncl}}dl%93sQk7u_DL7N5204BLI^z~C)U|J#y;xe_ae~7 zaTMsOzuOr5po{+)I4ig^A%QRZ$C#^&--eJvuC&X4Kt2v*tg4Hz0AwY%rpv#-e~c9g zW6absG%=~8!kHe0VKzvy+<<{c!^{cRcE>n#{$iZ1>*AeA`91EHF5MU6ID_6H(00xW zr(=ksa(#%r%vodKS^i#t2ci80E@bWES0M63+_O{#-5&WNOe|lb{m4O9@#z{+WT&`Y z3=QsS-@GpVHK32?e&1!TksJ3(-P>=v5|xenLMpsl$|D?F=m}uA4P!;?@a1bH>4TUp zyA&lGpyWHX>sMGXhA}03YESJM?$8EXqKx~LdI<(@i4)=Db`261l(ix<3Lr9R%yka! z$V)0>4XD+{n9wrG28Wd#j4pl#N;ZNU+vWdA-WNAsHhcf56-CEq ze=__1+3RM%g>yp}W{;S)_MwEST}fjSrgSBx(PKJ2=BqU+@YS^sY+YM-Jw8g4-W9+2 zAe6QFk=mpY3BRETsT2Wk)9Eo^TXz*`zX2N9ztJTz&X*)SN@x=Z4Ug&cm=B!sz!B{b z-TNQ)|AW!GE{sZR*zX(L#gFY8eHo=TZ|>b2sj%C%ifk84uJy>bT(Lh}i(92}=wIXdPziaO>bV$RH|%gzfCY!>Xc z_bANNF$G=CWS=rI6C-KdI%39llRd*bd2L9BRxv3nO`WMta`wuCdHbc=+izy&E1lYV za_m|4wOI?Jick5zzr=?l|4+Fym;CGHOVMYRd>ZI86<}vL;z|7n1wD0%XMp$__thoy z+j7NyH0FaFPWdie;(xs4<#uxQm;7txt6y2NcakFb)xnDzuD=>oVP9QUY2P}Fj!fDX zi4_`(3cZ%NFs*iBe(lo180|~cuMQ^pm0|Fy7e0l$=GbxX4^k2np_<#~iuI-;X0k7s zSAcrw&a1sv_;0I@K8N!skQqHYdR%kVLLym|2-!91c8fX#j#)G}#5h-$I#)h>TEUCi znkZcdJLffAWi){i&bPQqTyCo;k&VMK>+$Fv7seZx{X~aa!eK3Co(!ucgrtv#o&A0D z2WMoTo^}XZ6@q|%nS33kl2PG5`O;ds$H zc{%j|O9p2ziTYgzn;(^yk=+~u;<2a!;$_UOlsQszLKIQxK?^6V&&hn7F7cZ#c{ktB z6DTokCmkF>q&Jn$@A6eEF-?;M-9ScMW(@SS4oxI{{@^0^!6z_m;UA@{!Qey zfXlq(e@U*0`V11QY-SYed;JpsDgvJ4CQ{LKi{x>`L$r0$)a^GRgL_?9aCor@&Ump? zW|cn^@JS}Rp%d2$nIWlIZut%(ZM*m9>(6Bk16L*bLvP zcGo@XSF!m+aswR$o5EcZi%~d@51WSt=Pn#ki3 z&ztr8exr7Zxx^1ay^^`sPV;QJBE|`+xIk^N2m}yr)w1TR*Mk7cOS}?@Dcp%p|4jK( znCn=UYVUhi516F~kAP2vE3Mn?&719@^=t=m? zeMG%2Sa(*E9~u045J+Y|gMzDY{1QdR=rW(az05c#jWS>Eeg9d`+o}7lJd-8W zaw8vg^jK57$qNm+Di}Ne3^?iDKMyoqQYOws4y}%~RwK?R}R9`Ilt&MiWU{w{6oV+qK}eF;mR}%EWpA3G`fYkE-015&*vU6^dOhLXnocyA zCrv&xlJ(Vh@?Qd|;2Z=rPn4&+E+nT~X&YNtFLC7cl1ApQmjot{x<_@Zd-n&Zt~)yU z8YD5CTheKMTuu}k3MC(nxFM`VJMK~_&W2>sHuCQ<{DismK1*a|B z>zyOZRUkBhU=B{Ku$Ot)?+$Vm)dI)S=yKFn)Y1IycnSCK zZVGv)4Ew(;z4A>GqH5^Fp+bC6HxPefnZ2TJOS!_~UAZ9`CsGfQUm<+#3cm27TnXV94P#u;^mpi`xNuLJ9}%sYN&(Qndt)W4%uekGjCzE96gf}68i<)H z#JuH4OlBU|np+V$9Fec&X&x2hZj{7GG~?(UE-P?YfNqaOPn)5j=K&eBVlWfJ-gEU4 z4U=H(#6eBNRjGQSHTi0!TFty2%ix*ZEj6UoPS&=Rpf3PVqwh3H)ZNnYTJ_b)C*%pb z&SO%c!Y4{u&4Zm{l~G@u`HmP)+3DGa4t;{U^PeV7h{|v=l86&DNXV9GroITLEb*F! zzd9eEFgLO@SF4<$RSRxy4&(&uHF#CeV>F@t3F{?f0vnOX9Jt zjLK`6d=l9oE;5N~ln@SpM9)064N8MG`L54oYqIFrM=UEGx72B(8#VViH8J;ul9>h5 z%pi{yxxx3`W7I_Irx~P?rnwm}=GZKyOF2h%8DHV6X%`C9p;3LWi4fv|9{w{w+L|s} zS9z_48rSv&jEB-A7Xy3Bx}SUeg3}P-dFHWX1vLeB5gyA=paNjVx+h?_LQ}vBGJnx( z2Q?UlL9B(cYO~t2LV`vlnrEvKoTnXB2WLQ|;e2tXL|&ou2gB|dwACGqVXnaDyCT9C zNuA!PRq7+gYvTu-bmNSV>4V}2KP-a|hKvl|TB$88fMYP^>Wx4f>j+xu4btnz8pC=_e~B-blk4Ck#-C0zWk%=G%BSRU6T8-1qiH{_Ts^?_A{GxoG}A zC~rTxN|Ec9QhjnRIDSyXt{h9N>}Nu;lK+p3{Hlx9|8bF9ebMu65HOZrSh;gNeiz9m z!L|U-tmwQ4{M9}j#*7=DFI6GPv~H`^y?=zx$)1~27=%M3K%^LHlb|R>8Bwn+of>B0 zo<&40fnFff#$t!@u%pOWrdiGYYrE8dKBA{wcq&9y2jtOuWcXyy)c~ z`xqSo3v5frC*;CVm=cJu$$-u{`mFFm_$j74e zbGbnmX~H}fbS}sjeUXm>%*=%nO!rj~RMT!K$~UM*vCtrRCnmA5Y>+SfA|DFm0`6zO zxgmf_Zj9*|&Vbt{2Kgix`Jeq>ZUOg$-(Mf}bOtI`L?=5Pto_i|;?usL{QNb9FXpcJ z{a;ewbFjsJMT~t?`Zt1CP0R&2&y7=As2iQTkoda&{I`g~ajkx$!TzA)i3Yd5kfEW8 zB4=Zat{_wCS!Ei-oH{k^cwk)xj}+Fd#pIZ&-OslodNp^<@2?F~47P9o5DF^90tvqM z^QRH^30F@^`~M!~K0Mv?pFvo=89=3 zCUsN$*_hP1vPCWyqNsZD^2zH5`8Ypcjd)`?_#XKe}r4%*L@VE z`wMU>V^{jxWY^+zyE9VO$OmAr_49uPb`tlR-~3@vh6D;-Btq>{dqBn_|Z{6}O_w#>28p&LNU$>=i_>878Th`(Der1v*U-uk7EFH8) zN7vZWW7I}$-E2JYGd1$I5JTm1mtk=(HHB_e**v-L2>&uh2niV;LNf@KUR zmZ3{$(XehDeq4Gh9c$p|ISfAhOKyQ-jFiYnF?Nk?W(DpwRGZ@@ir|AfW@e$_4r)4! z^$S?y2!X5kwZe~CO9iz{(%|VMKLp$Q^d*jI$)ww0lZc;aB#?Q@x70LtW*AjQD%rTI zCWQvP2@6%-_8w&LfCRp`{&XvSFFASCeIyuD-Y+Sv>7m}C!+r{kD~F+I4-0mU7$eb( zNV!p@Es_)&?R8I(_x2%veyATjX^7ub8YGIpklw$>>Kpj#+QsN~{Wf>=f`4^Ts#c<##co*_ovM|F zr)3%&F7#!0ya!xKA*GWd!aS8aH_)Bb>1)w7zrDZ*`@Qc!$aP;ZuM9#*TZC@>>~gFQ zm%MX-p3seRS5m9e8u8i%{-+Dx_tjkM1OtUyb0)N8=oS*g{P=z zZ?|5x*R6;!sp6%DIOq`QQ_G1j`_*jpkE~v`u{Rw0ha7h3Sj;$tmO6>(Aj8C{V zz{@R7eaR!#0$qeXKHt_0`~j4g=eAt%yb^@kW8ehkPFg!_-5_7>1^({{JHWXv_!kE` zBsftJWSzM-%ewg7=^X7Ed7g{O!gv>X-Dem0kAZxcd;5ZJQBdwYCYvfw{^tIRc(5?0 zcFpRm_Rw;?mUl2^!}CKEG39QIAw7!cwVu6G_6}D>#7kj<&^zVg_!h=b1l;ei8|MjO zsOL}zh)2lta~C|%5)T76HcTQO zHcQ}PV0g*1mS+h(?5zvD9k>g)+zbBsLH;o;(Z(exaLygbz}?e|;AC_ZsBzXQeSf;Z zF9GtOxfvI9c|nRkj?ziBU)r&CJuwmUD?Q;$@cn8B9;VdjeKf~JtFesCmiUYoIv)kU$-$hXPN7g*8K8LF#gKM_(pWzo+Ig7LNJ4qhO_ zh!>dKSdka_l>uHbt4HJoBXrkEW$`EQ0$mHV{JLM=I(U?Lf%yd*`~GvR{II|a%+C|M z_!^AE|5Q)(l~s4~L|wT7z00t8=5p2-eSy~kJ%YP+UiUi3tm)PiwxJ4Vx0U70R=g)g41p(zK^ttk}gw!y78|(jxc@j`v1GNE!0DnE&0Kaex zg-rU};J@L=(7zp`CO~4>ah()Sb$TA}i5?|&NY3dYH%dMG*$S!l77zPa`R8z0NsW~aBwZbG_3=H?KYp_@pAt2HL9OLx zaZE*Peu@s_)}meknF0(f6_b3%+L;#^-`~&k_2<3rqul26p0Q{+iHBj~ODJ0_%|5`+ zHo}B4ur=RKH$oIoF&UwwHIfD{z-t+3!6~^(#B0}`=XajRE(7=1^Bw~vAhhXB@;CYH z+e6VO6!&F;?`AD-IN|%~Jiir)8@Ltcb-yCMDX@=iLH>`4u$GSKleDi70u#$r2@`Tk zPk26H1sxU@^9b1HFnpL&x6Z?mGJnNv?1nc<;)^W!CisO#HT02QzL+NPMdD4{S>lUt zo&N=2Tz#HjeZIQ*JU92eP8WnuCN#6Zib|vJ0R6VYYZ&GFS~*F@(?U$7~3mipu^-amBrXzqAxO3 zrnDoZzJ=%ch3BhZJkPy&-ZL!7zAvgxa4_bCj^T{>waQ)-vrUSFt3wkD(MuK54J-ubHBJgXh@Ms!i!(8TD?Kj(2zEXYH0>5MU*HKt39 z>C!wdYe60W?z*QvU;UWZ2yph%ARIiGc~-EM5|gS}8V~7(Xd|I?an@jz1ESk18W7&ePoF8Vwe2{+`t4$p}5o)=kL2sKnvNJ2FI#tWhb z(Hv|62xbRc8bJXtd>JZmqfpxc;`xq{a~ZZedW#pjLuh_Xg6_Y1U<7RoCVWT8l@Qx* z5wrM`twwxv)Ij&QGAY>hi5Ro=R)>&qQD~W05ktN{92|Fp!fL{H1nk1#&SC>qZK{{+ z>ex`_x3Jum4v&?!eM5M#soGjbW$o+$&24`Z^C`}8)abnAFK7>e zW}vF=d$#?|uq%5bdu71r-LP^X13T!53Ljx{Qg_5AvPp;7@c^pXlJ~ zI{a5jN1wtJ>I4$icj#VWH3unMn9)YN<(hpR&`dyd*N&3fwW?BKhIGa=BV&ivxnQNV zDcrh|TuS-&cbs)t_b!JYAS@3X92G+}wH+fj98yTO+I6{XsX!qIE`C7DI1D0^viCEc zMHy+W#8r0q|ICW_`oVGv75KhKvz6+MFuejE)VIWLb#w4EV1* zGaDZjAycdTIqa3)bX@4kj#Wi~;ap~oB33-oyX<;NnSom?&^8QP@Cddqz@0kxw;5HM zln#T|3W!(Bc#p`qH#$7OWkmyH!x$?inA8FHrW8fbB*jercu2zQlu(5fk;lj5twu6+ zkNHU!Bx6#{6yzSkv}si-4|S+V1QG_qR95o`3dAi79|vomrdLs^iMG&n_ShCvdmg+g z+-GJx(!Mg{amss0e`Ur&X(G}6k;RS-{-UecHaLfg=uqn^544@$JI*WDJ;N4aO*6EL z#(UTYL%@I_3*Zo*ZaxcRfu}q8r#rmv_qpT_^D)`BV01=6b2|80fO@#`9dLzA79g9# z>>8EmI?OhuqfVn*Rc!rmxl3&7)4nG=_$i3+0r#s8{}Gv;n~o9`O~a#X+RE5LzVr@$ zB7#5S;yV2G!fc@-Z2=|g)hKre`htalYmdgfdSubptU_;}JPp~@Yu*T}RPAEF=at_C zZrH%AyrTy}xP}MSFxU0SSyW9DpQ0z*MH}<#w4{{kTWw%01vlOZi{34({;|!?P4`=F zJcdQ|tB2EB1~ZGNNw)W?MNPDxpFGX3Q5I>i&8bRauTK;6sYwBCtB{Y99I)y3_K5eX z;kvJZXSZmMQFidkj%rm0_iejwuMBr>+-TROvbOE*s6Kkg$e2Gz*U4!W;p&MVFP{oU zm+sf>0PgS|A^ws(;NrxN_L3^AKaO>4RalcIOlcptVVGSTo={FPjL-)U4~+s)Nf*8glbk0bfx z9ozyHeKt6ygObjW&AbpaHG5l-X9Vl;Dxzr2q;aKRQdmW(OFX#{+LobOmDcu_>hmCO zXZxxu^DyWfRngO#Y3gvqyG2XfuuA*HR zi+xHo2>uKc)m&A(&cI?7)ThUC&0|VGr(_&(SAri{DB9kn87{yFG*uL+7q*@AHsG=5*L4^-K=3XRsv>g znW5zVYPMviN2d9MSRSGHlx=Z4zoxx*Bq5!^CW>W}Htb87*uLxsN{`gq(J$wAzK_jOOgWf7F|vEa6%ZK5h6zI{rffcDvNRuREH)NWR?=o|P; z5bM-iOVlP)EO0U0C!w*>)snpzjmjK5x#5_ zdf~QuDn?mInQ(+C#Bpf7Dblu|U==j)dvRLf$QVx;Yx{y=ju0G!e7L>hknPvhoi+}! z?dn6V2?3@*7#_|je@BS*dh)OvFj>UZzN);fQdRnWTU9wi%0g#$F}`8#{Qd3K_qTKY zHqWcFwhGm%soem#^QTtQpc5y92Rz#o`l_4W71^Hh4Y4spvA3 zt&GZ09%2h>S5Pg=L%4Hoo|`l%0(3du=D)$rv@@FjAWZZ|VmqEr=H`$xuPA<0sBDMZ z1eaP6d~lXj6jhepu$CKAjtVuGSw(|$nZsS20ol)g9(xqr?lyljldzO9z=0t2LAGR8 zIo({AP*>q{;Kli|idD|4@>GHNshJS8K8QKXecWdLp0Rx_Bp$LcCS`jYnh>kzZyEDs z{Cci3<02&T`x(uLgn|vY?=>H^!LuJ!T;DJWdk9GWOmzQXiko~#+xVkx-YwsArEMN~ z$_?{BdJ|n&;}ozYnsPCTk!$bQyp1%#;Vv-=Q`-cWkePx`r;DLG{-6Lm7)%{bvxZ`T zQX3_jA~A-a@jpfaNGAvjy3+8kZsV)ls=aO8;x_X$vXsA24T!0oXKaZSU+6tCn4yk> zR#*(aOe^;DVtneai`vX>4Dhg@w|BVSXfi@?Au*&b9|@h#rZ#?4TlL@CxM$itvt-%( zV!(eJ80~PCt)X?LGlA&LFkD`nu7yNoqaY%ezS0(uZMN13-bNIX=)9kW&D~&TjRZnR z=?iV6NG8EX-w>=hHY_huFD_(A)fAmAo%+(B*H*Kq`bbn{$K@5OayA@9;R=II)>ME;eFO{=6bsE`%N+8#lPkGfAe1LGLn%3*#AWe0E#)^fqo#n}4E==p57LuZLN>P??ajh?3p4wj)#| zM~MA21mx_EF`6&wTQAWz!oJp+nk~NvuB_rhMxU1O4^y;5iZAb%tl$0M$~`z@S__L@ z(#`&h<}TXUf)b2H$xsCDR;zzMk+W~kvOU-qU_s(|ycijWiS~z9e=UP)s7lP%h-Fiv z%3!pn&SSIZNw_Pmp4!0py%-q{Jl8Y(~A-JqJP*{vN#9bJPId^I>Z*?>TfE@3DPEaol_3LKJ%hIv)RKh}!;t z2I9d(p8N36P@-wEYhqkJ68iFOt^7x=)gQHTd9D5!62qQv_5Y30EElRG2^XqDQ$ChF z+9*vvf${2<7e2{o=CtaKdLt2J6+@IYuVMxWlYb!GUrx@T)^A%kRO#%{wyBEKY&%<4 zu6xkoO_2*Z$}&EgY*MDY*ebXbD3To{PNR^dB#WUl*IkcxhU&ig`QQlf$pivAB$}y0 zAWTLl!$O@r3BBIRFKw-Uy_I{ol~$(VGR;U4MUz6mI9xQMHf5Fol8yW%STkLOZnS@< zjj@>oc*v+UvGO6p@Ip`X7xdV~oI+ghtC37)h%H^jMRNGYIVMdi0Z8PMT+;|;#(T7v z_VT~}>0znRueqpJ&#HbttZenK#D#_mNYkYUS`ZuS*|J^hP2{k$*6OHMZeNS;JFMuVuxTc> zCas0)_uf|ip4RGnTe-b0=5M9p4triyh1aQ#T)#mgSK~6v5TV1(yL~9vwA?Y2d+D%; z@*F@16y2T|?sUak%WIu^^1eBzI)FD|&aq>5L7(MXMpgM|GO%P)NWpnTmv!K|t`^S= zjA3{`&&3dWw*|eM(AwTO5|}& zT&$#U1XG$uB_NeX`5<3o3xBMoy0L|Oy+zk5E%Kr}6fnceA=lw{wCuL!s_`Z~5?*f^ z?^szrOIXfjGv|K1vlA?%B;9mQUr;U-z%r|^!Y}`_!ZgV~90K{u+_I|5GnwHmEuKI2 z_t*I?{-+tuP>M%=`+G(cL+@o0dwkSThrJ+56Gh*82{)%j_p}&~78P6z&$U#$TeyrC zbE6bg%5E{|Ft!jvc7!?7E{os)oUUEF^fMkG z{FIW?0&YHo0nd|po?=q2HB)RHGV4n!!?g92jl&bZr2vz3L!#(0JP{hzgl-WQ{#F2; zg#V(%1V4f8!{JH5s^zlqR1oviW-we_i|%*A)yJ4)qOpEm*upPtseZAAi)t|+kSbyp zVP(5??aWTb_d*N5pr!ix7Ve%F&ps*j{rg%xnN0CX`@SqFW>?vfqQg`qA8#j3Bvm;t zgu%w5f`lW~GEO)8+Srij6CrVf!w*I?abw5CO-e0{VvLARcN73CCoy>vv7pSv!q|cE zj6`wrNrdyH0qB{D8c`t=B&(?4?u zn|0$DVN)6OCmAfVg^y{ej&0#SX*O?@Qu2xxKB}d9a0|D+S+|vX*sf-skulhNHH0))1iRI3s(G9jw2Ylb=^k|`ve2)sL1 zG6KcnEOzMY^b)%(R_&;z6B=kEf_Lb@A6X^~2x$R%u6ND6+Flf~>_)k^$XJvrj!V&= z6plqxd*wn$L{TJeq#B+So1~AQh!jndp)^nh4a-8T4a(XNYqi@Kjv8gvD%VJ;%@v{K%-6AU&%bPcZZ3lJ!y=>8RkFcZ zo@)Ec*F};fXQn%$pgBpq3{C`;DduLu3zzz9$k(Oj=GTkhCcmT%j=^hfdCjo99gjIP z7MX)iE;Mn$*I0MO+QFq@jG|s_J9e&LE=PFj%w|!b&5sGPO>cHvFog^4Sek`^4`P(z z5Oy2NY)>}Bu{1v*F4nV8l6c8H!m^sl&GBrKN|W9kuX$oG+%xeC+wQZ-M%(Zw{*Wp3T0c!2-uY;|HYm)Pv7`Z;dM zJw9VIZ)|>gC}(W;ydc_d;Oy;JXT{UY+VxGTkEmHbfZytpM^0;|c%wxyEJht97 zkFftak7+M{O;+$9FlPDn7s6m04}Edab*u(%p5t$x^SbJ|f1NY`L3&Q^`|%uq9e{n@ z)pInMpOIEd3Z&W(;Qgt0{1j&!Y>^~F8?2GmTq(93B9h@n6erWHxq`he`i8m_Z}`Qv z-4ShTRpXQJX!}sLyK2XH)%bF8KO-hN`M9R_oVr|@oT)~~&WsWJ)qfYql$St~-GKP$>&MWMF8o(t&BdfTkjA>!a}bwOSu6Jes+QCafa zDYg|92zA?A^cxRv1hD+$Z0D-nwg+gOX{_hOg_FojZEpx^g~Y}DV0-;sKowwHDgyIO zf-5Xa+xBvApqQ6I+ktK2-isw@?m?U3VjASR%R&!*g;o)JG~89c7GI}pmi+^FH`E@b zu2EkTe)zHQ+9WZ6mKXcDWi11O>kj|iAKtc4we0V=1>WipJchu(+!lDVKd^S6>iM4s z1iBCZ^jNq{$o5dpj?F4^rO}{tZKX0**5FY=%)K%Pm*0gS#@p$P+S>hw237mGbN#7g z^d(_&Q4(MDCt-0*?s2`l95MBll5$nu_BU%`KI^39%Prw_FNr>*q+~xg;+(4wp7F5Y z7r+MEmCiZ4K1Nykz`6A?_mm&;L-Cg4Dh=-R*Zn0} z3Uy)lW8ul;*FGuiAXvaw=7yimO;jdNq#|Zo;Kg#z%&ZT^)-hDE3BhM^)YIjr&`RpC z`lWcpYr@U+u7Rvg7kOxvxp{<9pb?;iL1-q6=EfQIa9S(HQ{0RS&8J_h$Iy3D1$|o* zuAadyAVOi12*XqsKrh3GUXMq-iry$Jo~1Wcg6C`G*B8PjxO>0Z{CG*>Nah-z1|}}^ z(a$pf5I0+La5RzDq|$VwC{#!xMU(0Y$-qxmGlzm7lMn)1s1C$wzAX-`$r^x^eqR8| zcN>yw^c@L5FfdM7AkO~V;;@>*1CZn__=Nv56-BF|N^33RJH?H+Ku^qwUP2+qW<*@d z_C|(fOQS6l@5^T|&9g`>*P4Usq|j`($r($eT`G+`6t?UJ6N1ZsN-;bXp`(MXOKO?> zl9_z2Bwvj*3!?I*l9`1v7#s$}LI{wh^9}i|&2h%MM49`nfHEIY%VI)S3D>Gn7;&vJSVbdokhb zg)@{dj{dqo(RDe)XQwQZvJ+-yWv9%Z2J1Z7DdK=Ki&MC>=E2q^1r5Dfga9?qj$4#bx0R&C#<+j9Mpza{C~q{%o>CnR4JP@T_)f@}dv$$dGG( zeMUV({iHB-1XtbUe>i$lkU<@FE-wp@h@(MN|Ict5WU^5ILAVnHcQ?pk;y!Ax>*Y4O>+&LIa*|Jymlc6Q(#QusE5y#AXx zWY_82=a2_`dAxq$9CGs1?Q;k*a^n0yaPHIn99)DZ%)JZG@0&yR-H{S8Ws8PVDjATH z2u&zDO>>CjbYKo4sEHnZa|kYn;@%n%@2+#mYp4IaIpmeocbh|0;vAy7a}FUEqNhji z9AY_r+Z>{;fZ@ZyIpmM0|ED>Gr6gIJFU6T7@ZEgMprw8s&&I{iyp|NQGu9jz`x-VY z<1&YSmici0Y&wu&Vl_)pfZnNrE^ZW|!DsuS2bBS!bO4YXQv|>n15!8`6aWfyWABUu zj$ce8NKa*p*Y`7dk{SOOd>*m>9UG56{r{DXO9v)bY!081mGyo<4_{P4Q$p?xH3~HQ z7u*}xQv;Z{S0lqA30vwZfqj#y<-cR!^>=6A9j6A=?9QURe_K%kO#9@iJD4`6$-DDx zb)DL~KPMLk?n9`2ck*npw5a3&o~Rm10E|d;A~>R5-{L{ImA*sPf}e({wo9jg)fqk^X&VD+Vrts39iy1 zob3%(SJ6U%y>A4fD=gn=&$ ze7a_7=}V_d_9#mqKNIi+N77-1rL^W`z=xcrbnU5tn@Fsd5ST==AG}AFox+k5ujL`8 zyx{YpCW&!4r7xbR9gzO-h4e?>k-n=x{nz`_7ZZ*>^Y0VJcoSw=$15*RI=hx?#4tR1{B|gyu(^KKX z6FaJ4om-`FoH#5q&|Cx*%ImZcl2;!GM_3;ch1>$bt=Ef~PK#cgpG&_vNeP{?y?F|E zo#M6Vj3CRPL*nEAme;bbk`F0~0eR79e_oA&yyg$gi~8cfU3Z@zxb8k3Ue&jHJx%NG z&D^r){`Ko=U?f}{gl%8g*j!p2s?c8wlL-j?SWO)lxBfDP{%2+p>Kbrw?kL99RUkr?u_*huU1R=?q+M`Otqg1-2n((BD z!wf=Bhia(#jpD%R4K~(8>)#uW4V7h|V^sAA-s%rLhQK#&3%uDMSZh>0e``RXv;HUe zvO@t(H9Iz_+}m5>ZL5#t)e!%g3o}g;uIxHR-u7>EE!64-2k@{26(96%JjHK3^~6+e z<0vlUM&Uwbq)j+||oppmmZ% zXU$jDebiIyQ07{QDh0Z{`kP0?a*t4JI&=%4SJiDB80xHta=PTX<(uS`ye_v~W&f}T zIhMR5w7Aa6vdCm$20M_!_SS!wfeRUI|6)Ms9T{L0+TqH*KLa}}$vgD(rm)<1m%l)| z+tWIAy{h!$iIP4=Bvt&7IXPeDyYD3Ep8LE|FLT}@v0g9GD)h8Y2^HXPPoa|p)R$r` z17baXYCtSFF@cf!nlQwv(BYU+bu$g!z}iP7u^P^b-%;Yc0dR`UmUYD^9WM=wLy`02 zLLE;PLeCc>EK4n8R^`*`4u^s%HM7CNR{x$-3&f7``j|3#v!x1ob^SW0Od_nY@Y{^ zD+gn}G+GL0WQIqCyD1iMxD4DvRV)~6`b0doxb`t&@O%naHMY~oL4B-dUyuSN>C;CQ zpUd2JdRzQ)WG?c89Bs|I=&!@=lBnpV;g0FAmmLkg>?k?3S@D3+b+UG!rXs@A#yX6M z?$$>t0y2-&5-9@U`cs%S0B~pla_S==4ePzifp7qDZ~qF^q4xk15-R;mfbaLERH{7D zmsFWin4R76gR9pa-vO8JZ_=dIJI4;Dtm6mUhMwqy)Vvja`Ce4v=IM$wY^^C6_41Te ztFbR%7s5Jm4|Ry7E(J!BQfG?9o+4GRNUEe6MMEa|cR*r3&~!~a_wBY_(2U~& zHGFa3dW1q8Cn{973p--YACL8zn z=BKCp^mXbHwDSl#%*LE7l9Vze(Bkr)caQh&f6ZwUK@S2%^uyUSstk%cv1ve2g*6xj zlwC@AtjUl>G)cN796czz)cNYa@Tt|4lJ8++{if@9IQ7c)&Tpwe$lvQCsnYXPPoB$_ zyTGc5*@COxLY@xJnq^O3Z%k6tCTv$PT#+^J#9K4(oU_q}vaBn{mf9En#!sD=0Dvsr|c6caBmD*Ef z_S7JIs@$F$oF5rtTXdo*0xLn>aS&o#NbfAp!2TI8Ns^gOQKpEL7fv99)OinO(8#fm z&?V@O8%)%rjPrGUu=yu`3Cq0;?CHC}a_)c`N6;*vBu*N%dnDv0e47Y3AYbE0J$L3C z3D2)4qKR>Sq)!2Z&ohp;}g$47u9JI1n{9uDY~0eThm79bwm4gw=ETH=K=8TD~( zpx-WNU;^~B#N`OxK^KwmExLn1KXXJM>~cRWkN`~#Ni>7!uuPgN%kV>TqDUkGXrd6K zC9;oPWKAQkQ6)1oaBoLkWP0((al;Gp6D0?6b<9J82%**Mva&`b&tMY{5OdDTreb7U zbee|ac+pGL>cK;`j3hZ(U5an5CGKG6fOrcLuO<-h;UF<4o~f3R@?=p&5z}`8Z~NWw z^zugr;vECt2Y17ZhX)B=0;@+Q#ujgm8=e!F_y#PF#kg=Sl9d%@n8Di0kBi;&F`+DZ zMBdcAC@7nSGK+Xwz$>{M-e5VcH4#O`L)~mE?}isGr?qAuUJ3C2ayPvD`sv?rNFQN) zjwou+dkEHb__By!LL)r_y@}+?QlVdY<44)H`mCa&r(l#`=;Ky}>2pc5w0J%req7F?!TFHm9Y?Nnp~r2Z0Wp`-X^2daAJ^91 zl!q{@vukT*v{b4sT7ER7Sq5JW(4p!h;7vfQT&10(Ep{G?@NU1kW3O`N&|W(`e?8+$ ztF>>xX-!kYS9r7@UfN{bKbveSf219m=BEW&81y>UwkKm|&}2qXFQm3GM6>frYR>69ppRI#P0{nzqtKPx}O=O zx+<+V(ee>E9E%mpaqdv+u?BU0Ds7#<#Z9ehgWRIch(Jw>BJ@-Pt#XQXx3*lO7?Bx4 z-j6_YwKb~vI+3Zm$@YOr2DA_krhJ6O2f{rcGgaC^8>&;)2)If9m%NN?Uv1SV_`C$c z1bESj_XKV=wLR~HeS3N$qre@fFWFshj;o2b{xf-4CCNO2lkef>}?U5cF*l0pq*KO&<|7rP?{mjgo4S;b+x zxU$rdI`jwt!vcV4z1+GZ7mE@)3;7eSkC?hT5DU)DmNxNALCAyLq9#ulF3irQ#j0>- zvb=W3YS^LM4=F`#eR?pA4?YiF6Do%v4uy1+^mFMNsT}sag7*u%3r9Ue-Fg;nRHYoZ zdysEl6F;xX%YDK9zR7={sW2VV!*(yXiO)rTP29{TPX}{O;rl}q|1qhN&M4sE3BsmMhDLoJ$hb zmx4NSm!T{b`Zi~tELtBs6V}nGO?)cKb%Yz+WIoH3#RPW^@;%?`QzI)@GNjcMY?Al7hB+vGXHjPIWM zk52SWLlgfiK;yXko6N_Vn%EsXqHr5hWh_#yQY!XrnJ~zwZ{p*CqUWNTbd5|!jLQ{e z7_Ux>F%eRta(CkRN6V#((G`)kh!|;#RSbbebyBP;MhtwcNLmqFdstl`ixiEnH@74P zyWHqAIF49cW%x09bhu$p?Lmw|i2p|P9ci-5RcdspaYH1!&yEQySE3c3=>>V&G#qD4 zt~T=DHPRTo+Ni5%;CJ%dM*a!_SGg;VxCjfo4Kiba3R!gttrB_SzQ^iT5pP3Ru8dcL*l5!vdCp;u^uds1gs0Nq!@wsF@1jQ zqhB)O0uNj(5nM3H*WSptH+nb!g==Wk)iTQtBW4vwNl~ccp$gO*T<*F7 zy1Dk$77AqR8**-!+EqzpbLyq&!DoEi8~Gg|O%vy7G=Ii4obzpKRN^RH4)t*R<7k6B$|C|8b8s#;qVy1E8Y_6eW6 z6Vb?P8~L?}xPV*I=&xcbB7?i>c5&3`uZ{c~1pJA6vC&+~puwt?PIRiIFzQqaK_p>{ zHKbl1hf7ZnM{3VD@)m(U&or9fXBwJ)=0<*= zK%d!-=FLp`V~qup9UE~KsC58^)}{i~PqQ=eBRAbap{>FIKRx7_Fu;jI0)M+3g=RJK zvjhq~)@a_ym?F`85|vXE$AdgVH`?%SQD~rJqMqZDf(@u==)qv7c&+Q7$)iJYwhY`@ z5$didwrenyjt+BeHaP2V>_{3g0!+h+nP?k%Eh_gdr)~7SO&7ds8u6`<|tR0$LBZ8F&n+BoKrOQ(#A0p-U4THD8H-yYUSe!1zuk&WSKQGqD4sX0+sKk zj}O9_{E+MPp=zE&X62_NgP)VE{JRGB_YzMDGKk(VA#49UTuAWPSCBCTA8zmk(#0_F zX`4QknF*I+-}(6Oebfz4_{?@D__8GZF^0T0RAWLE1%zrQ@n4kA0y0!K?lLr zb|qtU24Qnm;2GHvEe?=qfhmzhi*iHufg7oD(uz396JPrHLp~axpZmDi?$PESv5om>V+_E1^(hO7I7KU;~tSe2K z8GDW-jKRswbOdHiH)Nz{0h*e+?|MemvIDc;8syvQ<9DJeQ@D*j-C`yu8d~WY%BeHv zq-F2Bo;f%-O}QX#%EWpcC%>LC7-8bKC2H9jD47eQ0J{iS=3u0l6P1-LD~5kiAG|5k zWh)M!?$0zt-&STHQqI~XDJ*5O58a&el(nEX@8HeVo;{zUlkHLN95TpP?Bk0OXE^t| z&;J~=pl0fxopAe7hVG!0AVF-fELhNR(znFN zFF`^JxH&%0Jf4%mNyS~1nYiQN~Ywc>Fn>w!a z(~~R->CsrStpfIvr)O*{l82st?i*$7NFv?;JBDTQ4*dHG3ZJ9R0y1*^%P(v>!Zua1A+~nI?xT0m80gM%tW_r?%kVdKJgs32JhQF!#7)T5 zD>39N+<4n__zob7{)9rCLn!@H{DV{ucuAz06n!t@JC^i8Q-8P#eQ+W8R)5wsCUOEZ z2!<6|@m8+ZW-*TCON51c z;UM$;k5-F6-f=#h{Dt9k*b4lzzFTI}C+ITqZ~M`(I6uQhfHfdVc z3UU4xOZ$OeSdT|nS(``1lKy?#-Jjjgo-}UbZSk4@kT$DhMCN;QQNM+a)FIt&?})je ze;=FmDbi)MN;>oOevFUj*oe>-Be#QmXtPA2LK1J%cK-n>A3IIGoE#f3K7n#Q&&A7) z4%~(p5YIKt4Hz%spEGR1^LAo!Vtl&+U`=&9maRIi38>PxWp`c*ds; zw~Nyg`#z2gCdUfSae`AtrF(~BAsDL&Tk{dpSiR}4x9smbYW=8sFsFg|PQx+`*| zXa32|?I#EfqLzfjq<)_}+2}TiN_-dvH+*Q7VlRoea9+{dYDp#q|AZ(Ga8rG_`17N| z=$TuD7AMQ8S1VQWFAudixclzX+W(B{6{mp_9wU+&BO zb6??8?kfmQD^l9vI6!*M=2YZ<*%$YSK?$}t^BG)gm?iWMl9CoEazE`WobDbZrBn>` zQZ1?GW1!CfdwA5S$4?wRJZ2o)`K&oY04kEc2IMb;*L z2zk`K?GhY&RuM(!W%&D^_+LRkD8Y) z6eLEN!=N*wowvZZgx!c-#g$6Ka9l3QprW4N4#Pvjz2Bq%JRIju$i!bI(qi(ynTUrR zK(|HT8Jmw|9gBR9yr!HK;5`i#A4rPUA*<;uTtSI@2+dSxv2+f3-n|Ve43sy|ofxfp z41IW|$p(zTx5hixz`{UfU~Rw`sFQgdD7AQ6>0@yl&HTDvS6mDRhH%F}UkP|i{9!NZ ziPMgRezsl-uZU$pvH)`ilt{5kPJ#M<#v+AA?j7I}@dttfCAqML4_9sh;TcNpJZoI6v$^74up!7WE2oYTwoX zH|a^0cj{)|yMFuJ%@{(pI6fv-T9~NK7^_rSHTi?6`z- zSe#B_Z+5FUe7m<*3I=cXk`2s}N0()j&OBV$43Sy(2HwVAh{$haLDm<&;V)3yfnc;Z z`kq`=oT!CPMaEATI+bOH0yCHSP+{QJ1MD%Se_g~cp6$iag#sNSp1~LXhhC&}oQ@S~ zs)05ZO%41A&KJm7aZBk?`6tkkiWI_OL&>Nw;s^54PVz|~`YLpO)<5BxA~DZMI-HEt z-ju7LNw=h^I2Yy!S50#0;yFfjI7^+TGSkYdYU?yBG2@ov6hoyG}~mQjmJOi3zXiZCTR`#Q+Z0 zu*yO~hD+4&OT8^Gk;vWkj4UJF3}H*pA|}%8SHRmjj8d?Fpq`e&3j#X3>sgVZAf^Tqd(s1 zsy6zv)2dBvrAV$t@=MKPcCX)s?^hOg5MPJhCBM93HunmGVvr?}GE8MqT}t|&e8l%7 z7mINrGw2=8wk*DG*PC3L>8=UB! zG&3Scqgc~#k~l4P4&<^<)X+EpmjJD-ReE1N0dXviQ<^eWoVKW&+J%aDdq{D>BLyIH zH!K6hn=~S6|2v#EV`eF=!IAyTQfNjf`tM57qX-`@RUuq-v<44awcJrKCH~Belw{Om zgwT_DalM(_z(dNn7*+5bk~eZaJsmYPlD{N}e@L zmDL%KUGJA4-yf*33tE(olCnqz} z7h^3S^jy|UXZi5I_dxgbl*dXZBjbFoRf^3EiofW>D23Q+UG?wIn@#74+XXWEor;bX z9hL61W$T+?UcRczQPbgbr@qGeHU749x_K=JG|06{cifyhZ->vEwzVCbfg_}(I&*4c z-tyVdbrz`cun@NhuheYDR4QM+8SC>Z{}$Oz2BF*LPO}?rnJG5639o|rF*Ln2O4A-n zQ~3uxC2Et-ukgOzv24W#zjtMvrXFppIn5VVHHXATlFe`Qx3v;4Mfip)Jx&7ngLi$t zHj~t4Jg4ad$h-D>6sK^SH5#dk&|03q8FDjU&DMNEcb({KH$#pB z*Y$+g^|S_G4zB9~^t|8bvrGgUembr3NnK=wv(zgv?}_cUw{de_iLCFq2{n3a+s;FW z*4qVp)&8CH!XX=|7?^4|XsRY7Zrw2jjZs6s2tiHGBsQxw#_2FO!W?tI*A z>aws{m$MwP@Za=cU9RX6&7|=HW)*p?r}b6fj>#KP6fN~RRX*wOea zq-Znf8+~w&4uc-8zPro6Qv1D)XMWw5ZN73!rf)_poK5JP+9TdNMFvMvZwM$GQV5+a zBW9D$#Xe=bjK@Iv85TcQx(zJnuMw~j)5@7I#beHJC@)wQyJ z^RdKEa)`%z&-H|GrMY>lmrjz7}r|a9Oq1ZuDiQ*;Cr`=mf9@ z)%lG&yV3p*oe|10egA){bLpe&%>1A0jCZ))BY&YkU34)0^1>#!FBQfyq1qa^!ggsrtZ8eTX?3q*e@6z;=nqkamgt0$ z4L${vKY^7s6Rh(zWin6Lq#BC zR6e_dX1<8;$fPB2-(6~4n3WJ#Nz9V1D#+9F^_q)D`&vy^?#7E=T-UArt|#ZDnqhYR zN5-7d@*tPsD_5yUiCDIqf@-YcOHxPRSBk7_O+ihZmj%eD<8ckgn9y+UpFdQ?b(A=Z ziHd8h;3$Q%itASdk`n70&S%8^g;<^yJ^+Hy8JuM%|!$+@PRd98Tuhj*^$ zOr|}m!YZ}otk$n6ujW?)nJEc!hf5=>AD6T#$uwQF)VTCLjzFT~ec>?z6%LWSNmtu7 zhJ~Rh*o_M*6OiZnN%;LCHXueDFKSfELEQ(b|c5P6b zSOX!ah_3r4(RD9$6J0lS>YH`l`QyX?+1)bG4N>=B@;Rt18+XH-F7!?B>(2Z2%~9Rh ziaL&O*{Qb_b6@4;aZn?vDor|Jto|#@x;RZegr-+d5t`Ip-%OKSM_fMM+ZBm{Q_r@k zrV;ahx^W{LB=L0;moWu#+>~N6fG(XY6$7XmBO`o*0uvUSQ-JxRtH&_|jR{qA_pMk2 zqcV0fCU_-Y|LA5>Tzx8kZ1n{O(L-YhkSpCnhFH6fY$7q(av_*!e?dMi>0;w2F}Gru zDC_wwhT8giV`qR0CPLU)DHt#FA-YN)qU#yNsBO}~NsNWOA$^E6qU(Pf+TgzRe%cxy zpiP#RaoXnk1PBYA|{wRU~UBGoJRZTb}N|tUsp<&+3LA z8U2^9@6#jieh3Ni|Ds1)I#JTM)+52M)@y?F=bW7oJ2j&WjXwLv{TA;6v^m2%tYu0o zzXNb~zxzs$Lh#6(w=M_ATfcI^s+Z??@MS4TD;$5^YU{ANQj&RwFH@SDG@A3;@1CES zbG}dwKLDqO%9PKitj_##9`AN0dmZW}pDh>5bzU=>83A4U#1lAVTAwRnp- zLH5^bX51@JmH$z>fLWe3>bQ2!<=A*tIrU)+&0&U_;Xi|}W@oLRe}!DHJpEY#swVNA z(t5#7`X`g;FA$ojofYC$4mtiQo`+LV`6n5z3#@kkzw+e!!(&6HUqvS6w*BO4#=8Xv d-$*%c8CjV1`Q_jblQyg?U-s(ne^C3@e*^z7H5LE> literal 0 HcmV?d00001 diff --git a/Firmware/Targets/ESP32SX/firmware/openffb-esp32s3-hw_v1.1.bin b/Firmware/Targets/ESP32SX/firmware/openffb-esp32s3-hw_v1.1.bin new file mode 100644 index 0000000000000000000000000000000000000000..2deb114c9a2a601b47d28a715f3bf03c747ef527 GIT binary patch literal 626176 zcmeFa4R}=5wLg63BOwU~OaLPY>Pc|I1c6Kvgb+b8`GP1PAqgLT{U?)|Gnvt3Cd^DA zSXy&}g`l*TfUV%Aw=>aHM7auDi&`o{MXW-_+SYodA{wh!D=H-9^Zb8npEJo!2yO3s z@AJIR^S%?i&pCVTwbx#I?X}lld+j|`mZN`B2}C6jl|WPiQ3*sP5S2hw0#OM>B@mTB zR02^6L?!V5bqRc~(`Hoco}c%3j^pBy)IUvJ&dG(9$s9-hHa zS|FGj45pk0U{rZLes1y|rkk=eYl~2=KAk8CH6E*7sIz+gF2Bp;7W~$#8c`6vUXM2g z$h8Svt;grG)QE0j{sJL8i_0h}EY7fbYU@01(e3wT5Lu!jdM&>Cx;k)?;q%(U!HycM z&pFG+)w+D1FhVR_wZSi1-1W6Ye73?}R&B`;cWahr-MC_LLqmb!^Ml9wI>AxnS+6j5 zXjeLLmrcA}qr;KK2Ak;f2|m}o zqF`vaTrHc`<`gZA6H9Gvy~QVnhBsF**b^>SEvzx6u2;nHLEsjv-R`BqZV=oazc70y zNmj7dxT@Wv{l7_#IR9UvWEXw@|1qWYUKe^oq=xm;VE=#CNrfOApTS=K4{3RAVciri z75#|AX7M@fb!=Ly^G6v`u|c%eV^&~})mq(l_V&0(m>Y#IA115IT`iO?TO^29Z_Ne) zQ_}0H+aUNn^)N2reH}Kk6?Tk`dZ0w%Yt2?*;*N zhs=(_cR3Xeq5Dad{>3`WE6kCog0BX1!eGCAGeuvC0>kO76ohD;n@PwkLk$cTF! z4zdoUt4jTP_dRaUdN&u63Aa)KPYH5vIpv7>&f!48~cyx;TaPFk+c0R+cUp*L9-T<=7C`MW!iX^&*5? zI=C`bkV55N4sYsIVTrX$tP#TgR{BW22mdNI)QLiQg|J+arywj5-PL~QY07F449X*izH-8idj!)(ZaYTpA9OK7G^8vpx5E>LuPh!=9v_w?kKn7tiY^#y zU2az`)=FpsT*)Y5rg5SG=2#|`H!fFj?ovBI~ z>n`^?tUC4?XtstKrz)%Mo-j84`=kU|L0FYgKV~TQ2kc>as~KwfvL*yw3)v%qO_?EY z2a}U5+Yn&5E@s!R9&$#ChxI5l{{;A49~TYZhy55vONIp!)1SrM={Y<4rsWN{d}6TMOg6PCW(WmDEQ zBisuoDF$1uK-(#L#f9Oj7@;ANG}ToFOHRF;46;DZl4!@$W5tY8{S?t{&%Bwrdzt@= zx9M`*YU=G`xW`%Vch$^tav+qY5X#~_FgrEYI$xM?M^2*co}m(D6&6a$%uBf{cvX}a z$gLuG&TDm7i$l0WhEOq;glj>2!p*E2^Hly7=E+qa98`w?!UW8*({?G0SY(Hogw=(bCeC^Xo(R84>|Q&6TKmJyrB>#eWzE3}Fl>O>pa1x6`s zENS9cyka#x9-l>>GMuttTbPQg(?RrcinB@`@r}UCPzRxJAuBR7+?7!_NHpYwMu3qN zY))6*P~qhy&b(4`Mwb@eh$^b-Lq}nwJ?k$o`4Trt0=&7`22Yd@?RhTZQ1F@7k*9vnQZYwRU)7MUcOvZJt#`X=s3^F=NU1@H z8G=_NGHRFvBX?7FgoVjRp-^60B9zx*jT44h=m=E8TI#WfoYjAZTFhb`<+M*9h8p3o zl$I?Q3QAWpy{i(bD&Wy&9K|ZJHRE z?O#U_F~UEJt!DGRq;LkzfNGTtIQQZRyKRpo`}&sNHzdg^N6M94@CMfWaM+skz1Qp6(n zzZ!m6H+$CXSy?l)ax!ODXWeLbF=3u$X>tAVyn4l1TlqHC5KmBTQ9n88^WkZOo$L-Sw(?m zO@(=hrMRqUg{7=$5uAmZdNEgZ$ik{e_y+ZEH7-i37iw!zAK65SU=@2o%qW0{d*`at zL^FDb4r4*7k2o>KlkpmSa}UqYJ2aZ(=12rp{``nNZ$SDiiOYLChRfTC^vvRu3!g{& zHPTU}-y;1E>CZ@~krEo@t>HeAPcEzg z>;a_GGkj$Q(i)^Ykt&hwNKT|PNS`2mhV&)UIi&MQ7m?yPzLG~;jPE5#&rRg=&Lner zyC!jYr3qZ#5#;4(U)2+TGS??_r6kM0LaM!T9{RKdX-_PdH=$BniQaL3gi>=Cmq+7y>k`NX2|QIAr*hn9K%u`0C@VB@ zdC#KkYoyqvCl@Y2dJcWyj^f1O_A2cZ;GBmp+>g5Q!Xs!}Gf;SjtK?)IC%@b;=jC6) zUAgS!!c-)Vn+U4Z_&Irwq)E9)PA=mlmwYCU>xku&r*j=?`jl!06bX1V5I7j%5`%(d zrz}1UJ-QEa4fp8|C$#?`Irx8M$wCFi-^f(hafQC=>7NCr3tO)KWov+ISQa_=?zW zSzN#0>~-Ow|oOwNCL00eWidD)40&m-zN+KumqvZ9e|!~Rp`4CS0*scvS%p4<)( z^|?q67Rgd7(Yodw$90r*=9S&VE2hXM(`-|o zKW)NzE_>>VE9K-zIsUs96XdxQw0Mel>&zMQ;e=qOd^+ZYUK&lLGc!mEYjl~3@|Fu% zXI>#Mx-bFH^%urw#>;>0h{N+w9oo#%vgiO)!Ruwy?}fZq1*vZUm8sDL*))50TEVQ& z@<}UV&6@ zDONW{*LeTzw8d9Fzec-xImZ{I8F_wFXU-^o{K0^>@ur>9b;PnYPX0zOJ7Z7j<#xS% zSHj-dBhPBi207_!D%~F=O`><(&vbl(^n0RwR|0>vbc(g{+J%jO4CD&caihv!>#o&y z-k8*}h8Q_N0D-(tD136+E8lHAwB?7|%{OvcuXJ~2Ym2kF0#_1&SqKT+QwtM9Yw`y=&zMtPe*99R-V%AzmLCe56uH-9it_Vmjt zh$)la{N4aRUMc(&Rhy?b%;h?6<~DofB|3*wyZN9zL)VG-Gjh}M?yNxLv8P}i+DfR; zhp5jFP@jvB%iJ6*7m`=U-7(S|1CQt2)$+zbbNsGJyOUa8AK2cU$ezMhjUg>25SZ}x z1Dcjs2li^V?@MglCp@9)m=37^44^LqdMIfSzL1lCJ;29QyqfGJN7v^+(a&07At z?ad^ntu(=!wJnbi@XLtjUr;dv8snwM1{zZnrAP4;@I*m6v1N=4sEtr2KKQc%HBW_m z<5QpJgnfy8mavcLKS97F31{(vr3A#J3We3sQ8-Ji>;MC56XZ9r?_%}QU zYaEITZW}oA5{=pmad;d`c>5(Pd?A6=s`=iLeN@J%?~QBUOceMBRRUCDZ`^?&44fVd zFrmdA`2Ilfd(@&rAWp3wCv6xAZeVb#)SPf2eSqKij3SwYpnu?ip9-53o@pjJTN9+U z0|%e}5TASFLZn&dKn-z#&nM!7cMlx6d$864*FcCIO3{D=`NRWa1T;QaHE{TffYK}p ztT{et8Bp>~F0M7;lI~=a?K*ze>9L2eI2ss5u+;?nN_$$%U%_uY7|;-`jNx6uU-K1U zhsO|TDS^l)LRV5^22xYGj!tcB6}O#C4Pe2q`rE(i=SK(LIwGCwAHqHGZT~v~&C$R_ zK0(aW?YUZ?SsL2ndvFwul0xVDIV41aZG<&dqPY3wkEy!oqj<=YkQpG zMSU=3pgmUO+91di&ZNdx;eyW(;t?$*ppYI|P4 zUvpU7_G`S_j_BEZ>ENj!@dLr60qLj2W80fDb%SJK7_Cdu4g_8lPK_Q2jvf$>3et}l zN<5i>I0o2C0JYx-^aEHa4YC%q^KF86q{p;#0|&SPKEc50(4NE{80ep}WWR>jO8tbf zK(kcaI+e?NPMDn9F^;=6+fnv4KY9OX!#E;3C&;y&@1NJglpsrUeem0UAv-D1oR}^( zDt0Zt<%Rw%jX}|pzxAKX)}VCnU}>POUQCVID+$Jj0{acHJ((Z(w|v}B=&p(A0aDcRGX~-V0ybSZ zNgJQ=R6*L*q_pg-0pxY2v!y11D~3zSXPc@@Ied5Hdn$Yk{yP=W2`fJA@Adh4{tS+L z3O?HC`S8Q>zJP@4l^h=UgLetflH=+WuU~!{c^&!6cz=l0h4eMuD5;F+^7QlI!{0=H z9Gb%ae8^vgRD?u(0bDrA^Iym1J%%)KGMASKUwk4G`Q%tdxj3}T3yItZz&($&7x;Ns z!+)NFy#)O7|3dmJ(lX9Ji2r5pQLu!p%R2?~ot<4E%8N z!D){`KKZoVlMCySUPeN_`2u|OKJ+6VeYhRz7f3b8PXPQ2v^5((u7I?$R98uUHv0Z^ z@~QE@kNM!TJV5@sp!)3OKXavgCHc_VkT1A=6}Vg-zU!6zZ;;pgp1$)S#XP?9G4S;s z@}DD3TyS#X5~SI6T;+Xuzk)>jssX%jzWLj;=5=4uU zYi3i@7 z7?;FrxV-DIN862b7m^!kHeii-=i$8_V`xJEKRZ1?uMF?S5?A>gXmOmDljVN-Gknix z`9$QumbKs`C;#Nae5Cu4-b1>k;N-#|6=Q#fw*!f5oSVmSH|KGU$05_-E%XAm4Jn9p z2(mn&c%lFakClAsaFUxW)+CKiWuXBDLhdIG< zEf~?`FLT1VpUc;s`#F%F!8koFfZm-*w5MHxzWf5;Pa-{y^c9j&baLTZB$BfpZTtq` zsH^!q-r3-%0O>v?ejJy#uax6n!T0Zw{`NOnHZ1~gNIyk-7wJyO)`{dtQtoX*(;=r| z?t7)+<=>FLk>!O0vi!QtvHcoua-Ctbls1&z=)eHSO~JThpM`xM&D_txlOHth#vaX! zln6boM3TRgSv|jT)^1h!gV8aFvyq2ei*S0^EN`yYC}`ugXdwH2_A;fzkWGb0gc^ z)}bHhp%eGj8tKkjCGtO~He}#%jWvMLz7uQcVeJg<4vne$;Mh|q2iku(aIkez@Z>

eX`y?ErrnVSakSW{U}jsd29*Ofyu?KTn;bv~UqOf@E_y)kCvcif~r zZf9`&PSf_(L|V&Gl;bqlf(R|Z44-vT%{Lr_YGaJw32>#Q{xU0P`VQ*kKQZR;7$M*{ zoRQVNy7|t5tfy^z^^%1gBBP$4xJJusODpSh=a`I}1e5=*M^m4^d3u_VvuUR8sqsex zpcr@L*8+dLp+^n`&ZjfWX%jUSO!11}s+G7T;G{>Lzd+p|_#K4sD z)!Eu(uC_~@%&~mbQ5z##}LQ--QssL zKjuU%oHc|MzLn=*d0p%EoRmc<=%IpR_y#!qw!^ylSBvLlDxa>FzkNagky{L3%e?lQ zbLazg;79!J9bd^jKNEZ$=I7sT_)2cvmxN6R^{SPlUS0jWDY3lnF5Tw;lDWA**>>z} zOa;gkPTlk6V^57AXrC|;)MAqCX#0|TJ3Hyy0B17!=V{1I${DVq7{s<)h-;4H zCr?Q*T!hg5j^BRSCUEAiez=rf{S+SPB3Fq#U*RCyctJcPD{gRWU_;x7gWqz;U*y)u z-={o}c0q#l0lkx-jS1xWP3tO5feKSig{f?%DQD$jt@KAK1WR>~3)1iD`>?k17lPvG z?vFXFogX8eAn@E9M+3|Bs35&T*+ntx@L6(SsV+@P>?WUK?raz) z`p91`ej|7E4f^&ed;5i|JV6NglKXXOHp`19DN<6rTU`1iNGDEv%&ES6hx_2awan~r}D z*dwt$@|_8f?@nks{x^P7r|~b_1;5%4rWQo+3!U^sqW;VSaP*IyX-?YL{B|?+>nIRW zjtRG<^=Vo9KH;eI9{#wHFSH@zs-5=8*c|_Uz1S}Y9zDX34@}*p(r?m^$=C9Y%}II9 z0{>G!rg~zkp^|GgBOzkG^*uX8qmKU!z zbjsKYoz;$QxAq6C`cLbnrG&Nd!ps55|9rZJk84zh zie$iF&BqB3Y=X3Zrp?Rq_ZdwOZ2I!OR2_Fvr+KSv`h%(cr*%>+>vNYZ zO{I^(y@HfNA42K*k0$q*W;dSsxUv1KzRXGeM~)`;>(89Iuw=|(9p=d-!a^kTw}uai z+&6u}q<-w5zwHBf|0ro36>t7X=Eos(1!Yo;xm?4s)2fc9aQ#hMZqIQkhRP&8dyb+f zAJ>%6rRe%MUffD12xC#J6@&>e=zFTf_o%Hu_67Ufrkb7=0=EnMW2H9;Ff##x&Li{%jic3b^Q+7b?bk|22@1)WNi;M09f(@d2U zl7Fh{XlE+)y}sa+eUO~Ak!o+I#i(PGyy}5oh1vbuZEez{RAl(0y!kU(3etBhwzZ^> zSS7IvzI|BWZ)y7hSv0`r({SNUTFkdU(s%apjMMzwU2rc>?d)rRs4w_X-@!m?#}By1 z?GuuX+^Ve_*vh^9xi2V&e`w4z`%KS#p*#1|kzf7h?{Du;df*@zpBLEPvg1~6a`Wb| zNZLeu+3B$qUE(KGB?CJUT?9nMDNq6aM^(C6`>lf%-wK0!BrY)Yv8ByRiLc*>4c4?$(eRXHfKTz_;C+6;6{))U;67znQ$ma98rv=GC^>!sm*Y=eH zX&!&Y7a!;SGBNMi5JoX!WES*o>;TEdy8T*sm-s57fmy8E_=iwd8c&EFR&H0ynRssR zSZP+@fsDQf_C0>nv^|f0p4n|S?15sQx}mQ;2QZ89Ww`7{hCT>`7>QkQ(ajMKgNoF>0pm0YKi< z1i$SCB%m@m${CZFZGw;2D4AowV0~*IcUbq9kyc__pFZJmVAQc$ekT=fQ~Rmj;3vKO zIB7doN64M{wtm^0`DJhA^W^Nb{Y=I(x#IQS4<2dTL=6137uG>{B1XEOYA9YGceFhb zq1lcPxxn4Ro?8qOA^xSe^ff`cm#{WpfS}s$Bj}$Qw4R`t;{g1T0qO{_K;K|qpLX?r zeMdm<*hqz^SfPuxG;Z@JvR9`~eqHCdO1n9gTl=eC@m{%Usun}CHM4nq@YUX}DNQ=v zn;&mYYSL*>$8Od1j{={!Q|-x)tXG81e+3)Bc(CgwK*L~_A0I!CAD@-aZPs#J&f5K&4vYNw&JTF)=HoJG&*sz0%u6fJ%B~1g-aLMV>ooH8-W0TdjFd9YXa} z8u3ke&m(F2!V!zLbb~@&#-RmQD1yX9$omU{>A*%w5(S&i>6LRy9h0} zy^cAY+1Zo4Pj6UG9ZRE*Jxh&3XRwTekRgVe%x6s|zuU{&G`uQr>-p)<^X75A`Fbu{ zfBvwp_^iBN*YTXZ`BPcEg-A^xQUM}$GzdQ$!~Qj+`zHCN4#+&!m^Dw6o)oL&Hh#8c z=a`%_WB$CPbd6DylY|qui*s|XXi81n@@V`G+1McFrp96)t4-G&`FMEbfIHDk8FtQaSa(9d>K*P>S5JFa&y**FT|EW_1mB*p?PsSh^t6B1Gvy_0GlF0B zNZ%<{XuU;uSDocfebLkYbx-hbJqH6R9a=8R@4$I#d#r z4}RS9RSfKq_OM>E5}ZaHaOp2SXP!xF{PC4Sv*wwD=Ej$V;QKuX-|Uyx5Rz2c)9B0_ z<@3XX6)_)O~)i?sE}!4~Es1xTfppZjARHPu#vfk)PDK{>XNRsqbLl zPe-@ZZEZBYSARB+Gc{glJZ&=Je>rB;0$L5`UaRR#+tzeq)M2(s`9w~=el8z(uh(mz zi{p3%B5(>8*xN|u9k)fM(K7RCmowSq+_iNKf@z+d2#6v8`6zF>-IVNt$sLA}qyArmq z*;%^Xzvs3*w&mIOUfZALC-ljW#4_afy7?sN_^dB4tZo!pn(G%99_Zp*XRuIlV-n+Vjiqhi{{o?vxPzYh3+V)*d- zrfkZpp1b*Pa%xb6sXH^_*k_FYxv67L-PO~6SI?9;gLn0Soz>SToVv58{kEPd|8=ZK z<|od*9ve1((&|(VAHUt0$adLSFVoF4V;Akz##VBRrj9*z8&wG2)^jj;m-q+Sx~8XP zP0!u7>KC+|ujAIP>bcjy{srx^0&c(NKm|H+Lf86!BO)d(i+Walk$9lAC%B?VaWFbM zbV~4{GOH>J(c#x>R)MN4cviwJ;8ab z74d6%Rl?|1@AR&{w&%dL41~~#;qPcW`GFp3dQYA2h;>rW`W(G=7CM$Wt0#B%+?>P% zH-L%iCs}JJVzF;KkQ8b=w)lOtZRlD1mu?7R?gZ=9p1bY*xazrDtc@+Bdy45ki1oUj zwSQ#gUs|u}!CtH-rU$#wX+P0!eoNkAjcMy0*pU(gf5+T@t~+>T&u&e>{wtkS#l{r- zoR*8-#V}msV%ynRHfbj(AL3T&b@{1t^IsM6tLCQS1N5GqWB#zaFQ%gtN4V{O=??09 zzS3d0Q%ZD->Bs;T_G%9X&fKz(zh(yi;=zuq<)sP9clXX@A(fCV>LUD>=I#T{-H+^O z3TPgFN!t|QTJGy^Zv=p!{P3?e{Ae7CA24?Flcv2y3ezlLXaD6y$L-g*ob3+&JJ23} zF|p;N?nhF01T@ofwT-)k?Tvn#b$s0BeyAHA_i=MR;=Zq)*OH?J|F^B3J~jP7YGBXw zX}m5ynELvZ^cDP-Gj)}QO(h?}el0%WlIEGyzeybxlVhGXb!zG~ZrW_^=KHv^l=xLK zeA{2C&%y5CJKfR*Cbt_n?qEs`rar$7!4)2AvnSWkL0AuW2ak4R&Lj}tHRqaAMkVhe zTp-4^1>{{T_=KEE{A6tYDxcgLsPwP;W6TP3_uLy-uZfrPyB{oU$?q<)KJY_+!j>1X zVYbfeZprPwTjVEJzd$jMLILrOj?dc ze;@c+!Uy}>-X%G#><-@F4c|UiI!@H#-~1Ycn%5qZp4lr%_jUK(dpQ1tzO+r)VI3#U zB4O=F8Mi;aUr6lc*OTJ}b@5JPuei)Ud ziw+$>E06qj>!2G`ipcx0dHiLravK9$=Tc<6&K z8ecuq`WP2@%eHr|Li-(SBDwV`i3<5!n~T4Yk557Lf=it?*Lk2a^~f#x`PFlIZSjNX zg7c$o4(u&{PY$0@sc&wnvhrW<5?>pz=6AnUWzFl}HoTa%rkEd zrmrZz9c{wL6`F+9z}KnP_;1^<$Mk+aN7JuOm%Kr3vOamm%x>fOX}_G4HgR0XuOWQ+ zy%#P9)4L@Xwbz;wmwY>SFu-+uPlls|kG#hO={Yv$B_w73%I9;mWdZJxj@R;&YPHX} zb|T7ADJC9m7b=f#a+e31#_WKFVAMN_);;B`??M^*D za3cQsRLzNmcT(cceJvc0CCrZ3Wx`qY+b#t5J9D&cPYeXU5DvBMl}^W=&_m?kPgs@E zd1yRxplmqA@)vU2lc&b!NXfsCY5T>%s(WK{B<$u!!8vOtj`!;hYebLS{_Vws0RfJ8 zR^W`mCFjm&J3gIsj&Qz@V43(7DdBS$gP&iNrV+6Hvx_sZxQY&W?f#3rZk|M9d4#0F z>%_a|dD5!YUv;Klx#GQx#+YsI7_Z#6BkR>EhexIAW2W7pn>p&5b33NO%N(=)31Rm) z;h>d#@y~ z8|oACy$@r<%TI4R%T}8?R?UiUyWnZ8iK*2#T*=4Wrav7c{pRA1WUjIC{4=j5ZP{Jk zoV?{%qrQALHAtp(N1AqfbK{n|e=mDY(-f$zIwoxS$;srYqYXc!E+4oUeEwqbSnRL& z40x@yqxm*X`&n}PlNW&4(>7p4Cq zlw!JZij7O+Q7f)@kpz#X2y~w&Q3_D*V0&U~yHolP`kulcYxFo*~!vybn6p@E&N=+Bj#>Yw0#k8qfJhzUUr)?hB($ST2-^J2W{i^Ti zq%GAsgPz7T^Y<>|RE6v*2vlky@VX5Yo(5YHu$n&FWAr#TQ*&Pr8^Y9{|b=^|*V zQ*AJk_IUpOJa$D6I>H!A+~@~JX&D8o%AAZZ01E;1co6RFNQ-}jZ-#0 zYucAs%Fc*eol{r?*C{s+Q=N3gv%@&Wv^%l%Hl_0l!nn+dAft3O#KDAN5E5Ow1`ne~ z5Y8)tQS;P@b3D(k9y=!$zry^Q=5a>ts@C|@((BX4XC!ap(x%>$mVLKqqJhdXrlow- zg)?3ubbegsOv^KyQIzkTrpSwMKJTiF#9;_VM1NEwXS)I;Af#{pI}}+(dB5xJ5m2Td z>yXpNFMn!N+ElkAdmFUp|?o-SS|JwehzqTZtz1 zvS*8>tLZ7yoSgXm+D7FvfN}#g^R_Nib7IzGTnIIDRhP7?YpV_q5LEk8(IsrJI8feo zZa2*o+)CY@cwj{rZd-)*<_vmZX_xTR>*)^0?xe_q#a+T~DyWPo*cB(t(6{cFv8As| zO$uEf1}S4)%X}CU8mM%3SO$5*yMF4dl+zW==>m9HTuM$?sEwA{1R5F#A#lF!JUft; z(%>>QGW-xS%LJ#RG8xF-aX39>@eOHQ7n%TQZyeUpmej6ft^VNCFN0~oo}hDMT$jDJ z3lWOeU|#5~c>Z*0D#=~aDNH@}UDnh0!3;6Vklm8eVV8wbe=Mut#)ay`H^lXC({4eD z^+?}z21hBBa8Y)5qR^^|V7v7{NmlT^Mz;pu+<8PjmPTW^`rOlr^F@>fJ39sV)@oU6 zaE8=-fej>NnDXaNARD#9tSedUDCG~7Z`P))=wwQIMambQ+jgaV-butJowyZA*ztkX#NeMVKoE_}2*vZc(%GRlj;L+W=`Gqczt_3(5b8PgWx+Fz4ib$^ zk4J-^-(0z#hIoVC{Cwx?@!Df^NkiA@`6WkI3aLlWAak3ZpNqRUlLh~#vOr8z;A-L4 zP45KmX$?4pJ2wf=O?lS~QacU6kv}8}rxA0-z7ZFc9n!A}csiD!ujUU?zF&_yG?Twm zg?&Rj1GbE>^SpXp#S*&%vyp?$@oQ<_S?W!jJsO4&C>HV(qUpWbJWPsQa=tim%25p_@XRb!#c!fGGu{6AN`ldF_+)k1T$QK4;&h$jZcCRPMiArvA@cEGe3TR z+_AsQOLb(Rvx~o!!QHAy`-B~n^LLmtm`>b?t~8x9ZKDs)%J<( z7(XWET58oe?y9rewmr%v5ywPh+W0Bv`!6)@ds(TYSmZ1_Cw?X$8rSxgyf@46AM&y9 zWX@rHlYc<^j(RuczVH=*b+~Oet-3}%YHGzh587DiPZx&1-@RZA*Tb zYd7D;wf=U19~a2ql=3K1!r9##bK3RV&1<+(qBeFl4Tn?P6yzEmBm~EX6V#v`T*l7f-rl4Eo%V0f1MJdTe|AASMxVym z;Eyh-qZunTC?NbK0mc@$<=+WVzfv8Xw2O2?y8k?$!H>>MKfds@SgK9dIXN{U<=dE88!J4v4Qg zmzJg7+$Qt-;>R#IuRqo*r%lktJ}Fy^E;O|g9b+um!#h0|XpRYqO5Xr*Y1>y_fOB|` zbyfkaxNr=vG*!!q8}+ev^C`LbkIFo6JA_CdE~{t_AIX6qncv>08U2Xq)h5%st^D}M z=V^Fd;$h8^pgHiQFuHl)6Ax_SC%>I(emECKpUZ82;3cV?WWw$)Nwn$Yl3%#xWYK+j zr=E_z-~9Z5uY5_%8H2mD(Z8q!q7sNoAS!{V1fmj%N+2qM zs05-Ch)N(Tfv5zc5{OD5DuJj3q7sNoAS!{V1fmj%N+2qMs05-Ch)N(Tf&U*!U|*?5 z%l-w!N{(}{^RYwa@CV8@9PUWrp9$vboEwyX`Z)|?UnylYD7=bO|8IQ4N;!`HU8?Xu z{nXq3t;6_(>ZB@mTBR02^6L?sZFKvV)z z2}C6jl|WPiQ3*sP5S2hw0#OM>B@mTBR02^6L?sZFKvV)z2}C6jl|WPiQ3*sP5S2hw z0#OM>B@mTBR02^6L?sZFKvV)z2}C6jl|WPiQ3*sP5S2hw0#OM>B@mTBR02^6L?sZF zKvV)z2}C6jl|WPiQ3*sP5S2hw0#OM>B@mTBR02^6L?sZFKvV)z2}C6jl|WPiQ3*sP z5S2hw0#OM>B@mTBR02^6L?sZFKvV)z2}C6jl|WPiQ3*sP5S2hw0#OM>B@mTBR02^6 zL?sZFKvV)z2}C6jl|WPiQ3*sP5S2hw0#OM>B@mTBR02^6L?sZFKvV+%Z%N?uSZ&7f zin)1z=Qu7NN&V9ZTFUZuYF%v$AGpbag)y_&Uv&WX;$8z|N8eE@AFK2WY%}}mixBe|C0|o(yCqKvO{$Lx88Mr zr&qMvxyg5!ZpzNAEfS1|`gFnI%N2^Pt{TxU_&tKPrp9Bl`bEKS_1z;_z16-Hpw}i4 z5C2jM z!B0e61x}F-x79BcEMHb!vdB_gR#a40vAo=}a#=~i^0Fc#F1oE%q_!@%P%FCYZ63G7 zRejk$mXn021H>8xG6VJf-VH*X#|8F5&QPb;wbk6C$T7Qi^^hF1vxIvV-z%)CFfS3@ z9>3u5)Vu971RpwwZi6mn4=9OVx3xydywSw4JwBVOrpAh)RcU+uHVf!m8vGb?hgV!{ zsTTdlbYZ~)p{%07Le#0RD=f23OEH|6=#J>Cv-mdn{9Obaz zsU0+nrW%)F7uR7vvU(ZFuSfbv?Gayss_a6o$6j9}Vm4yV)>wT`i&yjyaXNxthjf;) zAk_Q7x6NZ0FFXD;2T0(#g2ApJ;dxW`@VQdsg86en$z5)T$6L$3b1#Pv9_|)l_1)H5 zF;}2JgYR;lrJ`C-8cvI4Zc~k zBIZ{_KQa6>VLVkn!uhs)EHp(2b!L7Rmw#sU+`N_Ud)%J&Zf=wYe;6Z5+&N99mfoDU zvQVt5uNK4xn^?!B2ea;@j#6nkGZ*E4u}c!ZR(ktep-m8p0^w#5@ffYv-*r#B%+cB?vg4Jz@{kvBrivfy8r1UWfA)0kq zTRc|#|ClUS_`k@bO6z}(x7A)3mTX#lRM~iJwt6oO+j=J{pGM~=;I zNDSk%bfs#89abz3BNMaQY!ExMuNaHaD6p{=n_L$0DOr~^pW5QOEkhvjDvdb8Ey5O(N(`0P4)Z6u)1@?zUPL0=Y)OF3;WJg8YX_snM%XT zx6-iktu(BBD-A2(O2hPBZZ2a)>APr6L4kQ?d6D|P%3N4jM&C>Fixv?Q`z~5mIQU(# zeC4tV1%7`*?ELLWdyw`bJ%@Az=`E!9kUl{=ht!7@H#&Cy1SA1z22u`EAyO&Qok&h3 zKT;#oHl$rhMS~WI2D;wu#C(O1FpGFnZ5Y&oY!l_xQo$Z+y?|TjsfTYdWYCoQA^dx= z%8NDOr3IckuZJ|%;|&LMFh#H*^>uK9?0#m?J@AgOycCqKG=bKZ?HYz1GHDa~3ri6@Sy^YK<#&3gGsuyic{6=lu=qM$4VOYJHKLn&|1bu__p_*-fyA6^$j9x&MnYtiRbRVa+4E&k zUGlwsB9=0{)r$>ixGzqkqQA;wE}!1-s&D$9f$yh~9twjI{9pMyuhyBkh|AAG3a{fw zeizbnNbe3SJ2xy(_0|C9v)KF%v+6|g9*fOsW&4z6%a;{hPA-;uUzG)JzY9C5yZzQ` zCZ}OA+C^X(89l}xf2+dhF7-`7=VC5@epyUDe@kQ^ScQM`u$L%xj3drN z#rr4ZxC|Km41|vycH!m)CfcCkYrc?8^)90y7E%N1Jd@uY4*Oluxl4qI`i!O!NtEhZ zn1jY~?0cBNR2ZU0YPqe(;}g|y2ligD>kP+XbNg#T-{e{o$C$sjEAp}5HYD3uS9AGq zt%1%VDOmsd^Ln+;M9_1BPI%3{3NHV6DfABMPNeX%<4Ys+R5k;!Gv+rI>hs@NXhd?+ ze$ymu+$i`Elc`3C1>1x>j4olOkeM!I2w8W{suta%x1MT5s1$r4sOh>1q z9J_vnzMVByL(47kM0<8>n*pHRv~ed`o!^T+nFRq#YpunOhtass1J8GQx}9x+$?+ZD zrc<=mWzYjH)hTA=pV1QC_H@pukQCB{?2u^!3`JoOA{z>Du`mL*)*|8<2(qASN>Bxx zDr}PA@A%*=2&{&ce6LjSdP#j0kc$BlY&ZCi>UwJI7PbYuSs)jR(PCZ1gJ?v;RKgK> z#B6vy6~|JAu;55=yU2DZ5j1L9TZqSniZ~h;ivsm~ENrA`zY@luQfH7q)`|HGgvCYX zQcHQs?M0TQW~_<-cpTZ*oaqr8S~a+HX)L1z(G{3R*S)kr31#M`f^{9jyyQnBcI6f9 z8xXZ{*+LOt3QH)V3x!{oy+HtL?)q8_wF9?>gJ>z-cnN=`pU(9bHL924a@%U^?IH_J z`RZIOkUGnWdYAHO_6y_&2v`J_52L{@-0I3tN03Ghq@bSCPw<238O~4nl9KWYOVQHO zirb87p}ihLql{ln2==yEyNL4L@w065~WjobxVzP17?Gz-tVfR={Q6}xIxGa(Y`r?G;0sEBPbxxWEl}N zEni+>DJv>3Dq96LDq6a{>^5WH0FUd2p+_wYB0V^kX8kT zN)?6hk@tc?iHOxykpw=7B$Q$_bv$ow9+$^)I{K!+#^VdQMnr|!H_Es-7Lv!t@>~w` zzYWXJL;lUMd@k~Dh2?KT{zO=QKJq6QCO)jv=N9B8K74ZFIVc$UgozI?K>6=j`^YMP zXRfH^1dfYUv*i`$f?G2RF(t6i%P3)Q<%xY#sqzKK!&11^Y*DZ{4#8Q5S8+9bVDkv-}FR&CZu^^yW zR#cdtmNs%R#H3b*8tV$iG+#X7t%wH*a4++UAS4+Y*4tEO`#bVpcJTC!Y#-SHtRD6skh`6;2u zFl4T>R>aN^Mxt#%oUh*Rq3EwIEW(d74n|xd8WAv5W(v+?U~#L^Jr?IBgGK(_uyd+m z}J(ih(s-UCD$h92uYkmRjrG9xp}9RWh6u@gK$pK8%MNwp4AfFp}Xh6=(rEK#>pF zbVMj9{%)|RVE2abKDjj`%7r4{-VE%Z=v2ePa%`;|%%L8GLQ1ejjE-_1!jKg`iI7bh zOcguT@Hy-KcEqp+C5q!^T^DhL!{ki%utAa6U|Z~@g%}#i-VzqmbT)-lVglm9hKBSo zxkuRO!8*#()ku}lzhZbNbk;>OVq+IUBUCC>&F&L==NFJx3X?g zxE9K-92MBfWyaE=0%1azEm>Z0D?78JJ{T`k#+*wdc@;Bnw&665ku;?c=T7Zd>} zlp=f&J5B&>hyu+T!OoP2&JBIo4bT~tm$cbjT1p|j&=JeOT(7ijc|^TIe?&c$CV36Q zthTlFE-$l#bq%E$h>%a`6Ty|@j8TQKzs2~bgyo16unvVMWU&ToI6aBtRj}2PIhB}d%FcbR8#uTeGgttp3dn+!VQ4Uh2(!RAHk9C4y`0YWpm`>MA;=Nyqd2UY5$b~vwSGg09_{r!4x_*r zCH7$#M`#k3l)wy2?Krh2s6zrGCGPwUe$0-H@Y;-5BRiOkXas6?Y0Y%V@)9UyiJ`JK z6fi?f9Y9{SHwp}!o*@jNtFl=~QmgPN12D2=)Bz=B7!)xL)$s9L876ZuQn#Vhf>n$DQnf!o6%qhV`ClBk8v)10h{$ z4}f)w^~KPP+*nwh2)?O>%cu(nHFY8k?6 zWjRHXSV!1AC}fwks4aCW53_-sieDz$MAtg8*z2jqK{Vm3^D?9_71n#iNZdi3MG+O1 zdW$GL>9Un0AQ48h&bw|I&M6!l7;h0~U8NmXZ;*Zgwg-NZO?_DHK@A=(C)SuUqZ9Sm z?yO^eipmFamn~CxRLDoRufS+xx+sV@=m*iRM~&4)^s@}%2W{Bv~8d^Zt)^HZ;vcmy^ z>tF0QH(8ugR&U`kw%Q(6x!#0K=(C}iEJtPH+-A8z>CaV7{G z0IL`>t0A@U()iO7!Hmzyw&cH6M^P0H3^g#Lt#*UFF4~PT*4czpb(;99YY18Sg8Vy} zmSGW7^uw5b7xlLwpCr$wo9J6C*05M4gA?Qbq&}_jcCkTS-{w=eg01@urdSQgEK`w9 zg-3xXa%Sz;Fo;XEvnv31z(Y{!sdew7KK#J6atRv?qmV|b!&n{~P*N5e5v)V&20cyy zA}urdv`c6!z%`VyLp^0!WmBi*>?5cL+ zYH_GNV!M1PZSlCa5&oD?mbh`Xn(kfDd>GR0$cYXfv56%*bv8v7{$+>=AkSzh)&jOq zl`rN4HVPpJDja8UwZU3Lgi3sj$It{*>#Np>VU#On)OLitu{;gJn27;z-gU}?%4U0b zehG__u=?tf%lHmoM@n68H|&ImrVI#^*&xNCeG)sjT8T6M(CT7mu?4zIYEK33@b$zk zu19bW$9QlEX~*Ma8aU+j5#OQp$sgwDk-c=1f?uN1puzTWtYC17RgAF35C02Cx@;@0 z+&c&Te-+;wT9nnAwAWVnr|ahT_}G3Q^w@I5Ko+;heJ@?-VKPF5pIws2^>!rSmODswhk&CEjwc8L{uXeHVQ+y(08Uxl^p(}I)o>&O+P>p+8 ztbO_zxm~J^D=M&U?mG@H}gVVE0g&)4C2f_uQWP zYNt?L=kkQ)I8r+ZYT-`!0s{gsrO;hO&_yqDp(-54jV7mZUmgO?PzN_8SVl7<-b}xz zS?N{}L&XrKk=wR8S<7gcy&Xd>1uW}ILS9^YIlM{EHGap)*$Xz(r_n~K%u5}^>+6qV}*)*3nj z*$}!94E$QUT=p;5!x032#(|+wYO;C}b2_5ENMy9*hb>Y9h|0d5ur1z(5#~cU9W?1h zs9I%qIV@;#aX$SlaZf*UdZL{+^GKE&n-c;>XEy~*JO-WG?MOa8XB&!G>26a#Nuv5K}U_=|yWee@7EYyId zq=Ik>zS3M)0Y+GufxraA0ga`C?i1F!{A$r9@KAliEul`S^uqIumpa`0Qk@Ab>a^aC zF!s2%?1!DkH3A=eEBrzX_a<@XNUkkA1M^uO_(4T&T@CYC!xz6x^@v5{U18>f!wE40 zD5}0kjG@7Y3ndYkNlNM0UUY=x!0G{y+d`9p)-Cm?1k}$koO{F#k%eJp^kct>Lihp# z{bp1>)+66QpvzF$t5$_ztUnQRjecT+DNTsD9RfE89tg}>4gHLS4yfD+{d<*TDjya$ zb>fHd!G6LL&Iq+jRx!L?I+moq6^f2h`pFA@Un*O6B23i{v>=6)p5aBvH4HCu+KoU{ z`Sr(eq8Uz4t(b8y9bCFH)_c77WQ19=jAEC!c0DefWT>Vsqnr+e*)YSKDJrsHli=X%LA7vtooiVNuMjrcLvvF-Fg~OENOu;?+sC?3bRBqqs{5<%Gq&8N zB<2e?)BSmErN=7&S{Em!x7-rE=fJl1{ijZT^z}EH;}W&Ltg_T!mOYf;<^5yTyqyy_ zeec=ZA3gm0J+H{${pI7T>kDQJdd-AS?XUYEEBs-_-EFu2()h#ppZvY!cNd;|?MHto zpW{tSN;bv*XUUiAh1WjzvF^8Rd!g>DYc45_a6Y_z#wUMs z?_Z98``bUh;vXh`apOB{Kk(pf|J3s@Z+fxe)mN9yz3!sEvp#g{fBo*q|Mt?e7kp{S zr{4IX;d}4C@8frT^V7e6c=C7V{By(adw;rp%ERW%ML$bKn`V_&_ib4FxjT2i`_CWw z*MD60l^Nf={bz6b!>7Z$&i%tB-z`~?oZ5WLx|RLE`s}wp`sf31zpLkujXy8^msfrD z`O>e<+%xr#==#Cs9Wz^Fl|Opq|2+BLw>|Y>>fdksuM5Av^atO&>^~OX7k~V`RqMBG znO0r8vhIhQzj4-|=Kb>Edw==%dp`Hr$17KN*2IS|-*{m9PxIcg=pzMx_pLQQy5`px z{rOMtIQ;%EJodgX485WC`YUfb@7CW>JoMUsyy9I;=KkgE-*o@D{@b7U;yeD~*ayDx z&dat<=ZlYa8gI{be4~v%!`eX3z{gOfQ$f+c`e2g%yoPICe?%5h$;T@?o9p-@^ED zq`Q@zq@kanx$T32{I6>p;u!k;WhD!WHYG}zEhqvlSXNxGfxGR*>?s^$GX*R zKZW5Nti5O0(%a1u=uYQHgU{}*36GX2*u2ut2|8I45SywV9op#UMlhGOIR+wHU+;kP z%ZJ@^AuKA;&6+iM3s4Cgo=0Q`#Z|xF0ks5*7ZtLd<2M}M^mc399bq$M|9rg@qwpd(}s?+qgua{lmtyLveRmQv&ov<`f7GH|L#iCrz^K-6na0Nru8W zKp4*u#%kQG-f5D@J^G8Hzl-|?@R$yd`^A&*iH>v&rbYo zBaDM9V#%j~L%?C+i038`UTbhyhr1KdPXenSGs*7boa2Yr{rJ8A2z-8Ol2dS-w=$O8 z{~eP&^gZMN?)wk;|JEe;d;E41&&lv=x}UV&5KBsS${S(*j?M}FkH{_9u(C==TVXDRu;16&7wk$kcB-_Ckm45DVD z`DgQG6AFbx5&nh4k#IB|3&+C~!c)SDa8bA{Tpmt_E5eoGs&G?yZMZGGJ$y&_E#W)E zJHmH`-x}T(Hjz*y9En7tkyvCxBrlR5nHWh#E{`WOIP$T` zBaufVACG(@awzhh$af=dRN}@Y-iRa`icX216E(3=EF6o(qOn*k9-9!$i{-~A#wNuk z$6ggXD|U8lO6;82xv^KrrpBhl&WoKN3vs^Zm88G#Wk}%C$jr#BfE->wrX<=U9g)s} zJT{Ffk&i_7Mm`#l%VYo7C8Iut&f8v5M@xgdG|4?sjy`hAMI9MBVTuyvm5WM?uhe%* z@EcQIRefT4W1_f7rA+o2T1k-BT3^vp%vrQ@?XOj@+d^+*%puzN-PNu?As#6|9;~VA z=BRCBAI1J8?yg_Vfhe>$v<(c@78iTN;Cg0PYORZltwoB}{r;`BI!)9vxX9;LJ6Kh< zqI*kK%JyH+z^}=Rz*F=M)0expXAksX7>fvE)y7WldZ%D?5RRr%K&*Gwrp#e_O zD%!7*t>wGJi}L~Ey!+F%qrg<4p}2%8r?hC;ql(1~l3hJqeoL>TOX%tkPvE~p{UpDs zHSwkUo;1k=fXx0{RYc$6NOb92III4|)6u4{7_;ZCAyacKWOf~jn8d>qOj+`5JAPx= zkcDbZW*M~<6fI#GcUAEca(mNNB}CMVl2gpehmcVCfklxYA>F%h{u(TPqeiycN(L#);c+cG21Z+*p5*WeDGU~{chU+ zqo1RF{(Pnnt@*m^L-#`8M*lgUKJ;DS&m?L{feRE_uFtm<=s^p>7;1r|E zfkc7Z#$c&o)~%>rx*FXQUEB`UxiGevvu$(pB$z|weTO~aLEa((Z$V38X}o&k4c^#- zabn&flbit@AU|@)JGGT6Q??l~S^|-OyA+b9>i$f3pXh8mqFyN>qrEvKsj+RPVrbeo zcW`QvIfA3HVxjx?T>R3mGWK>wkaIb5JJJo47wmMYhf@RMC8aB-T9*(My*8E~ZBUwW z6|{Bqb90yd-Yz1(8MQys{Ns=RXD^v(8c<^7Ma|y#$5FnWO6BXOL4TKR_9sp>`7D4Z znt@e5bOwUvbl_Z8$Ox2&zK7WUI8ITlvbS6VNX znV<_3Hf363Hi-fPWLB0asH{q^SZ?QM#ibsHoIE|iSYLir-$ol_8OER*pK6RX-n!MY zm6M&rOC~b^T0$`xn2RnAmm70uBe!cbDI0}M9!#WJ8qLcxvSM^!vbhGIN&}UST=S-A z+{2#L?anK3V4M8wvE}4OBoth~(Y+;q_2!k-b5|y6)1hkfapam-y1uid#6pp`(Vw*j zmbGq|cCTqDhf6ll`Ji*bOPcSG^imRZSE8q*nI;#ND5iL!m(OX}uI@hC5MNqS(qh*i zM!Il)+0(e$FLy<7PK|V>Hu^aBlC|3CqkIc%o*1?x6y_hEz1)=jrz$e)Wd;dV+JBHgRbJV@lLdhx2EBXR%pF1aH`R>Q>bm%4=N*>bu2=_&} zoeAuQruh+dU}JCZfbIm@%BFDDu04%gf6gkJ`t_sG4g^bTgEwxf<9qdtQ1U6@z=bZarY1tkhtF1SjQ*>st+Zvoq=>IE%Ia?T%D2j>%4NE& zYBQ4GCwz;G>w8(sa+^|^Rh|l2b5VI(fCrXREUgAwLBB^UakMoaEm-cd_`Qt6r?TA# zzaTFceip}Z7d-LXKN`Q1SI%#8OLqJgS8~Q`=D%wIg~tsz1Gy^ zXD05N@K=dHf6b@xys#aQ?7-b*!XD39|A5DDD!g7Of&X&SLcM+oh!VzRposXi0#o^Z zhIO;;o}a_WZx4Q&NdGpEwj0`N@p;j;0d*d|75WtDQ{W@{caVmH`Ir~{ZRD@;o-0LG z3$x`f`flj^ppRGn@+0}L!QV#w{deUrehGNZgx7fGFF!9JzoW>{m%nIxq3wnil)va} zp*KPI<*#+_7Zy>*7vt7j3+*6HgFrW+b>voH8=!UMHQ?ean{>aHOPxJ~-jyC(_m8nw zt~}LzbblH7TTUN<`+dNbY+U>EShe*tR->Z`SPZ?gb4!Rg_0yG`StEAib5Da$8)~(iiC7ZMctm;VMr1YRF6A z0AU;i4gtY%PhhrlGE^8Mgw1wlMm*duC^{9bzCAWQwXLE)-)iAwCN!{-^Gw=%=RUh# zypXcILTkFGAVSLYq<4!w3#wQtisV7K*+J?5QJA|axkKr*DoO&glV+#LYt&Klhe zliNxK)$G`4Vzh)=g~gh0$k5fX1YHxF0Bs?3$VJ29j(}@PzTY}UK^4+WpK@z`nWi{k7o<#`wU!rNOx6g-9>(*}ixl8I| zW{m^w-HNZvCr80(byaA=OW*EtB6-G~nf*AsA+9AU_q_~#n!RivvMFUF!%Cb~G})ssS#icE_jC;dV8z0r* z=Wd`K_2$LL_|9XTxBU&YIe^~|YF_-@+E8*3+W6C;x($yqxsDYA~; z!RfRP#VT)He4|^F&hfLrdclny&)_D|b!?09-R?uG%yp%pyi4#pV3 zF!2>lcAAF@WcGi1px5ATE$|TVIFMfG9cLO#(;h}o=M?5TT6@n)$Ktd%FReA+oM&!DTF9LG-EOc|LDl4yD9uI$hrF1IIny-*!Ixb7AE$y?zZ8{g_^lVF0 z26AKJQ0INx5_VlM?IhjaIbAO4DjZygS2!x0(^K)K$Jcp%9p8s`Fx~?80Z$Nz>Gx7r z*UYwjGLOn;|3 zGsRoKp6bR6;ZnIaCT}e|Mv3!&AOUE9^l8%A`gS)C_UG!)K+|0PIq(a>&U+X$zk@Lc z?h1exk&k%ohQ}e`2yQg~7HzN0z;S3AdyA%Zy~m*)1Jdmm{3ORhXjAzf&p7%SkKa!G zya>!7E;WFa8|icBG}g0$VfFViF;8ruSJpXC?})ZB>AfJ_tE|+tA(K13I?MM);&KwG z1XNZw?#wPLb$=I54g+}~2q&k$6B+XT;yc30ZFh!~JNe!ZR{3d#m&(sR_)La}pYB~B zb@PWN=!N(nPr3&^eox_NI=prfhU!wKM?VO?0QyqGU%E4#Ec57xp)Z2IPjruV3|b8| zza3rV(I0}o8u~GKvvQKK`(=rOVtf3DK3%P=J;|6;5qnU2%t0a!y${Sy*ySyql7sU$oA5#H+ty%v!e)wtF((+KFh8>`!gW zOUOe8)%$(Bm?z$coT%HUtM5_j{4i~S@=N}dUo-JH9q{w(33%A^5#U|89Z!BGb=Hbe zp(irC{>_Slw8izI<94$aXAVnK7FAn>OC6G%faU@s;kQ zq}1W&ibPWrdY+1A%~=4j9X2abfzYL8T>gq2iJ<3I);6cj9YYK+A)3BhHh1)IVV+Lh z`nX%$i}r7OZq*w*ijL}fHOnTwF2c9+XM2Z}e+0AUIqCeN?Xi1iMe=3LPTXxro-^M? zUkKJ&n!fuftH^#PFxnib6?cnpQ~D6?^8@4s`6E8|{0e*)0e=1zeAwmBQv4ji?RfG> z>$Bq51fNP^5WgLSXOCF9^KW*&M)4O-XWHyRGR$rz$2+;k7ENF)R>G_ zP$Lq3OD~sHq~;|PM~@^G11@S_7{T)AdQ%dqwD#K4p!77?u4Fv0*m>onxI%FzCgQg8o(KH!s-6U~DuOM3y$ zqaOly?4yi)g!wq`3c!b;J%`_u;3mx7_)GjF?ZjtY`BE7B2;(Wjcmez4*sK2%{ix`V zF$c!K`ph!yrvrWP_WR5{%te6Kj^t-5e1`FJWN&E1{-n-h$&Jc$4el1<&M(g=o^a)P zBVo-TZsRG>I*%oO!^HC>{Ej0R^$(KWap=1~MjRet{_sB}JB4wQFe)F-mYwK@gd2sv zf1I+DpSAKsTE>!{+(>r!rV{|#v^AxzBwfzf~rff#FS^L&1cc1Ht$TeIA%-?QiI#rNoAS#j92eUG=kW4AH9#U7>?D4o-Kh&vnYYakrl++K5wG!DUd zrKi=8xe0t&B4bvo@bF7dwZX0>mFvTwpJ3<0;8bxZ8pyl)Z9gWHKI+kh>+~1ezOPOF zUiZ+jVI^&_|7JJl=R`bCaHa z7IphY%W8#+mo?2z6fL`M(cDFe;$?~JO6L~azOAndHTs?HrfFkWRl>`NzK!ghlR=I4 zaJZFI!rs=jLxuHSs69yB!oxD1QrS*?lurL3c?+ki81ivjHh~&$?Zw~R*9Eq!76vq| zOO#_YT`ihtX37A{mX)_c>2g8d@!*wvB(7~;4@U9?C1cVGt-Pd+RV1!RFwP`@po_0v z<6}7MZo;(L!sF|hO`vsNM z_tx;rlX?fo9lJ3&O@C|YHG3q)Scnq4&Ks~*c;Ct$mIv)xU)r7$u5~|u;I@JVJr#b| z7#a6&vG=v+Cekaih|69rkTpfa-6UWkVjagTy{e@%K`g!w+L@^KtobJ1I3nl7m$Q(R zJrcdL9(rQ)N~MvdUljpixAtpInSAMBJyHiT6enK%(G`vaN%5baNar7!$q-N}W#y9k z%!;Vz+TMgWN}FyKy0R0Lns)+>1xvZ{!7SxMo1OvySd=35@l9pwavJjztjJAj*VR?>SnBv!0kwQPQ24a?TcN(=q`T(;n4 z`NJ{!19RPi>+bFCgFOWizUZdwUbdjHjq!~N3xzptp99w7;n|aE^ z&Clvk+2{u2{tXu=XUUlPG*ACYe}JP=7`aB*)4r9`+S`|THA>-JiLi7CK;Igt&**pK zbWN50xmoUA3pVeIpSu|J&==wMHA?dSeB`-lqfC}9l( z!F^oOk3-uIjCUVVezcEx55k;E; z?Q!vW(T&%&k1P5iXa@l8Bl=nE;Y2}2OQK}9Efen`F3903oVF!yLAc%|y{=Y-Y%WqBFzc*vz;IO^Ce( zUC&eUqnyUqiN6W4P`FI!4x#*L*n}Bx#quvc2Va+bF*+f3XQWKt!a{^~Eytw(uY_gJ zB?`X_hsyHLef*RzM34rq6y!%A4n1t{3dQnY6PjH%&CCitP&O30BaBT^esO+D{`~yX z`~~?}<}W-iwEePEw+~HuAn%Uwfw(!9ZypZa^>BV@nz<}$hVmW=-51UeV=Wx<%+aBQ zHOGd^ERH*zW~?x-$pz zoAO)pugiagWSzQwfvG-q`})X~6@MXN(fh(H3=LI&v+N9ZQ!e+Iw-8t!O!7SQiibS` zDt^Q*e4mZ{N(teC*nN@wRS$MH z$OySzN)jT7I5HHqS(p`*p|Fj{c}7KJq1jGY`D*ORDoBa0t4j2FYPCSU~ zf&3l5x+Ob~v4zf61%@TWxIh`gAz|9Yktp z{yX#kUa6uc*p$I3e;>JW>Xa?oLK3COx5MSt`P*fjMzOa}mdgRW?xc2ApR#QVrjO@; zLN&;*>52#}A_oo_#>Io^lL0TT*xbQ|b`I>arpkWki(9?*h+FB?O2Wxf;1!%1u3~Mz zeQ90$z@iR1&_g9UU6e@kdJfi)h@j|JNk_%{YOaxB6d#7|Y%thCwWq_w>bqe&)V?+C z{<<;UUN?uEv_z1*vwe0*y@4|yW<4q<-K%o6G2S=3NZ6oXfZa;wQ`RKK!OXfC<6G=I zF@8!`v@p>140=Q6H@ux*Uc0 z5*zy4>^2G+%Pnyp)_E~hZAy6T;vzHQ^*VK;u9h%@Y~TO;4LuMnNHAX-)Nk4V|_9%Y{H(e1HG zjTP(~fadS~v_5U;@?hHz&>rX~T3>fKxdV8#&Fu#iLfeh`Ip87k>xr9Oo{Ihiw39&H zCYJ|j^fQb3r}J|%OgAO1-llaq(UkxRbGy_CsfE_q(+abAY`O&eDXR_T+canBYBz;u3r6Bx#jD z4HTESj~4s3&v-91sJ+@je6si!TfT1Ls<_1Am&ex1?}(SqN)Jnx+S}222;t!Qr}3U? z5igyQ5HJ1wy6vB75#N_{rX?7+U7S1d&$I;T!80vEx__o6NDH263DR|@1vNM6L%bSs zYRy+W@9PG8w2rT#lDF2}k?;zXTb;*-qp&#h?X#`ruY15++qLAj^f}R7F7RQ@mm5}_ zTcJ#c#UUFz*3`OmhF7jD8z<|~TOZGIpg3*paz_@#hjDlp2WZm+QeQca9nRpkox9r7 zz?1E^T_bF7cPlA2~e}&Li1AaY7;7@0T z4#BsS@A33;2R(jM;nM+}NgoF7-yGEbT3K~?vc$}WL}u>>vVRns_rNyrctE!VIHKTY zw_#3>t#@bojbry_Uv=Mnuy{LX;h3ivWC_(vzhqwqceukpyRGA$A1@YugetV-ph3G|frr>@ZN zlhLK-4`kCy0^IzQY>u*LdzUR`Ac-**S!F)=u?FeDqTrKiJNl6+q|af_DCWl;Yy@ z;m(12+9Y06$B*K&&*QNl9Q{%_SqAj=-eT<=*{rfx3ap+jX2jlGIUSuFxD7C1ntN%i z?Y5R}bC4D`@a-IM6Z0zCByEb&%&lMF<1Q^o+cOIRMvu5B+N=sQ##h>pvv*0gFK4NG zvT4B1|Dp5E9M3KWWMV-+;8@s)8Z`*4Whdz(=Mk9VH3x1Q$m{n6zS6-Yz=Gis@Hxx#uY2N;4tJ_nT zDdXBI^%|I`lZWevvFhse#d1wH&Zk#wI zUQA|Pn0%`odI#P_C;A>OkPzs$53z2&6hV`w7;1>;-ZOEOamZhg@nym~iLwOI@{o4g zfxUsk%R#z>k|>^2vVc`$W86H|Umx<80PGar25j$n_yN0$Z6%xIPwOGp>CIP==8Sjb zXD7_%HtN}7jpD}(h`aJ=_E2~16%-C|D6SfpC}H}xvY{2x(!)A*ut_7usoRT&inu>Q zwYy!wR)?+$-hR=;ZAd5@Sjt7F8Z4UZLY`(Q>4?fhH&i|wI^~`&C~1YNK^s&pv^zZQPtpT*H~OF6 z4y{y?Cmi+5`~E)DFXygHXbuu)U4m7F1v$FhYMcCP>1WX1rg_H^!tiysJ;8VD*XVnI zeczF@_Nhy ze0fVsRv-NccWTZHyw_*w`$pK~zkn-#-J{rzSQRT6L9t;jbn$X$|L>z;^zIln@!j_a z=DfiEzlM`j!P~=;WI-g790cm3k>pdch|SZZ6C%lHfaAcyyhw7!WJlj!97)bBi6qwo zPi^FU;(SLBpPXRAzXyJqXTm=Peh7RA_y+KmxCws|coO&&=8uCv0(<~?0P{P6?YwOG z`M4?IT#@%E9}o6%s)iTPM6%5}kIkZ{(a~q?b?f;h%8%T49_&-5={HEN$-ddlu^+2T zp9fO)TuYH&EztMUL{;^Qma0^;?pphRx8A5$$OKSeA6=FT+79$Xcl4*w$;;~tH{k8Y zx%3mf!+bgy%)t7224bH7u$%vW=WN!#Negkedka~*d7JG3fF=>;o^5Evr;D-&W7-CJ zL_s%-IH99nYJdYTo70ctA|t&uAw`z`ia3qAePdnn(c2MOQgG?fk)=`W=s&DBGh+(- z%-S-b5?Bqa0fvEXz;56n;3;4~a2PlOoCID1rr&JV&HxqxOM%tE8ekZ3Zx^AJKleGD z(ejeGclEWWbCYkIhu~QqxCi^sI@+|zY?XLpr?&$@#=iOMbQHNm?LN&L!OC3M-#q(ttvnU_;VqjrR47|; zdhcjtc~ERpJju%HJnzNYcqE*2Vy$AT*Nup+w1W=L*1IT5b8ek*qp*1j+RD#{leMR% zaP)x0wnS|YS2(JAq$X=yqS0RHNShcJR`%0`Im;$uKh&4`RvMkDmMt*p-|`b?WojeA z(W^e*_UP8(MJH7&5BnxF%G0~`^0dplRECB&wJe&%#_OHdsm*84t>}t`wF&U`+*>xI zx|dDBy;^knN%?m}0(P#{C(|kIXMuS62lN7Q4M@`S zoAtnWhUVC%dV6Tf>~lYkFQvOb9rFNROSe-E=PoPvJ$GqZAn!%tRKkcj7WpQFra4`V{}Yvw(gXXIN-;3~IcfagBas)I$Z6sfStZzpwM&j@@nfOQ2nz zSKpY8F(F`rM?_Ae+uiridKmAucuM-m7$r0EzX{XXC3R!&c<7o;Nh z@Olf1s@HYeH6xl+OPd~tHR!^ zAD+D@E*di19=lX@Z_qbE=E5wc;ZMrLh6kCs$DU!74S+ssFv{-+f z%tp_wD-w=@qdUL!ADr>$i4V0!O%)BT($(mXTCCYyp7F;wa_gT|_Mg{LyP#WlBg<8g z!)BJZdfOduSMROuiiX^=Y(P?pe zW1I=kEs|%XD%BwGa=biEA=w7tFa5)j7fNd#uWyv34vya);w~^^n zQ&n?qQ%#EOSW#DHr%+?{d(KNrnQFT~;}MAP5a(O8Ys~wgman=#(fkn%VQ10(*t0+0 zT2b4G{705gzYVc2JT;}k63UwOV0+58iXEMH+Hfg+wBC0aa52Er4J+!#OadkYuL9J! ztB>y*P)2lhq&D^r3>N4g2XvUXkJ0Dz+8J`(yt!8|%5~_uH)^dOC)1lemG$MRikejU ziq=LWP9D7^RnyqgN?3RvC4Km0jP2J`w6@Uav$waE_h06^I@s8*oe|u*`O3URqyK4p zZoS+xfQms5R?7_o0Zp-WXsf=B{<+^Ys(;6-gc}WgTGAUxjLDK{a=wQNwYqF88Dnng z3!2!obJsjPTbnLB(Jc))BdwOUU=-TIQD|2NXq&mT5-=KWBJ>8$Bg=@5v9^vh5@GP# z;Yn_^@?^P>*R4Vxr&~M5(di8vfG(2Rczg3xJIL=&PxVJB2kYb)QOhS1&PmD0G%LIz zZKBY!ZswvH(HwB)Wu#j_H%b$?F|u)yEfAvKLa*k^Ad_nqcU$+U#esOJMp!o~;%A#a?Mv|3`>KeCO2Yplh~HV*UW+(HvK=YNDZcf?qH)bNJM`PEOR~E)ti5WW_wP3lv{F$|vv-GUCtF<{(-`G-X^Q6AEr7_XR zQ*ktsYF6FACesa_CT+HoQVMBkq>Lspp!e7q0Cc+dk<}e!S9`BYX587)DDk4a3~Zt` zwXw31Nq2*148MQ;;d{-_X~ItnV?5zk4sG7N)yd8mb*3@Ph}t%2bf#9^)YO2K{Ypxk zog(+xVdzgWuHzwEUbt1mGUsZ@vrjjsbImnlI_I2S-c+r z-PmQyd%c*gT?XN3SJ6ZB>SZ(Ki6!Q=pvLA?L5Hc%*}c7NJ6bE9Hl3-Fk`@+KXzq1- zj8zxA%A}*@98DPT2a==KF^{kR;IlgL|0{g`_6=D|!xBSKC0#k_EmdW5SzXpBiSRqC zGs?uOs~Lp>3E{-O-EiUV`<5#65ltyBE&Tcdo< z>s-c=4;;|hVrLReBRv1^^l89}U3YJJfGI%c6GdFg!`d87|P+hy+K1NzySKd;SYN;u2u9CSpm0Z!{J*+WyIN9;j zD9el5Y2xOOdpy5qOpA>@y>dm18dp~8{O32v8HNjB>@;YkN3ozu@nYsDVI2JJyu&W? z`kuxPM;ge8e8L4rN+|T&3X~k2@3H-IoXcHFcZ;^}NQeZJZbba>)dqT{&8rYB5l~iN z2HkFWQgn@bm)^E2Zjua5e6$)TdwsixlOR<*-G)Z4U6zgfXw1zqmNQ` z%M07AO3%5PB;3$-<80Nfaqo-yti|CB>+*g}#FUF3caVxqyU;&aU%on3Syfm5#_R^# zm+jd3lhcN_S=9sLtB24B^d9;;o?!IbZohdsBR^hXwkKN?>>=Cp>yD-0e!89@y^Ub= zL$Z|sQu3QxM`Pq3^au*aYlk{r$#D6Y_UH94ZvQ@G=Vh>1E9;G3!xkQyhH{>s8BD(b zZtb()>=?y9YY#KqzAt58x9(kc)10drBW)hwy?X7C+s%K%A7h+U#czum4n+sR!)RclMiJ`G;m(Au18XNGBq8%y7@L|!KArz4aaI=xHRoCgRv1Z3H^F}yfTDpruDm?~eVv9b? ztartk9P62N!|fHj4m~p8{r+AqGCT3O%e?14>K>UL*fBNDUeg;oe{M^pFjM(_AjdgR%&BdhF2vMUiZmz$g) zTR)MwS3j-PET4=LKTFe6bU?9R+8vu>Qh}3p6EusD#FWyE-;0O%wU+u%V<59FqzUbpk(ip%^D(?dCL1V{-pJWJiDc`}eWKl-~hvnc1m;HZi@by|E(|17vO-XoF-3f2f)~n%(O>|TtDK8@-+VZajw5jJC-c{ zc9ZepX5JCduuzfGx|Fv_dgicl{r0~MzkWOiSb)+jGCRekfQ5M*AZl7?Ks({7ee}F=^A>!F6-E}bHgo` z?ydApI{Ck0tD~`9=q1{jeS_~u{gx*LsYX^vt z#=e6LR5S-*Cc#qLKwFQ_smYPImD^D;I~W$nLDkjIDVFT*tSXg0A!=!MJk7aI6@Y?( z0|vzDXeiS5Y;J1(P)~ay7bSXG`?z9uBH(_MadO&!PBKjH(Nx9I#)>$mV;2w@G;?vm za4Sh)-Z|JpYPEUY-@>gPJr9a+Nu{Ntrb>I{&8ddQ)>KPtd2?%3rJ4uF5_zLS)L3B= zj;&i+^P5^KOuxG;!r+NOme`ZUv<=rL3;DmQZR-HrVzS{T3~T0Y^wN=}K)BW;onUYG zKU48IJ}iHX!#GfqmYVV;?l%8V@0aIenqOOe0f!>wjpJgmjf)#pN;D083bvEfh;%orRp)eFdEvn6NCDVdre;+xO!j?y0v@z8LeRg;~- zkM=ux{nk!iY|n&hkl|Uwt}wuRWEn&8iu0}ARk|I2C3UpEB70tJw_{b}Jnoiv*8Ck+ zFOIj1l`WlDCyEy)>M6dO1?ct=*ALfqZbiq5v+!3~)5}%f>KDD6eik)$tt}LkqvOH4U?X`K5>t9nK8*bJ-)rHFysQ#^eI_>Cb z5OvQEB{rj$MSC@*-wt3YXm+K}6W_S`5f{BN?X$C6W_ogjp?mQ{kC|OJ^A>$nR&89f z^2T+HU41kIeTbG;)NTgYlY1<51=pffrM-iqW5?B&1}1sB!Jt(vFaJ0l&}Q#1I87@& zKIF5oeTS2>l@l$LCpWJS1ndHC866F%J=wt41@)3m{eErgoAvDIyRQ!BF`^jLLG5wB zo71$L(=<(*bo_;vl>f)Rda1_5i%96J!+xdPET}eLvdU1p&ZxF9Lftew!FygzoV~?I zjp}SgqA2T|1A#oEp5Qh-PiSpK*qs{JZbAc7Q-3d|&DGhgx{_56MhTB=*W7Z|0-_6I ze8H`-2TaBhJwGl%IjHn#41odz+e0YV#%?duK2yE)$rN4cscrVvP8a*b8hSYGT%@*2 z+c5lC_84>;Zby@QLvNzKasiDE7ZVx9GAH+3)04B^?v>h+`PG!|&N5+ChH`>-Pes^B zKN{`nsIrUB0Cfaq#8M-h)mCeDacmi+z1#MQ?LtPy^kIQ9T-rr|sFp2FxBav0Z!$Em ze(6>cGRt=F>ZzVs%ck2&xqW#pn{M5Y;x?BrZPAFZ=++?iY#HD_YEMTk%9#s#26R<< zxX_*!)A7v0#HP-zh1xDfsoVyPh+(saeYxwSu6K%`b11120a>r5y5uV5%<4H}DLP_^NTjwxQOC0{0aT;R}^ z=~NFrSZ%|6LMmR+u(GAbp6F@m(z{VrRqk&2v4gzNB^Qg+4zO`{iEnszBYoTZsY|h! zTb)~+&UZM<{Z(NR`L*&r{riz*Au!_yk>oJ=Ip8F)2J;T!2_TC3D7Xx~4>(;qN{D8Y zU!X~Z?#Dk)I4%tt8-*{s7ksFhyM zb7KX^nH!s1Mz$R)73g7oW+2naN0MNr);|0A+htr zTRLo?JhCm+6e@9xTFVa9T5q5yPHR1yn`otjThEb&-dpUU1)MU#@HV;a;bGearhThR zAj~x5opgcdsc3GH2XAV#b;!@BwHTR%Q{*=L!|;IDmlH#3F_WW zDV8ra^DoFy+Sk#9+d8E*_}sJstU8}X^ETh6K!5i4ZoU2MXf{;yP{AZG0uCpqzXE%gdwvP+y4}2CD<`m9FrprD zmS~=qsliSre9XC8M}|5&2iX3w<3WFqz;kG)ecq^9C*(O1=hnaR>(*5>F38gXckTJ^M8K)W%hZ>z3*4_ z-1PbOzI&gaZx&V>Mj2YzKA#cLO_tUBLaoZr~wc53m<_6xatm4m<%o1?&e708ax-*Tul4 zz$L&;Kr*--(EX4CK#kMufh&MQU=A=3mLH27Y&s)1RNu@+TfU$LUYy!Vgb8c8$}Y%8&0)`2KPFQ@Qf}DeT?j^p^6j2<1YKZNi5Q@z|bUVjMD_our2_;~#hIp3e| zHa#(3e`KKVPxqmo8n3@Z#-Hv`?H{kdvW!2qO$Wy7Z+pg{+Owy}>yHfd)2Fg}aNPdF zX@6=rpBcBmXxg9J+C$^^mzVaZ_T{;8`@Q)~c6M%9-?j0kP2HP)+|%2~dgJ6t zuR1G#;!fTVs%8F`rh{nguYi_60bO}4}6i*}AZ_^ZBtA4|727o0`^(USh zM{5?}#ejaYSDz#I`WC1!lD%(Mza*vFzFEAil5MblGN%TC({1;Z-zOjBvlQs=1}M z5na4qx_r0w4?isp<@ICQaZ|gp4%=WH zo2%*@S611q&yu-#UR(9XRBK~ns;;qt(w^1I&;1#BzT|?EXl$Ylt%Nprh%|XtphKgoC^+CU~ ztE?K^uA+Lm?=34G@+Z5o^4^9g%a%+*4~C1(M;59ac~mquH?L@FwfUp;=jIJ3`CF?R zT2qy^)zuXFtoW^JCNpiS-`Lcu$`IhaqONH~85gaEl5NGGtj#J1vh(x1u90%9yY@N! zDpxes)mG3Zr0U9(RdwmO$S<|Os=BtJs&W*&BsUwmKTWkyHCCrEEU!xYZEP(c5l_)1 z3uYTG{u4<|x;+0w+ z%(TfBjg_^kxqeuIHe^KumqsgX2};&AR$Qwpmv&cKUQaEoOsOpr1K)qG$e!f0Y3{;E zu>VlM;oN%(5Aa`ATV3mCpwGwB6hSqnxo&NJafd5{@l5+^X>Dw3%5JZ%U2Szb?$#{* z61lu{^255L_^4uJ$8BYnj2u5Yj$C&wEw#bE%ereSZ)sW8*gS&ncWk*lR+V4t3Smwh zRyD4ut3<6G88#wZ$w=|0gpaUmaykdH(jz(R#0tH27G1I&jV}HqqmdM6xo_o~L~RR| zqjA-UaGR@^*S2s+nF^ETXH}Ey1V^MBdP{xUEq)prSGkhn`}b(+l9EH$kR;c?KYYoh z+nSW)Uwr*xNv?ZVRjZmC$}?@47niElBr=$Gk51{!#HF&yjjvWTXXGs!;#0K|IUSzp zesfXY+N$OzZC78#%`9&q;>X?6Y(Z1>$y2(7EW5O{a`=d@rjllTgnP$k6uRrTM#M?H zY$?dDhc<352O66h)MSN2Zmp=Nr^-!V)#{2W+MoK?71{1bsV|m41<)({Sz{P$cioCA z1`oDr$_m?~YXp-;C$97(>OM!513xXz^h&U_qt`6IjkZhWj;PbwH=5mwhHEL)nK7v4 zxtcDEa97nfRH_zb`Lm_2+K%|L=t^^Wb7d;s!RGk$>__C&%Es1Uo9@D|&uVi6a#I(O z9iOJmc*44CuBuo`Pf=Ip2FHFJg&V3`YpZkmiwxZ_;Yho@KC5jFxT&@SDa9gCb~5&r zH5E-2uEhJDZ=ft?yQ6ekKF+xcitN=-jp@$oRYtl?Hz{NBa20V(|CP z-f&|SUbd0;tw*u1MEgUg64Que%~>+(s;{eDm=0zXKDA8??ci;s8`rbD2FI6!qtQ*+ zT9uJwnm$TBb#{uYKU-q_5y&fNzoH>MEcO)TWQQ5Fuc)bQ$_<<0Pb(cDoPB>at*z-n zZ5B=U`;b>Hu7A&6 z1yzNl(%oNMmD{)3aDq7@sW(OO(yEzn6(a)wr6*> zvN_3JY4uX+VAARK-L8WXCmE)iT1L#nf^llCRf$%%WTNkfxvH_Q+NSLc`4)6%!#qP?`0j1FqTR2p zeq(k70%hEHE0K)mbhNmhPCh=r3I>{4i5?}))8(trM{dT-SG7?sDb0wp`-=>ngWsB= zN+Dk$e3T(C&mGfRn%{PMLj*tV*3RpKeVW2xYGel?*)+H6)$GljN3~1YA$-;t#PRbZ z&|Gz0AimCyL{L*v!L*_wFeggeHKl5_7?K^HRxHT|Rz0)n8aQ)4o<-JddTV)0U>&w* z6*ZYF$K20q8sPhW& zA{}a&{47m};nTs)1>7>I$7Y0^7M6{eP_Urnhm?t{&nMt!)ODg!+^t~#R=b=fr_thz zYRmfN^~75f>`bsD;>J{-X}BNrk?~^sP#&A@E^v82lnQ z0-pRM!T}e6W8gAy9K04h0lW>I2i^_N2R{Lx2tEX!1U?3y42~X&FMSm_0X_@76nr*# zHFyem5PS~!e(<^A$HA`#KLef$J_eo!&iiqE>3QG+@cH06@N{rD_%-0W!8)|?C^!V( z4-SJ5gCpRR;3#2A2+SMarU}e@#-;W8fp;IQS&ku#q$4 z-|z=61naHrGH?XE1{?(sgJa;k!Ex~8V3S~95gY;^0f)gSMK9vq#Zl~ql;8%fnfzJXz3O*Zr5IhC^0{9%T`8o0e z&j7y~ya+rM+ytHm?gO6(z8ic#_#yCg@Z;dufDeLS3qA}sO~?}*0#E)0=>ivlBj6@* z6g&)$f%kyp;Dg`^;1|Go;Jkk)UEm_{L~s*$5_lLq8N3^8)>0q9A@Ct^7<>#I0Y{G! z4tORw2Cf9h!5v`Jhn&G7@Gfu|ybl}!KMjt8kAP#~li)ab`Y(wexCopFt^}Ly@B@dy z+rVM)UT_5b3^)ot366mizk(0A20Q`W4bB7K4W0si6nqZ&AoyJH3Gl1I(|=7qfGfe% zz=PoPzz>1X2Ok7a2OkH&20Z0B`2a2jUjSYMo&nwtE(SjeE&(3`&j*{|Q0~En;054T z@Ivr5@Ri^_;6>o4!Dbio1&6@qx0v^kE^r833J!ytM1LH)hz=eW9eh7H0)7G<1s?*( zz{kOHaP)V`1v~?s2QCGh1IP;;0~~omqwMoVZCzdR9SFYeyF{MyC);=E9lIo9v{Qq zJP+H0kfZQiI+A584~5hAX9qQ`7!0gJXN}GTbER*#Zd4ZJxFSN$L`YX3vb|%m3UwHq zwACfVY`dXAnyOJ8I*@=de=CyQInr@|?gz|HlqP1+*1mnVUq8UHM59QK?qjTkkM5ew zk|QjtIb1tRe9ArjZ!arKzNfKbMShGTlLFp7aYcjNNE8$e7tQCsrn~HEWp$>4BYApm zxI8%`g{~whjTJ-vDruelbFQ`}p$^?EBYX^_RYS$3g)0~;KLJle)?)13Eg^T^6{MHv zuQ=^*MC6q{BjZSNl@K00%Jwj9BW=r9?LZ4UItEpU3;Y|-x-mRv9t6s-t&_G2jFuy$ z?d3_)uP(l?@rco;!S^uE%#t5!uI@vP*~-|eURhf-c3HD++siL(#LtR+gfzX8oxc;SEJM&bb<;Pu$BvubnDb?!7HNGdGV2%r6<3FYHHzXAe-P(X~#4ug+>5 z;n&`8%DM||o!IwM(Pt)B3F0&$9kJQ((s8#Dy?EZ^!oHiJ${OAvcK&D@iV14uxNsa1 zl~OES*DuAt?#Ae43XCYPN~_tmcY;QjwJzA)U~k!QJG687MuaRTsA})%Csciq}Mo#IKWroxQrX2>-w z&Q-(Ww7Zhhv3OM}o_arZE_X~%a*s6VYhkvpAosMt4$q(Xtmb?7CDG&o;BG*)UC{RN z{T#4Mw1(m$*bWc##NC$S;w$;)a_B%xx05Mb>wDp>_-+i**UXphjpUcgH_L7~(7bHGDq-kApxi|NWOmlSM!j$l_m& zcs<<@!N@D_+kw@(lgn=}-!I_zB=9tG6#u1#(d1$9&&y# zreBs!By(ReMs^T3jQay&?B~03PBgh1P#DvJy>p#hS_T*M*mP%~^lK;#-G4rYd+6zS z`*cGY85rzXvSgiJAk_;?24@+A zqy`W5vgnCH>9<<*0#AA0z|cB9&Td~aSU0pG(QjXIPDmB);9%dtl6mtsw71Wt2G8y7 z-!Kms1M_AtnV8^bii%6-mo8X%<)Xz#u}h1IE~J|%7hgG&s<{7aAnqslUNk?NtOJUG zY}wDZvgbW=^aOG7F)gVoEBh&VE`PU}N-I;zViC06rP1U*z|Utt4_dLS281ht?687m zzc+)cOfe>9C+lD`tB~n-1-Maxb#I9vhx}L&Ld4k>G{sT%;Ix^bWU9mO&+DJiFO>?VQ2*b+GPA3hqg38 zD}-h+H+eK_5NSb62^|2iKJ&`Gkj0Ui0UR{{-ZhZTsQ5Wq-c-HD$Nn^g6yTDf_}-W?`OPwxqfc+Ptzg z%Zs6xls&Ym6uSjwKf3oy>=&0^@Wc|_EG@hK@G{(8Q&x9d8E(tVx~C*@pDf$?;R^gz zmhCI8!e3R{x4u@5-{ob$UR{I#+Oj+U^kGxk&|_YjJ2OzwWQ*3qN_xJlXbDv!rLH`EGsE)W(mR{9nyAGv4(b^Xm)$YG!|WhB#v(-Z~ndcuf-G0 zyPEGYf8O*f^SV!`Ox}&}G)0k>=FKlvn-h<1GKu}qnuRaE&;0D;7n(P<-DmPH_=UOa zy`5&y)RcK=*+#Sd@F{coK)s2_i_JGzPBY(lbilm%`L$-#&I2}Ejy(IF1@_~>H1cOV zaNrtqHe;HzYs<{H-c!bRnQ8em-{+N^-^?qAUT%ulm7A6~mzyQ`l;ft{toaCbpDH)E z?Z^Hb-?$71sKX^NO$;70&b4n6ENi+4rq*-%m(tLDw5}rx3aADHi zwlrzJo=n0&`C%gTC*V#%mAnC{1f~HR`@RHR4loQc-vRyzXsq~S-~jL=U>)!P@LoU- zW(BYdCsfyct*qOaPRg z{{rp-n2uWcipM74&wzMJwu;j`fg6E5U?mU%iU7sqB|v=T?=j#6pm^^G62SWa$xUfe zzI`0{86X*}JiH0`1#kf%{++;kfx7_3VJff(*a(yXipweBFt8n{2L=G8xfqChFb%A@ zeiM-Wqrf+SwZQX$;(P$u2`KLTXutk+jXvg74qgq~zPtM5Qt;aAo*BMM<~uJqA+ePu zX5G3-=1bo4GbPJh_=m0qVX)*91r!HDgO}X@>y^fQcs`|uqS*9$W4=sO4_(6U#_Npv z8p*CaUw)J)%47E{s_^hvz}EubG8!P*Ux9wUA%pLplKqROe;&Qu{xVaG%$IYJv_B>q zBYv_^xCs6K`u~=@qRG8)i6%b|MDL6yt1wpp$9F`NQ^4PTYc#nS`abXjfXqIKJ{HJr z(C*8gIY@uqY5eU|>pXn* zgVE%l34hH8Gj)wd_@Wo({4y2aeR09cFf}#}{~T`R-VfWSkH$k`iSCCfUjBE`y}nNY3!h}H51dDOR{~r3K8{@84G4e9 z1AU+UMdm<1c5q?fTeKhU&)8jp-OT^x%&!B_^S;-8DVp3k4)DVm4R<~5`2H0>&mV{; zW%eQ0{iC??X?@RTzD4`;_ni5s&t>S#4rji7f4Mjq{%>-^{|CYo?SvQZH$41>Z$*=D zCj2Ac&DbgY4_7!2 zlk2YTj)8ey9qXNPuEE~k?l}$1=gqYi^M+Xp9cY`wlATs|=X7?jw>qNHG|i&!JP+~N zK9AJ{kvax@rOiHR*AP&~`9BuW=Dz+;_vk{59``lC-+4Xn{I$U(*g%hbg;UxIjmYOStKYagdH-pQ5{!r0}!MiScdjI#qrI-D2 z)8q<2o@USCyH=HMsW1l{Hf;Xq$12RS`_{g&`ll6TJ3l*o;islmHvR7T@4xWcYb)RN zFZJ)|<{t$gAGKipa|{Hx0M<;9W*W3lA7fr5A}*@bxnFa;7oIstuQ-i=->)BjD{kf958J1Y#zSF=?uRK}{&zHbNq z30ONJmV6KJec(Sacf;e=fbcaQ==-tBvE=s49PAH#i#8N6|1;kQU*+6=1^ff=JOAuh z^6YVdAI9nMd*IVPB@>_1xs}}y&d+w+F~gZxUznl)HIex~6F-IDlN0_MJlfr0`Csqh zE2hSh&lCRL7XThj;V(f>?Sb&SJsFCArnbzWLPK)^X`Jvr%mn`uJzX$wg^j>XkH z;LGXU;M4b|sg(VtKRk0NJSjRU77tBIe@vPbfilVd(jU$k%fs=c2^jL_OT>wyxS#(K z@dEAnzf8Vbk{tLL3dp~TSJ{k{&CAuG`c=_K!_xe7&FqW(aP5_sY-u1v+`F;txd;}1F z)&qTCS`tehzSco$;9Ioc6ld%fV^=ocnXdvjc;9!F#*%Lv2l!!}4xhCkmi!TLIyXVP zYnSD?EqCT;k{NnWb>=(hceHPX|BIaPzf9UhGamgXnC183#j)f{!hfkOV|SSFKZDGE z5D5PTPllqOsZFXdTwt$VZP(Ad#fWsh&i(lRy8XuM7&8D$*Wt4#ENx0mSh}Bmlrw>^ z*T-SC!{sd%wY9n837F4hFI_<)LEft)7n>p$l5)O_BOeU0*cpmH9x`7FVgJ8F=e{d! z9tsz*)D@0@FKnI3m@_592KEm>q81g%1&Iv_dfA%f9_u!8uu{!te&=Dw)N8-PZ znO{WSAGRJAoez(B@kUb}evu*IUIq{yJNF$#3rsh z`%5A7t`OOrmI+~BehUl4q4+l=q2EN}e~6er%a0%S;!yF~7%z$+jGBu0XQJlzJ41V- z=E?B1&qd5{!|}IALXSq`pNyDaMb5fEYQ8HOcz#cXF25wj`eJD6lOglTQ2bETd|grA z9)8V%sCh0N|5VgG6#1rHpEA=zlj1+&^RMCf+t~8=N{sR>|14IEZ;kI@wa!wBdS0S* zmFa*L-wURK=Y2H%<9zd@@T_;|oA*W{d_NNTr_eX@%`c+ykL8=U$Nnpf;jMX>JehBP zo9{ecWj%gA^wr7cL@55}N#+ybM!vU4;yWjsFGN=I{jumD!k?aO_DqO>XtMd&315p~ z`2J*hH?u+)NN(Q@#XlSl{Uj8>Hyqg?jej|6zUfK87O_)ib|^6&4>wKybI5!!6n`cZ zIv$GuN63689N!TkwdeWbvL&Ygr&q^&&28t}v|WJe8uOk|{C|bb=g=Z+4us;*h0RYx zZ$}a&ed3LC?hPr=J=5j)gyh6?z6ICnNC>L_?!wh~#l9bk!0 z?K`p1$D;8+MMJw|@ejr#zlz0A#LUUqR}uZU@~3@tQt0m|#(z37bZTP!u1S%nC&j-x zDYEBP@sGXAeDYN=ASSLgBV89yfN*~NV)J@%Mf_OEG{>I`(O8~!kT%sT8R_QfQXJt) zkZx>kc^OxGGi`_XeLoa`I&6;W^CwD<^-p=5f8ISgYW=HaPDfBtg`}4@~y%dI+9G(jOKkU5^TxC_c|G&=R z%pPWjMpPUbMLmvaSg5Gzke!<27L~eGT*KlPH&K~!OdQF`q2q zG%Qj|Ofse@sj#T%hDAlmy_ar`k&=AhpLPDsIcN4pueb00^?m)^uldZg_xkMh|5?v^ z_S$P74$mg)G0qnQ=47YY>~ua7Fkg*Vb;EB1PFt4gP|3a>bUu@1mV>;9Ad+o_qn($V zQmDlBA>ioxORr zGns8Zp>FkV&CIcSdTo?DT0XiOWf8oT)v+bSjcjo$uifa z*lTOk-e+yv=YoM>2b_-uvmOaK-wv6_(`}m4c{eZEeC7;V_XM)sB0I(qrF;jicTm8( zGnoe_;-!0~Ow4QFdNYaJkbv`8h>kk!J_btAVTSo!)@DL>hj=wb3Uk3nWB|V#aBe)s zEGY(sd$t6d?c8;W84BkAI%w_=IfOhI+VI!+B1Uz5c>%WItPm6*rZIuDeX>(@DiT$jTuxuqmlIeDjb$p@$FVIgjRJFkooap||KW4_ z&QDJ>gZUdCInCU)(fRyo<_{Z>_~>co`$ru9ms8D`j&zO3_ndCN8g}-bW_}qyiSth$>wM&N^Z2o^<@}yapC|jf3!U$sVIC;V z``Q`i)5jm$dxlv&e#1x3Fh4uN*;i`1Uw*`srRKSpZ`fOEeqH3;S8DD$G5@orrtK9Q zK2&P{@CxUt)6Je&Zv5Hl=Eh>@k<-ocNoSGT?8z;>htmL%j%{#C%vngTh3vdDUvH}S zU)!)F&#lLCN#0}Q9Ua%Yc)U$Y>rjpIW1NA2Io;V4V9B{&8dSt3Xi4}FLG|pGPede`(eNtWS9h*^d1g6(^<@M zZ?X4nQn$a-dWgRZnzwVY&6x_CK2jYIn#GtgT-H2w7K7j_=Y~LFPr&(7AZwK27r=1h z@R6AFNLFAv%lR$Nltu($N#E4GE3hq}T>@VTI6nvk{uprn956R4-L7EuN7s?XVZB-A z_AF;Zo8`=AoBzymp2)_g{GJK!N2{HVHD+qH^IW!R%a&(P z_8Shrft$aYC7*LR%lS&kTpxOyWe%0Ou68~eVB*J1?y+;la^TQ|c+-3t&XsADW84*Z zoxS^2>j%hUI_OL|v{vpP9P?eZ@vpL+&ei7Dkh7b%3DL)&TjdO`Ha}YB+`HP`;^1hb zCvpFd&bnho|1N&tW2jOv@UwvP;b0ce0pAFj$I@-){`6M%7hmXo*x*;x8M)PW1_L8O z=Q~03L+J*EmmcexLVTYOk4^R%UNYNfIaiqX#oSM8#{V1FJ&^LUPZWZ3CEnj&)k}oso4a zOYb`Khc(*=)|qMhc(5GES6iImbO%l8rnVrq;r9XauW>WTsN`zX7B_wO1bCih zpa-1?jx#?A-LdvF$C>L7a-IvDv4b4We|eDe$z#pG-jc<|&kC*& zJiN(#`e-p%$8Fx8q`6lfC|+FwexcZ4Y|& zYuH55yS@R%~X z+T6Sf8==|&ma{~`J>}(1-A2~8Mz<(5S zK7XXS!C{BXxa0i%NOQ+(=UYdbxz%iY8O(N`C@>#hv*GIndg#2Tz&y0}$d445Pp@;H zKGO8&oWb789OrvSnQy!-|AC{-9qXOX9A$pD{>ZkY%%cZ6e=IPgxp)E}KG^9$+Dsgr zcgNB6wDXap&FmrhOGlZ94t0Kil-ZN_*|mxBz6n=}$?OwZfx#^28(HSD_=F;!X9M}) zg@=t~nNA*^Ue+TvnmqE$M0?nZv1eT`B6NIqWK~D zcv2WoUUR-Y@3;mg;w8Q5RA0Kwe|_Y?d;g|GnM5wZ7`~tUZw?&7I)(LJ@r!Q=Vj}SE zsl~B}pQT@3~0pQMog?? z&4SY}tvb&ZtWm2r#ZJo_6On;^o3oH@-l^I5ea=+2?B94cBg*+&wp@zqvDhK!q1EPI z>+XLep%KXQwgE(Ja<$S_14?8Duesg?9<&}DZ zE^}tGOyyxe%#wLU3nv5leOWSnpUPr*=Ry7{t_2<=_^plKVtfq;oxQ=>^Q3%*)utun zJi6NSgvNqg+@1YyJ{VZzd~uEWrZWB!uKRlPtF_ME>&@6?j&a=*)6%&oXyqO;gTTQOVJxY;Twee4aU%C5Ss$nXl+=+P59&^J~qd z<9u?hS#(B&T>N}3#hBtDX`OjS9^Qvv=KMa_-2O7>C%NWpFZ*_oi%%bn#Z|4|pvTWm zPFpt1s7tFcI?nuR^97ahn*oQXr#}SLv&#y&J1EoqW82C1u5t!fn?J3R26j7*J?u|b zE344zBYfdU0<1a3f)4*Ab9X?gV$U#lkF#_ow+sL0Wr1vGQ&HgL9AmCkzrC68EnqP9Ip4hWC+_modKrYw1+)++~( zIB}$g7y5)pe8Q`J!W(?TTYbWDhm%KYdGwr?I8wvMe8Mr&Cj36*;zy_T*An3+gm20S z&tuF~6W*N+Z?Sq{v1A24n>86NA$Uc#FQugM4>B7BVSo08$NbS4O|`JizRN`}YM!GSZ|3D2x=h(>HD z{QTtov3eB{K0!DevD4bElIsi}gf|mjn-QK*Tpd~O ztE!Yqb4#;s0<$8=3~s3ZK?WO&%uw)b%#UVB;}sT?zeAD#@4>5R&; zMELO;;VMVr4aR*U86K;T%CSVazAcD%jkQtjT$kLKu46fb>BwxSSSayzk}`LBg{ypt zBlUijFL9)Xt9*$gHC*LO9I4^Gi9lp(_>fQdgirXKPq^lu#F3i*N^_6b{eR23r;C2d zBa_tr)7;ZX`2M*(rXQMnMhMrQ%hdZd_e>L>Imc`6nIk;Yc4+R&X*2G+jC3^jx@bPmo)U*jSB zX5(I!5w7vDi}2L)=v>E2Hop4J?1ADQ+h*~KC|(4Nk>1NZxEjK0J{)fo&Jzuwc&cX; z+))Y!shn(lC!ANBeQ>>SUse^R8a%HZnZ<XeEH=$*xE?sKIyAyn-D2GH(&O1Ww8M45+3~nPKbs~m zUXn*FKV7c3oW}hi@eU;R^yhYr)kSl z1=j>un9kXdn&ne!)o6>xVUbvi(8<#b;)cV={hT)=c2T_;Ix=+DX zb$a*TB3w7zXnMRV>4bdF`rC~AhV=Yw9ZKK^;D$ZoRl+rO#phMxrt0-}xFI;Nc&%{F zw;T6dkNouVWdzPEo_x;MZsW4tbwJ%G;l|;->bC&b^;z%w<s}03aEEdC zrRQhctO72t$G9x{A5a$goK6SKZf{m@N7sMovhgK#7F zc(?TgT+Zi=E8R~?)0UHMAZG4gjC+}~0;zqJ!<&cdJ;u#aS&Eb8Y*bl_;A*}QpNli% zmBTf_@jfm!O#Rr8%PgLVoXqdotTpbHvDfar^qgWV%+L zYTym=sszL4nHnoe|?{yVySVjPS(esH;NO1$XIyH zzbZcMLK8WNvAsCyviw^N*uPWVhv`?DV^#j-m%;e}4Cgftr{QF~bx%sX zDz%yX4nNy1zr$~g%J0zp^Oy9p*fiyL_{~4^J2d~?lOE6Zk^GJ!>M)Yd*}j$E(eicg z`N#Sl4|~tq@;m%&iTn=Pi%dJC@vAwojWo|omSqiRIjgJqUwo<5Ab!N!R2S>VX-=bd zt%e^bet(y@75=7i-==t#$#}_h`{0{LjQe>-+aK4q z(I{New+^s>R##^f4{mR|4wNWQB~6|T3jne(DaX+5U*)||(Mf)dS2T8>LTOZ4hOFx~Iy;&f?n-fuD?P_&P_Qp>SWN%u= zjLS|Z6BAgKvM* zxbMJs4jo^Z@R^3iOFwF)w(%oHKdhZXUKn6t4p=X~z{mR-c${fRi29 zoRHE+)`yoZj{L#6Z0j+h_GFqZiSo|ECytczSYG*N6FBjdaX-mt zv`3R%qvA#33jQ;0gOow6yf$8~GMbOuq5WKm;_ZT~fV1=R?~-X+jxSQoJX{a=h4!U$ z{cs(BH103Nfzws62YkWEweFL9*Avu!5(S3^9_6Pab1A0c_AmcwMJ2g}L+C5}u^_Rr6T z%l`TKA=7Y48?MAt?1Su|myaTSk^S?tId%<<`su#Rw$_-gf%DRr1J=H6XdBVROe zq}1JVvVV=F`6g}%6(-x-a&&DawCHP^|wKlgjnQo)4KlY+-(YS1SHld3B zocuTWq9xDpe8!qWs5Z$ZeMb4B)yu}cm-ItlP4ab0SH7qT24Z%G(|CVay^$~41?Tn5 zv`6vcM@swHcq86iSouxE^^#_d$}*EIi{;pU!tZYe+`G6h6h0wg6Y5mQd@|`-6>zUp zywW7sLvzIJKYU?!ztI7vQ%I;Cqw2ZG$GbiGu^~MQU5_jkiIS;&s>n z1tvJ1#r>ZZI5u6I(nABWJta1>8YQM%GjRENftWwIpR;Xe=)*cVujju4xNf-BD(7Od z4pEh}3@&llV%h&<`6+xQds@38;GWGrv3Rzewk)!zt#9SGFVZ>NS2cw7_{5Vv^;?6I zJuN;w;GU#3k9v8ck8PT=r&>$$dKV#k+D%!)ig!|aycxn8NYnb!oTu6p+0!1lG6kQS z9#8hvZ*52Rbdq>>Z6}(HXP=j3Py6!&?lxqoKFQhVCE0r&na>4_N5pj@rUuskFXq0f zBjb7yYxd-GL196>J~ljT)Wc=tSoWng{{g``H!2MToEy?oQK2|>t!+H7l3M)N2 z;PQCzYm=_MXnmlfLX*jI zw$LO-^$&64*H2+MzqvR97ky>GeOPJydoo>Ho*E@y9Ppfro8bKVNdqgNcmv2#c2UA+ z)V+{?Dy>e{#TmCo;4tnV;=LJ0>;<+evUq-%BsrU(`l$`h=67|Hv-!!k`Poa^wpQvMR({u|$J3bb>syTp zzrK~P=r;y6)^pDC?pyhag+82oMZbQZhD)y5Q;$^NM82ZlpLp4P`HB(pt5TF3l6AN3 zDPOT2u1wr*NzS&Xd_}+cK)&J#@$7uin~Z1Y1Nn;GuMW5$WB`UnlbjwMO%BkJIUnSm z?L8kX5?;amF}HaCntjv_xNb81h0O=m|FkM|Ej+3FEk2xdzt4x0?oaw~(*0J-=(QFu z-A^1b8>Oq54<+4C9GRSSKXGJo(*4Ad$w~JUM|gVZO}?@h(*4Ad z8Be;uK)g3{LyX%WPr6@zPQZO_I%joJy59VD$^zN3w& zvE(;bXe{}?1JYRXdk3Vk7?G-Wwj&$3*{$3zRkMs3rllj``GS#@H<7qDd= zh11%J-D}2espDw|u8eq|^+(?pFZ8KDoZoy{2In_-Rl`Nz5^%GTq5Nbyt5nBE!d6-b z=zuS;VZ14BSu$>&tv_LY>ye{yUh9#HHv`xGwm|HCR9!Mnt8YwBrl&UGR;tNByxn5` z4>t$*DFwGA<3*LG)?C~EIp9u->qv5q;JDR<1N7DzqZO0Jd|K0aBMQ>l_`5rhzxxya| zxKU*2oL8plat&dLBi1Ln+P_{|M?5e4FWb=s7e;3G<=@FXZ0C*Vn};dBJyPm!PV@- z9gphYHqx|x|K@a^QU8v@)hgaw)4AvKvs%zmFMTo$yiU0FO7qh6GznUE^~12R&rOQmu01P;r12lSATuL za}S5=*9X_A{FF$n4%TL>-wRp;u|1jZNiU1)mpHuo-ESUN{ru)3)h|kZryx_xVs&YT zFu%D?{hl~7%cAg=#)Dru)h>Sdsa^c);L|R3(g__ewk)yOiFVP|dOIJgEPlR=%Hrj_ zs4UXKybm4Vw_3ZUKB|K|3mJNUdcUiWy5YQhLG@AHjRDVnrRt+TxHFX|NF6Kk+h*YM zJUH22zkZP4wn#kd&u645+nd`K@Z6)UTUY9ba^l(Nst>2vT{>ZI@?P&&KNP@ut%<20 zir_N#QrosxKUDa{Q$JM09fr(oGap-E(^NnB>9zX7&kt5V`00}R!B3a$It=BkOfRP` zi~3;@?uX)TO}3}yiI zm$VL(M?5cG(z9r>52x%`(xvU~@%8El((5i=nu7CMla?;|^`CUfuMW~BzcnxEQr<_r z>nB|*_Ti*Uezs1!PdZj=x6xYy2kbXiJhj^-oYxwK z+AaUqfLn(QO(yfSx~6vXYg4tGUz@7kc9Eu+UZ~wV;Jm(rP`mZO<*5F%$+FnF3~I%? z7uK%w%ujWXz1dA23M@)_Q$h*B%P^;^D3w8Pa|BFM_FWl^lgQg9g+QM!p3;5 zGsyn<>4fZ$pPf`bes)p{?q?^T&;Iz0UD==EPk8H~*1{%zIIV^Gja{vU%@gly>W6}4 zzuPrpt%dd77I4=hLy;ucPu1-E>k-0NS`*OoyWexap5OhR`wQUwp2PM0?)4lln_!>c zJ)X10>G|Dn?Ol5Y2D<`o3l$61C+lhF5$zf9Tb~($E4!U%cg1T@#D zG@9gEXcD9HNa>62@}8eWuVfP^h*ug*f^^6z{vDU>LiXL>c1U(%9Gl^_)-Ah`?-NgU zp$e`OnK@2n7iQqR#;NSWJlr2V;>j*d^?K{E>_Wjk0r&Lucu}>P>_Qb>#`kU3mRh?2 z=VgOs7h2$~A4@v%HnVBUE)2mHrRQhUlwFvCyH{M~q%@l-yRi6$1N>O4GqMXMeSyqx z0c?J<3-xd}AXD1Y<|jRx`=WPy$}d@j+vHJB*@c2HdHbWX3uSPprN^^nkzHtj`#?Hp zZLsXZ9Gq9*$}Z^Ju(9+y*t%~JO;(m6$1g^=)u2;h88AWTX zDQ#`bxn091e#AIk#MY`>y*V4N2d-_wO2 z@E-5_p02=$)Aw{k#1Z3k71Og80@}Xe-AA&a-EcQ> zcPyS7Bev&JHni~J1NODra>|Ad!QGu6&vLS%b;I6k%(9`)aJ*hh&CkY@4ef%{_)aN{ z<@8N*Kb+SyW*yupoR=S_Z<1%>y!2DwBrm~j^T&DlECz_pR)nZ!-0yXBhThT*(yc?VqC zqXExv`}wiIUbdhBZV7G&DQDK93@&k`<|lun z8m?g^;PR8HsqvEjMiYF+D9?M`o06_=(+;0>HSYL<|KyRHr?^qL$X?dYI8RB_w#^LO zb|22H!JfmN;1RC?ZU`=;pQ6gI3@&kGmZf?Hu5ksf9j^2{0rzdp%PDoR?Xm~19rQF@4P2?>slYKG)watLTsz#`Jh=R|*e1BkJh)Q0s_z}J zo@mpog6n`Q^oZ90Hvm`R!L`9H!DW1_Yt!t7tNwn#<#%IK>tNeu7%p$zdkt?2t`yG8 zZY{!9z}Y?Clqc19&%?p&|ABYB61Wi`t`cq%?iP>wZHL?b!+<-T&e=M&!d3j}fcHwa z?mcjcBU8_Y;5v!7F+EKiZxU_+E<<-Mw*c2L;q6D~=CF1PcSL$yTRkg=>wMyX_ntP* z3b@dZ1MW}K%VO(S4>tv;s$}Y53tZwzsh^G44L3`?jQu_~zd`SKwwx1ie$U7A;(ikF z+$WTSGZ^`)_dcN_IKSuWa=0d+=ju8*?dS4ZXKjY_+t1Yn*Fssmp2r8^Dt_+$&Tt$q zaip}TZI?MXzvuC6HUMb_C?{e;f^Lzg9gi9PL zb+9_o59hZwFb3zhHZTk4w>A(uh&hmQdaZed;ilo_Yp2xRwr2!RzkM^~F;;5e^xHRA zc(hp)oZs_*2b|xUcAxS)%iBL4g)8#mX5bP>N_$2%cbHtpCg0>_*j>9WTL8E8WWeRG zTbR(tl5@^5)wOHd5%@j7Wqk516?!>1T=uG<`(M(<_aw{Gf*RQ|IS#*+9du8|)`uQW^7D!InTIdl5cGW4pOd%3 z8X{bjc-wi73au?k=s>dF$$rH(L_b>}A)cQttby~hg-vi?c1t?Y0TM@w-dUU02QgX@ zbT?}ZT%W8*(q<{%N^7CbM+e<;?n$*F%2V}dJ|^fY@6a8|bnTp_dgwc?zlsB?zNYHo z*EUMa?|X37!>`>&;r!ZW2F_1k4Q=A5uUZTB(^sv9`su6ILd(KI_xISIP`}zmix4@h z`Qv?dymGN(doP?GjJ;>qjVaGAYVWSnU~K=)+fGgBW3s(x;Jo}zwf8*SV@hsYGM=>$ zYVW}_g0cP1?@i~_-koR0x%PBU?cKOJ&fSsD+4e3Ay4NW`kXi?|w|Q02%}$?JY+2Oa z*^yx8vze`*+PkTaza{RGrrLY*H9_~&9%(iz-q}I-%gB_n*!;BiTy#$GfHf@H9IZV^ z;aU~%{$%}Tcn zN`6ckyx(4lYB)c?q7lx|uV{zsdSl$Lm`Rp*8uhj1?SuEToBAyPKijC^0`Rh_s;9vQ zj=U-8K22MM{+ynt_%Qs?n}hD_@!vz|otCh1JtS$%6ot>e)O#*h@+-|_-Nf@+^NJ8} z1kUT3Tk}|PebD_mQe{XT=g&8f`RShQs-GUnuCAm9v+&tlgRyt(3(5AW(tI3}-Psm& zzpZl+FRQh6VK~3OjKKNzrTky7zEplqaNU<-KT+^d-sy?HY82N2H}KA&+ofW>D#=;< z*aug6h4)+~yIKR6@mn!rr762Q0QV-P3DV{j%204+(DSz`WLM3O18kWsi|lFzoY(sg z+0}ZuTa?wg$vW8flwD06v9jnY_Fh3}E9v3HdxDW|U{wPyB;_J>D2%c(yaiFcaf^{2;^ztsy@B5oj^lfN|q z$C9xL4JJ8TcjdKGd;0wbf!Z_w13}Mqa2y07-Ege&&o)SkQGc*Hv(p4zh= z&g(s-+OrQX*CU?vdkD_9^-xCLX_q;;lN4_(ol|>O`~236+B0%V{I?6H(&Nd0txa&# z>74x6f@{6oMgD8C4=4XM0++FdXKkMR#Kr6IZ9VEf4cC0VcY7|ub-;P`k@imXz-c^Y z_Mi4n48gt9gVQ?EEF8-e2gK9fiAZZO_AU8Ldb?=vgx@#H+B?xiyeE|Ad@`P``!JmG z`F>e@Cn`U5;PZgSyY^1h!<8w`ywb#6XUBu~PISZB{K84j=4bbx-Qb<3_D=ZObnTsJ zC*H4>A4r|cTj3gSjO*Zj&Xz@cCwkyE(pRDKWLfG|2kF94TQK%Fx89k~Y461FO+oi} z%4&a_QN`2Vi9I)a=cm0BlW;b_Thr6j-id_|2i=S{blWc4I}vI3)+OzosD!hB8|;Df zG+W^O{3`98@bl@lcS7Gac+K;8BVF-z3nh}WrcuMC_92cb&;4AL^iFo)Zy%!UzTY#p?7rVKw(Ne= zrf{BGmKiSnY>MVl<6}2u_mj3EBTd=;#1X5Xu43Qh$?p5Tvy}ws>S-&+8fz@zp*O2?>AOu_x;A4?EXq)PU{xQ z-zeB$9;;7UxA1$0o`&;#hSs`;-#FH~#nh+c&(NPsju|zlkq(!^`;F6TIKOe)D4Xl? zyAK+t?Qq$j3A#6A)7)P>FwZ{tj(fbHBh(}-`EHG$3ugXqnzh#&!!vMR>x&x0^KiEz zQ*^+_(-?017w>o)!<}%$G*(JH+gBRH)t^6L?g*<*HHQ6kd@(xuRsy!A!8lnZxCdOTYe=~C1uo^;7?eN?*Sw>~Oe(%;5Wo2Qi1_Pcbc)n}i- zbg7cE412_rF6HzG-E&AgB~9D!(xoamul%G-dG~vd2kBBR+}`vwZM#U9ioWcxOMc@> zy5u)kNSFM^k#xy#97&fVl;t|&hfYn7qdMuC<^w;y)_gEL;H}r14<>y$l3h_hBM*2# zi%LK1;QoM2=_A{o($B;Z<8&3XFVauH@7krGe(fp!^lMM)r&oJw9+&}@U*Xw_f`lrQ z?b%QB8ntPN6Tfx(Fr42yeFU!V!JzvV+V+6@!wn7v-R~%UkUFlL;99>Pbk9Pja3wjq zk0bMUOoqP^Z-3@1mRxL^?R_i!J<#fhgYI$Ml#;$3JLa*fu+7B7pmFyQ=&Kbq?S;wVT$fo%DJhCa>l*R5xXie5nCd2mOC?|!F2HmSwe~_vpGjLsSzf$lglX0WU)1Yt7 zBi{3n)^O{-6?9KjJl&fzpOnG1!~J7AXUD5yL6T3x`gzp@Ufuh`jGUgwtkhT zuE_@Mg7aE)l0J389gNJZgY>Bn?sN}M`ZVp6ru4~d_5Yol@oxv+Ft=ppCw-cSdq+BF z=Qin6)o9RtUpi;oQ~ERpcV0SY`%n5*w>Rkdjg$1L4bJNwi}Yz0?%wn??Re2#*!P{F z`+g5j3YYvnQ211x&>WTgZRHG3bJXB>gR$Qq&EPag&5i}#2Z^83X14B{qk6xO-Ycv9 z@hqn~D)}3n`#D>O=Ub~XKF^iIq&ZH0ho$Fd)6`nkEZn0i=a-V>$oAhNT=Ms!Gs>d1 zs=jgWG__VW0@q6X&{TSwTC1w~5jx?~E?TSddlqkn^LrNWflC~*_S98uF4bCx?RtJbRgz7y42mEWA-0yi?@?ayhgs_n-?&u=WXRy7GH zJIX2lsi9)+qP41?$)M-=rCO_+gY)tqwN};tv!HvflG~rA)g^uB!_? z!}+Z@RKQjKD(JonM=o^wfq8C+Z~G7LXN*=juWxkKUp;W$#5;n{47DcHZS?aOC;uxL z`&QBphZp8Aj{Yg=YA#RF7pwC&Y{9!~PV;-^zM$tEuKB$M?h21I zHNVfnZT8@12rGO#=&~K+fI1iyzto4*{9X=ssz*G{@4Mii^G2n zdVcRAe{tx!VCHv4c77!36@9|lO(EZBjGW^_F?|Y^CB~lBC+SQDT!znUeFN#t5Zpf@ zQ`*#W(wY3iQ0)EeVd-f~XDZnoYC!I+gu{3oRdv86HAds1ybf%FsFA#TBdYaOi z5jeZXchY{&Z#*_Pu8Ni0j>p*VdKZ&!=kXyotn}mK)GvPMgi!3AZv4JH;wXLz;qA%z z(d6Hc7$W?HWO&&4rJM8eP;8In^W;Csea*@A_LqN*dX*-_!^!+hi$d-#(nXtYOF-qV z1tYJZT(n&*JnCC6^RD>sHD%N{QXGoyueISNzVUNT3dP<(K2N{ux4vsi zc)99%awzt@^3RiQ5#|4EvR$ft%UMl&>YGIW%hu~y={c-{IA?NCsC2*Hy^wfI#5+y# zD$?Vr{)r=V3}~z-j#SQ1p9W6}xt9>(zf&Kt^i>~~tE?pCxkl?(F2$1#NgSzV7AG50 zMVhw#wx+j3Um_ARGoEZnTO!`O(&Np*4JWt{q;s+%iwW+g{agWYWJ5|$4aIEoC&V$y z$N%P?tv`I?$ShCI3Y^xK5=Uk{J0Zl$R4%hWmA}U99?JIyWPDq;jjr$#P<>j+{h2sB z7W~I@-Rbc@jE@6T2v*-NSNEvXAVX^L#q7hj?m_SX;&_ zsQhNYkPj!F8290%6Nw|Ge{5OgizJQ|&d#awMG{9QCtoCSWODLF5=SN{UnFs4a`HtI zM+#@lDPJUUq;R&JODk|%Ye*cK@wC>EI8r#9pVk@@M+#^2(^^B~$mF!vkT^0q?Tbkq znVi-d5=RPW%c8Z0#F5Eqts!xwaJHOUYe*cKoYoo=M<%DWhQyJ=*|KP@A#r4Kt=^n1 zi_cnv<+Rq2I5N}JT0`Q<ZWO%a=)ep4iIq@-!Ns5fWx({G9- zj+A(o({G9-jug(O>GPW+mV2?kDWbeqn&W1e1CHaKSXnec_V1<5E)B)j%R_S7jGs( zAY<*WfcReY73L=0wNhV-*O8g#|BZH0K7TJ|kw2X{GVA`o-A6vYlN~qmo%+uR9q>1W zq`QA7zEf0X`Cs&%Y`grO_)fMgKE9LX{ug~Go1c&GWVyew?_|^T@trL9H};)unm)dh z|H<#M`OP_DEuqAZ%=zf~evi#Bo_>!laipYa>!9CbOB|Wp|K#`BJkqr7 zso!Hu94Yx(PQS;NI5IiE-($1$-wN>>SK!)L;P$M*jd*jmJ*T}nTfZf^iZkQuwW?@* zkx_NX=eNpQ;WjBagDZtg9GUG>wF1}R&DnCcd2_a$z22OSH|)*XcvCBIiz{$>YZJ9g z)LqvxpSENLu5tx#yEkX+(CW?EI`pi-4S91m%}H<0rn%tF*?6ky-+?b|%km<8VHxhXXlk@R~ZGQg`tRvd`z2pnWdN;Fg{~z#$t)9Jzbwu0m zKEAL`)5jOK+>2O8wE6vwePNsCi|~bQ9bSYlZ1eN+g>9NXzOaq=ce0LX%kno~N3?1F zjeTL8=KrEEZ2Rx|ew)~D4)|a6g>Ad|_`;U^8~ehxU0#GQY~#HMU)aX`d-H|;`dz+o z;>eske0*VBzZc;P+rE7fzHs=3u1Ra1x;Rl8q^%EY)e0{^AUw7&rPe3B>45N9I_*B; zy*}YX9^o&vKL20cmnib$%Z*!nopE>FXx#R%8n^6g#%%>vj~ln~2gYsA<2US&;O}b} z1l;!H_#OOcz#abpzv;{05N*6A;70BWxO)nMuHkp?3N8)03p;}D)NMhxmv1Pi`L1A) zeTUJqEVqt*VcI)Y$NPw7jtF_Sk!Lq~R*+vOs3Xr7@@yy1BJ!*t&u;Q8CeLZ|Y$4AU z@~kA!LGsKc&tmcnT@`fa$a8=^=gD)FJhc}~?>=ljrw^(#LUePqFIpBg(a|k^(Y;&x z!T60aS;_}4!*AUjl?yE_FK_v>;wGeZt>*)EiR*;qmO9;MT9C`j|4;u@zu3B@{HJ

boml7sTaJaj9=)6n;!xhU4N@`!3r1r^5 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�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

  • _>Ztvd4E5rHrpD>yE5$1hps8SF{Xuro5XV}g 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

    &XU!?18xT3WY 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

    p7^U0w3a~#E3frp>Jt)ea*84-+&3?9H zLkFud*%vIc>)|=|`QW`_huJGa=!ieq5okyGSQ89Cy*hNo*EObOF_TOdj`wkl;*Q2K74e;KS?%36@`}Sq1q6Z z!d}gi>S;&=&Tc_Hp(M`soEnqjY9j38tu}YWhaBq>j`h?Z@8LZ?mPZtXt?%_%idbn% zh{TWY77Y%`zYdoHzxxJ?zhilROk1tm3bCqh9SgkRdoU~x_09&h?XUC*doADv2m?xh zE-k;Kr@GFQ$GQ#S5f6Y3Y(*8h=2VpCDN0s(FdM=gsW`OH87s)^%U)FV9D zQ~zKOukW$UQjqtlg*_m1Ceo@}&?C(2sbA2;NB4N{2<(%PVU^Wcny%_ZmN2_1N_rsj zeov1`-m!C>j3mUmy~p!7tDi;<$l+p$=UBmId*pgnYLeJgEl~0oJwkj>eS8nk^canb zgzt$!X3F#`0&@^AEozG|8sx-Bc&X5vnng!9w_x)H{oIGKjKS(LBaUs22I zV!EoQeS$>rH2*XSf9tl)zh!lQ-YxCkApSL*tRfvIr1}>Yy%-%H=MBX7xtZ}fWdxzt zD)r68<2RwD#0s_xM-KlMS{r~w3JG)^L5|aVl1+ZdZ#n~zKZk1w@_v@=S$a!j&%+rd z6`*c%Mb_`{cALi8_H_f<_dVw??9BhWFyF6O-y>r;+x_az&hVSME%QWzf^j0cxU*Z> z*k@abBQoEUB=Yzr?N`#6uj)45 zMSDK(&p(`IGAElsusAr~qk62}g0;Ke*3HlFwtOQ`KRN}UPGs%V!YlT7?Q|xA_~9R) z)$M_O0he(fy-D?e-GxXy1I|wa?>lrDFE0F{~BYfz1{yq{0fPZ`Q(M za4~kBHd(ghsCvJ`Hy&7;mO$8)DJPhxASxv^ zzm3q`qF~+AZXt$$?pjctkaINk2A%!iPCuOmF}!=yO+399cq)dx$3j!wV61{0j#{38 z%L23#n<5|4ZM=gf1>Ub2tBGm#Lq6aI3?4Vp9~YWdCd^Zsg>LMC-$_#6NTD0$3k-xjmY2tx zRp=eGRQw;;aQZNO3mV(TSiblXe}^Uh#`6UBc>B6_`cKfFSyQRe8*|Im?8E_42w5g(^qp#R?GM= zJB?>(=0tcc*25<>VIt5RvuT==rFKecf?~bt40zL7@R0=4MBf-D5C=84W2OmRG;tXL zVB`@m*54^6*VuIujX?m(;m$3zr-R?B6b%mjx^|pxPv=k9c}u77n4d2Ow_uCyNOcgm zkf{b<8IPb5OPbUqtUL%sIti_e2bqdbI;ErBg77JjnFqy}ZttxPtC-j!Vi8Fd6qSZ> zY&(R?b)B9)a5GA6Tw-D*eoYPeNpQTUsCQ*pg1Td+#L~>z9&2fX*bUQ`wU@xnMh4Ny z=)uBR-u@BJ%2^sHi1hitcKSYt3Vkq9#dk54mf;^0T;ey=n3^*SDyP7<7{|Edox<@> zucw;-bEoeGKWCY?DX!Q%0Jy{vBRloC*iita>&0&;*e$h z`bQjqn*9mJ1A#JpXUE*1{)meN18$bhMKf@B_*SQIptJr!C!g2pd7ciff9&*l{m7bD z)QPE_8H-LiI7x)U`DYEu#O|Bq*-ex6Yo#g8t_)JHxxwehhNT*;2r#72>coS|Y%-Gw zawoGj9c$?o2Ir?N`b;`MJwlJSo}Xy1o!5!I*7Fi>CIVzOQek_eqh!b3&cML~XPVdB zZ|=1zF{wB9;9Ts%qApaayczR=9+yiYnFB{O+Z~+}$p!lC_)2RI_U-%;?6ZD~*ep(e z5Bv^4Q6`HXi|`d`u` zv>5Z=GiYrFFlbLbXVb!;Sae!kNwXw^~br0-BPK8sW9KKNT2aBMq^XUe@oDd;D03akoS}Fugx?cy=(_%5k+#)*FoPl6F6f z={Qz0hf~gAuroB=!t^Z)!*ndRJbMf2o*Vec zajdjS|DO&t&%~8@)%t(Zqdkx5HBs;u{1V+i-k}4-^#`!+N|>A6Qp_E<76ZZ%#8et0 z{XGZih;8?i4pY+%7)f!(0qw1DUg+?=;-_Oo^p12GUu3jgke?gF(R*UM)qmKS0hh~{ zJA}&}^;bH0SBLRox!nM-!C1T2l!`ob!B=%y9+ZbT?0FMm@QJN7dVcmM zIia*?E%vg=(xxd@Fu<$YpsxyaSf$@dkEPs={t|9)6Z@OXK)3}Of=0`L5JW4scNn)a z@zZXedogD(b&vqyS%@KmD-9FL{PcK-@M%Z=rycy$lx0judn1z?*ljT4AhzW;X8L4O z-SA`l-mr@Hwg9!O8sB=PL-?Sh{zwOJ>9E`{FKu#ecx5evhL=h+TNE}YnM4z7I_6+z ze z{PRkQi18jbF~(tEJMm`Lz5qP`aEIqvnointv{;NUbqFtY)c>V}pWR_JiyZF!4&xKI zRJBX`)S<3yS_(0;AH}cTa^Tw$fSBp1PePT$nmYII{?CJB$}L48E%;WLO|qDcB==fByXbjYwv26^*dc^= z)JJykZ@2rx;Kg~mmMf0MjVN&emUs|nO~Rpev>{ox;B8gHJM_3v25Cd8D1s6<{Psn4 z788@~E&FGytV6ioUN7t5Uu*Yiq1~vI(`UH z!FK-7?LIke#pUh36lRWH#7)!$U(sNAH8b((&b)P49I_bkenW;;UwoI;mlN&6$@cnx zwe!DgH!|{@==D;&9}TZXhz$O1jH4;x_tF@ZV2plCkEOESWJG8MI?@p3wc}7{)RO;5 zKN9*gNq)DYeM=pYph!9m1-h+B_@FHb{#kF4a~?kJua^c(T{92bD)M64je$}i`i61i zkNnbh%e|62CeoFFUT&yJ7R{@aDOpWIs8@(|3sIK;VdBCZt4c}oE9;USORImioqx66 zvh7Fk?6#HdbCUPU;0G_7w!h#Q!*0We`Jyj+ql(Foo}mL1I;~yQU`@g0VY}ahoPG|> zZP51=&Jxbx^u*)`k8dq$7fRaeOWOIEcHeo)Cjmelm1Pc7lZmstChHuHWNC>xrSyK& z6~qGdyUXy?Dp3%H{H^xV@Xg;1>y0rAAdvR!tr4uO8HAi@91R`-xOPQQvihBSh zCc`1U*d{$X;+bGy`z$+hY|AAIAOTpE!ge)oTTC6$|Ai||8LGi$^5 zPIgAksFL9YoN!e-$~LJ(i23&WL6hpiEjjs#^UMKD?fXJjpV@w0a`e2Q9pU%{neimf z^=J=<(h_{#6LI|}ar#Rem?}-USmONicntjui*ZQRhfI!YPHt}SFz5$Mwb8x#5($0sVp9t|qp>7$d?4d&)OjT?#ZQQ~A{x%Nn(A#f zFugD7>aVmlx!8jZR*mf7ecR&?7M4s7)1C*f(k2YG@k4E1*K?jmT&}XvZV)+{qC?>K z2it_cHogzJfAahdMle5?ygk0PzfI_9<2#YK*>VKLq%>TrP@2|+jyBrW5(CCKnuODB zTZ;Col`LA!kZC;4F%81;HsNF&|0&9;_BF^bGU&d8L%UkPXcJo6_@l`AD^ZI>GDmm_ zs%dfBhEA9I@BIy~u@dk2){oi*p^a|<=o_AQWG=DiD5rdvTz|L?8+p0GYBw;uqay6b z#!9)C2K~Ej)s?kDwkgt-P+M?X?je%{<{p;S*CYN@N|^p&Tb%x(4>cifL1POdl+kdX z@u1bX`Lx<77Kv>y6#!L(_sr)$<9%;mlkflmp1K15Dnpj7T)O$A)McURZ+*0A*=9A= zG**Zg4XD#r-6jf;fyaK?^D4&nDiw*G8^0%A%*j>E^>Lcpgp5=5FSYS6wfTPazRp-X7Tg4Bj;QPQ1cGOx}|J|wgy_ixV;?yC@-@?Jd ziV3Hx4U{(i)Qo@+hJy}Wi@IioCfw#e1)Xa!32^e(5>#df6CGfZ18wIc{PpzRQcsV+ zp6~v1v!1X2Bh~ZOfBs87j{o0!vOYV74aL2iu{`|klfkfI-(l%i!CcKG% zq|NgG)%Gp$O;y?6Cy%CS8Zb#qZK0z{O4AhTHBD900y9Y{B^8}1Rj@MS*as~EbuQuy zwO*5;C53u#8&sMCbCW8P7LZbBw1C`c3Y4@>UlbAMq93DnL`O$PQ2`nH{nt4sX+g%B zx%d0{X?o5&Yp=cc+Iz3H_G_(|M5iRf#V3PB037reo}~WrqNqiV)k6 z(R8KxO3iiuTso69H4d(OShqi{3r^zO->KJ&+MUdmm4Y62etDu1;Q_p5wGTVba*vkU zMb1BSkBF2h&-7w1tyby$(+RM!LrQUowEh{IPy3Oj4p0H-6GCwji zVSgv@P0RW3$JtaNZD*^&w}Wt5XIcTNUdXp9@7#&yF~-$EG-qc&_lS_4d-^Ez#&}w@ z&;+#A=Y0VK{PqCmjrTuBSv1Pv6Vh$qZ%lD9!yA4_soZh$rl{Yk{U`X{9?Fh**6=2W z1FvGdr!B6J^oz|eS)6<5qgU!|qraI_wHh}T{JT!HHalk>$H3R@Ji)C)aQfca!qSrJ zlFvD}pWyeKoUd}gD$}`0o=$({BbS#!`vF$3B7w%Za=8)g5P9yk(GM-Rb?Niqv}Pyu z<7Rsv518S*3UK>Gz}^thPK1IrCpvdgx)$!;uYT8e#rv}-e7o3|2?ez8#rsZf1nwDk z^%rOVtl-0ex#L^s#`|*V!wEO_fLHw0?;{8pPsa^-fGmy{Q?qb#JlUCNN`{8=eeVhW zvf{)znUW|Go9`9n2fKeaPQunKoaEcQ7Af_%dav^6?q`Q^)yG4{_S)i`2wqZOpMTaX z@Gm~$i+(Dh5Bkn7!t2rB1SeGJ({9zCr{8INQ&C!a4&X+8+wPd5R%P|ma3$tzVHrUT z4d@&G?KtmRjDP=q^xfkdo3(c#Vc8lUj6iK%-?0!;3(isc%ilpKG~^P=Tn?Ij^N9)X zCIaT#Yj7)%;b`4^6|HQdl})sMHh~rg&HmYOgI7VnTn+>^(NC^D{uWACfTD)gWd`H9JixNM2`kX1CCuR1T zcR?`vMUXujc^6daXz%44{2CZt!JIb>eyLj`?9L+InUoswJL)tlCri-%oFn}C6-dggs|XiUzC2+#dEwHWz9Nl4Q#b)hB0zgr z{)n1DtD#D3?NltShSFx*qWADK7nP{a6t^eHw4x;2yz7?<&Q#bJ*(A1e?+Lp^6fXrn zEfQ9XM7kpgAIZd;9gYT?_eirOa1O#GnVEdQgI!H5oMIITmpmuN1X`jlpvJY&H{=T* zKKRb6DuuZ~4M5?1{MhJ_LZOnK71)$88p?Mw@F=UEP8eK6QEO!`)`|p(S3Z$k5y-Vo zB)7%Gk$J4_@pp0SnSP^`9$S-EE)vYvskN3&i(HJEK8$ez+~?>j`h^s-SK$2k804qx z&~!3CpKl~4dG+e5&ZkyS%hy{nrVnUE!mCPPMhP1D52ogi@hvC!Q+q#E-Du*Vy)3 zN9k*7=RPWP!6as`9Gh@(zG_vcRYa-)s%|~*wZK{aE{U^^FjtnVb2_Kx&&~iFGHw}) z?5mV9b``MK6ny-Ioc=CZ_4NE{Pf)|gR=XWfD0D|ARbN1B*Vi|&9SI(jv!tumbTZdd zgV%Eno;36rHIHyH8c49j7OO=z7CUlQnvN%Lb2U`KfoepLN2;L5%?aSQb^ zs;LC$@t%SA6bbxHgDV*kuGA1=o+~t;TS>@yM)ecKmUNaagTxlHU5F=c>ALe@b2>3H zw~qYP(|4$NJr2P+v<5cB9Noi&tY?n>BeGuf&XuzM{^=?;<{Bp9MpeFjs zb$bq-=EQn^-rB7sQ%vWXsb<*2)u%dh6fgn-@}B230!7*kmL2PiYNa(3w1J(p!Q02i zwSj%2Hn5X6cHn4L#U`>7f-4seQssq+g5$u}RAplt^ZpXVVK(-|peKSDs_UvuP z5Pg$XL%D=uYUI%y@xmzv0X+$_SCR(8BdZ>adQSPjVn;Bm1aPLu#!o8-Ff{ADFSS}J?D!|pZSlmqkV9hc^mB4xPF^G`TV%oaO_{Bv57}Fe3D6!hcToo<{69?uEShuL5CHJB34zT6XMDWgSKQSychqwoAxr z3Vqebt4SK`Np2S=4Lz^KYL(F8p_b8CnY{u_--tOnJge#&DS+OK5&=3Ej0Hk^&AO8T z2JevdHGpgq&F@B_0qEB;gXlHpj+`PX+G>#4WY#<|O5&&0WfEUW0K1%dO`C+nL?FnG`7r(;dS%#r&2 ziiWax>NitjFUg^Ry`cwgbl7)$!Chcc7Um`1*g zPKzRYPJbi6>)8C}UzOjk@%);i`EeQD_RhqN;@FH(FFqe9r}y38RKL4w4b|((Z}8)O z{Nb;Uu5v3Lp83wIHB^r$sg>PT4{tomzgIo%7+ICCa6Beh#p`|`tf{ixF=AdoWD2Wh z&o^5wg%(_c4(>X-Qk^TQD7X|% zdfPc=1PMKQ9e%weq5=ZXP7(ZWrxx<vfp_?jRO5vw&v3p2PIm^_2vTOJ6#z|Cogp=ymlaL5h)Vr1KyPt() zdY&&8@wJq}JclFyK^g2C+_1rW?L3S}dmP5b=;&CDfML)2eKzLOO5jV2jwy{52w2!z zIt=4Vo1xwPp;}Ls*H+BHDkNHgTPLG&?v^eop$qNc2|K>D`YCGlbXYQR>Lx0bOP`4q z7qVpBen+M{l9rVb&6ydn@>m(+T9Lu>H44{X33qEdavC4GXT{AR+32p{em7PNr{q2s=dkX+3agpn zi*I9X%&N?E4)3jqK;o%Qz{3?oY<960C9>Ul6=Eb?k6ny{%V(e;AixLWfz;o`_8MFD zpTILUCtYoUN042kZKzdo77=^I$Ho^^Y|SEZ{66|xf*yKyzu*s_kDT@@+V)bP+FNpy z+;V5gXR>2?MA342W;bTX5OG$k{r3u)4Tae~=<85a% zv;AMRwmK&l{+j(gX84iBR*l&)lHax7LO zddABU$vfZvV%>4~BdIzRxk@K@--RnPI1w+P0W6o=Z&_bO=Nn6FiM@^U4Q_$|j^S~+ zJs(gvt?krm*z4eK4bF3Tb~9KH;G0K-k9PW5vDlu!xT}E*t|h%tU(USe&mW;&de1r@KHi6X~$W6}Gz*>hyBS z0ZDufiqp8b2zB}J=y+WgqoTNpR@z7-Bs5>`6%1cT?NPslc;}rTznS2bFnfn%C%`M) z*c)p^a=rp28z!>HmB>KCWvrM{TPeYD6$Vcnt{_W> z)9<9I0)FQZ*)W7D@cGU)d@zVwAx;+nEo85_>8G|bu*~#GM#UuOVIH|<3a&qAiApOv z4Yw(1;clg0E`@JVAg`EW`VDFfcMluqvSZ_UxN3Bc450-Kx2Ycz0VVhOIr0PI<}ST+ z>JTy~om$3}Xggj^dU}#en3$A2$$j_Jjs04;JINEto6HrxN6oghgUpspC zZDsFfUklFkfz}xrjHvgiyeDP7`+Yk&Xh;T8|27T(z3`2IKcU_|nql5j>E+(rhIou# z7Ng@+-ctFLd+lv_;V2L34Xkcdo!zHwjneN}AKWn1=~V>X!{heJ`oQY5P0Cg;6^&J0 z?uHdn4b|~n(n1v~cPSLehqiX4S!T@iV0}&~@Ze6rP}pS=XoVt=MX0ri9O)Kq*L2z% zO6Tlbtt+J7ZtA6*KCbCvPbjv&N6j&3@^Bf;9O1Szf>62h{-bAiDtq_&HY=NGS>kAB zvV2kisar_UlRv!&rHVl5N8gqNNywsb@cd`5AnLHhkO4T@5I8E9-cwy^4v@% z%A%=55f(L;2ZU$$Dx3DRdAIu<`{>+zC~nv?1T{7MW9r%5Ru25cm!Hj4Hfenx9CX`9 zoNAP63Wg#v?z9+NX;?v_EnuzwMg0HV7P7xg^ELXT{3^u!ZV~lafPON>FT>1tzsPwq z-}nM)0R4QkTacYQ1VRp%`X-X`wezDk8Pyo`w#&o3`A7oP9mg#g0{+}!1kUwMqZZM! z#a3bBl3TnBucc8KykVnPEDDmStb3x=r)pa5wBf{2_NcIJG`3Av;NPdRP)mf8QUbV;PL* z)UX6cVHgc=)C%bDNnC)&-yg^~aqxTjDhJ2&l@GG{#tm}$#t(A&;+@Smc96?AW{}HQ zFv#Wmb~In-zF}86)9jm`&KlruS%Yba7hbZg+Yu#-7%OWyOhO5dwX#NPS~Sqx6B-Dz z!!5*SIlo2jzc9cDQc4a8aVl*6dLoki?wYe3(y#zOV+LOJbnEUW>4vokt;BwBlF(vWo{h@|FIngA4Xe9<~n$?ZY?}dnf+$m5F;ey@ z2ix)$dn!m_n{TH)3RA{Qz~Aw@W*KnWndVNPj7?;M`F01wZQO!rF-w2G)Ef{E-KAXG z3Zf->&VoUQSK*vDxF$ek+Yb# z1kHj$^>hWdl9Ny)x%#VfAiB;&hbO=*H=mr=SKSE;tO|Eim*KY#xN{W&VzO&S3Z_#D zW+vBfV3nk6WePPkXt3YiveZuuxo`|fIP3upB&x;cJ2y_?l_DVA`8;4Dnc@wBVRq-) zjfhla_I7%DQss%XkNl#)K1?j`kauh{((YNAd3iey^be$2w z^ls(wx#|FxeP-Z$Fk|yI@OBJ+4=;N=!R{l&1O+7z#`IL4RglkM@x$pQZn`Q`+{8_f zUX^|Gpdpv#rd<_YqmSDYQ=!Yl}5kq_W?d({4lr z1#TUo;zl-mqdpajgglq*sU<9lbXE4FXe5?Pm8vLg)i{h#n}AB?l8sj)VDgnkhI8f| zjukA>L|=^dgeX&Ju-r!DZHY8quhJ#t)IFk0Tdd1@3YSp`RjVs1nr>b!aNaq@HQn48 zfQ!p@W{U+6*7A&k!g0h^K!mVhOCLdGvGm<=;uWRm1KR9sp=GjeyrOtRz%yP8Z-yj` z!OdVQ@U3;P=+a7bInSWL_{XMI6%{2KkXCnPT4E&JwPFQh0$vgDesnFoDH8rn1l<0C z9#!p&!7Hl9(Mw`=WHjG;;C*y0yz3(}$1Mzp^eX4Kw65tY&#DnZdpXZ>ZxPw$7Jjy- zk?6N)&UAj=@8A{{SIpEC%%jzDt6{EkT@4g74&uAspX{haZowR894;JK`X z|ESYEGN8_+{;sv_y2VIMY4s}2Ls4)f&1Y`549zIRvHr-S)=WM@g4`9!BLU{Y_X=?% zwplaPY&JKtx?w^YW@<1u45M(QQflOOPtXY^bCH`ciz8o(7BP!KprQYkX=8h?Ye|%=Z50WPB9(Y?W|qf zFMUspO95DC*Q?-PORHF~U8=3~gjDUjFJaEIWZGZ(^rJnvebwpOeF+=lh@q6Ehj(PL z|CvGOQ(IG|CkNNU(zGEwK)lMlM6YtqYmYo3mu**3JR}62fp}ti2Z*F`eVE`qWTn z&Ak`eZYOsf)3DNw%l|8#KlG6SmX^CLvP)1g5zdp+h4S%<>v z9xJKS=lly>$>!18RH#X4S=DsI>A(|U#S>4hrtB#OSGb|luFCpOId*5!L(|5{;I8Ne7J;@<>~E=?_g)^Z}55si@?B3%9%7f2d$H%aQ-2TJKF_0 zj2v%|xO~sf>PT{YWln9O_;M*!Hm^V4o9BhixmASEn@q9w9*sO6t=@LQ?DDDGIy#&C zE;2GYA>r^E+WRiJeDb#f!Ws?Z>J!t3pv#?HeUZ$IR=>}!_-u{aI;{@?tOK%uB)!DZ zR@&xPIL@0>ze8BPg&X)(aGxU_3<|->ZvUl$%1;?beJQjG;a$MxueJ>^n_VFVhpjJ& z#o3GlaWBM?;Vu_#6&(=0Ad2_MIpNeaa^>jh||R>dsXoSzQ_KOboK|IR-;u`9VZ3|F#Yy^< zCAggu9teg}u0H?o2GakVX-tm&Wb$y&K(Gg}C;j^da80?ouWhfwb5OBqzp&t^%!U&f z1Wj8FJ35A&iH+tir^>%!T&<4H2=;4+yKw}bm5;rM&H~w5bOX{=ife% z{tnZT($<#P)~aqv0o7K8eE;^@lZV>}f^9(2`+q$!dXxzy`+SLpJauleiI8#>`*QN; zct!H8Ktd-HCYVy>Q+)`DmSRfgKF+Nb1yVYD)jcVoX!Je1Jtx-Jj6PE%W$IcD7tLAm zhW(x07=!IT#)!TnGW&cDMxWZyO5LYzw$hj5K*TQArZ%O)@y=5N!Kcv(U-+LIfYgA$ z_a_H}j{|Vl|M&ndD(7Zq=6Z9Gt~Do;%$p`ppq+<u{gr?7FBur^WDrf+t7ye2sg0A*44~FvXY;}Hp{Y<-$b9NswZfWtm<*B+oL>$E z?+0)1^Dh`kKfpKd&j*4_0sE!jIxxDQqt=X}mM5*}*~Zj)eHXwt-~P@TmTl1?*X&dK zT8M4%zla`uayWM&n8)_un+MY0V#23}XAJ~zWqa_S@0^}YtcJIGJv|DX@o(}p zW^O_UoCxX3%-CW;4+Y0suo3h9?wg6e4c@lW#};&7Vw<%wxw?0IEn+?n1ZSWwxBIUf zNZ*aiQ|AK-h;!DeSlHU?^(Jo!7`%$S+}4e?aT_|2d3@^nRzp|#f+tOMA`R63(jWY?pGNq{{ps77@O#5w^ano&;F$lz{`6NF@7$v+ zgl$`tKCfc}hc;RP8t2)`%#5v9acCzyz)wsaVh1>Jh?Q^G;?N)ZgMa8J4jt)F-^!R0 z(0g(e3v=?o9=01bZLW>uJ0|KmD>v4Fdd79fGIbk$UzxMweBMq-V|iS24~boap&?7= zd&S`CxzLt7VFZ}cNSN&G5AH?K&pLaklosu0bd z4bhmRcneo_oBMlDzjaI|k=eV9c;7t2{M^L=ADNfIw$mW=Q*&$hih_K>VbBIrVgNrh%ws#sr{SM{C* z+Gw+4Y3;%@TW*>C93-1t@t0fs*8M~-$KxY40_FuV@j@EAQaN1HAN-I0_E&cJAEv5R zu4C5yHY4@{;ky6Q9DrNfZS}|?scCl6p$Y_N+tCXfgGy4_H75ClVGv8AU?H;vE_a{q z4_2dfR{HPnPhZQ#{z+Icmmv@PPxJ>L2mDw5d;7OJ;W_KsO}DU`Fs2()ZLb-lGX@*^ ztjs8eX>piL3#ddgExd-}!xyYNQ9uWKVo`sv7`z?vFX&HyhJniYS%2_O0FL?R^`}=e zg?TO0+vY2qqV|Gq`hu<2D3R6yzg)zm<-0iPE zS9G_*W-S8LYCU+~mRNoGp64bHTl#|*wgc+=M;~QMlB&CLKWyRRrTN7N&lgT9%~!0< zzkObh7;m;I_{P246LA8hO0gmlus;HZQ?3Wkmn0UM#dUCK-XAoAVvS$bKl-mUL|;-A zhZOhh71lH`#i2_}?{ci@EbqRwVcY)SqLb}cyft<5a8iFT32D;&G5y;fWL9=8-2WEb z;U)A3m4NE}W8w4%n8qpf^@;UW>J?K!6M-N0F3+7j9N!<50!8otHk`f!7T|e%N@;0g zDLP|{ox^LYTDkIGi91O?le%b%B5CE`&`E)N;8=T3W7wd53>VHH`TiGV;u} zPS+u-VQDE_!zzRI(1o_=lq)CLU#M$+IM{%yeSu}9%AYatq5L1=U>yKw{dM8WpE9-S znYZ3+x+2-VdifM4(agLAae`Ghh!gC`RQea@lL4+RQI%GzSEvw{4gR%76v_{_>j7b= zHq(fZt=8^1X5o6)`ok5=UK7*o1ra)4ZwYHw8YK)+!He4;+PIjP!Nw2)W76 zO-Od1@w)4mpB>C3w~Jh_YiYvuk1tfPfv8*;Yi#OsYSTDRxG2|_pB??MNF12sfc zVdMWi=zlouyrl=yrSdk;CWnI%a&*Mfrn`G9!@-r{Nwt4vIDIYyCrEe7fYADCb8q7I zoax4cpJCy$xhrvW-dKxu%TgR55~hDM=6j{HMU`u8(e4sbnPogZZg;~PuG=SUE*A!bbx)-~bqSS$ z?aaq5urQnq-zrnHIZOzFVr)Nn9y0aMV@Q`*{at$1B{(Pl6hvDCp#B;EK$%xQNH|j~ zAC27YBOYXhgIVCgNB+$4XciMbh!I3Fvx|l2+UdN48Xz(fcWfkH+qk zQS3g{$qU>qPX$yysLWkAIQ#!w}jhNhJZB zmXhDP^re>q*7U5ge$gIjxKcG%GOzOVm~XMmW~_XF%=c=iO@(P=5rTv#*tCT?HdUdh zByG!Y&0m|HJw5ykGIXQE_J(=B#%wM%&sP+tQJvE(BXzEO)D8D(wet%^*crydVs|=b zUB*hGyAp4eY~NrV$5Q_^vMc=2eu;POct77~2!mtN;qkR`NU41RW(IGQ5{(8 zQ{d^|A8*$%qt{tMiA+V1ho!eYPbSrh=EN{9i+Z4((mi?+{)@?$~BYoMohVpJHTZy-f6x>Jyy5z4!@>=#63QF&kXvOZ(kZQ(>qX`i)h zR(3hwOxm(sO=+LDEKlnjnW52^&DQ)9Z*z*v=4$T4!;j^{`-Dc{k(zi0{)5VWC#F{2 zUSJpF#>5hqFZ1ZsqGW&B($}2#6CcevowqMes$iyR<t->*o>qI-*zCzIfKf~_B|mxo<`?=2Z@Xptz@0mv9L(~xI!tcRUv{5 zd<4`M*Ou3=u65OV@D*hz=NoY~w=&Fvbnz~-E_cNw-6o#B-r+cRwny0%KK-_GUwiP4 zUT9t{qJ`oej^7N+`>;doIHd;QWIPKsyOqOI7FtKp+IZYlIm0G7p;5pXonoXGf-$$Y+PAM9qmTem++K~>h7Agxhh1rFR3d;%~mYZA_Ns7hg z`%l**Ih_?2EZhUWHwv8nz3Xe`Q*RPGW~@~^9<9H9Ey8DDMQ+O(&GI7{ut`F}w$8EO z;a)_;Jk;xa@CZ_A{p3xu-u$&|H-o#P;4p4bFi96dHAutAQwBf+d6jN}IR zv;9k7lqdWBM>g#fx_o3**7XMKKy8=b-Mj4oM~W|0u|<&je6dATsLa*K)4oTH9Zv^D z?nlCI=QF)nN)Uh{&ZqH&A8?T!XKY(hRpAyIl|@x+A4b42YAbmO+f?pkwxz;(2vRnW zO+Dz zpcdD8Vwc6n=S<6|wK=?egqNB5-Ka>|7`sd`Ex&Ya*EDyj-i3qrcSs%Dirg6W^a^e~ z{=+nKV{q|-w>x8FJfye~-7bc73I0U-mfXta#il_Y z%h0?>X?WDRdqIG$hm<6KLUpZbSc(`bNc` zqR?9@Qk=5#_@=DOO+4Dn1Qoq|6unT?=}l+=GFXrLguzL@dnOTZTO-4YNSq0vZdrL3 z6K-`2&bpQILbB&YygbzklM&Iuguv~@cP+Nmv{AVlDu-Px>U5lwPunVUY+gHjz6Wmm z;f}mk9qynNi4Fc*45=C@+v7*rcC|2N9~`kuGLk56blK-U<+>Pn4luu}J}p7OFj?6r zJ$4Bm<&qjTV=lcrTu|=&$vwASXD-bT|1tF&Jf+Hs5qHSZD^uktQI@>yRF7Tv49)Y! zNNHEu-(&-fQ}E>_O)+K1dhEBc@N%6p5&;|U56Ld|+Ep&y6nj!f!7jRg@R01YRv1`r z8=hA!?xZ;EjwLUBt2xx8)e24v$)y)l?w1r0|LK9BADppbgXFa1oKC;^8&iCeEmoIY zs%bh>V`Ox;X>Z!HbS1*>e_@<&^th!*rGlip5UvgZzPmMyb2keTCcP3Pm$v&Jq|GyD zD+^F+1OoOH*eT z;LatI|ET~L3P?Xt0FL5)e*v6_O6Tmaq2q<}{pghVGdnylwGJ%=PS)A7vyT}5tPTkE zaQk>2>Wp54wyWaWu8cVv6nFWgbAFC^3co^cc~2m(lm8rDsq!Hv{Ha#JxRnn42eIl> zNBa*O=nOH<(Z!I6Iu-JQ{|;h)f~zd4Tz3QCUeCzD)#X!#-8V3!%f|NDdp?i%{azhh z-a|IyJ!1toAmJVIcHh`Szw^3ov{FE5m)Vbh01urMQjA(ZB7%U=mM*U_ z*L1qd64wabvF?udWDED~ibbktWL=uXg$*vvl=XXIoaYlIk0!Axr!*h-#+?$}Uc##W zs+zSJN<}NyLO(Beuj4|&QMrv}Q%ovjS_0zd%4O)K<7N8vaHLGHv1KYctY!=KjLa=? zyxO^R*;8&u6ky@i|*)fEt;Gqby;o*2(>e?`6$@1hfN$1bSUGS zTt30}TXgq|@vo5VRLDbU}nLWHipT79~FRnVIOA!$y<9yO}**~RbUc%wM1J`VS~RUZ8m z1fY?kv&3=y$GHadSa#q_nX56lWA45!v`pZ7{o?xd(MT>krIfi#FCwn4#%WZn!K{vu zPp1AI$GH5Gbxksse57|q^ydE}Pq*y=WolZDyCv8>T5VFf(x#XaI&# z9w5@be0I}F0QxqN0PK4GB96jtUR&>b6epeQTG{N@V2(u%sB#BbgO+T<@KY`UGb>rh zStjU%ET$a-kh2(BIiwTC+$kbmBNtE0?)!mV|i;5SH1ZB&%s$tFI7 z=kU}eul}_}Rh%9XRx!+y^BGmxAJ&uuNdZlg#L<#k#M-PA*W+t$5fQt;o7?0NPGMBQJwbxBS(ZrjbuM#poOEA7pwZEQdu@Om1rx5~fD z$dSX0WKC&e!bJNwBJ0?iBl;>BF;^D;G%~)a>QX@H3d>fR0X>`nOp2}&8^$z2dzyflPLxSrZM;HWv2MO-uJb>V9VpKg zb4XHRm_ue8ez;J7UzYtpF3+UDTb{oo=cqgr70cfib8nwvSu#F<&5GgYui2kZ2=l9)NFQA9*IoX|hCm9|3FSF!PJNQ-hFgntBy3QiZ z425DsFyO)N;9&PJ9`Fx#rysdo)gB91$LthAuFYgS0@*6AgALAuWUFl0gSF)EXgbbS z1_n!QgG(Si+lbo+9?RscIFIGJlrqbKZtEgCY2vbAX_?e@dr*$v=C;DoL8C!02q@JE z`Ls`^eV_=BkY=x05F)&oc+F{{5Ocyf6ye((N4kk|v ztNQHfkA+r$3Y}Jg*czp^=&9~v(8^a@iIz=fO9U-3^I4Nsz~~Ysy6N%Vn4_hN;a(Q! za8eWTmsCxm$R^TTGZM)uBs;5(+QiGdgUh?yeVhEH-IY5o|D!hX56uBS=>osldC5OD z2S_6}leGB%H3xvqzCxb9@weqEEyh81vA4dj7@p(HJ?vTx=KNB!4C;9!fwdQKdU8vk z?h)4xF^tFp0pn_sTxm^`(a;`5j2Tq;CfSnpj7yLjH53o5OBp`EkNiZNJ6~oFcjtFGS z59o{~y+VF<`@+~~tSRBrE9)8?RiRqe(4>M*v}$>p-z(Px-z(R0KqN-rDU25kY+7La zUeTy+{*kh!UtKouRb}I}0$;ZAb%3RhIv?(O!X&|2KN6pq1Yu=>bgTr1OF6qR0RQKe z6_@u^AO1Z4Jz4PS!?Z`V`0^F&0&&st_rF~i_%TuXht~zz@ul^Bk+BhGWKGmS0ndJD z{cKwb-|2vdD_k`Awp#{(Y^Q%7ZVK%m*jR9?q9T;|e(SI(PhGD{EtLJvtUOwh3(j8Te*(F`Xok7s_V0QtSI{hP#ul ztyOnnk=PljA@BRXhYKToS1brI1R7ugtjpqE@?UC6lz9YC`xLMjr~A+{Rb%-ALL2W7-P@bLeaemE7G3KQI}a14zuLjF=Kxe zW;}e(Br+a6O)Cv8X?t_j%@#Fq{yYZEqy1;&CZ+r+^QPRn#vss+_A87Bha%K!=XvjH zso49-S%+Eup~c`Zcb`TmY$QXG;Ac~=f(AzcQ7E}^(pkA`5;%WWtDL4a*hJrcAdAnz z%eUp<8VtA4RUV_#pmnO%=a9l65@iXaHIi#7A#R5<;&Yl> z{SnoTJ+lnB*v!FiQLA$d2JREx8N|OL1^xc*f4kT0oi +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "nvs_flash.h" +#include "driver/gpio.h" +#include "esp_rom_gpio.h" +#include "hal/gpio_ll.h" +#include "hal/usb_hal.h" +#include "soc/gpio_periph.h" +#include "soc/usb_periph.h" +#include "hal/usb_hal.h" +#include "glue.h" +#include "cppmain.h" + +static const char *TAG = "app_main"; + +extern void cppmain(); + +static void configure_pins(usb_hal_context_t *usb) +{ + /* usb_periph_iopins currently configures USB_OTG as USB Device. + * Introduce additional parameters in usb_hal_context_t when adding support + * for USB Host. + */ + for (const usb_iopin_dsc_t *iopin = usb_periph_iopins; iopin->pin != -1; ++iopin) { + if ((usb->use_external_phy) || (iopin->ext_phy_only == 0)) { + esp_rom_gpio_pad_select_gpio(iopin->pin); + if (iopin->is_output) { + esp_rom_gpio_connect_out_signal(iopin->pin, iopin->func, false, false); + } else { + esp_rom_gpio_connect_in_signal(iopin->pin, iopin->func, false); + if ((iopin->pin != GPIO_FUNC_IN_LOW) && (iopin->pin != GPIO_FUNC_IN_HIGH)) { + gpio_ll_input_enable(&GPIO, (gpio_num_t)iopin->pin); + } + } + esp_rom_gpio_pad_unhold(iopin->pin); + } + } + if (!usb->use_external_phy) { + gpio_set_drive_capability((gpio_num_t)USBPHY_DM_NUM, GPIO_DRIVE_CAP_3); + gpio_set_drive_capability((gpio_num_t)USBPHY_DP_NUM, GPIO_DRIVE_CAP_3); + } +} + +void app_main(void) +{ + //Initialize NVS + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + glue_board_init(); + + for (size_t i = 0; i < 2; i++) { + HAL_GPIO_WritePin(LED_SYS_GPIO_Port, LED_SYS_Pin, GPIO_PIN_SET); + HAL_GPIO_WritePin(LED_ERR_GPIO_Port, LED_ERR_Pin, GPIO_PIN_SET); + HAL_GPIO_WritePin(LED_CLIP_GPIO_Port, LED_CLIP_Pin, GPIO_PIN_SET); + vTaskDelay(pdMS_TO_TICKS(100)); + HAL_GPIO_WritePin(LED_SYS_GPIO_Port, LED_SYS_Pin, GPIO_PIN_RESET); + HAL_GPIO_WritePin(LED_ERR_GPIO_Port, LED_ERR_Pin, GPIO_PIN_RESET); + HAL_GPIO_WritePin(LED_CLIP_GPIO_Port, LED_CLIP_Pin, GPIO_PIN_RESET); + vTaskDelay(pdMS_TO_TICKS(100)); + } + + ESP_LOGI(TAG, "USB initialization"); + // Enable APB CLK to USB peripheral + periph_module_enable(PERIPH_USB_MODULE); + periph_module_reset(PERIPH_USB_MODULE); + // Initialize HAL layer + usb_hal_context_t hal = { + .use_external_phy = 0, + }; + usb_hal_init(&hal); + configure_pins(&hal); + + cppmain(); +} + diff --git a/Firmware/Targets/ESP32SX/main/cmsis_os.h b/Firmware/Targets/ESP32SX/main/cmsis_os.h new file mode 100644 index 000000000..9d7056b85 --- /dev/null +++ b/Firmware/Targets/ESP32SX/main/cmsis_os.h @@ -0,0 +1 @@ +// nothing to do \ No newline at end of file diff --git a/Firmware/Targets/ESP32SX/main/cpp_target_config.cpp b/Firmware/Targets/ESP32SX/main/cpp_target_config.cpp new file mode 100644 index 000000000..3643f1f67 --- /dev/null +++ b/Firmware/Targets/ESP32SX/main/cpp_target_config.cpp @@ -0,0 +1,59 @@ +#include "cpp_target_config.h" + +#if defined(SHIFTERBUTTONS) || defined(SPIBUTTONS) +extern SPI_HandleTypeDef hspi2; +static const std::vector external_spi_cspins{OutputPin(*SPI2_NSS_GPIO_Port, SPI2_NSS_Pin), OutputPin(*SPI2_SS2_GPIO_Port, SPI2_SS2_Pin),OutputPin(*SPI2_SS3_GPIO_Port, SPI2_SS3_Pin)}; +SPIPort external_spi{hspi2,external_spi_cspins,true}; +#endif + +static const std::vector motor_spi_cspins{OutputPin(*SPI1_SS1_GPIO_Port, SPI1_SS1_Pin), OutputPin(*SPI1_SS2_GPIO_Port, SPI1_SS2_Pin),OutputPin(*SPI1_SS3_GPIO_Port, SPI1_SS3_Pin)}; +extern SPI_HandleTypeDef hspi1; +SPIPort motor_spi{hspi1,motor_spi_cspins,false}; + +#ifdef EXT3_SPI_PORT +static const std::vector ext3_spi_cspins{OutputPin(*SPI3_SS1_GPIO_Port, SPI3_SS1_Pin), OutputPin(*SPI3_SS2_GPIO_Port, SPI3_SS2_Pin),OutputPin(*SPI3_SS3_GPIO_Port, SPI3_SS3_Pin)}; +extern SPI_HandleTypeDef EXT3_SPI_PORT; +SPIPort ext3_spi{hspi3,ext3_spi_cspins,true}; +#endif + +#ifdef UART_PORT_MOTOR +extern UART_HandleTypeDef UART_PORT_MOTOR; +UARTPort motor_uart{UART_PORT_MOTOR}; +#endif + +#ifdef UART_PORT_EXT +extern UART_HandleTypeDef UART_PORT_EXT; +UARTPort external_uart{UART_PORT_EXT}; +#endif + +#ifdef I2C_PORT +extern I2C_HandleTypeDef I2C_PORT; +I2CPort i2cport{I2C_PORT}; +#endif + +#ifdef CANBUS +/* + * Can BTR register for different speed configs + * 50, 100, 125, 250, 500, 1000 kbit + */ +CANPort canport{CANPORT}; + +#endif + +#ifdef PWMDRIVER +// CCR and channels must match! +const PWMConfig pwmTimerConfig = { + .channel_1 = TIM_CHANNEL_1, + .channel_2 = TIM_CHANNEL_2, + .channel_3 = TIM_CHANNEL_3, + .channel_4 = TIM_CHANNEL_4, + + .ccr_1 = 1, + .ccr_2 = 2, + .ccr_3 = 3, + .ccr_4 = 4, + + .timer = &TIM_PWM, + .timerFreq = APB_CLK_FREQ + }; +#endif diff --git a/Firmware/Targets/ESP32SX/main/glue.c b/Firmware/Targets/ESP32SX/main/glue.c new file mode 100644 index 000000000..e3b902bfa --- /dev/null +++ b/Firmware/Targets/ESP32SX/main/glue.c @@ -0,0 +1,667 @@ +#include +#include +#include +#include "esp_log.h" +#include "esp_wifi.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "hal/pcnt_ll.h" +#include "hal/gpio_ll.h" +#include "nvs_flash.h" +#include "glue.h" +#include "target_constants.h" + +static const char *TAG = "glue"; + +ADC_HandleTypeDef hadc1; +ADC_HandleTypeDef hadc2; +SPI_HandleTypeDef hspi1; +#if defined(SHIFTERBUTTONS) || defined(SPIBUTTONS) +SPI_HandleTypeDef hspi2; +#endif +UART_HandleTypeDef huart1; +UART_HandleTypeDef huart3; +CAN_HandleTypeDef hcan1; +TIM_HandleTypeDef htim1; +I2C_HandleTypeDef hi2c1; + +GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin) +{ + return gpio_ll_get_level(&GPIO, GPIO_Pin); +} + +void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState) +{ + gpio_ll_set_level(&GPIO, GPIO_Pin, PinState); +} + +void HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin) +{ + gpio_dev_t *hw = &GPIO; + if (GPIO_Pin < 32) { + hw->out ^= (1 << GPIO_Pin); + + } else { + hw->out1.data ^= (1 << (GPIO_Pin - 32)); + } +} + +void glue_pcnt_init(void) +{ + ESP_LOGI(TAG, "%s", __FUNCTION__); + +#define PCNT_UNIT PCNT_UNIT_0 +#define PCNT_H_LIM_VAL 30000 +#define PCNT_L_LIM_VAL -30000 + /* Prepare configuration for the PCNT unit */ + pcnt_config_t pcnt_config; + + // ch0 + pcnt_config.pulse_gpio_num = ENCODER_A_Pin; + pcnt_config.ctrl_gpio_num = ENCODER_B_Pin; + pcnt_config.channel = PCNT_CHANNEL_0; + pcnt_config.pos_mode = PCNT_COUNT_INC; // Count up on the positive edge + pcnt_config.neg_mode = PCNT_COUNT_DEC; // Keep the counter value on the negative edge + + pcnt_config.lctrl_mode = PCNT_MODE_REVERSE; // Reverse counting direction if low + pcnt_config.hctrl_mode = PCNT_MODE_KEEP; // Keep the primary counter mode if high + pcnt_config.counter_h_lim = PCNT_H_LIM_VAL; + pcnt_config.counter_l_lim = PCNT_L_LIM_VAL; + pcnt_config.unit = PCNT_UNIT; + pcnt_unit_config(&pcnt_config); + + // ch1 + pcnt_config.pulse_gpio_num = ENCODER_B_Pin; + pcnt_config.ctrl_gpio_num = ENCODER_A_Pin; + pcnt_config.channel = PCNT_CHANNEL_1; + pcnt_config.pos_mode = PCNT_COUNT_DEC; // Count up on the positive edge + pcnt_config.neg_mode = PCNT_COUNT_INC; // Keep the counter value on the negative edge + pcnt_unit_config(&pcnt_config); + + + /* Configure and enable the input filter */ + pcnt_set_filter_value(PCNT_UNIT, 100); + pcnt_filter_enable(PCNT_UNIT); + + /* Enable events on zero, maximum and minimum limit values */ + pcnt_event_enable(PCNT_UNIT, PCNT_EVT_ZERO); + pcnt_event_enable(PCNT_UNIT, PCNT_EVT_H_LIM); + pcnt_event_enable(PCNT_UNIT, PCNT_EVT_L_LIM); + + /* Initialize PCNT's counter */ + pcnt_counter_pause(PCNT_UNIT); + pcnt_counter_clear(PCNT_UNIT); + + /* Everything is set up, now go to counting */ + pcnt_counter_resume(PCNT_UNIT); +} + +int16_t glue_pcnt_get_delta_value(void) +{ + int16_t count; + pcnt_get_counter_value(PCNT_UNIT, &count); + pcnt_counter_clear(PCNT_UNIT); + return count; +} + +void glue_pcnt_deinit(void) +{ + // pcnt_intr_disable(PCNT_UNIT); + pcnt_counter_pause(PCNT_UNIT); +} + + +HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef *hspi) +{ + if (hspi != &hspi1) { + ESP_LOGE(TAG, "Can't init spi2 on esp32s2, don't have enough gpio"); + return HAL_ERROR; + } + + esp_err_t ret; + spi_bus_config_t buscfg1 = { + .miso_io_num = SPI1_MISO_Pin, + .mosi_io_num = SPI1_MOSI_Pin, + .sclk_io_num = SPI1_SCK_Pin, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + .max_transfer_sz = 256, + }; + hspi->Init.BaudRatePrescaler >>= 3; + uint32_t freq = (84 * 1000 * 1000) / (uint32_t)(powf(2, hspi->Init.BaudRatePrescaler + 1)); + ESP_LOGI(TAG, "%s, freq=%d, Prescaler=%d", __FUNCTION__, freq, hspi->Init.BaudRatePrescaler); + spi_device_interface_config_t devcfg1 = { + .clock_speed_hz = freq, + .mode = ((hspi->Init.CLKPolarity) << 1) | (hspi->Init.CLKPhase), + .spics_io_num = SPI1_SS1_Pin, //CS pin + .queue_size = 7, //We want to be able to queue 7 transactions at a time + }; + + //Initialize the SPI bus + ret = spi_bus_initialize(SPI2_HOST, &buscfg1, SPI_DMA_CH_AUTO); + ESP_ERROR_CHECK(ret); + //Attach the LCD to the SPI bus + ret = spi_bus_add_device(SPI2_HOST, &devcfg1, &hspi1.Instance); + ESP_ERROR_CHECK(ret); + return HAL_OK; +} + +HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout) +{ + esp_err_t ret; + spi_transaction_t t = {0}; + t.length = 8 * Size; //Command is 8 bits + t.tx_buffer = pData; //The data is the cmd itself + ESP_LOGD(TAG, "%s, len=%d", __FUNCTION__, Size); + ret = spi_device_polling_transmit(hspi->Instance, &t); //Transmit! + assert(ret == ESP_OK); //Should have had no issues. + return HAL_OK; +} + +HAL_StatusTypeDef HAL_SPI_Transmit_IT(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size) +{ + ESP_LOGW(TAG, "%s: Unsupported", __FUNCTION__); + return HAL_ERROR; +} +HAL_StatusTypeDef HAL_SPI_TransmitReceive_IT(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size) +{ + ESP_LOGW(TAG, "%s: Unsupported", __FUNCTION__); + return HAL_ERROR; +} +HAL_StatusTypeDef HAL_SPI_Receive_IT(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size) +{ + ESP_LOGW(TAG, "%s: Unsupported", __FUNCTION__); + return HAL_ERROR; +} +HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout) +{ + esp_err_t ret; + spi_transaction_t t = {0}; + t.length = 8 * Size; //Command is 8 bits + t.tx_buffer = pTxData; //The data is the cmd itself + t.rx_buffer = pRxData; + ESP_LOGD(TAG, "%s, len=%d", __FUNCTION__, Size); + ret = spi_device_polling_transmit(hspi->Instance, &t); //Transmit! + assert(ret == ESP_OK); //Should have had no issues. + return HAL_OK; +} + +HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart) +{ + ESP_LOGW(TAG, "%s: Unsupported", __FUNCTION__); + return HAL_OK; +} +HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) +{ + ESP_LOGW(TAG, "%s: Unsupported", __FUNCTION__); + return HAL_OK; +} +HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) +{ + ESP_LOGW(TAG, "%s: Unsupported", __FUNCTION__); + return HAL_OK; +} +HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout) +{ + ESP_LOGW(TAG, "%s: Unsupported", __FUNCTION__); + return HAL_OK; +} +HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) +{ + ESP_LOGW(TAG, "%s: Unsupported", __FUNCTION__); + return HAL_OK; +} +HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout) +{ + ESP_LOGW(TAG, "%s: Unsupported", __FUNCTION__); + return HAL_OK; +} + +HAL_StatusTypeDef HAL_I2C_Init(I2C_HandleTypeDef *hi2c) +{ + hi2c->Instance = I2C_NUM_0; + i2c_config_t conf = { + .mode = I2C_MODE_MASTER, + .sda_io_num = DIN2_Pin, + .scl_io_num = DIN3_Pin, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master.clk_speed = hi2c->Init.ClockSpeed, + }; + + i2c_param_config(hi2c->Instance, &conf); + +#define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C master do not need buffer */ +#define I2C_MASTER_RX_BUF_DISABLE 0 /*!< I2C master do not need buffer */ + + esp_err_t ret = i2c_driver_install(hi2c->Instance, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0); + return ESP_OK == ret ? HAL_OK : HAL_ERROR; +} + +HAL_StatusTypeDef HAL_I2C_DeInit(I2C_HandleTypeDef *hi2c) +{ + i2c_driver_delete(hi2c->Instance); + return HAL_OK; +} + +HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout) +{ + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, DevAddress, true); + i2c_master_write(cmd, pData, Size, true); + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin(hi2c->Instance, cmd, pdMS_TO_TICKS(Timeout)); + i2c_cmd_link_delete(cmd); + return ESP_OK == ret ? HAL_OK : HAL_ERROR; +} +HAL_StatusTypeDef HAL_I2C_Master_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size) +{ + ESP_LOGW(TAG, "%s: Unsupported", __FUNCTION__); + return HAL_ERROR; +} +HAL_StatusTypeDef HAL_I2C_Master_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size) +{ + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, DevAddress, true); + i2c_master_write(cmd, pData, Size, true); + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin(hi2c->Instance, cmd, pdMS_TO_TICKS(1000)); + i2c_cmd_link_delete(cmd); + + extern void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef * hi2c); + HAL_I2C_MasterTxCpltCallback(hi2c); // call the callback + + return ESP_OK == ret ? HAL_OK : HAL_ERROR; +} +HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout) +{ + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, DevAddress, true); + if (Size > 1) { + i2c_master_read(cmd, pData, Size - 1, I2C_MASTER_ACK); + } + i2c_master_read_byte(cmd, pData + Size - 1, I2C_MASTER_NACK); + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin(hi2c->Instance, cmd, pdMS_TO_TICKS(Timeout)); + i2c_cmd_link_delete(cmd); + return ESP_OK == ret ? HAL_OK : HAL_ERROR; +} +HAL_StatusTypeDef HAL_I2C_Master_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size) +{ + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, DevAddress, true); + if (Size > 1) { + i2c_master_read(cmd, pData, Size - 1, I2C_MASTER_ACK); + } + i2c_master_read_byte(cmd, pData + Size - 1, I2C_MASTER_NACK); + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin(hi2c->Instance, cmd, pdMS_TO_TICKS(1000)); + i2c_cmd_link_delete(cmd); + + extern void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef * hi2c); + HAL_I2C_MasterRxCpltCallback(hi2c); // call the callback + return ESP_OK == ret ? HAL_OK : HAL_ERROR; +} +HAL_StatusTypeDef HAL_I2C_Master_Receive_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size) +{ + ESP_LOGW(TAG, "%s: Unsupported", __FUNCTION__); + return HAL_ERROR; +} +HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout) +{ + ESP_LOGW(TAG, "%s: Unsupported", __FUNCTION__); + return HAL_ERROR; +} +HAL_StatusTypeDef HAL_I2C_Mem_Write_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size) +{ + ESP_LOGW(TAG, "%s: Unsupported", __FUNCTION__); + return HAL_ERROR; +} +HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout) +{ + ESP_LOGW(TAG, "%s: Unsupported", __FUNCTION__); + return HAL_ERROR; +} +HAL_StatusTypeDef HAL_I2C_Mem_Read_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size) +{ + ESP_LOGW(TAG, "%s: Unsupported", __FUNCTION__); + return HAL_ERROR; +} + +void HAL_TIM_MspPostInit(TIM_HandleTypeDef *htim) +{ + ESP_LOGW(TAG, "%s: Unsupported", __FUNCTION__); +} + +HAL_StatusTypeDef HAL_TIM_PWM_Stop(TIM_HandleTypeDef *htim, uint32_t Channel) +{ + ledc_stop(htim->speed_mode, htim->channel, 0); + return HAL_OK; +} +HAL_StatusTypeDef HAL_TIM_Base_Stop_IT(TIM_HandleTypeDef *htim) +{ + ESP_LOGW(TAG, "%s: Unsupported", __FUNCTION__); + return HAL_OK; +} + + +void glue_board_init(void) +{ + ESP_LOGI(TAG, "Board name: %s", OPENFFBOARD_NAME); + gpio_config_t io_conf = {0}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = (BIT64(LED_CLIP_Pin) | BIT64(LED_ERR_Pin) \ + | BIT64(LED_SYS_Pin) | BIT64(DRV_ENABLE_Pin) \ + | BIT64(DRV_BRAKE_Pin) | BIT64(CAN_S_Pin)); + io_conf.pull_down_en = 0; + io_conf.pull_up_en = 0; + gpio_config(&io_conf); + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pull_up_en = 1; + io_conf.pin_bit_mask = BIT64(BUTTON_A_Pin) \ + | BIT64(DIN2_Pin) | BIT64(DIN1_Pin) | BIT64(DIN0_Pin); +#if DIN3_Pin < SOC_GPIO_PIN_COUNT + io_conf.pin_bit_mask |= BIT64(DIN3_Pin); +#endif + + gpio_config(&io_conf); + + HAL_GPIO_WritePin(GPIOA, CAN_S_Pin, GPIO_PIN_RESET); + glue_can_set_speed(&hcan1, 500000); + HAL_CAN_Start(&hcan1); +} + +void glue_ledc_config(ledc_channel_t channel, ledc_timer_bit_t duty_resolution, uint32_t freq_hz) +{ + ESP_LOGI(TAG, "%s, ch=%d, res=%d, f=%d", __FUNCTION__, channel, duty_resolution, freq_hz); +#define LEDC_TIMER LEDC_TIMER_0 + // Set the LEDC peripheral configuration + ledc_timer_config_t ledc_timer = { + .speed_mode = LEDC_LOW_SPEED_MODE, + .timer_num = LEDC_TIMER, + .duty_resolution = duty_resolution, + .freq_hz = freq_hz, // Set output frequency at 5 kHz + .clk_cfg = LEDC_AUTO_CLK + }; + ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); + + const int pwm_pins[4] = {PWM1_Pin, PWM2_Pin, PWM3_Pin, PWM4_Pin}; + // Prepare and then apply the LEDC PWM channel configuration + ledc_channel_config_t ledc_channel = { + .speed_mode = LEDC_LOW_SPEED_MODE, + .channel = channel, + .timer_sel = LEDC_TIMER, + .intr_type = LEDC_INTR_DISABLE, + .gpio_num = pwm_pins[channel], + .duty = 0, // Set duty to 0% + .hpoint = 0 + }; + ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); +} + +void glue_ledc_set_duty(ledc_channel_t channel, uint32_t duty) +{ + ESP_LOGI(TAG, "%s, ch=%d, d=%d", __FUNCTION__, channel, duty); + ledc_set_duty(LEDC_LOW_SPEED_MODE, channel, duty); + ledc_update_duty(LEDC_LOW_SPEED_MODE, channel); +} + +void glue_can_set_speed(CAN_HandleTypeDef *hcan, uint32_t rate) +{ + twai_timing_config_t t_50_config = TWAI_TIMING_CONFIG_50KBITS(); + twai_timing_config_t t_100_config = TWAI_TIMING_CONFIG_100KBITS(); + twai_timing_config_t t_125_config = TWAI_TIMING_CONFIG_125KBITS(); + twai_timing_config_t t_250_config = TWAI_TIMING_CONFIG_250KBITS(); + twai_timing_config_t t_500_config = TWAI_TIMING_CONFIG_500KBITS(); + twai_timing_config_t t_1m_config = TWAI_TIMING_CONFIG_1MBITS(); + switch (rate) { + case 50000: + hcan->t_config = t_50_config; + break; + case 100000: + hcan->t_config = t_100_config; + break; + case 125000: + hcan->t_config = t_125_config; + break; + case 250000: + hcan->t_config = t_250_config; + break; + case 500000: + hcan->t_config = t_500_config; + break; + case 1000000: + hcan->t_config = t_1m_config; + break; + + default: + ESP_LOGE(TAG, "Unsupported CAN rate"); + break; + } + ESP_LOGI(TAG, "set CAN rate to %d", rate); +} + + +static void twai_receive_task(void *arg) +{ + CAN_HandleTypeDef *hcan = (CAN_HandleTypeDef *)arg; + CAN_RxHeaderTypeDef rxHeader; + uint8_t rxBuf[8]; + while (1) { + twai_message_t rx_message; + //Receive message and print message data + twai_receive(&rx_message, portMAX_DELAY); + memcpy(rxBuf, rx_message.data, rx_message.data_length_code); + rxHeader.DLC = rx_message.data_length_code; + rxHeader.ExtId = rx_message.identifier; + rxHeader.StdId = rx_message.identifier; + rxHeader.RTR = rx_message.rtr ? CAN_RTR_REMOTE : CAN_RTR_DATA; + rxHeader.Timestamp = HAL_GetTick(); + rxHeader.IDE = rx_message.extd ? CAN_ID_EXT : CAN_ID_STD; + + // for (int i = 0; i < rx_message.data_length_code; i++) { + // printf("%X, ", rx_message.data[i] ); + // } printf("\n"); + glue_can_receive_msg(hcan, rxBuf, &rxHeader); + } + vTaskDelete(NULL); +} + + +HAL_StatusTypeDef HAL_CAN_Start(CAN_HandleTypeDef *hcan) +{ + twai_status_info_t info; + twai_get_status_info(&info); + if (TWAI_STATE_RUNNING == info.state) { + ESP_LOGW(TAG, "TWAI Driver is already running"); + return HAL_ERROR; + } + + //Filter + twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL(); + // + twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(CAN_TX_Pin, CAN_RX_Pin, TWAI_MODE_NORMAL); + //Install TWAI driver + esp_err_t ret = twai_driver_install(&g_config, &hcan->t_config, &f_config); + if (ESP_ERR_INVALID_STATE == ret) { + ESP_LOGW(TAG, "TWAI Driver is already installed"); + } + ESP_LOGI(TAG, "TWAI Driver installed"); + ESP_ERROR_CHECK(twai_start()); + xTaskCreatePinnedToCore(twai_receive_task, "TWAI_rx", 4096, hcan, 6, &hcan->task, 0); + ESP_LOGI(TAG, "TWAI Driver started"); + return HAL_OK; +} + +HAL_StatusTypeDef HAL_CAN_ConfigFilter(CAN_HandleTypeDef *hcan, CAN_FilterTypeDef *sFilterConfig) +{ + ESP_LOGW(TAG, "unsupport config filter"); + return HAL_OK; +} + +HAL_StatusTypeDef HAL_CAN_Stop(CAN_HandleTypeDef *hcan) +{ + vTaskDelete(hcan->task); + ESP_ERROR_CHECK(twai_stop()); + ESP_ERROR_CHECK(twai_driver_uninstall()); + ESP_LOGI(TAG, "Driver uninstalled"); + return HAL_OK; +} + +HAL_StatusTypeDef HAL_CAN_AddTxMessage(CAN_HandleTypeDef *hcan, CAN_TxHeaderTypeDef *pHeader, uint8_t aData[], uint32_t *pTxMailbox) +{ + twai_message_t msg = {0}; + memcpy(msg.data, aData, pHeader->DLC); + msg.rtr = (pHeader->RTR == CAN_RTR_REMOTE); + msg.data_length_code = pHeader->DLC; + msg.extd = (pHeader->IDE == CAN_ID_EXT); + msg.identifier = pHeader->ExtId; + ESP_LOGD(TAG, "send id=0x%x, dlc=%d, extd=%d, rtr=%d, ss=%d", + msg.identifier, + msg.data_length_code, + (int)msg.extd, + (int)msg.rtr, + (int)msg.ss); + // printf("send data:"); + // for (int i = 0; i < msg.data_length_code; i++) { + // printf("%X, ", msg.data[i] ); + // } printf("\n"); + esp_err_t ret = twai_transmit(&msg, pdMS_TO_TICKS(1000)); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "twai error %s", esp_err_to_name(ret)); + } + return ret == ESP_OK ? HAL_OK : HAL_ERROR; +} + +HAL_StatusTypeDef HAL_CAN_AbortTxRequest(CAN_HandleTypeDef *hcan, uint32_t TxMailboxes) +{ + twai_clear_transmit_queue(); + twai_clear_receive_queue(); + return HAL_OK; +} +HAL_StatusTypeDef HAL_CAN_ResetError(CAN_HandleTypeDef *hcan) +{ + ESP_LOGW(TAG, "%s: Unsupported", __FUNCTION__); + return HAL_OK; +} + +static void continuous_adc_init(uint16_t adc1_chan_mask, uint16_t adc2_chan_mask, adc_channel_t *channel, uint8_t channel_num) +{ +#define GET_UNIT(x) ((x>>3) & 0x1) + adc_digi_init_config_t adc_dma_config = { + .max_store_buf_size = channel_num * 4 * 2, + .conv_num_each_intr = channel_num * 4 * 2, + .adc1_chan_mask = adc1_chan_mask, + .adc2_chan_mask = adc2_chan_mask, + }; + ESP_ERROR_CHECK(adc_digi_initialize(&adc_dma_config)); + + adc_digi_configuration_t dig_cfg = { + .conv_limit_en = 0, + .conv_limit_num = adc_dma_config.conv_num_each_intr, + .sample_freq_hz = 1 * 1000, + .conv_mode = ADC_CONV_SINGLE_UNIT_1, +#ifdef CONFIG_IDF_TARGET_ESP32S3 + .format = ADC_DIGI_OUTPUT_FORMAT_TYPE2, +#elif defined CONFIG_IDF_TARGET_ESP32S2 + .format = ADC_DIGI_OUTPUT_FORMAT_TYPE1, +#endif + }; + + adc_digi_pattern_config_t adc_pattern[SOC_ADC_PATT_LEN_MAX] = {0}; + dig_cfg.pattern_num = channel_num; + for (int i = 0; i < channel_num; i++) { + uint8_t unit = GET_UNIT(channel[i]); + uint8_t ch = channel[i] & 0x7; + adc_pattern[i].atten = ADC_ATTEN_DB_11; + adc_pattern[i].channel = ch; + adc_pattern[i].unit = unit; + adc_pattern[i].bit_width = SOC_ADC_DIGI_MAX_BITWIDTH; + + ESP_LOGI(TAG, "adc_pattern[%d].atten is :%x", i, adc_pattern[i].atten); + ESP_LOGI(TAG, "adc_pattern[%d].channel is :%x", i, adc_pattern[i].channel); + } + dig_cfg.adc_pattern = adc_pattern; + ESP_ERROR_CHECK(adc_digi_controller_configure(&dig_cfg)); +} + +HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef *hadc, uint32_t *pData, uint32_t Length) +{ + if (3 != Length) { + ESP_LOGE(TAG, "ADC channels error"); + return HAL_ERROR; + } + + static uint16_t adc1_chan_mask = BIT(0) | BIT(1) | BIT(2); + adc_channel_t channel[3] = {ADC1_CHANNEL_0, ADC1_CHANNEL_1, ADC1_CHANNEL_2}; + continuous_adc_init(adc1_chan_mask, 0, channel, sizeof(channel) / sizeof(adc_channel_t)); + adc_digi_start(); + + // volatile uint32_t* getAnalogBuffer(ADC_HandleTypeDef* hadc,uint8_t* chans); + // while (1) + // { + // uint8_t c=0; + // uint32_t *p = getAnalogBuffer(NULL, &c); + // ESP_LOGI(TAG, "%d, %d, %d", p[0], p[1], p[2]); + // vTaskDelay(10); + // } + + return HAL_OK; +} + +void Error_Handler(void) +{ + ESP_LOGE(TAG, "Error_Handler!!!"); + assert(false); +} + + +void RebootDFU(void) +{ + ESP_LOGE(TAG, "Unsupport DFU"); +} + +/** + * @brief Returns the device revision identifier. + * @retval Device revision identifier + */ +uint32_t HAL_GetREVID(void) +{ + return 0x01; +} + +/** + * @brief Returns the device identifier. + * @retval Device identifier + */ +uint32_t HAL_GetDEVID(void) +{ + return FW_DEVID; +} + +uint32_t HAL_GetUIDw0(void) +{ + uint8_t mac[6]; + esp_wifi_get_mac(WIFI_IF_STA, mac); + uint32_t ret = (uint32_t)mac[1] << 8 | mac[0]; + return ret; +} +uint32_t HAL_GetUIDw1(void) +{ + uint8_t mac[6]; + esp_wifi_get_mac(WIFI_IF_STA, mac); + uint32_t ret = (uint32_t)mac[3] << 8 | mac[2]; + return ret; +} +uint32_t HAL_GetUIDw2(void) +{ + uint8_t mac[6]; + esp_wifi_get_mac(WIFI_IF_STA, mac); + uint32_t ret = (uint32_t)mac[5] << 8 | mac[4]; + return ret; +} diff --git a/Firmware/Targets/ESP32SX/main/glue.h b/Firmware/Targets/ESP32SX/main/glue.h new file mode 100644 index 000000000..c06e1c453 --- /dev/null +++ b/Firmware/Targets/ESP32SX/main/glue.h @@ -0,0 +1,117 @@ + +/* Define to prevent recursive inclusion -------------------------------------*/ +#ifndef __GLUE_H +#define __GLUE_H + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "esp_timer.h" +#include "driver/gpio.h" +#include "driver/pcnt.h" +#include "driver/ledc.h" +#include "driver/twai.h" +#include "driver/adc.h" +#include "driver/i2c.h" +#include "driver/spi_master.h" +#include "sdkconfig.h" +#include "stm32f4xx_hal.h" +#include "target_constants.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Exported macro ------------------------------------------------------------*/ +#define HAL_GetTick() (esp_timer_get_time()/1000) +#define NVIC_SystemReset() esp_restart() + +#define HAL_FLASH_Unlock() +#define HAL_FLASH_Lock() + + +#define TIM_CHANNEL_1 LEDC_CHANNEL_0 +#define TIM_CHANNEL_2 LEDC_CHANNEL_1 +#define TIM_CHANNEL_3 LEDC_CHANNEL_2 +#define TIM_CHANNEL_4 LEDC_CHANNEL_3 + +/* Private defines -----------------------------------------------------------*/ +#ifdef CONFIG_IDF_TARGET_ESP32S3 +#include "./openffboard_esp32s3_v1.1_pins.h" +#elif defined CONFIG_IDF_TARGET_ESP32S2 +#ifdef OPENFFBOARD_ESP32S2_V1_1 +#include "./openffboard_esp32s2_v1.1_pins.h" +#elif defined OPENFFBOARD_ESP32S2_V1_0 +#include "./openffboard_esp32s2_v1.0_pins.h" +#endif +#endif + +GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin); +void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState); +void HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin); +uint32_t HAL_GetREVID(void); +uint32_t HAL_GetDEVID(void); +uint32_t HAL_GetUIDw0(void); +uint32_t HAL_GetUIDw1(void); +uint32_t HAL_GetUIDw2(void); +void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim); +HAL_StatusTypeDef HAL_TIM_PWM_Stop(TIM_HandleTypeDef *htim, uint32_t Channel); +HAL_StatusTypeDef HAL_TIM_Base_Stop_IT(TIM_HandleTypeDef *htim); +HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef *hspi); +HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout); +HAL_StatusTypeDef HAL_SPI_Transmit_IT(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size); +HAL_StatusTypeDef HAL_SPI_TransmitReceive_IT(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size); +HAL_StatusTypeDef HAL_SPI_Receive_IT(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size); +HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout); +HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, + uint32_t Timeout); +HAL_StatusTypeDef HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size); +HAL_StatusTypeDef HAL_SPI_Receive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size); +HAL_StatusTypeDef HAL_SPI_TransmitReceive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, + uint16_t Size); + +HAL_StatusTypeDef HAL_I2C_Init(I2C_HandleTypeDef *hi2c); +HAL_StatusTypeDef HAL_I2C_DeInit(I2C_HandleTypeDef *hi2c); +HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout); +HAL_StatusTypeDef HAL_I2C_Master_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size); +HAL_StatusTypeDef HAL_I2C_Master_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size); +HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout); +HAL_StatusTypeDef HAL_I2C_Master_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size); +HAL_StatusTypeDef HAL_I2C_Master_Receive_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size); +HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout); +HAL_StatusTypeDef HAL_I2C_Mem_Write_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size); +HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout); +HAL_StatusTypeDef HAL_I2C_Mem_Read_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size); + +HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart); +HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); +HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); +HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout); +HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); +HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout); + +HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef *hadc, uint32_t *pData, uint32_t Length); + +void Error_Handler(void); +void RebootDFU(); + +void glue_board_init(void); +void glue_pcnt_init(void); +int16_t glue_pcnt_get_delta_value(void); +void glue_pcnt_deinit(void); +void glue_ledc_config(ledc_channel_t channel, ledc_timer_bit_t duty_resolution, uint32_t freq_hz); +void glue_ledc_set_duty(ledc_channel_t channel, uint32_t duty); +void glue_can_set_speed(CAN_HandleTypeDef *hcan, uint32_t rate); +void glue_can_receive_msg(CAN_HandleTypeDef *hcan, uint8_t *rxBuf, CAN_RxHeaderTypeDef *rxHeader); +HAL_StatusTypeDef HAL_CAN_Start(CAN_HandleTypeDef *hcan); +HAL_StatusTypeDef HAL_CAN_ConfigFilter(CAN_HandleTypeDef *hcan, CAN_FilterTypeDef *sFilterConfig); +HAL_StatusTypeDef HAL_CAN_Stop(CAN_HandleTypeDef *hcan); +HAL_StatusTypeDef HAL_CAN_AddTxMessage(CAN_HandleTypeDef *hcan, CAN_TxHeaderTypeDef *pHeader, uint8_t aData[], uint32_t *pTxMailbox); +HAL_StatusTypeDef HAL_CAN_AbortTxRequest(CAN_HandleTypeDef *hcan, uint32_t TxMailboxes); +HAL_StatusTypeDef HAL_CAN_ResetError(CAN_HandleTypeDef *hcan); + +#ifdef __cplusplus +} +#endif + +#endif /* __GLUE_H */ diff --git a/Firmware/Targets/ESP32SX/main/glue_stm32f4xx_hal_spi.h b/Firmware/Targets/ESP32SX/main/glue_stm32f4xx_hal_spi.h new file mode 100644 index 000000000..26b788440 --- /dev/null +++ b/Firmware/Targets/ESP32SX/main/glue_stm32f4xx_hal_spi.h @@ -0,0 +1,304 @@ +/** + ****************************************************************************** + * @file stm32f4xx_hal_spi.h + * @author MCD Application Team + * @brief Header file of SPI HAL module. + ****************************************************************************** + * @attention + * + *

    © Copyright (c) 2016 STMicroelectronics. + * All rights reserved.

    + * + * This software component is licensed by ST under BSD 3-Clause license, + * the "License"; You may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * opensource.org/licenses/BSD-3-Clause + * + ****************************************************************************** + */ + +/* Define to prevent recursive inclusion -------------------------------------*/ +#ifndef STM32F4xx_HAL_SPI_H +#define STM32F4xx_HAL_SPI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Includes ------------------------------------------------------------------*/ +#include "driver/spi_master.h" + +/** @addtogroup STM32F4xx_HAL_Driver + * @{ + */ + +/** @addtogroup SPI + * @{ + */ + +/* Exported types ------------------------------------------------------------*/ +/** @defgroup SPI_Exported_Types SPI Exported Types + * @{ + */ + +/** + * @brief SPI Configuration Structure definition + */ +typedef struct +{ + uint32_t Mode; /*!< Specifies the SPI operating mode. + This parameter can be a value of @ref SPI_Mode */ + + uint32_t Direction; /*!< Specifies the SPI bidirectional mode state. + This parameter can be a value of @ref SPI_Direction */ + + uint32_t DataSize; /*!< Specifies the SPI data size. + This parameter can be a value of @ref SPI_Data_Size */ + + uint32_t CLKPolarity; /*!< Specifies the serial clock steady state. + This parameter can be a value of @ref SPI_Clock_Polarity */ + + uint32_t CLKPhase; /*!< Specifies the clock active edge for the bit capture. + This parameter can be a value of @ref SPI_Clock_Phase */ + + uint32_t NSS; /*!< Specifies whether the NSS signal is managed by + hardware (NSS pin) or by software using the SSI bit. + This parameter can be a value of @ref SPI_Slave_Select_management */ + + uint32_t BaudRatePrescaler; /*!< Specifies the Baud Rate prescaler value which will be + used to configure the transmit and receive SCK clock. + This parameter can be a value of @ref SPI_BaudRate_Prescaler + @note The communication clock is derived from the master + clock. The slave clock does not need to be set. */ + + uint32_t FirstBit; /*!< Specifies whether data transfers start from MSB or LSB bit. + This parameter can be a value of @ref SPI_MSB_LSB_transmission */ + + uint32_t TIMode; /*!< Specifies if the TI mode is enabled or not. + This parameter can be a value of @ref SPI_TI_mode */ + + uint32_t CRCCalculation; /*!< Specifies if the CRC calculation is enabled or not. + This parameter can be a value of @ref SPI_CRC_Calculation */ + + uint32_t CRCPolynomial; /*!< Specifies the polynomial used for the CRC calculation. + This parameter must be an odd number between Min_Data = 1 and Max_Data = 65535 */ +} SPI_InitTypeDef; + +/** + * @brief HAL SPI State structure definition + */ +typedef enum +{ + HAL_SPI_STATE_RESET = 0x00U, /*!< Peripheral not Initialized */ + HAL_SPI_STATE_READY = 0x01U, /*!< Peripheral Initialized and ready for use */ + HAL_SPI_STATE_BUSY = 0x02U, /*!< an internal process is ongoing */ + HAL_SPI_STATE_BUSY_TX = 0x03U, /*!< Data Transmission process is ongoing */ + HAL_SPI_STATE_BUSY_RX = 0x04U, /*!< Data Reception process is ongoing */ + HAL_SPI_STATE_BUSY_TX_RX = 0x05U, /*!< Data Transmission and Reception process is ongoing */ + HAL_SPI_STATE_ERROR = 0x06U, /*!< SPI error state */ + HAL_SPI_STATE_ABORT = 0x07U /*!< SPI abort is ongoing */ +} HAL_SPI_StateTypeDef; + +/** + * @brief SPI handle Structure definition + */ +typedef struct __SPI_HandleTypeDef +{ + spi_device_handle_t Instance; + + SPI_InitTypeDef Init; /*!< SPI communication parameters */ + +#if (USE_HAL_SPI_REGISTER_CALLBACKS == 1U) + void (* TxCpltCallback)(struct __SPI_HandleTypeDef *hspi); /*!< SPI Tx Completed callback */ + void (* RxCpltCallback)(struct __SPI_HandleTypeDef *hspi); /*!< SPI Rx Completed callback */ + void (* TxRxCpltCallback)(struct __SPI_HandleTypeDef *hspi); /*!< SPI TxRx Completed callback */ + void (* TxHalfCpltCallback)(struct __SPI_HandleTypeDef *hspi); /*!< SPI Tx Half Completed callback */ + void (* RxHalfCpltCallback)(struct __SPI_HandleTypeDef *hspi); /*!< SPI Rx Half Completed callback */ + void (* TxRxHalfCpltCallback)(struct __SPI_HandleTypeDef *hspi); /*!< SPI TxRx Half Completed callback */ + void (* ErrorCallback)(struct __SPI_HandleTypeDef *hspi); /*!< SPI Error callback */ + void (* AbortCpltCallback)(struct __SPI_HandleTypeDef *hspi); /*!< SPI Abort callback */ + void (* MspInitCallback)(struct __SPI_HandleTypeDef *hspi); /*!< SPI Msp Init callback */ + void (* MspDeInitCallback)(struct __SPI_HandleTypeDef *hspi); /*!< SPI Msp DeInit callback */ + +#endif /* USE_HAL_SPI_REGISTER_CALLBACKS */ +} SPI_HandleTypeDef; + + +/******************* Bit definition for SPI_CR1 register ********************/ +#define SPI_CR1_CPHA_Pos (0U) +#define SPI_CR1_CPHA_Msk (0x1UL << SPI_CR1_CPHA_Pos) /*!< 0x00000001 */ +#define SPI_CR1_CPHA SPI_CR1_CPHA_Msk /*! 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5) + return __builtin_bswap32(value); +#else + uint32_t result; + + __ASM volatile ("rev %0, %1" : __CMSIS_GCC_OUT_REG (result) : __CMSIS_GCC_USE_REG (value) ); + return result; +#endif +} + +#ifndef __ALIGN_END +#define __ALIGN_END __attribute__ ((aligned (4))) +#endif /* __ALIGN_END */ +#ifndef __ALIGN_BEGIN +#define __ALIGN_BEGIN +#endif /* __ALIGN_BEGIN */ + + +typedef enum { + HAL_OK = 0x00U, + HAL_ERROR = 0x01U, + HAL_BUSY = 0x02U, + HAL_TIMEOUT = 0x03U +} HAL_StatusTypeDef; +typedef enum +{ + DISABLE = 0U, + ENABLE = !DISABLE +} FunctionalState; + +#define GPIOA ((GPIO_TypeDef *) 0) +#define GPIOB ((GPIO_TypeDef *) 0) +#define GPIOC ((GPIO_TypeDef *) 0) +#define GPIOD ((GPIO_TypeDef *) 0) +#define GPIOE ((GPIO_TypeDef *) 0) +#define GPIOF ((GPIO_TypeDef *) 0) +#define GPIOG ((GPIO_TypeDef *) 0) +#define GPIOH ((GPIO_TypeDef *) 0) +#define GPIOI ((GPIO_TypeDef *) 0) + +#define GPIO_PIN_0 (0 ) +#define GPIO_PIN_1 (1 ) +#define GPIO_PIN_2 (2 ) +#define GPIO_PIN_3 (3 ) +#define GPIO_PIN_4 (4 ) +#define GPIO_PIN_5 (5 ) +#define GPIO_PIN_6 (6 ) +#define GPIO_PIN_7 (7 ) +#define GPIO_PIN_8 (8 ) +#define GPIO_PIN_9 (9 ) +#define GPIO_PIN_10 (10) +#define GPIO_PIN_11 (11) +#define GPIO_PIN_12 (12) +#define GPIO_PIN_13 (13) +#define GPIO_PIN_14 (14) +#define GPIO_PIN_15 (15) +#define GPIO_PIN_16 (16) +#define GPIO_PIN_17 (17) +#define GPIO_PIN_18 (18) +#define GPIO_PIN_19 (19) +#define GPIO_PIN_20 (20) +#define GPIO_PIN_21 (21) +#define GPIO_PIN_22 (22) +#define GPIO_PIN_23 (23) +#define GPIO_PIN_25 (25) +#define GPIO_PIN_26 (26) +#define GPIO_PIN_27 (27) +#define GPIO_PIN_28 (28) +#define GPIO_PIN_29 (29) +#define GPIO_PIN_30 (30) +#define GPIO_PIN_31 (31) +#define GPIO_PIN_32 (32) +#define GPIO_PIN_33 (33) +#define GPIO_PIN_34 (34) +#define GPIO_PIN_35 (35) +#define GPIO_PIN_36 (36) +#define GPIO_PIN_37 (37) +#define GPIO_PIN_38 (38) +#define GPIO_PIN_39 (39) +#define GPIO_PIN_40 (40) +#define GPIO_PIN_41 (41) +#define GPIO_PIN_42 (42) +#define GPIO_PIN_43 (43) +#define GPIO_PIN_44 (44) +#define GPIO_PIN_45 (45) +#define GPIO_PIN_46 (46) +#define GPIO_PIN_47 (47) +#define GPIO_PIN_48 (48) + +typedef struct { + twai_timing_config_t t_config; + TaskHandle_t task; +} CAN_HandleTypeDef; + +/** + * @brief CAN filter configuration structure definition + */ +typedef struct { + uint32_t FilterIdHigh; /*!< Specifies the filter identification number (MSBs for a 32-bit + configuration, first one for a 16-bit configuration). + This parameter must be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF. */ + + uint32_t FilterIdLow; /*!< Specifies the filter identification number (LSBs for a 32-bit + configuration, second one for a 16-bit configuration). + This parameter must be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF. */ + + uint32_t FilterMaskIdHigh; /*!< Specifies the filter mask number or identification number, + according to the mode (MSBs for a 32-bit configuration, + first one for a 16-bit configuration). + This parameter must be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF. */ + + uint32_t FilterMaskIdLow; /*!< Specifies the filter mask number or identification number, + according to the mode (LSBs for a 32-bit configuration, + second one for a 16-bit configuration). + This parameter must be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF. */ + + uint32_t FilterFIFOAssignment; /*!< Specifies the FIFO (0 or 1U) which will be assigned to the filter. + This parameter can be a value of @ref CAN_filter_FIFO */ + + uint32_t FilterBank; /*!< Specifies the filter bank which will be initialized. + For single CAN instance(14 dedicated filter banks), + this parameter must be a number between Min_Data = 0 and Max_Data = 13. + For dual CAN instances(28 filter banks shared), + this parameter must be a number between Min_Data = 0 and Max_Data = 27. */ + + uint32_t FilterMode; /*!< Specifies the filter mode to be initialized. + This parameter can be a value of @ref CAN_filter_mode */ + + uint32_t FilterScale; /*!< Specifies the filter scale. + This parameter can be a value of @ref CAN_filter_scale */ + + uint32_t FilterActivation; /*!< Enable or disable the filter. + This parameter can be a value of @ref CAN_filter_activation */ + + uint32_t SlaveStartFilterBank; /*!< Select the start filter bank for the slave CAN instance. + For single CAN instances, this parameter is meaningless. + For dual CAN instances, all filter banks with lower index are assigned to master + CAN instance, whereas all filter banks with greater index are assigned to slave + CAN instance. + This parameter must be a number between Min_Data = 0 and Max_Data = 27. */ + +} CAN_FilterTypeDef; + +/** + * @brief CAN Tx message header structure definition + */ +typedef struct { + uint32_t StdId; /*!< Specifies the standard identifier. + This parameter must be a number between Min_Data = 0 and Max_Data = 0x7FF. */ + + uint32_t ExtId; /*!< Specifies the extended identifier. + This parameter must be a number between Min_Data = 0 and Max_Data = 0x1FFFFFFF. */ + + uint32_t IDE; /*!< Specifies the type of identifier for the message that will be transmitted. + This parameter can be a value of @ref CAN_identifier_type */ + + uint32_t RTR; /*!< Specifies the type of frame for the message that will be transmitted. + This parameter can be a value of @ref CAN_remote_transmission_request */ + + uint32_t DLC; /*!< Specifies the length of the frame that will be transmitted. + This parameter must be a number between Min_Data = 0 and Max_Data = 8. */ + + bool TransmitGlobalTime; /*!< Specifies whether the timestamp counter value captured on start + of frame transmission, is sent in DATA6 and DATA7 replacing pData[6] and pData[7]. + @note: Time Triggered Communication Mode must be enabled. + @note: DLC must be programmed as 8 bytes, in order these 2 bytes are sent. + This parameter can be set to ENABLE or DISABLE. */ + +} CAN_TxHeaderTypeDef; +#define CAN_RX_FIFO0 (0x00000000U) /*!< CAN receive FIFO 0 */ +#define CAN_RX_FIFO1 (0x00000001U) /*!< CAN receive FIFO 1 */ +#define CAN_FILTERMODE_IDMASK (0x00000000U) /*!< Identifier mask mode */ +#define CAN_FILTERMODE_IDLIST (0x00000001U) /*!< Identifier list mode */ +#define CAN_FILTERSCALE_16BIT (0x00000000U) /*!< Two 16-bit filters */ +#define CAN_FILTERSCALE_32BIT (0x00000001U) /*!< One 32-bit filter */ +#define CAN_ID_STD (0x00000000U) /*!< Standard Id */ +#define CAN_ID_EXT (0x00000004U) /*!< Extended Id */ +#define CAN_RTR_DATA (0x00000000U) /*!< Data frame */ +#define CAN_RTR_REMOTE (0x00000002U) /*!< Remote frame */ +/** + * @brief CAN Rx message header structure definition + */ +typedef struct { + uint32_t StdId; /*!< Specifies the standard identifier. + This parameter must be a number between Min_Data = 0 and Max_Data = 0x7FF. */ + + uint32_t ExtId; /*!< Specifies the extended identifier. + This parameter must be a number between Min_Data = 0 and Max_Data = 0x1FFFFFFF. */ + + uint32_t IDE; /*!< Specifies the type of identifier for the message that will be transmitted. + This parameter can be a value of @ref CAN_identifier_type */ + + uint32_t RTR; /*!< Specifies the type of frame for the message that will be transmitted. + This parameter can be a value of @ref CAN_remote_transmission_request */ + + uint32_t DLC; /*!< Specifies the length of the frame that will be transmitted. + This parameter must be a number between Min_Data = 0 and Max_Data = 8. */ + + uint32_t Timestamp; /*!< Specifies the timestamp counter value captured on start of frame reception. + @note: Time Triggered Communication Mode must be enabled. + This parameter must be a number between Min_Data = 0 and Max_Data = 0xFFFF. */ + + uint32_t FilterMatchIndex; /*!< Specifies the index of matching acceptance filter element. + This parameter must be a number between Min_Data = 0 and Max_Data = 0xFF. */ + +} CAN_RxHeaderTypeDef; + +typedef struct +{ + uint32_t ClockSpeed; /*!< Specifies the clock frequency. + This parameter must be set to a value lower than 400kHz */ + + uint32_t DutyCycle; /*!< Specifies the I2C fast mode duty cycle. + This parameter can be a value of @ref I2C_duty_cycle_in_fast_mode */ + + uint32_t OwnAddress1; /*!< Specifies the first device own address. + This parameter can be a 7-bit or 10-bit address. */ + + uint32_t AddressingMode; /*!< Specifies if 7-bit or 10-bit addressing mode is selected. + This parameter can be a value of @ref I2C_addressing_mode */ + + uint32_t DualAddressMode; /*!< Specifies if dual addressing mode is selected. + This parameter can be a value of @ref I2C_dual_addressing_mode */ + + uint32_t OwnAddress2; /*!< Specifies the second device own address if dual addressing mode is selected + This parameter can be a 7-bit address. */ + + uint32_t GeneralCallMode; /*!< Specifies if general call mode is selected. + This parameter can be a value of @ref I2C_general_call_addressing_mode */ + + uint32_t NoStretchMode; /*!< Specifies if nostretch mode is selected. + This parameter can be a value of @ref I2C_nostretch_mode */ + +} I2C_InitTypeDef; + +/** @defgroup I2C_duty_cycle_in_fast_mode I2C duty cycle in fast mode + * @{ + */ +#define I2C_DUTYCYCLE_2 0x00000000U +#define I2C_DUTYCYCLE_16_9 I2C_CCR_DUTY +/** + * @} + */ + +/** @defgroup I2C_addressing_mode I2C addressing mode + * @{ + */ +#define I2C_ADDRESSINGMODE_7BIT 0x00004000U +#define I2C_ADDRESSINGMODE_10BIT (I2C_OAR1_ADDMODE | 0x00004000U) +/** + * @} + */ + +/** @defgroup I2C_dual_addressing_mode I2C dual addressing mode + * @{ + */ +#define I2C_DUALADDRESS_DISABLE 0x00000000U +#define I2C_DUALADDRESS_ENABLE I2C_OAR2_ENDUAL +/** + * @} + */ + +/** @defgroup I2C_general_call_addressing_mode I2C general call addressing mode + * @{ + */ +#define I2C_GENERALCALL_DISABLE 0x00000000U +#define I2C_GENERALCALL_ENABLE I2C_CR1_ENGC +/** + * @} + */ + +/** @defgroup I2C_nostretch_mode I2C nostretch mode + * @{ + */ +#define I2C_NOSTRETCH_DISABLE 0x00000000U +#define I2C_NOSTRETCH_ENABLE I2C_CR1_NOSTRETCH +/** + * @} + */ + +/** @defgroup I2C_Memory_Address_Size I2C Memory Address Size + * @{ + */ +#define I2C_MEMADD_SIZE_8BIT 0x00000001U +#define I2C_MEMADD_SIZE_16BIT 0x00000010U +/** + * @} + */ + +/** @defgroup I2C_XferDirection_definition I2C XferDirection definition + * @{ + */ +#define I2C_DIRECTION_RECEIVE 0x00000000U +#define I2C_DIRECTION_TRANSMIT 0x00000001U + +typedef enum +{ + HAL_I2C_STATE_RESET = 0x00U, /*!< Peripheral is not yet Initialized */ + HAL_I2C_STATE_READY = 0x20U, /*!< Peripheral Initialized and ready for use */ + HAL_I2C_STATE_BUSY = 0x24U, /*!< An internal process is ongoing */ + HAL_I2C_STATE_BUSY_TX = 0x21U, /*!< Data Transmission process is ongoing */ + HAL_I2C_STATE_BUSY_RX = 0x22U, /*!< Data Reception process is ongoing */ + HAL_I2C_STATE_LISTEN = 0x28U, /*!< Address Listen Mode is ongoing */ + HAL_I2C_STATE_BUSY_TX_LISTEN = 0x29U, /*!< Address Listen Mode and Data Transmission + process is ongoing */ + HAL_I2C_STATE_BUSY_RX_LISTEN = 0x2AU, /*!< Address Listen Mode and Data Reception + process is ongoing */ + HAL_I2C_STATE_ABORT = 0x60U, /*!< Abort user request ongoing */ + HAL_I2C_STATE_TIMEOUT = 0xA0U, /*!< Timeout state */ + HAL_I2C_STATE_ERROR = 0xE0U /*!< Error */ + +} HAL_I2C_StateTypeDef; + +typedef struct +{ + uint32_t Instance; /*!< I2C NUM for ESP32Sx */ + + I2C_InitTypeDef Init; /*!< I2C communication parameters */ + + uint8_t *pBuffPtr; /*!< Pointer to I2C transfer buffer */ + + uint16_t XferSize; /*!< I2C transfer size */ + + __IO uint16_t XferCount; /*!< I2C transfer counter */ + + __IO uint32_t XferOptions; /*!< I2C transfer options */ + + __IO uint32_t PreviousState; /*!< I2C communication Previous state and mode */ + + + __IO HAL_I2C_StateTypeDef State; /*!< I2C communication state */ + + + __IO uint32_t ErrorCode; /*!< I2C Error code */ + + __IO uint32_t Devaddress; /*!< I2C Target device address */ + + __IO uint32_t Memaddress; /*!< I2C Target memory address */ + + __IO uint32_t MemaddSize; /*!< I2C Target memory address size */ + + __IO uint32_t EventCount; /*!< I2C Event counter */ + +} I2C_HandleTypeDef; + +// In order to be lazy, I refer to the header file of STM32 +#include "./glue_stm32f4xx_hal_spi.h" + +typedef struct { + uint32_t a; +} ADC_HandleTypeDef; + + +typedef enum { + GPIO_PIN_RESET = 0, + GPIO_PIN_SET +} GPIO_PinState; +typedef struct { + uint32_t a; +} GPIO_TypeDef; + +typedef struct { + uint32_t speed_mode; /**< Speed mode of the LEDC channel group */ + uint32_t channel; /**< LEDC channel (0 - LEDC_CHANNEL_MAX-1) */ +} TIM_HandleTypeDef; + +typedef struct +{ + uint32_t BaudRate; /*!< This member configures the UART communication baud rate. + The baud rate is computed using the following formula: + - IntegerDivider = ((PCLKx) / (8 * (OVR8+1) * (huart->Init.BaudRate))) + - FractionalDivider = ((IntegerDivider - ((uint32_t) IntegerDivider)) * 8 * (OVR8+1)) + 0.5 + Where OVR8 is the "oversampling by 8 mode" configuration bit in the CR1 register. */ + + uint32_t WordLength; /*!< Specifies the number of data bits transmitted or received in a frame. + This parameter can be a value of @ref UART_Word_Length */ + + uint32_t StopBits; /*!< Specifies the number of stop bits transmitted. + This parameter can be a value of @ref UART_Stop_Bits */ + + uint32_t Parity; /*!< Specifies the parity mode. + This parameter can be a value of @ref UART_Parity + @note When parity is enabled, the computed parity is inserted + at the MSB position of the transmitted data (9th bit when + the word length is set to 9 data bits; 8th bit when the + word length is set to 8 data bits). */ + + uint32_t Mode; /*!< Specifies whether the Receive or Transmit mode is enabled or disabled. + This parameter can be a value of @ref UART_Mode */ + + uint32_t HwFlowCtl; /*!< Specifies whether the hardware flow control mode is enabled or disabled. + This parameter can be a value of @ref UART_Hardware_Flow_Control */ + + uint32_t OverSampling; /*!< Specifies whether the Over sampling 8 is enabled or disabled, to achieve higher speed (up to fPCLK/8). + This parameter can be a value of @ref UART_Over_Sampling */ +} UART_InitTypeDef; + +typedef struct { + UART_InitTypeDef Init; + uint32_t a; +} UART_HandleTypeDef; + + +#ifdef __cplusplus +} +#endif + +#endif /* __STM32F4xx_HAL_H */ diff --git a/Firmware/Targets/ESP32SX/main/target_constants.h b/Firmware/Targets/ESP32SX/main/target_constants.h new file mode 100644 index 000000000..0e87f13ce --- /dev/null +++ b/Firmware/Targets/ESP32SX/main/target_constants.h @@ -0,0 +1,118 @@ +/* + * target_constants.h + * + * Created on: 01.05.2022 + * Author: Zhouli + */ + +#ifndef INC_TARGET_CONSTANTS_H_ +#define INC_TARGET_CONSTANTS_H_ + +/* + * Add settings and peripheral maps for this specific target here + */ + +// Hardware name string +#define HW_TYPE "ESP32SX" +#define HW_TYPE_INT 2 +#define FW_DEVID 0x413 // F407 + +#define HW_ESP32SX // Defining this macro will implement the STM32 HAL library using some of the functions of ESP-IDF + +#ifdef HW_ESP32SX +#if (!defined OPENFFBOARD_ESP32S2_V1_0) && (!defined OPENFFBOARD_ESP32S2_V1_1) +#define OPENFFBOARD_ESP32S2_V1_1 // Default use a V1.1 version of the board +#endif + +#include "glue.h" + +#endif + +// Enabled features + +// Main classes +#define FFBWHEEL +// #define FFBJOYSTICK +// #define MIDI +// #define TMCDEBUG +// #define CANBRIDGE + +/* + * FFBWheel uses 2 FFB axis descriptor instead of 1 axis. + * Might improve compatibility with direct input but will report a 2 axis ffb compatible device + */ +//#define FFBWHEEL_USE_1AXIS_DESC + +// Extra features +#define LOCALBUTTONS +// #define SPIBUTTONS +// #define SHIFTERBUTTONS +// #define PCF8574BUTTONS // Requires I2C +#define ANALOGAXES +#define TMC4671DRIVER +#define PWMDRIVER +#define LOCALENCODER +#define CANBUS +#define ODRIVE +#define VESC +// #define MTENCODERSPI // requires SPI3, not support on ESP32SX now +// #define CANBUTTONS // Requires CAN, not support on ESP32SX now, besause the can filter has not been implemented in glue.c +// #define CANANALOG // Requires CAN, not support on ESP32SX now, besause the can filter has not been implemented in glue.c +// #define ADS111XANALOG // Requires I2C, not support on ESP32SX now +// #define UARTCOMMANDS // Not support on ESP32SX now, besause the uart has not been implemented in glue.c + +//#define TMCTEMP // Enable tmc temperature shutdown. replaced by hardware selection +//---------------------- + + +#define TIM_PWM htim1 + +#define UART_PORT_EXT huart1 // main uart port + +#define UART_PORT_MOTOR huart3 // motor uart port + +#define UART_BUF_SIZE 1 // How many bytes to expect via DMA + +// #define I2C_PORT hi2c1 // open it will change two GPIOs: DIN2->I2C_SDA, DIN3->I2C_SCL + +// ADC Channels +#define ADC1_CHANNELS 3 // how many analog input values to be read by dma +// #define ADC2_CHANNELS 2 // VSENSE + +// extern ADC_HandleTypeDef hadc2; +// #define VSENSE_HADC hadc2 +// #define ADC_CHAN_VINT 1 // adc buffer index of internal voltage sense +// #define ADC_CHAN_VEXT 0 // adc buffer index of supply voltage sense +// extern volatile uint32_t ADC1_BUF[ADC1_CHANNELS]; // Buffer +// #define VSENSE_ADC_BUF ADC2_BUF +#define VOLTAGE_MULT_DEFAULT 24.6 // Voltage in mV = adc*VOLTAGE_MULT (24.6 for 976k/33k divider) + +extern ADC_HandleTypeDef hadc1; +#define AIN_HADC hadc1 // main adc for analog pins +#define ADC_PINS 3 // Amount of analog channel pins +#define ADC_CHAN_FPIN 0 // First analog channel pin. last channel = fpin+ADC_PINS-1 + +#ifdef OPENFFBOARD_ESP32S2_V1_1 +#define BUTTON_PINS 4 // The ESP32SX board has only 4 button input +#else +#define BUTTON_PINS 3 +#endif + +// CAN +#ifdef CANBUS +extern CAN_HandleTypeDef hcan1; +#define CANPORT hcan1 + +#define CANSPEEDPRESET_50 0 +#define CANSPEEDPRESET_100 1 +#define CANSPEEDPRESET_125 2 +#define CANSPEEDPRESET_250 3 +#define CANSPEEDPRESET_500 4 +#define CANSPEEDPRESET_1000 5 + +#endif + +// System + + +#endif /* INC_TARGET_CONSTANTS_H_ */ diff --git a/Firmware/Targets/ESP32SX/partitions.csv b/Firmware/Targets/ESP32SX/partitions.csv new file mode 100644 index 000000000..8bc7d7c8f --- /dev/null +++ b/Firmware/Targets/ESP32SX/partitions.csv @@ -0,0 +1,5 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, , 0x6000, +phy_init, data, phy, , 0x1000, +factory, app, factory, , 1500K, diff --git a/Firmware/Targets/ESP32SX/sdkconfig.defaults b/Firmware/Targets/ESP32SX/sdkconfig.defaults new file mode 100644 index 000000000..165b529da --- /dev/null +++ b/Firmware/Targets/ESP32SX/sdkconfig.defaults @@ -0,0 +1,17 @@ + +CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP32S2_DEFAULT_CPU_FREQ_240=y +CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y +CONFIG_TINYUSB=y +CONFIG_TINYUSB_CDC_ENABLED=y +CONFIG_TINYUSB_HID_ENABLED=y +CONFIG_TINYUSB_MIDI_ENABLED=y +CONFIG_TINYUSB_CDC_RX_BUFSIZE=512 +CONFIG_TINYUSB_CDC_TX_BUFSIZE=512 +CONFIG_TINYUSB_MIDI_RX_BUFSIZE=512 +CONFIG_TINYUSB_MIDI_TX_BUFSIZE=512 +CONFIG_COMPILER_CXX_RTTI=y + +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" From 46ea57c2869137f6fea55904c80dd664c5ceedae Mon Sep 17 00:00:00 2001 From: TDA2030 <1932489836@qq.com> Date: Sun, 5 Feb 2023 14:05:21 +0800 Subject: [PATCH 2/2] update for v1.12.1 --- .github/workflows/build-firmware.yml | 2 + Firmware/FFBoard/Inc/OutputPin.h | 4 +- Firmware/FFBoard/Src/flash_helpers.cpp | 4 ++ Firmware/FFBoard/UserExtensions/Inc/TMC4671.h | 4 ++ .../FFBoard/UserExtensions/Src/TMC4671.cpp | 2 +- .../Targets/ESP32SX/firmware/generate_bin.sh | 2 +- .../firmware/openffb-esp32s2-hw_v1.0.bin | Bin 609840 -> 622720 bytes .../firmware/openffb-esp32s2-hw_v1.1.bin | Bin 609840 -> 622720 bytes .../firmware/openffb-esp32s3-hw_v1.1.bin | Bin 626176 -> 638080 bytes .../ESP32SX/main/cpp_target_config.cpp | 35 ++++++++++------- Firmware/Targets/ESP32SX/main/glue.c | 37 +++++++++++++++++- Firmware/Targets/ESP32SX/main/glue.h | 21 ++++++++++ Firmware/Targets/ESP32SX/main/stm32f4xx_hal.h | 35 +++++++++++++++++ .../Targets/ESP32SX/main/target_constants.h | 8 ++++ 14 files changed, 135 insertions(+), 19 deletions(-) diff --git a/.github/workflows/build-firmware.yml b/.github/workflows/build-firmware.yml index 6b6dba701..9a951fdc2 100644 --- a/.github/workflows/build-firmware.yml +++ b/.github/workflows/build-firmware.yml @@ -63,7 +63,9 @@ jobs: cd /opt/esp/idf git checkout c29343eb94d git submodule update --init --recursive + git status tools/idf_tools.py --non-interactive install cmake + rm $IDF_TOOLS_PATH/idf-env.json ./install.sh source ./export.sh cd $GITHUB_WORKSPACE/$PROJECT_PATH diff --git a/Firmware/FFBoard/Inc/OutputPin.h b/Firmware/FFBoard/Inc/OutputPin.h index 4b69e1683..7875b4b54 100644 --- a/Firmware/FFBoard/Inc/OutputPin.h +++ b/Firmware/FFBoard/Inc/OutputPin.h @@ -37,6 +37,8 @@ class OutputPin { return(this->port == b.port && this->pin == b.pin); } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" /** * Sets a pin into output mode in case it was previously reconfigured */ @@ -48,7 +50,7 @@ class OutputPin { GPIO_InitStruct.Speed = speed; HAL_GPIO_Init(port, &GPIO_InitStruct); } - +#pragma GCC diagnostic pop //const std::string getName(){return name;} diff --git a/Firmware/FFBoard/Src/flash_helpers.cpp b/Firmware/FFBoard/Src/flash_helpers.cpp index a35f942c5..17d9d92c0 100644 --- a/Firmware/FFBoard/Src/flash_helpers.cpp +++ b/Firmware/FFBoard/Src/flash_helpers.cpp @@ -25,6 +25,7 @@ bool Flash_Write(uint16_t adr,uint16_t dat){ bool res = false; if(readRes == 1 || (readRes == 0 && buf != dat) ){ // Only write if var updated HAL_FLASH_Unlock(); + #ifndef HW_ESP32SX // Clear all the error flags __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP); __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPERR); @@ -32,6 +33,7 @@ bool Flash_Write(uint16_t adr,uint16_t dat){ __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_PGAERR); __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_PGPERR); __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_PGSERR); + #endif if(EE_WriteVariable(adr, dat) == HAL_OK){ res = true; } @@ -60,6 +62,7 @@ bool Flash_ReadWriteDefault(uint16_t adr,uint16_t *buf,uint16_t def){ if(EE_ReadVariable(adr, buf) != 0){ *buf = def; HAL_FLASH_Unlock(); + #ifndef HW_ESP32SX // Clear all the error flags __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP); __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_OPERR); @@ -67,6 +70,7 @@ bool Flash_ReadWriteDefault(uint16_t adr,uint16_t *buf,uint16_t def){ __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_PGAERR); __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_PGPERR); __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_PGSERR); + #endif EE_WriteVariable(adr, def); HAL_FLASH_Lock(); return false; diff --git a/Firmware/FFBoard/UserExtensions/Inc/TMC4671.h b/Firmware/FFBoard/UserExtensions/Inc/TMC4671.h index 9f4411acf..5d881878c 100644 --- a/Firmware/FFBoard/UserExtensions/Inc/TMC4671.h +++ b/Firmware/FFBoard/UserExtensions/Inc/TMC4671.h @@ -32,9 +32,13 @@ #ifdef HW_ESP32SX #define TMC_THREAD_MEM 4096 #define TMC_THREAD_PRIO 25*25/56 +#define TMCENC_THREAD_MEM 2048 +#define TMCENC_THREAD_PRIO 33*25/56 #else #define TMC_THREAD_MEM 256 #define TMC_THREAD_PRIO 25 // Must be higher than main thread +#define TMCENC_THREAD_MEM 80 +#define TMCENC_THREAD_PRIO 33 #endif #define TMC_ADCOFFSETFAIL 5000 // How much offset from 0x7fff to allow before a calibration is failed diff --git a/Firmware/FFBoard/UserExtensions/Src/TMC4671.cpp b/Firmware/FFBoard/UserExtensions/Src/TMC4671.cpp index fc95c13f1..c45214401 100644 --- a/Firmware/FFBoard/UserExtensions/Src/TMC4671.cpp +++ b/Firmware/FFBoard/UserExtensions/Src/TMC4671.cpp @@ -2996,7 +2996,7 @@ void TMC4671::setUpExtEncTimer(){ /** * Medium priority task to update external encoders */ -TMC4671::TMC_ExternalEncoderUpdateThread::TMC_ExternalEncoderUpdateThread(TMC4671* tmc) : cpp_freertos::Thread("TMCENC",80,33),tmc(tmc){ +TMC4671::TMC_ExternalEncoderUpdateThread::TMC_ExternalEncoderUpdateThread(TMC4671* tmc) : cpp_freertos::Thread("TMCENC",TMCENC_THREAD_MEM,TMCENC_THREAD_PRIO),tmc(tmc){ this->Start(); } diff --git a/Firmware/Targets/ESP32SX/firmware/generate_bin.sh b/Firmware/Targets/ESP32SX/firmware/generate_bin.sh index 9898fda41..6df4868ff 100755 --- a/Firmware/Targets/ESP32SX/firmware/generate_bin.sh +++ b/Firmware/Targets/ESP32SX/firmware/generate_bin.sh @@ -20,7 +20,7 @@ build_and_generate_bin(){ cd firmware } -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +SCRIPT_DIR=$(dirname $(readlink -f "$0")) cd $SCRIPT_DIR rm -f ../firmware/*.bin diff --git a/Firmware/Targets/ESP32SX/firmware/openffb-esp32s2-hw_v1.0.bin b/Firmware/Targets/ESP32SX/firmware/openffb-esp32s2-hw_v1.0.bin index 25c0bb3b1b128641b971bd85c191b67750ecd949..cb626848cc934e02dc3dd61de0725f28909686c4 100644 GIT binary patch delta 295103 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

  • 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