diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0b5fdc376..d0c6cfe3f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -53,7 +53,7 @@ jobs: { id: guition-jc8048w550c, arch: esp32s3 }, { id: heltec-wifi-lora-32-v3, arch: esp32s3 }, { id: lilygo-tdeck, arch: esp32s3 }, - { id: lilygo-thmi-s3, arch: esp32s3 }, + { id: lilygo-thmi, arch: esp32s3 }, { id: lilygo-tdongle-s3, arch: esp32s3 }, { id: lilygo-tdisplay-s3, arch: esp32s3 }, { id: lilygo-tlora-pager, arch: esp32s3 }, diff --git a/Devices/guition-jc1060p470ciwy/Source/devices/SdCard.cpp b/Devices/guition-jc1060p470ciwy/Source/devices/SdCard.cpp index 3a08e11ff..9bee001c8 100644 --- a/Devices/guition-jc1060p470ciwy/Source/devices/SdCard.cpp +++ b/Devices/guition-jc1060p470ciwy/Source/devices/SdCard.cpp @@ -18,6 +18,7 @@ static const auto LOGGER = tt::Logger("JcSdCard"); // ESP32-P4 Slot 0 uses IO MUX (fixed pins, not manually configurable) // CLK=43, CMD=44, D0=39, D1=40, D2=41, D3=42 (defined automatically by hardware) +// TODO: Migrate to "espressif,esp32-sdmmc" driver (needs LDO code porting) class SdCardDeviceImpl final : public SdCardDevice { class NoLock final : public tt::Lock { diff --git a/Devices/lilygo-tdongle-s3/Source/Configuration.cpp b/Devices/lilygo-tdongle-s3/Source/Configuration.cpp index 8358fd2e9..d7462601c 100644 --- a/Devices/lilygo-tdongle-s3/Source/Configuration.cpp +++ b/Devices/lilygo-tdongle-s3/Source/Configuration.cpp @@ -1,5 +1,4 @@ #include "devices/Display.h" -#include "devices/Sdcard.h" #include @@ -9,8 +8,7 @@ using namespace tt::hal; static std::vector> createDevices() { return { - createDisplay(), - createSdCard() + createDisplay() }; } diff --git a/Devices/lilygo-tdongle-s3/Source/devices/Sdcard.cpp b/Devices/lilygo-tdongle-s3/Source/devices/Sdcard.cpp deleted file mode 100644 index 6315691a9..000000000 --- a/Devices/lilygo-tdongle-s3/Source/devices/Sdcard.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "Sdcard.h" - -#include -#include - -using tt::hal::sdcard::SdmmcDevice; - -std::shared_ptr createSdCard() { - auto configuration = std::make_unique( - GPIO_NUM_12, - GPIO_NUM_16, - GPIO_NUM_14, - GPIO_NUM_17, - GPIO_NUM_21, - GPIO_NUM_18, - SdCardDevice::MountBehaviour::AtBoot - ); - - return std::make_shared( - std::move(configuration) - ); -} diff --git a/Devices/lilygo-tdongle-s3/Source/devices/Sdcard.h b/Devices/lilygo-tdongle-s3/Source/devices/Sdcard.h deleted file mode 100644 index 5cb65a735..000000000 --- a/Devices/lilygo-tdongle-s3/Source/devices/Sdcard.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include "Tactility/hal/sdcard/SdCardDevice.h" - -using tt::hal::sdcard::SdCardDevice; - -std::shared_ptr createSdCard(); diff --git a/Devices/lilygo-tdongle-s3/device.properties b/Devices/lilygo-tdongle-s3/device.properties index 28622b83d..ec4f21869 100644 --- a/Devices/lilygo-tdongle-s3/device.properties +++ b/Devices/lilygo-tdongle-s3/device.properties @@ -22,3 +22,4 @@ dpi=186 [lvgl] colorDepth=16 uiDensity=compact +fontSize=10 diff --git a/Devices/lilygo-tdongle-s3/lilygo,tdongle-s3.dts b/Devices/lilygo-tdongle-s3/lilygo,tdongle-s3.dts index 50f8a5e53..147987a44 100644 --- a/Devices/lilygo-tdongle-s3/lilygo,tdongle-s3.dts +++ b/Devices/lilygo-tdongle-s3/lilygo,tdongle-s3.dts @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -30,6 +31,17 @@ pin-sclk = <&gpio0 5 GPIO_FLAG_NONE>; }; + sdmmc0 { + compatible = "espressif,esp32-sdmmc"; + pin-clk = <&gpio0 12 GPIO_FLAG_NONE>; + pin-cmd = <&gpio0 16 GPIO_FLAG_NONE>; + pin-d0 = <&gpio0 14 GPIO_FLAG_NONE>; + pin-d1 = <&gpio0 17 GPIO_FLAG_NONE>; + pin-d2 = <&gpio0 21 GPIO_FLAG_NONE>; + pin-d3 = <&gpio0 18 GPIO_FLAG_NONE>; + bus-width = <4>; + }; + stemma_qt: uart1 { compatible = "espressif,esp32-uart"; port = ; diff --git a/Devices/lilygo-thmi-s3/Source/devices/SdCard.cpp b/Devices/lilygo-thmi-s3/Source/devices/SdCard.cpp deleted file mode 100644 index 03137e90b..000000000 --- a/Devices/lilygo-thmi-s3/Source/devices/SdCard.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "SdCard.h" - -#include -#include - -using tt::hal::sdcard::SdmmcDevice; - -std::shared_ptr createSdCard() { - auto configuration = std::make_unique( - SD_DIO_SCLK, //CLK - SD_DIO_CMD, //CMD - SD_DIO_DATA0, //D0 - SD_DIO_NC, //D1 - SD_DIO_NC, //D2 - SD_DIO_NC, //D3 - SdCardDevice::MountBehaviour::AtBoot, - SD_DIO_BUS_WIDTH - ); - - return std::make_shared( - std::move(configuration) - ); -} diff --git a/Devices/lilygo-thmi-s3/Source/devices/SdCard.h b/Devices/lilygo-thmi-s3/Source/devices/SdCard.h deleted file mode 100644 index ab4ec439f..000000000 --- a/Devices/lilygo-thmi-s3/Source/devices/SdCard.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include - -#include "Tactility/hal/sdcard/SdCardDevice.h" - -using tt::hal::sdcard::SdCardDevice; - -constexpr auto SD_DIO_CMD = GPIO_NUM_11; -constexpr auto SD_DIO_SCLK = GPIO_NUM_12; -constexpr auto SD_DIO_DATA0 = GPIO_NUM_13; -constexpr auto SD_DIO_NC = GPIO_NUM_NC; -constexpr auto SD_DIO_BUS_WIDTH = 1; - -std::shared_ptr createSdCard(); \ No newline at end of file diff --git a/Devices/lilygo-thmi-s3/CMakeLists.txt b/Devices/lilygo-thmi/CMakeLists.txt similarity index 100% rename from Devices/lilygo-thmi-s3/CMakeLists.txt rename to Devices/lilygo-thmi/CMakeLists.txt diff --git a/Devices/lilygo-thmi-s3/Source/Configuration.cpp b/Devices/lilygo-thmi/Source/Configuration.cpp similarity index 90% rename from Devices/lilygo-thmi-s3/Source/Configuration.cpp rename to Devices/lilygo-thmi/Source/Configuration.cpp index 8d04e56f0..68aa2d31c 100644 --- a/Devices/lilygo-thmi-s3/Source/Configuration.cpp +++ b/Devices/lilygo-thmi/Source/Configuration.cpp @@ -1,5 +1,4 @@ #include "devices/Power.h" -#include "devices/SdCard.h" #include "devices/Display.h" #include @@ -11,7 +10,6 @@ using namespace tt::hal; static std::vector> createDevices() { return { - createSdCard(), createDisplay(), std::make_shared(), ButtonControl::createOneButtonControl(0) diff --git a/Devices/lilygo-thmi-s3/Source/Init.cpp b/Devices/lilygo-thmi/Source/Init.cpp similarity index 79% rename from Devices/lilygo-thmi-s3/Source/Init.cpp rename to Devices/lilygo-thmi/Source/Init.cpp index f64d64c37..0a44f1fb6 100644 --- a/Devices/lilygo-thmi-s3/Source/Init.cpp +++ b/Devices/lilygo-thmi/Source/Init.cpp @@ -5,11 +5,11 @@ #include "Tactility/kernel/SystemEvents.h" #include -#define TAG "thmi-s3" +#define TAG "thmi" static bool powerOn() { gpio_config_t power_signal_config = { - .pin_bit_mask = (1ULL << THMI_S3_POWERON_GPIO) | (1ULL << THMI_S3_POWEREN_GPIO), + .pin_bit_mask = (1ULL << THMI_POWERON_GPIO) | (1ULL << THMI_POWEREN_GPIO), .mode = GPIO_MODE_OUTPUT, .pull_up_en = GPIO_PULLUP_DISABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, @@ -20,11 +20,11 @@ static bool powerOn() { return false; } - if (gpio_set_level(THMI_S3_POWERON_GPIO, 1) != ESP_OK) { + if (gpio_set_level(THMI_POWERON_GPIO, 1) != ESP_OK) { return false; } - if (gpio_set_level(THMI_S3_POWEREN_GPIO, 1) != ESP_OK) { + if (gpio_set_level(THMI_POWEREN_GPIO, 1) != ESP_OK) { return false; } diff --git a/Devices/lilygo-thmi-s3/Source/devices/Display.cpp b/Devices/lilygo-thmi/Source/devices/Display.cpp similarity index 100% rename from Devices/lilygo-thmi-s3/Source/devices/Display.cpp rename to Devices/lilygo-thmi/Source/devices/Display.cpp diff --git a/Devices/lilygo-thmi-s3/Source/devices/Display.h b/Devices/lilygo-thmi/Source/devices/Display.h similarity index 100% rename from Devices/lilygo-thmi-s3/Source/devices/Display.h rename to Devices/lilygo-thmi/Source/devices/Display.h diff --git a/Devices/lilygo-thmi-s3/Source/devices/Power.cpp b/Devices/lilygo-thmi/Source/devices/Power.cpp similarity index 100% rename from Devices/lilygo-thmi-s3/Source/devices/Power.cpp rename to Devices/lilygo-thmi/Source/devices/Power.cpp diff --git a/Devices/lilygo-thmi-s3/Source/devices/Power.h b/Devices/lilygo-thmi/Source/devices/Power.h similarity index 81% rename from Devices/lilygo-thmi-s3/Source/devices/Power.h rename to Devices/lilygo-thmi/Source/devices/Power.h index ed7be56fa..c831083c4 100644 --- a/Devices/lilygo-thmi-s3/Source/devices/Power.h +++ b/Devices/lilygo-thmi/Source/devices/Power.h @@ -6,16 +6,16 @@ #include #include -constexpr auto THMI_S3_POWEREN_GPIO = GPIO_NUM_10; -constexpr auto THMI_S3_POWERON_GPIO = GPIO_NUM_14; +constexpr auto THMI_POWEREN_GPIO = GPIO_NUM_10; +constexpr auto THMI_POWERON_GPIO = GPIO_NUM_14; using tt::hal::power::PowerDevice; class Power final : public PowerDevice { ChargeFromVoltage chargeFromAdcVoltage = ChargeFromVoltage(3.3f, 4.2f); - bool initialized = false; esp_adc_cal_characteristics_t adcCharacteristics; + bool initialized = false; bool calibrated = false; bool adcInitCalibration(); @@ -25,7 +25,7 @@ class Power final : public PowerDevice { public: - std::string getName() const override { return "T-hmi Power"; } + std::string getName() const override { return "T-HMI Power"; } std::string getDescription() const override { return "Power measurement via ADC"; } bool supportsMetric(MetricType type) const override; diff --git a/Devices/lilygo-thmi-s3/Source/module.cpp b/Devices/lilygo-thmi/Source/module.cpp similarity index 91% rename from Devices/lilygo-thmi-s3/Source/module.cpp rename to Devices/lilygo-thmi/Source/module.cpp index 9d5b1ef07..799711a1a 100644 --- a/Devices/lilygo-thmi-s3/Source/module.cpp +++ b/Devices/lilygo-thmi/Source/module.cpp @@ -13,7 +13,7 @@ static error_t stop() { } struct Module lilygo_thmi_s3_module = { - .name = "lilygo-thmi-s3", + .name = "lilygo-thmi", .start = start, .stop = stop, .symbols = nullptr, diff --git a/Devices/lilygo-thmi-s3/device.properties b/Devices/lilygo-thmi/device.properties similarity index 94% rename from Devices/lilygo-thmi-s3/device.properties rename to Devices/lilygo-thmi/device.properties index 9e70d045c..787069cbf 100644 --- a/Devices/lilygo-thmi-s3/device.properties +++ b/Devices/lilygo-thmi/device.properties @@ -1,6 +1,6 @@ [general] vendor=LilyGO -name=T-HMI S3 +name=T-HMI [apps] launcherAppId=Launcher diff --git a/Devices/lilygo-thmi-s3/devicetree.yaml b/Devices/lilygo-thmi/devicetree.yaml similarity index 64% rename from Devices/lilygo-thmi-s3/devicetree.yaml rename to Devices/lilygo-thmi/devicetree.yaml index 0474b6950..68d5c934f 100644 --- a/Devices/lilygo-thmi-s3/devicetree.yaml +++ b/Devices/lilygo-thmi/devicetree.yaml @@ -1,3 +1,3 @@ dependencies: - Platforms/platform-esp32 -dts: lilygo,thmi-s3.dts +dts: lilygo,thmi.dts diff --git a/Devices/lilygo-thmi-s3/lilygo,thmi-s3.dts b/Devices/lilygo-thmi/lilygo,thmi.dts similarity index 64% rename from Devices/lilygo-thmi-s3/lilygo,thmi-s3.dts rename to Devices/lilygo-thmi/lilygo,thmi.dts index 7392e9676..a83a31db2 100644 --- a/Devices/lilygo-thmi-s3/lilygo,thmi-s3.dts +++ b/Devices/lilygo-thmi/lilygo,thmi.dts @@ -3,11 +3,12 @@ #include #include #include +#include #include / { compatible = "root"; - model = "LilyGO T-HMI S3"; + model = "LilyGO T-HMI"; gpio0 { compatible = "espressif,esp32-gpio"; @@ -21,4 +22,12 @@ pin-miso = <&gpio0 4 GPIO_FLAG_NONE>; pin-sclk = <&gpio0 1 GPIO_FLAG_NONE>; }; + + sdmmc0 { + compatible = "espressif,esp32-sdmmc"; + pin-clk = <&gpio0 12 GPIO_FLAG_NONE>; + pin-cmd = <&gpio0 11 GPIO_FLAG_NONE>; + pin-d0 = <&gpio0 13 GPIO_FLAG_NONE>; + bus-width = <1>; + }; }; diff --git a/Devices/m5stack-tab5/CMakeLists.txt b/Devices/m5stack-tab5/CMakeLists.txt index 53e5abbeb..c84b1ada3 100644 --- a/Devices/m5stack-tab5/CMakeLists.txt +++ b/Devices/m5stack-tab5/CMakeLists.txt @@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lvgl_port esp_lcd EspLcdCompat esp_lcd_ili9881c GT911 PwmBacklight driver vfs fatfs pi4ioe5v6408-module + REQUIRES Tactility esp_lvgl_port esp_lcd EspLcdCompat esp_lcd_ili9881c GT911 PwmBacklight driver vfs fatfs ) diff --git a/Devices/m5stack-tab5/Source/Configuration.cpp b/Devices/m5stack-tab5/Source/Configuration.cpp index d17d47e4b..6a39ad0ce 100644 --- a/Devices/m5stack-tab5/Source/Configuration.cpp +++ b/Devices/m5stack-tab5/Source/Configuration.cpp @@ -1,12 +1,11 @@ #include "devices/Display.h" #include "devices/SdCard.h" -#include +#include #include #include #include -#include using namespace tt::hal; @@ -19,50 +18,115 @@ static DeviceVector createDevices() { }; } -static error_t initPower(::Device* io_expander0, ::Device* io_expander1) { - constexpr TickType_t i2c_timeout = pdMS_TO_TICKS(10); - - /* - PI4IOE5V6408-0 (0x43) - - Bit 0: RF internal/external switch - - Bit 1: Speaker enable - - Bit 2: External 5V bus enable - - Bit 3: / - - Bit 4: LCD reset - - Bit 5: Touch reset - - Bit 6: Camera reset - - Bit 7: Headphone detect - */ - - check(pi4ioe5v6408_set_direction(io_expander0, 0b01111111, i2c_timeout) == ERROR_NONE); - check(pi4ioe5v6408_set_output_level(io_expander0, 0b01000110, i2c_timeout) == ERROR_NONE); - check(pi4ioe5v6408_set_output_high_impedance(io_expander0, 0b00000000, i2c_timeout) == ERROR_NONE); - check(pi4ioe5v6408_set_pull_select(io_expander0, 0b01111111, i2c_timeout) == ERROR_NONE); - check(pi4ioe5v6408_set_pull_enable(io_expander0, 0b01111111, i2c_timeout) == ERROR_NONE); +/* + PI4IOE5V6408-0 (0x43) + - Bit 0: RF internal/external switch + - Bit 1: Speaker enable + - Bit 2: External 5V bus enable + - Bit 3: / + - Bit 4: LCD reset + - Bit 5: Touch reset + - Bit 6: Camera reset + - Bit 7: Headphone detect + */ +constexpr auto GPIO_EXP0_PIN_RF_INTERNAL_EXTERNAL = 0; +constexpr auto GPIO_EXP0_PIN_SPEAKER_ENABLE = 1; +constexpr auto GPIO_EXP0_PIN_EXTERNAL_5V_BUS_ENABLE = 2; +constexpr auto GPIO_EXP0_PIN_LCD_RESET = 4; +constexpr auto GPIO_EXP0_PIN_TOUCH_RESET = 5; +constexpr auto GPIO_EXP0_PIN_CAMERA_RESET = 6; +constexpr auto GPIO_EXP0_PIN_HEADPHONE_DETECT = 7; + +/* + PI4IOE5V6408-1 (0x44) + - Bit 0: C6 WLAN enable + - Bit 1: / + - Bit 2: / + - Bit 3: USB-A 5V enable + - Bit 4: Device power: PWROFF_PLUSE + - Bit 5: IP2326: nCHG_QC_EN + - Bit 6: IP2326: CHG_STAT_LED + - Bit 7: IP2326: CHG_EN +*/ +constexpr auto GPIO_EXP1_PIN_C6_WLAN_ENABLE = 0; +constexpr auto GPIO_EXP1_PIN_USB_A_5V_ENABLE = 3; +constexpr auto GPIO_EXP1_PIN_DEVICE_POWER = 4; +constexpr auto GPIO_EXP1_PIN_IP2326_NCHG_QC_EN = 5; +constexpr auto GPIO_EXP1_PIN_IP2326_CHG_STAT_LED = 6; +constexpr auto GPIO_EXP1_PIN_IP2326_CHG_EN = 7; + +static void initExpander0(::Device* io_expander0) { + auto* rf_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_RF_INTERNAL_EXTERNAL, GPIO_OWNER_GPIO); + check(rf_pin); + auto* speaker_enable_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_SPEAKER_ENABLE, GPIO_OWNER_GPIO); + check(speaker_enable_pin); + auto* external_5v_bus_enable_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_EXTERNAL_5V_BUS_ENABLE, GPIO_OWNER_GPIO); + check(external_5v_bus_enable_pin); + auto* lcd_reset_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_LCD_RESET, GPIO_OWNER_GPIO); + check(lcd_reset_pin); + auto* touch_reset_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_TOUCH_RESET, GPIO_OWNER_GPIO); + check(touch_reset_pin); + auto* camera_reset_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_CAMERA_RESET, GPIO_OWNER_GPIO); + check(camera_reset_pin); + auto* headphone_detect_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_HEADPHONE_DETECT, GPIO_OWNER_GPIO); + check(headphone_detect_pin); + + gpio_descriptor_set_flags(rf_pin, GPIO_FLAG_DIRECTION_OUTPUT); + gpio_descriptor_set_flags(speaker_enable_pin, GPIO_FLAG_DIRECTION_OUTPUT); + gpio_descriptor_set_flags(external_5v_bus_enable_pin, GPIO_FLAG_DIRECTION_OUTPUT); + gpio_descriptor_set_flags(lcd_reset_pin, GPIO_FLAG_DIRECTION_OUTPUT); + gpio_descriptor_set_flags(touch_reset_pin, GPIO_FLAG_DIRECTION_OUTPUT); + gpio_descriptor_set_flags(camera_reset_pin, GPIO_FLAG_DIRECTION_OUTPUT); + gpio_descriptor_set_flags(headphone_detect_pin, GPIO_FLAG_DIRECTION_INPUT); + + gpio_descriptor_set_level(rf_pin, false); + gpio_descriptor_set_level(speaker_enable_pin, false); + gpio_descriptor_set_level(external_5v_bus_enable_pin, true); + gpio_descriptor_set_level(lcd_reset_pin, false); + gpio_descriptor_set_level(touch_reset_pin, false); + gpio_descriptor_set_level(camera_reset_pin, true); vTaskDelay(pdMS_TO_TICKS(10)); - check(pi4ioe5v6408_set_output_level(io_expander0, 0b01110110, i2c_timeout) == ERROR_NONE); - - /* - PI4IOE5V6408-1 (0x44) - - Bit 0: C6 WLAN enable - - Bit 1: / - - Bit 2: / - - Bit 3: USB-A 5V enable - - Bit 4: Device power: PWROFF_PLUSE - - Bit 5: IP2326: nCHG_QC_EN - - Bit 6: IP2326: CHG_STAT_LED - - Bit 7: IP2326: CHG_EN - */ - - check(pi4ioe5v6408_set_direction(io_expander1, 0b10111001, i2c_timeout) == ERROR_NONE); - check(pi4ioe5v6408_set_output_high_impedance(io_expander1, 0b00000110, i2c_timeout) == ERROR_NONE); - check(pi4ioe5v6408_set_pull_select(io_expander1, 0b10111001, i2c_timeout) == ERROR_NONE); - check(pi4ioe5v6408_set_pull_enable(io_expander1, 0b11111001, i2c_timeout) == ERROR_NONE); - check(pi4ioe5v6408_set_input_default_level(io_expander1, 0b01000000, i2c_timeout) == ERROR_NONE); - check(pi4ioe5v6408_set_interrupt_mask(io_expander1, 0b10111111, i2c_timeout) == ERROR_NONE); - check(pi4ioe5v6408_set_output_level(io_expander1, 0b10001001, i2c_timeout) == ERROR_NONE); + // Enable touch and lcd, but not the camera + gpio_descriptor_set_level(lcd_reset_pin, true); + gpio_descriptor_set_level(touch_reset_pin, true); + + gpio_descriptor_release(rf_pin); + gpio_descriptor_release(speaker_enable_pin); + gpio_descriptor_release(external_5v_bus_enable_pin); + gpio_descriptor_release(lcd_reset_pin); + gpio_descriptor_release(touch_reset_pin); + gpio_descriptor_release(camera_reset_pin); + gpio_descriptor_release(headphone_detect_pin); +} - return ERROR_NONE; +static void initExpander1(::Device* io_expander1) { + + auto* c6_wlan_enable_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_C6_WLAN_ENABLE, GPIO_OWNER_GPIO); + auto* usb_a_5v_enable_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_USB_A_5V_ENABLE, GPIO_OWNER_GPIO); + auto* device_power_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_DEVICE_POWER, GPIO_OWNER_GPIO); + auto* ip2326_ncharge_qc_enable_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_IP2326_NCHG_QC_EN, GPIO_OWNER_GPIO); + auto* ip2326_charge_state_led_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_IP2326_CHG_STAT_LED, GPIO_OWNER_GPIO); + auto* ip2326_charge_enable_pin = gpio_descriptor_acquire(io_expander1, GPIO_EXP1_PIN_IP2326_CHG_EN, GPIO_OWNER_GPIO); + + gpio_descriptor_set_flags(c6_wlan_enable_pin, GPIO_FLAG_DIRECTION_OUTPUT); + gpio_descriptor_set_flags(usb_a_5v_enable_pin, GPIO_FLAG_DIRECTION_OUTPUT); + gpio_descriptor_set_flags(device_power_pin, GPIO_FLAG_DIRECTION_OUTPUT); + gpio_descriptor_set_flags(ip2326_ncharge_qc_enable_pin, GPIO_FLAG_DIRECTION_OUTPUT); + gpio_descriptor_set_flags(ip2326_charge_state_led_pin, GPIO_FLAG_DIRECTION_OUTPUT); + gpio_descriptor_set_flags(ip2326_charge_enable_pin, GPIO_FLAG_DIRECTION_INPUT | GPIO_FLAG_PULL_UP); + + gpio_descriptor_set_level(c6_wlan_enable_pin, true); + gpio_descriptor_set_level(usb_a_5v_enable_pin, true); + gpio_descriptor_set_level(device_power_pin, false); + gpio_descriptor_set_level(ip2326_ncharge_qc_enable_pin, false); + gpio_descriptor_set_level(ip2326_charge_state_led_pin, false); + + gpio_descriptor_release(c6_wlan_enable_pin); + gpio_descriptor_release(usb_a_5v_enable_pin); + gpio_descriptor_release(device_power_pin); + gpio_descriptor_release(ip2326_ncharge_qc_enable_pin); + gpio_descriptor_release(ip2326_charge_state_led_pin); + gpio_descriptor_release(ip2326_charge_enable_pin); } static error_t initSound(::Device* i2c_controller, ::Device* io_expander0 = nullptr) { @@ -113,13 +177,11 @@ static error_t initSound(::Device* i2c_controller, ::Device* io_expander0 = null return error; } - uint8_t output_level = 0; - if (pi4ioe5v6408_get_output_level(io_expander0, &output_level, pdMS_TO_TICKS(100)) != ERROR_NONE) { - LOG_E(TAG, "Failed to read power level: %s", error_to_string(error)); - return ERROR_RESOURCE; - } - - if (pi4ioe5v6408_set_output_level(io_expander0, output_level | 0b00000010, pdMS_TO_TICKS(100)) != ERROR_NONE) { + auto* speaker_enable_pin = gpio_descriptor_acquire(io_expander0, GPIO_EXP0_PIN_SPEAKER_ENABLE, GPIO_OWNER_GPIO); + check(speaker_enable_pin, "Failed to acquire speaker enable pin"); + error = gpio_descriptor_set_level(speaker_enable_pin, true); + gpio_descriptor_release(speaker_enable_pin); + if (error != ERROR_NONE) { LOG_E(TAG, "Failed to enable amplifier: %s", error_to_string(error)); return ERROR_RESOURCE; } @@ -132,10 +194,12 @@ static bool initBoot() { check(i2c0, "i2c0 not found"); auto* io_expander0 = device_find_by_name("io_expander0"); + check(io_expander0, "io_expander0 not found"); auto* io_expander1 = device_find_by_name("io_expander1"); - check(i2c0, "i2c0 not found"); + check(io_expander1, "io_expander1 not found"); - initPower(io_expander0, io_expander1); + initExpander0(io_expander0); + initExpander1(io_expander1); error_t error = initSound(i2c0, io_expander0); if (error != ERROR_NONE) { diff --git a/Devices/m5stack-tab5/devicetree.yaml b/Devices/m5stack-tab5/devicetree.yaml index 516236386..bc6b7b156 100644 --- a/Devices/m5stack-tab5/devicetree.yaml +++ b/Devices/m5stack-tab5/devicetree.yaml @@ -1,4 +1,5 @@ dependencies: - Platforms/platform-esp32 - Drivers/pi4ioe5v6408-module + - Drivers/bmi270-module dts: m5stack,tab5.dts diff --git a/Devices/m5stack-tab5/m5stack,tab5.dts b/Devices/m5stack-tab5/m5stack,tab5.dts index 04be1ce2e..620001d98 100644 --- a/Devices/m5stack-tab5/m5stack,tab5.dts +++ b/Devices/m5stack-tab5/m5stack,tab5.dts @@ -5,6 +5,7 @@ #include #include #include +#include #include / { @@ -23,35 +24,20 @@ pin-sda = <&gpio0 31 GPIO_FLAG_NONE>; pin-scl = <&gpio0 32 GPIO_FLAG_NONE>; - /* - - Bit 0: RF internal/external switch - - Bit 1: Speaker enable - - Bit 2: External 5V bus enable - - Bit 3: / - - Bit 4: LCD reset - - Bit 5: Touch reset - - Bit 6: Camera reset - - Bit 7: Headphone detect - */ io_expander0 { compatible = "diodes,pi4ioe5v6408"; reg = <0x43>; }; - /* - - Bit 0: C6 WLAN enable - - Bit 1: / - - Bit 2: / - - Bit 3: USB-A 5V enable - - Bit 4: Device power: PWROFF_PLUSE - - Bit 5: IP2326: nCHG_QC_EN - - Bit 6: IP2326: CHG_STAT_LED - - Bit 7: IP2326: CHG_EN - */ io_expander1 { compatible = "diodes,pi4ioe5v6408"; reg = <0x44>; }; + + bmi270 { + compatible = "bosch,bmi270"; + reg = <0x68>; + }; }; i2c_port_a: i2c1 { diff --git a/Devices/waveshare-esp32-s3-geek/Source/Configuration.cpp b/Devices/waveshare-esp32-s3-geek/Source/Configuration.cpp index 422ab5a83..5f28b1f3c 100644 --- a/Devices/waveshare-esp32-s3-geek/Source/Configuration.cpp +++ b/Devices/waveshare-esp32-s3-geek/Source/Configuration.cpp @@ -1,5 +1,4 @@ #include "devices/Display.h" -#include "devices/SdCard.h" #include #include @@ -10,7 +9,6 @@ using namespace tt::hal; static DeviceVector createDevices() { return { createDisplay(), - createSdCard(), ButtonControl::createOneButtonControl(0) }; } diff --git a/Devices/waveshare-esp32-s3-geek/Source/devices/SdCard.cpp b/Devices/waveshare-esp32-s3-geek/Source/devices/SdCard.cpp deleted file mode 100644 index 542eec0b1..000000000 --- a/Devices/waveshare-esp32-s3-geek/Source/devices/SdCard.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "SdCard.h" - -#include -#include - -using tt::hal::sdcard::SdmmcDevice; - -std::shared_ptr createSdCard() { - auto configuration = std::make_unique( - GPIO_NUM_36, //CLK - GPIO_NUM_35, //CMD - GPIO_NUM_37, //D0 - GPIO_NUM_33, //D1 - GPIO_NUM_38, //D2 - GPIO_NUM_34, //D3 - SdCardDevice::MountBehaviour::AtBoot - ); - - return std::make_shared( - std::move(configuration) - ); -} diff --git a/Devices/waveshare-esp32-s3-geek/Source/devices/SdCard.h b/Devices/waveshare-esp32-s3-geek/Source/devices/SdCard.h deleted file mode 100644 index 07d556490..000000000 --- a/Devices/waveshare-esp32-s3-geek/Source/devices/SdCard.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include "Tactility/hal/sdcard/SdCardDevice.h" - -using tt::hal::sdcard::SdCardDevice; - -std::shared_ptr createSdCard(); \ No newline at end of file diff --git a/Devices/waveshare-esp32-s3-geek/waveshare,esp32-s3-geek.dts b/Devices/waveshare-esp32-s3-geek/waveshare,esp32-s3-geek.dts index 9b98719e6..28b3c9327 100644 --- a/Devices/waveshare-esp32-s3-geek/waveshare,esp32-s3-geek.dts +++ b/Devices/waveshare-esp32-s3-geek/waveshare,esp32-s3-geek.dts @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -30,6 +31,17 @@ pin-sclk = <&gpio0 12 GPIO_FLAG_NONE>; }; + sdmmc0 { + compatible = "espressif,esp32-sdmmc"; + pin-clk = <&gpio0 36 GPIO_FLAG_NONE>; + pin-cmd = <&gpio0 35 GPIO_FLAG_NONE>; + pin-d0 = <&gpio0 37 GPIO_FLAG_NONE>; + pin-d1 = <&gpio0 33 GPIO_FLAG_NONE>; + pin-d2 = <&gpio0 38 GPIO_FLAG_NONE>; + pin-d3 = <&gpio0 34 GPIO_FLAG_NONE>; + bus-width = <4>; + }; + uart0 { compatible = "espressif,esp32-uart"; port = ; diff --git a/Drivers/bmi270-module/CMakeLists.txt b/Drivers/bmi270-module/CMakeLists.txt new file mode 100644 index 000000000..3f05a93a6 --- /dev/null +++ b/Drivers/bmi270-module/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.20) + +include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake") + +file(GLOB_RECURSE SOURCE_FILES "source/*.c*") + +tactility_add_module(bmi270-module + SRCS ${SOURCE_FILES} + INCLUDE_DIRS include/ + PRIV_INCLUDE_DIRS private/ + REQUIRES TactilityKernel +) diff --git a/Drivers/bmi270-module/LICENSE-Apache-2.0.md b/Drivers/bmi270-module/LICENSE-Apache-2.0.md new file mode 100644 index 000000000..f5f4b8b5e --- /dev/null +++ b/Drivers/bmi270-module/LICENSE-Apache-2.0.md @@ -0,0 +1,195 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/Drivers/bmi270-module/README.md b/Drivers/bmi270-module/README.md new file mode 100644 index 000000000..c447c7b16 --- /dev/null +++ b/Drivers/bmi270-module/README.md @@ -0,0 +1,7 @@ +# BMI270 I2C Driver + +A driver for the `BMI270` 6-axis IMU. + +See https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmi270-ds000.pdf + +License: [Apache v2.0](LICENSE-Apache-2.0.md) diff --git a/Drivers/bmi270-module/bindings/bosch,bmi270.yaml b/Drivers/bmi270-module/bindings/bosch,bmi270.yaml new file mode 100644 index 000000000..188955bb2 --- /dev/null +++ b/Drivers/bmi270-module/bindings/bosch,bmi270.yaml @@ -0,0 +1,5 @@ +description: Bosch BMI270 6-axis IMU + +include: ["i2c-device.yaml"] + +compatible: "bosch,bmi270" diff --git a/Drivers/bmi270-module/devicetree.yaml b/Drivers/bmi270-module/devicetree.yaml new file mode 100644 index 000000000..99f3dfd7e --- /dev/null +++ b/Drivers/bmi270-module/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - TactilityKernel +bindings: bindings \ No newline at end of file diff --git a/Drivers/bmi270-module/include/bindings/bmi270.h b/Drivers/bmi270-module/include/bindings/bmi270.h new file mode 100644 index 000000000..903958b04 --- /dev/null +++ b/Drivers/bmi270-module/include/bindings/bmi270.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +DEFINE_DEVICETREE(bmi270, struct Bmi270Config) + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/bmi270-module/include/bmi270_module.h b/Drivers/bmi270-module/include/bmi270_module.h new file mode 100644 index 000000000..d1e4f5dfc --- /dev/null +++ b/Drivers/bmi270-module/include/bmi270_module.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct Module bmi270_module; + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/bmi270-module/include/drivers/bmi270.h b/Drivers/bmi270-module/include/drivers/bmi270.h new file mode 100644 index 000000000..aa8227a60 --- /dev/null +++ b/Drivers/bmi270-module/include/drivers/bmi270.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +struct Device; + +#ifdef __cplusplus +extern "C" { +#endif + +struct Bmi270Config { + /** Address on bus */ + uint8_t address; +}; + +struct Bmi270Data { + float ax, ay, az; // acceleration in g (±8g range) + float gx, gy, gz; // angular rate in °/s (±2000°/s range) +}; + +/** + * Read accelerometer and gyroscope data. + * @param[in] device bmi270 device + * @param[out] data Pointer to Bmi270Data structure to store the data + * @return ERROR_NONE on success + */ +error_t bmi270_read(struct Device* device, struct Bmi270Data* data); + +#ifdef __cplusplus +} +#endif diff --git a/Drivers/bmi270-module/private/drivers/bmi270_config_data.h b/Drivers/bmi270-module/private/drivers/bmi270_config_data.h new file mode 100644 index 000000000..e77423480 --- /dev/null +++ b/Drivers/bmi270-module/private/drivers/bmi270_config_data.h @@ -0,0 +1,442 @@ +#pragma once + +// BMI270 configuration file +// Copyright (c) 2023 Bosch Sensortec GmbH +// SPDX-License-Identifier: BSD-3-Clause +// Source: https://github.com/boschsensortec/BMI270_SensorAPI +#include + +static const uint8_t bmi270_config_data[] = { + 0xc8, 0x2e, 0x00, 0x2e, 0x80, 0x2e, 0x3d, 0xb1, 0xc8, 0x2e, 0x00, 0x2e, 0x80, 0x2e, 0x91, 0x03, 0x80, 0x2e, 0xbc, + 0xb0, 0x80, 0x2e, 0xa3, 0x03, 0xc8, 0x2e, 0x00, 0x2e, 0x80, 0x2e, 0x00, 0xb0, 0x50, 0x30, 0x21, 0x2e, 0x59, 0xf5, + 0x10, 0x30, 0x21, 0x2e, 0x6a, 0xf5, 0x80, 0x2e, 0x3b, 0x03, 0x00, 0x00, 0x00, 0x00, 0x08, 0x19, 0x01, 0x00, 0x22, + 0x00, 0x75, 0x00, 0x00, 0x10, 0x00, 0x10, 0xd1, 0x00, 0xb3, 0x43, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, + 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, + 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, + 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, + 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0xe0, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x92, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x19, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, + 0xe0, 0xaa, 0x38, 0x05, 0xe0, 0x90, 0x30, 0xfa, 0x00, 0x96, 0x00, 0x4b, 0x09, 0x11, 0x00, 0x11, 0x00, 0x02, 0x00, + 0x2d, 0x01, 0xd4, 0x7b, 0x3b, 0x01, 0xdb, 0x7a, 0x04, 0x00, 0x3f, 0x7b, 0xcd, 0x6c, 0xc3, 0x04, 0x85, 0x09, 0xc3, + 0x04, 0xec, 0xe6, 0x0c, 0x46, 0x01, 0x00, 0x27, 0x00, 0x19, 0x00, 0x96, 0x00, 0xa0, 0x00, 0x01, 0x00, 0x0c, 0x00, + 0xf0, 0x3c, 0x00, 0x01, 0x01, 0x00, 0x03, 0x00, 0x01, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x32, 0x00, 0x05, 0x00, 0xee, + 0x06, 0x04, 0x00, 0xc8, 0x00, 0x00, 0x00, 0x04, 0x00, 0xa8, 0x05, 0xee, 0x06, 0x00, 0x04, 0xbc, 0x02, 0xb3, 0x00, + 0x85, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xb4, 0x00, 0x01, 0x00, 0xb9, 0x00, 0x01, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x80, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x2e, 0x00, 0xc1, 0xfd, 0x2d, 0xde, + 0x00, 0xeb, 0x00, 0xda, 0x00, 0x00, 0x0c, 0xff, 0x0f, 0x00, 0x04, 0xc0, 0x00, 0x5b, 0xf5, 0xc9, 0x01, 0x1e, 0xf2, + 0x80, 0x00, 0x3f, 0xff, 0x19, 0xf4, 0x58, 0xf5, 0x66, 0xf5, 0x64, 0xf5, 0xc0, 0xf1, 0xf0, 0x00, 0xe0, 0x00, 0xcd, + 0x01, 0xd3, 0x01, 0xdb, 0x01, 0xff, 0x7f, 0xff, 0x01, 0xe4, 0x00, 0x74, 0xf7, 0xf3, 0x00, 0xfa, 0x00, 0xff, 0x3f, + 0xca, 0x03, 0x6c, 0x38, 0x56, 0xfe, 0x44, 0xfd, 0xbc, 0x02, 0xf9, 0x06, 0x00, 0xfc, 0x12, 0x02, 0xae, 0x01, 0x58, + 0xfa, 0x9a, 0xfd, 0x77, 0x05, 0xbb, 0x02, 0x96, 0x01, 0x95, 0x01, 0x7f, 0x01, 0x82, 0x01, 0x89, 0x01, 0x87, 0x01, + 0x88, 0x01, 0x8a, 0x01, 0x8c, 0x01, 0x8f, 0x01, 0x8d, 0x01, 0x92, 0x01, 0x91, 0x01, 0xdd, 0x00, 0x9f, 0x01, 0x7e, + 0x01, 0xdb, 0x00, 0xb6, 0x01, 0x70, 0x69, 0x26, 0xd3, 0x9c, 0x07, 0x1f, 0x05, 0x9d, 0x00, 0x00, 0x08, 0xbc, 0x05, + 0x37, 0xfa, 0xa2, 0x01, 0xaa, 0x01, 0xa1, 0x01, 0xa8, 0x01, 0xa0, 0x01, 0xa8, 0x05, 0xb4, 0x01, 0xb4, 0x01, 0xce, + 0x00, 0xd0, 0x00, 0xfc, 0x00, 0xc5, 0x01, 0xff, 0xfb, 0xb1, 0x00, 0x00, 0x38, 0x00, 0x30, 0xfd, 0xf5, 0xfc, 0xf5, + 0xcd, 0x01, 0xa0, 0x00, 0x5f, 0xff, 0x00, 0x40, 0xff, 0x00, 0x00, 0x80, 0x6d, 0x0f, 0xeb, 0x00, 0x7f, 0xff, 0xc2, + 0xf5, 0x68, 0xf7, 0xb3, 0xf1, 0x67, 0x0f, 0x5b, 0x0f, 0x61, 0x0f, 0x80, 0x0f, 0x58, 0xf7, 0x5b, 0xf7, 0x83, 0x0f, + 0x86, 0x00, 0x72, 0x0f, 0x85, 0x0f, 0xc6, 0xf1, 0x7f, 0x0f, 0x6c, 0xf7, 0x00, 0xe0, 0x00, 0xff, 0xd1, 0xf5, 0x87, + 0x0f, 0x8a, 0x0f, 0xff, 0x03, 0xf0, 0x3f, 0x8b, 0x00, 0x8e, 0x00, 0x90, 0x00, 0xb9, 0x00, 0x2d, 0xf5, 0xca, 0xf5, + 0xcb, 0x01, 0x20, 0xf2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x50, 0x98, 0x2e, + 0xd7, 0x0e, 0x50, 0x32, 0x98, 0x2e, 0xfa, 0x03, 0x00, 0x30, 0xf0, 0x7f, 0x00, 0x2e, 0x00, 0x2e, 0xd0, 0x2e, 0x00, + 0x2e, 0x01, 0x80, 0x08, 0xa2, 0xfb, 0x2f, 0x98, 0x2e, 0xba, 0x03, 0x21, 0x2e, 0x19, 0x00, 0x01, 0x2e, 0xee, 0x00, + 0x00, 0xb2, 0x07, 0x2f, 0x01, 0x2e, 0x19, 0x00, 0x00, 0xb2, 0x03, 0x2f, 0x01, 0x50, 0x03, 0x52, 0x98, 0x2e, 0x07, + 0xcc, 0x01, 0x2e, 0xdd, 0x00, 0x00, 0xb2, 0x27, 0x2f, 0x05, 0x2e, 0x8a, 0x00, 0x05, 0x52, 0x98, 0x2e, 0xc7, 0xc1, + 0x03, 0x2e, 0xe9, 0x00, 0x40, 0xb2, 0xf0, 0x7f, 0x08, 0x2f, 0x01, 0x2e, 0x19, 0x00, 0x00, 0xb2, 0x04, 0x2f, 0x00, + 0x30, 0x21, 0x2e, 0xe9, 0x00, 0x98, 0x2e, 0xb4, 0xb1, 0x01, 0x2e, 0x18, 0x00, 0x00, 0xb2, 0x10, 0x2f, 0x05, 0x50, + 0x98, 0x2e, 0x4d, 0xc3, 0x05, 0x50, 0x98, 0x2e, 0x5a, 0xc7, 0x98, 0x2e, 0xf9, 0xb4, 0x98, 0x2e, 0x54, 0xb2, 0x98, + 0x2e, 0x67, 0xb6, 0x98, 0x2e, 0x17, 0xb2, 0x10, 0x30, 0x21, 0x2e, 0x77, 0x00, 0x01, 0x2e, 0xef, 0x00, 0x00, 0xb2, + 0x04, 0x2f, 0x98, 0x2e, 0x7a, 0xb7, 0x00, 0x30, 0x21, 0x2e, 0xef, 0x00, 0x01, 0x2e, 0xd4, 0x00, 0x04, 0xae, 0x0b, + 0x2f, 0x01, 0x2e, 0xdd, 0x00, 0x00, 0xb2, 0x07, 0x2f, 0x05, 0x52, 0x98, 0x2e, 0x8e, 0x0e, 0x00, 0xb2, 0x02, 0x2f, + 0x10, 0x30, 0x21, 0x2e, 0x7d, 0x00, 0x01, 0x2e, 0x7d, 0x00, 0x00, 0x90, 0x90, 0x2e, 0xf1, 0x02, 0x01, 0x2e, 0xd7, + 0x00, 0x00, 0xb2, 0x04, 0x2f, 0x98, 0x2e, 0x2f, 0x0e, 0x00, 0x30, 0x21, 0x2e, 0x7b, 0x00, 0x01, 0x2e, 0x7b, 0x00, + 0x00, 0xb2, 0x12, 0x2f, 0x01, 0x2e, 0xd4, 0x00, 0x00, 0x90, 0x02, 0x2f, 0x98, 0x2e, 0x1f, 0x0e, 0x09, 0x2d, 0x98, + 0x2e, 0x81, 0x0d, 0x01, 0x2e, 0xd4, 0x00, 0x04, 0x90, 0x02, 0x2f, 0x50, 0x32, 0x98, 0x2e, 0xfa, 0x03, 0x00, 0x30, + 0x21, 0x2e, 0x7b, 0x00, 0x01, 0x2e, 0x7c, 0x00, 0x00, 0xb2, 0x90, 0x2e, 0x09, 0x03, 0x01, 0x2e, 0x7c, 0x00, 0x01, + 0x31, 0x01, 0x08, 0x00, 0xb2, 0x04, 0x2f, 0x98, 0x2e, 0x47, 0xcb, 0x10, 0x30, 0x21, 0x2e, 0x77, 0x00, 0x81, 0x30, + 0x01, 0x2e, 0x7c, 0x00, 0x01, 0x08, 0x00, 0xb2, 0x61, 0x2f, 0x03, 0x2e, 0x89, 0x00, 0x01, 0x2e, 0xd4, 0x00, 0x98, + 0xbc, 0x98, 0xb8, 0x05, 0xb2, 0x0f, 0x58, 0x23, 0x2f, 0x07, 0x90, 0x09, 0x54, 0x00, 0x30, 0x37, 0x2f, 0x15, 0x41, + 0x04, 0x41, 0xdc, 0xbe, 0x44, 0xbe, 0xdc, 0xba, 0x2c, 0x01, 0x61, 0x00, 0x0f, 0x56, 0x4a, 0x0f, 0x0c, 0x2f, 0xd1, + 0x42, 0x94, 0xb8, 0xc1, 0x42, 0x11, 0x30, 0x05, 0x2e, 0x6a, 0xf7, 0x2c, 0xbd, 0x2f, 0xb9, 0x80, 0xb2, 0x08, 0x22, + 0x98, 0x2e, 0xc3, 0xb7, 0x21, 0x2d, 0x61, 0x30, 0x23, 0x2e, 0xd4, 0x00, 0x98, 0x2e, 0xc3, 0xb7, 0x00, 0x30, 0x21, + 0x2e, 0x5a, 0xf5, 0x18, 0x2d, 0xe1, 0x7f, 0x50, 0x30, 0x98, 0x2e, 0xfa, 0x03, 0x0f, 0x52, 0x07, 0x50, 0x50, 0x42, + 0x70, 0x30, 0x0d, 0x54, 0x42, 0x42, 0x7e, 0x82, 0xe2, 0x6f, 0x80, 0xb2, 0x42, 0x42, 0x05, 0x2f, 0x21, 0x2e, 0xd4, + 0x00, 0x10, 0x30, 0x98, 0x2e, 0xc3, 0xb7, 0x03, 0x2d, 0x60, 0x30, 0x21, 0x2e, 0xd4, 0x00, 0x01, 0x2e, 0xd4, 0x00, + 0x06, 0x90, 0x18, 0x2f, 0x01, 0x2e, 0x76, 0x00, 0x0b, 0x54, 0x07, 0x52, 0xe0, 0x7f, 0x98, 0x2e, 0x7a, 0xc1, 0xe1, + 0x6f, 0x08, 0x1a, 0x40, 0x30, 0x08, 0x2f, 0x21, 0x2e, 0xd4, 0x00, 0x20, 0x30, 0x98, 0x2e, 0xaf, 0xb7, 0x50, 0x32, + 0x98, 0x2e, 0xfa, 0x03, 0x05, 0x2d, 0x98, 0x2e, 0x38, 0x0e, 0x00, 0x30, 0x21, 0x2e, 0xd4, 0x00, 0x00, 0x30, 0x21, + 0x2e, 0x7c, 0x00, 0x18, 0x2d, 0x01, 0x2e, 0xd4, 0x00, 0x03, 0xaa, 0x01, 0x2f, 0x98, 0x2e, 0x45, 0x0e, 0x01, 0x2e, + 0xd4, 0x00, 0x3f, 0x80, 0x03, 0xa2, 0x01, 0x2f, 0x00, 0x2e, 0x02, 0x2d, 0x98, 0x2e, 0x5b, 0x0e, 0x30, 0x30, 0x98, + 0x2e, 0xce, 0xb7, 0x00, 0x30, 0x21, 0x2e, 0x7d, 0x00, 0x50, 0x32, 0x98, 0x2e, 0xfa, 0x03, 0x01, 0x2e, 0x77, 0x00, + 0x00, 0xb2, 0x24, 0x2f, 0x98, 0x2e, 0xf5, 0xcb, 0x03, 0x2e, 0xd5, 0x00, 0x11, 0x54, 0x01, 0x0a, 0xbc, 0x84, 0x83, + 0x86, 0x21, 0x2e, 0xc9, 0x01, 0xe0, 0x40, 0x13, 0x52, 0xc4, 0x40, 0x82, 0x40, 0xa8, 0xb9, 0x52, 0x42, 0x43, 0xbe, + 0x53, 0x42, 0x04, 0x0a, 0x50, 0x42, 0xe1, 0x7f, 0xf0, 0x31, 0x41, 0x40, 0xf2, 0x6f, 0x25, 0xbd, 0x08, 0x08, 0x02, + 0x0a, 0xd0, 0x7f, 0x98, 0x2e, 0xa8, 0xcf, 0x06, 0xbc, 0xd1, 0x6f, 0xe2, 0x6f, 0x08, 0x0a, 0x80, 0x42, 0x98, 0x2e, + 0x58, 0xb7, 0x00, 0x30, 0x21, 0x2e, 0xee, 0x00, 0x21, 0x2e, 0x77, 0x00, 0x21, 0x2e, 0xdd, 0x00, 0x80, 0x2e, 0xf4, + 0x01, 0x1a, 0x24, 0x22, 0x00, 0x80, 0x2e, 0xec, 0x01, 0x10, 0x50, 0xfb, 0x7f, 0x98, 0x2e, 0xf3, 0x03, 0x57, 0x50, + 0xfb, 0x6f, 0x01, 0x30, 0x71, 0x54, 0x11, 0x42, 0x42, 0x0e, 0xfc, 0x2f, 0xc0, 0x2e, 0x01, 0x42, 0xf0, 0x5f, 0x80, + 0x2e, 0x00, 0xc1, 0xfd, 0x2d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9a, 0x01, + 0x34, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x20, 0x50, 0xe7, 0x7f, 0xf6, 0x7f, 0x06, 0x32, 0x0f, 0x2e, 0x61, 0xf5, 0xfe, 0x09, 0xc0, 0xb3, 0x04, + 0x2f, 0x17, 0x30, 0x2f, 0x2e, 0xef, 0x00, 0x2d, 0x2e, 0x61, 0xf5, 0xf6, 0x6f, 0xe7, 0x6f, 0xe0, 0x5f, 0xc8, 0x2e, + 0x20, 0x50, 0xe7, 0x7f, 0xf6, 0x7f, 0x46, 0x30, 0x0f, 0x2e, 0xa4, 0xf1, 0xbe, 0x09, 0x80, 0xb3, 0x06, 0x2f, 0x0d, + 0x2e, 0xd4, 0x00, 0x84, 0xaf, 0x02, 0x2f, 0x16, 0x30, 0x2d, 0x2e, 0x7b, 0x00, 0x86, 0x30, 0x2d, 0x2e, 0x60, 0xf5, + 0xf6, 0x6f, 0xe7, 0x6f, 0xe0, 0x5f, 0xc8, 0x2e, 0x01, 0x2e, 0x77, 0xf7, 0x09, 0xbc, 0x0f, 0xb8, 0x00, 0xb2, 0x10, + 0x50, 0xfb, 0x7f, 0x10, 0x30, 0x0b, 0x2f, 0x03, 0x2e, 0x8a, 0x00, 0x96, 0xbc, 0x9f, 0xb8, 0x40, 0xb2, 0x05, 0x2f, + 0x03, 0x2e, 0x68, 0xf7, 0x9e, 0xbc, 0x9f, 0xb8, 0x40, 0xb2, 0x07, 0x2f, 0x03, 0x2e, 0x7e, 0x00, 0x41, 0x90, 0x01, + 0x2f, 0x98, 0x2e, 0xdc, 0x03, 0x03, 0x2c, 0x00, 0x30, 0x21, 0x2e, 0x7e, 0x00, 0xfb, 0x6f, 0xf0, 0x5f, 0xb8, 0x2e, + 0x20, 0x50, 0xe0, 0x7f, 0xfb, 0x7f, 0x00, 0x2e, 0x27, 0x50, 0x98, 0x2e, 0x3b, 0xc8, 0x29, 0x50, 0x98, 0x2e, 0xa7, + 0xc8, 0x01, 0x50, 0x98, 0x2e, 0x55, 0xcc, 0xe1, 0x6f, 0x2b, 0x50, 0x98, 0x2e, 0xe0, 0xc9, 0xfb, 0x6f, 0x00, 0x30, + 0xe0, 0x5f, 0x21, 0x2e, 0x7e, 0x00, 0xb8, 0x2e, 0x73, 0x50, 0x01, 0x30, 0x57, 0x54, 0x11, 0x42, 0x42, 0x0e, 0xfc, + 0x2f, 0xb8, 0x2e, 0x21, 0x2e, 0x59, 0xf5, 0x10, 0x30, 0xc0, 0x2e, 0x21, 0x2e, 0x4a, 0xf1, 0x90, 0x50, 0xf7, 0x7f, + 0xe6, 0x7f, 0xd5, 0x7f, 0xc4, 0x7f, 0xb3, 0x7f, 0xa1, 0x7f, 0x90, 0x7f, 0x82, 0x7f, 0x7b, 0x7f, 0x98, 0x2e, 0x35, + 0xb7, 0x00, 0xb2, 0x90, 0x2e, 0x97, 0xb0, 0x03, 0x2e, 0x8f, 0x00, 0x07, 0x2e, 0x91, 0x00, 0x05, 0x2e, 0xb1, 0x00, + 0x3f, 0xba, 0x9f, 0xb8, 0x01, 0x2e, 0xb1, 0x00, 0xa3, 0xbd, 0x4c, 0x0a, 0x05, 0x2e, 0xb1, 0x00, 0x04, 0xbe, 0xbf, + 0xb9, 0xcb, 0x0a, 0x4f, 0xba, 0x22, 0xbd, 0x01, 0x2e, 0xb3, 0x00, 0xdc, 0x0a, 0x2f, 0xb9, 0x03, 0x2e, 0xb8, 0x00, + 0x0a, 0xbe, 0x9a, 0x0a, 0xcf, 0xb9, 0x9b, 0xbc, 0x01, 0x2e, 0x97, 0x00, 0x9f, 0xb8, 0x93, 0x0a, 0x0f, 0xbc, 0x91, + 0x0a, 0x0f, 0xb8, 0x90, 0x0a, 0x25, 0x2e, 0x18, 0x00, 0x05, 0x2e, 0xc1, 0xf5, 0x2e, 0xbd, 0x2e, 0xb9, 0x01, 0x2e, + 0x19, 0x00, 0x31, 0x30, 0x8a, 0x04, 0x00, 0x90, 0x07, 0x2f, 0x01, 0x2e, 0xd4, 0x00, 0x04, 0xa2, 0x03, 0x2f, 0x01, + 0x2e, 0x18, 0x00, 0x00, 0xb2, 0x0c, 0x2f, 0x19, 0x50, 0x05, 0x52, 0x98, 0x2e, 0x4d, 0xb7, 0x05, 0x2e, 0x78, 0x00, + 0x80, 0x90, 0x10, 0x30, 0x01, 0x2f, 0x21, 0x2e, 0x78, 0x00, 0x25, 0x2e, 0xdd, 0x00, 0x98, 0x2e, 0x3e, 0xb7, 0x00, + 0xb2, 0x02, 0x30, 0x01, 0x30, 0x04, 0x2f, 0x01, 0x2e, 0x19, 0x00, 0x00, 0xb2, 0x00, 0x2f, 0x21, 0x30, 0x01, 0x2e, + 0xea, 0x00, 0x08, 0x1a, 0x0e, 0x2f, 0x23, 0x2e, 0xea, 0x00, 0x33, 0x30, 0x1b, 0x50, 0x0b, 0x09, 0x01, 0x40, 0x17, + 0x56, 0x46, 0xbe, 0x4b, 0x08, 0x4c, 0x0a, 0x01, 0x42, 0x0a, 0x80, 0x15, 0x52, 0x01, 0x42, 0x00, 0x2e, 0x01, 0x2e, + 0x18, 0x00, 0x00, 0xb2, 0x1f, 0x2f, 0x03, 0x2e, 0xc0, 0xf5, 0xf0, 0x30, 0x48, 0x08, 0x47, 0xaa, 0x74, 0x30, 0x07, + 0x2e, 0x7a, 0x00, 0x61, 0x22, 0x4b, 0x1a, 0x05, 0x2f, 0x07, 0x2e, 0x66, 0xf5, 0xbf, 0xbd, 0xbf, 0xb9, 0xc0, 0x90, + 0x0b, 0x2f, 0x1d, 0x56, 0x2b, 0x30, 0xd2, 0x42, 0xdb, 0x42, 0x01, 0x04, 0xc2, 0x42, 0x04, 0xbd, 0xfe, 0x80, 0x81, + 0x84, 0x23, 0x2e, 0x7a, 0x00, 0x02, 0x42, 0x02, 0x32, 0x25, 0x2e, 0x62, 0xf5, 0x05, 0x2e, 0xd6, 0x00, 0x81, 0x84, + 0x25, 0x2e, 0xd6, 0x00, 0x02, 0x31, 0x25, 0x2e, 0x60, 0xf5, 0x05, 0x2e, 0x8a, 0x00, 0x0b, 0x50, 0x90, 0x08, 0x80, + 0xb2, 0x0b, 0x2f, 0x05, 0x2e, 0xca, 0xf5, 0xf0, 0x3e, 0x90, 0x08, 0x25, 0x2e, 0xca, 0xf5, 0x05, 0x2e, 0x59, 0xf5, + 0xe0, 0x3f, 0x90, 0x08, 0x25, 0x2e, 0x59, 0xf5, 0x90, 0x6f, 0xa1, 0x6f, 0xb3, 0x6f, 0xc4, 0x6f, 0xd5, 0x6f, 0xe6, + 0x6f, 0xf7, 0x6f, 0x7b, 0x6f, 0x82, 0x6f, 0x70, 0x5f, 0xc8, 0x2e, 0xc0, 0x50, 0x90, 0x7f, 0xe5, 0x7f, 0xd4, 0x7f, + 0xc3, 0x7f, 0xb1, 0x7f, 0xa2, 0x7f, 0x87, 0x7f, 0xf6, 0x7f, 0x7b, 0x7f, 0x00, 0x2e, 0x01, 0x2e, 0x60, 0xf5, 0x60, + 0x7f, 0x98, 0x2e, 0x35, 0xb7, 0x02, 0x30, 0x63, 0x6f, 0x15, 0x52, 0x50, 0x7f, 0x62, 0x7f, 0x5a, 0x2c, 0x02, 0x32, + 0x1a, 0x09, 0x00, 0xb3, 0x14, 0x2f, 0x00, 0xb2, 0x03, 0x2f, 0x09, 0x2e, 0x18, 0x00, 0x00, 0x91, 0x0c, 0x2f, 0x43, + 0x7f, 0x98, 0x2e, 0x97, 0xb7, 0x1f, 0x50, 0x02, 0x8a, 0x02, 0x32, 0x04, 0x30, 0x25, 0x2e, 0x64, 0xf5, 0x15, 0x52, + 0x50, 0x6f, 0x43, 0x6f, 0x44, 0x43, 0x25, 0x2e, 0x60, 0xf5, 0xd9, 0x08, 0xc0, 0xb2, 0x36, 0x2f, 0x98, 0x2e, 0x3e, + 0xb7, 0x00, 0xb2, 0x06, 0x2f, 0x01, 0x2e, 0x19, 0x00, 0x00, 0xb2, 0x02, 0x2f, 0x50, 0x6f, 0x00, 0x90, 0x0a, 0x2f, + 0x01, 0x2e, 0x79, 0x00, 0x00, 0x90, 0x19, 0x2f, 0x10, 0x30, 0x21, 0x2e, 0x79, 0x00, 0x00, 0x30, 0x98, 0x2e, 0xdc, + 0x03, 0x13, 0x2d, 0x01, 0x2e, 0xc3, 0xf5, 0x0c, 0xbc, 0x0f, 0xb8, 0x12, 0x30, 0x10, 0x04, 0x03, 0xb0, 0x26, 0x25, + 0x21, 0x50, 0x03, 0x52, 0x98, 0x2e, 0x4d, 0xb7, 0x10, 0x30, 0x21, 0x2e, 0xee, 0x00, 0x02, 0x30, 0x60, 0x7f, 0x25, + 0x2e, 0x79, 0x00, 0x60, 0x6f, 0x00, 0x90, 0x05, 0x2f, 0x00, 0x30, 0x21, 0x2e, 0xea, 0x00, 0x15, 0x50, 0x21, 0x2e, + 0x64, 0xf5, 0x15, 0x52, 0x23, 0x2e, 0x60, 0xf5, 0x02, 0x32, 0x50, 0x6f, 0x00, 0x90, 0x02, 0x2f, 0x03, 0x30, 0x27, + 0x2e, 0x78, 0x00, 0x07, 0x2e, 0x60, 0xf5, 0x1a, 0x09, 0x00, 0x91, 0xa3, 0x2f, 0x19, 0x09, 0x00, 0x91, 0xa0, 0x2f, + 0x90, 0x6f, 0xa2, 0x6f, 0xb1, 0x6f, 0xc3, 0x6f, 0xd4, 0x6f, 0xe5, 0x6f, 0x7b, 0x6f, 0xf6, 0x6f, 0x87, 0x6f, 0x40, + 0x5f, 0xc8, 0x2e, 0xc0, 0x50, 0xe7, 0x7f, 0xf6, 0x7f, 0x26, 0x30, 0x0f, 0x2e, 0x61, 0xf5, 0x2f, 0x2e, 0x7c, 0x00, + 0x0f, 0x2e, 0x7c, 0x00, 0xbe, 0x09, 0xa2, 0x7f, 0x80, 0x7f, 0x80, 0xb3, 0xd5, 0x7f, 0xc4, 0x7f, 0xb3, 0x7f, 0x91, + 0x7f, 0x7b, 0x7f, 0x0b, 0x2f, 0x23, 0x50, 0x1a, 0x25, 0x12, 0x40, 0x42, 0x7f, 0x74, 0x82, 0x12, 0x40, 0x52, 0x7f, + 0x00, 0x2e, 0x00, 0x40, 0x60, 0x7f, 0x98, 0x2e, 0x6a, 0xd6, 0x81, 0x30, 0x01, 0x2e, 0x7c, 0x00, 0x01, 0x08, 0x00, + 0xb2, 0x42, 0x2f, 0x03, 0x2e, 0x89, 0x00, 0x01, 0x2e, 0x89, 0x00, 0x97, 0xbc, 0x06, 0xbc, 0x9f, 0xb8, 0x0f, 0xb8, + 0x00, 0x90, 0x23, 0x2e, 0xd8, 0x00, 0x10, 0x30, 0x01, 0x30, 0x2a, 0x2f, 0x03, 0x2e, 0xd4, 0x00, 0x44, 0xb2, 0x05, + 0x2f, 0x47, 0xb2, 0x00, 0x30, 0x2d, 0x2f, 0x21, 0x2e, 0x7c, 0x00, 0x2b, 0x2d, 0x03, 0x2e, 0xfd, 0xf5, 0x9e, 0xbc, + 0x9f, 0xb8, 0x40, 0x90, 0x14, 0x2f, 0x03, 0x2e, 0xfc, 0xf5, 0x99, 0xbc, 0x9f, 0xb8, 0x40, 0x90, 0x0e, 0x2f, 0x03, + 0x2e, 0x49, 0xf1, 0x25, 0x54, 0x4a, 0x08, 0x40, 0x90, 0x08, 0x2f, 0x98, 0x2e, 0x35, 0xb7, 0x00, 0xb2, 0x10, 0x30, + 0x03, 0x2f, 0x50, 0x30, 0x21, 0x2e, 0xd4, 0x00, 0x10, 0x2d, 0x98, 0x2e, 0xaf, 0xb7, 0x00, 0x30, 0x21, 0x2e, 0x7c, + 0x00, 0x0a, 0x2d, 0x05, 0x2e, 0x69, 0xf7, 0x2d, 0xbd, 0x2f, 0xb9, 0x80, 0xb2, 0x01, 0x2f, 0x21, 0x2e, 0x7d, 0x00, + 0x23, 0x2e, 0x7c, 0x00, 0xe0, 0x31, 0x21, 0x2e, 0x61, 0xf5, 0xf6, 0x6f, 0xe7, 0x6f, 0x80, 0x6f, 0xa2, 0x6f, 0xb3, + 0x6f, 0xc4, 0x6f, 0xd5, 0x6f, 0x7b, 0x6f, 0x91, 0x6f, 0x40, 0x5f, 0xc8, 0x2e, 0x60, 0x51, 0x0a, 0x25, 0x36, 0x88, + 0xf4, 0x7f, 0xeb, 0x7f, 0x00, 0x32, 0x31, 0x52, 0x32, 0x30, 0x13, 0x30, 0x98, 0x2e, 0x15, 0xcb, 0x0a, 0x25, 0x33, + 0x84, 0xd2, 0x7f, 0x43, 0x30, 0x05, 0x50, 0x2d, 0x52, 0x98, 0x2e, 0x95, 0xc1, 0xd2, 0x6f, 0x27, 0x52, 0x98, 0x2e, + 0xd7, 0xc7, 0x2a, 0x25, 0xb0, 0x86, 0xc0, 0x7f, 0xd3, 0x7f, 0xaf, 0x84, 0x29, 0x50, 0xf1, 0x6f, 0x98, 0x2e, 0x4d, + 0xc8, 0x2a, 0x25, 0xae, 0x8a, 0xaa, 0x88, 0xf2, 0x6e, 0x2b, 0x50, 0xc1, 0x6f, 0xd3, 0x6f, 0xf4, 0x7f, 0x98, 0x2e, + 0xb6, 0xc8, 0xe0, 0x6e, 0x00, 0xb2, 0x32, 0x2f, 0x33, 0x54, 0x83, 0x86, 0xf1, 0x6f, 0xc3, 0x7f, 0x04, 0x30, 0x30, + 0x30, 0xf4, 0x7f, 0xd0, 0x7f, 0xb2, 0x7f, 0xe3, 0x30, 0xc5, 0x6f, 0x56, 0x40, 0x45, 0x41, 0x28, 0x08, 0x03, 0x14, + 0x0e, 0xb4, 0x08, 0xbc, 0x82, 0x40, 0x10, 0x0a, 0x2f, 0x54, 0x26, 0x05, 0x91, 0x7f, 0x44, 0x28, 0xa3, 0x7f, 0x98, + 0x2e, 0xd9, 0xc0, 0x08, 0xb9, 0x33, 0x30, 0x53, 0x09, 0xc1, 0x6f, 0xd3, 0x6f, 0xf4, 0x6f, 0x83, 0x17, 0x47, 0x40, + 0x6c, 0x15, 0xb2, 0x6f, 0xbe, 0x09, 0x75, 0x0b, 0x90, 0x42, 0x45, 0x42, 0x51, 0x0e, 0x32, 0xbc, 0x02, 0x89, 0xa1, + 0x6f, 0x7e, 0x86, 0xf4, 0x7f, 0xd0, 0x7f, 0xb2, 0x7f, 0x04, 0x30, 0x91, 0x6f, 0xd6, 0x2f, 0xeb, 0x6f, 0xa0, 0x5e, + 0xb8, 0x2e, 0x03, 0x2e, 0x97, 0x00, 0x1b, 0xbc, 0x60, 0x50, 0x9f, 0xbc, 0x0c, 0xb8, 0xf0, 0x7f, 0x40, 0xb2, 0xeb, + 0x7f, 0x2b, 0x2f, 0x03, 0x2e, 0x7f, 0x00, 0x41, 0x40, 0x01, 0x2e, 0xc8, 0x00, 0x01, 0x1a, 0x11, 0x2f, 0x37, 0x58, + 0x23, 0x2e, 0xc8, 0x00, 0x10, 0x41, 0xa0, 0x7f, 0x38, 0x81, 0x01, 0x41, 0xd0, 0x7f, 0xb1, 0x7f, 0x98, 0x2e, 0x64, + 0xcf, 0xd0, 0x6f, 0x07, 0x80, 0xa1, 0x6f, 0x11, 0x42, 0x00, 0x2e, 0xb1, 0x6f, 0x01, 0x42, 0x11, 0x30, 0x01, 0x2e, + 0xfc, 0x00, 0x00, 0xa8, 0x03, 0x30, 0xcb, 0x22, 0x4a, 0x25, 0x01, 0x2e, 0x7f, 0x00, 0x3c, 0x89, 0x35, 0x52, 0x05, + 0x54, 0x98, 0x2e, 0xc4, 0xce, 0xc1, 0x6f, 0xf0, 0x6f, 0x98, 0x2e, 0x95, 0xcf, 0x04, 0x2d, 0x01, 0x30, 0xf0, 0x6f, + 0x98, 0x2e, 0x95, 0xcf, 0xeb, 0x6f, 0xa0, 0x5f, 0xb8, 0x2e, 0x03, 0x2e, 0xb3, 0x00, 0x02, 0x32, 0xf0, 0x30, 0x03, + 0x31, 0x30, 0x50, 0x8a, 0x08, 0x08, 0x08, 0xcb, 0x08, 0xe0, 0x7f, 0x80, 0xb2, 0xf3, 0x7f, 0xdb, 0x7f, 0x25, 0x2f, + 0x03, 0x2e, 0xca, 0x00, 0x41, 0x90, 0x04, 0x2f, 0x01, 0x30, 0x23, 0x2e, 0xca, 0x00, 0x98, 0x2e, 0x3f, 0x03, 0xc0, + 0xb2, 0x05, 0x2f, 0x03, 0x2e, 0xda, 0x00, 0x00, 0x30, 0x41, 0x04, 0x23, 0x2e, 0xda, 0x00, 0x98, 0x2e, 0x92, 0xb2, + 0x10, 0x25, 0xf0, 0x6f, 0x00, 0xb2, 0x05, 0x2f, 0x01, 0x2e, 0xda, 0x00, 0x02, 0x30, 0x10, 0x04, 0x21, 0x2e, 0xda, + 0x00, 0x40, 0xb2, 0x01, 0x2f, 0x23, 0x2e, 0xc8, 0x01, 0xdb, 0x6f, 0xe0, 0x6f, 0xd0, 0x5f, 0x80, 0x2e, 0x95, 0xcf, + 0x01, 0x30, 0xe0, 0x6f, 0x98, 0x2e, 0x95, 0xcf, 0x11, 0x30, 0x23, 0x2e, 0xca, 0x00, 0xdb, 0x6f, 0xd0, 0x5f, 0xb8, + 0x2e, 0xd0, 0x50, 0x0a, 0x25, 0x33, 0x84, 0x55, 0x50, 0xd2, 0x7f, 0xe2, 0x7f, 0x03, 0x8c, 0xc0, 0x7f, 0xbb, 0x7f, + 0x00, 0x30, 0x05, 0x5a, 0x39, 0x54, 0x51, 0x41, 0xa5, 0x7f, 0x96, 0x7f, 0x80, 0x7f, 0x98, 0x2e, 0xd9, 0xc0, 0x05, + 0x30, 0xf5, 0x7f, 0x20, 0x25, 0x91, 0x6f, 0x3b, 0x58, 0x3d, 0x5c, 0x3b, 0x56, 0x98, 0x2e, 0x67, 0xcc, 0xc1, 0x6f, + 0xd5, 0x6f, 0x52, 0x40, 0x50, 0x43, 0xc1, 0x7f, 0xd5, 0x7f, 0x10, 0x25, 0x98, 0x2e, 0xfe, 0xc9, 0x10, 0x25, 0x98, + 0x2e, 0x74, 0xc0, 0x86, 0x6f, 0x30, 0x28, 0x92, 0x6f, 0x82, 0x8c, 0xa5, 0x6f, 0x6f, 0x52, 0x69, 0x0e, 0x39, 0x54, + 0xdb, 0x2f, 0x19, 0xa0, 0x15, 0x30, 0x03, 0x2f, 0x00, 0x30, 0x21, 0x2e, 0x81, 0x01, 0x0a, 0x2d, 0x01, 0x2e, 0x81, + 0x01, 0x05, 0x28, 0x42, 0x36, 0x21, 0x2e, 0x81, 0x01, 0x02, 0x0e, 0x01, 0x2f, 0x98, 0x2e, 0xf3, 0x03, 0x57, 0x50, + 0x12, 0x30, 0x01, 0x40, 0x98, 0x2e, 0xfe, 0xc9, 0x51, 0x6f, 0x0b, 0x5c, 0x8e, 0x0e, 0x3b, 0x6f, 0x57, 0x58, 0x02, + 0x30, 0x21, 0x2e, 0x95, 0x01, 0x45, 0x6f, 0x2a, 0x8d, 0xd2, 0x7f, 0xcb, 0x7f, 0x13, 0x2f, 0x02, 0x30, 0x3f, 0x50, + 0xd2, 0x7f, 0xa8, 0x0e, 0x0e, 0x2f, 0xc0, 0x6f, 0x53, 0x54, 0x02, 0x00, 0x51, 0x54, 0x42, 0x0e, 0x10, 0x30, 0x59, + 0x52, 0x02, 0x30, 0x01, 0x2f, 0x00, 0x2e, 0x03, 0x2d, 0x50, 0x42, 0x42, 0x42, 0x12, 0x30, 0xd2, 0x7f, 0x80, 0xb2, + 0x03, 0x2f, 0x00, 0x30, 0x21, 0x2e, 0x80, 0x01, 0x12, 0x2d, 0x01, 0x2e, 0xc9, 0x00, 0x02, 0x80, 0x05, 0x2e, 0x80, + 0x01, 0x11, 0x30, 0x91, 0x28, 0x00, 0x40, 0x25, 0x2e, 0x80, 0x01, 0x10, 0x0e, 0x05, 0x2f, 0x01, 0x2e, 0x7f, 0x01, + 0x01, 0x90, 0x01, 0x2f, 0x98, 0x2e, 0xf3, 0x03, 0x00, 0x2e, 0xa0, 0x41, 0x01, 0x90, 0xa6, 0x7f, 0x90, 0x2e, 0xe3, + 0xb4, 0x01, 0x2e, 0x95, 0x01, 0x00, 0xa8, 0x90, 0x2e, 0xe3, 0xb4, 0x5b, 0x54, 0x95, 0x80, 0x82, 0x40, 0x80, 0xb2, + 0x02, 0x40, 0x2d, 0x8c, 0x3f, 0x52, 0x96, 0x7f, 0x90, 0x2e, 0xc2, 0xb3, 0x29, 0x0e, 0x76, 0x2f, 0x01, 0x2e, 0xc9, + 0x00, 0x00, 0x40, 0x81, 0x28, 0x45, 0x52, 0xb3, 0x30, 0x98, 0x2e, 0x0f, 0xca, 0x5d, 0x54, 0x80, 0x7f, 0x00, 0x2e, + 0xa1, 0x40, 0x72, 0x7f, 0x82, 0x80, 0x82, 0x40, 0x60, 0x7f, 0x98, 0x2e, 0xfe, 0xc9, 0x10, 0x25, 0x98, 0x2e, 0x74, + 0xc0, 0x62, 0x6f, 0x05, 0x30, 0x87, 0x40, 0xc0, 0x91, 0x04, 0x30, 0x05, 0x2f, 0x05, 0x2e, 0x83, 0x01, 0x80, 0xb2, + 0x14, 0x30, 0x00, 0x2f, 0x04, 0x30, 0x05, 0x2e, 0xc9, 0x00, 0x73, 0x6f, 0x81, 0x40, 0xe2, 0x40, 0x69, 0x04, 0x11, + 0x0f, 0xe1, 0x40, 0x16, 0x30, 0xfe, 0x29, 0xcb, 0x40, 0x02, 0x2f, 0x83, 0x6f, 0x83, 0x0f, 0x22, 0x2f, 0x47, 0x56, + 0x13, 0x0f, 0x12, 0x30, 0x77, 0x2f, 0x49, 0x54, 0x42, 0x0e, 0x12, 0x30, 0x73, 0x2f, 0x00, 0x91, 0x0a, 0x2f, 0x01, + 0x2e, 0x8b, 0x01, 0x19, 0xa8, 0x02, 0x30, 0x6c, 0x2f, 0x63, 0x50, 0x00, 0x2e, 0x17, 0x42, 0x05, 0x42, 0x68, 0x2c, + 0x12, 0x30, 0x0b, 0x25, 0x08, 0x0f, 0x50, 0x30, 0x02, 0x2f, 0x21, 0x2e, 0x83, 0x01, 0x03, 0x2d, 0x40, 0x30, 0x21, + 0x2e, 0x83, 0x01, 0x2b, 0x2e, 0x85, 0x01, 0x5a, 0x2c, 0x12, 0x30, 0x00, 0x91, 0x2b, 0x25, 0x04, 0x2f, 0x63, 0x50, + 0x02, 0x30, 0x17, 0x42, 0x17, 0x2c, 0x02, 0x42, 0x98, 0x2e, 0xfe, 0xc9, 0x10, 0x25, 0x98, 0x2e, 0x74, 0xc0, 0x05, + 0x2e, 0xc9, 0x00, 0x81, 0x84, 0x5b, 0x30, 0x82, 0x40, 0x37, 0x2e, 0x83, 0x01, 0x02, 0x0e, 0x07, 0x2f, 0x5f, 0x52, + 0x40, 0x30, 0x62, 0x40, 0x41, 0x40, 0x91, 0x0e, 0x01, 0x2f, 0x21, 0x2e, 0x83, 0x01, 0x05, 0x30, 0x2b, 0x2e, 0x85, + 0x01, 0x12, 0x30, 0x36, 0x2c, 0x16, 0x30, 0x15, 0x25, 0x81, 0x7f, 0x98, 0x2e, 0xfe, 0xc9, 0x10, 0x25, 0x98, 0x2e, + 0x74, 0xc0, 0x19, 0xa2, 0x16, 0x30, 0x15, 0x2f, 0x05, 0x2e, 0x97, 0x01, 0x80, 0x6f, 0x82, 0x0e, 0x05, 0x2f, 0x01, + 0x2e, 0x86, 0x01, 0x06, 0x28, 0x21, 0x2e, 0x86, 0x01, 0x0b, 0x2d, 0x03, 0x2e, 0x87, 0x01, 0x5f, 0x54, 0x4e, 0x28, + 0x91, 0x42, 0x00, 0x2e, 0x82, 0x40, 0x90, 0x0e, 0x01, 0x2f, 0x21, 0x2e, 0x88, 0x01, 0x02, 0x30, 0x13, 0x2c, 0x05, + 0x30, 0xc0, 0x6f, 0x08, 0x1c, 0xa8, 0x0f, 0x16, 0x30, 0x05, 0x30, 0x5b, 0x50, 0x09, 0x2f, 0x02, 0x80, 0x2d, 0x2e, + 0x82, 0x01, 0x05, 0x42, 0x05, 0x80, 0x00, 0x2e, 0x02, 0x42, 0x3e, 0x80, 0x00, 0x2e, 0x06, 0x42, 0x02, 0x30, 0x90, + 0x6f, 0x3e, 0x88, 0x01, 0x40, 0x04, 0x41, 0x4c, 0x28, 0x01, 0x42, 0x07, 0x80, 0x10, 0x25, 0x24, 0x40, 0x00, 0x40, + 0x00, 0xa8, 0xf5, 0x22, 0x23, 0x29, 0x44, 0x42, 0x7a, 0x82, 0x7e, 0x88, 0x43, 0x40, 0x04, 0x41, 0x00, 0xab, 0xf5, + 0x23, 0xdf, 0x28, 0x43, 0x42, 0xd9, 0xa0, 0x14, 0x2f, 0x00, 0x90, 0x02, 0x2f, 0xd2, 0x6f, 0x81, 0xb2, 0x05, 0x2f, + 0x63, 0x54, 0x06, 0x28, 0x90, 0x42, 0x85, 0x42, 0x09, 0x2c, 0x02, 0x30, 0x5b, 0x50, 0x03, 0x80, 0x29, 0x2e, 0x7e, + 0x01, 0x2b, 0x2e, 0x82, 0x01, 0x05, 0x42, 0x12, 0x30, 0x2b, 0x2e, 0x83, 0x01, 0x45, 0x82, 0x00, 0x2e, 0x40, 0x40, + 0x7a, 0x82, 0x02, 0xa0, 0x08, 0x2f, 0x63, 0x50, 0x3b, 0x30, 0x15, 0x42, 0x05, 0x42, 0x37, 0x80, 0x37, 0x2e, 0x7e, + 0x01, 0x05, 0x42, 0x12, 0x30, 0x01, 0x2e, 0xc9, 0x00, 0x02, 0x8c, 0x40, 0x40, 0x84, 0x41, 0x7a, 0x8c, 0x04, 0x0f, + 0x03, 0x2f, 0x01, 0x2e, 0x8b, 0x01, 0x19, 0xa4, 0x04, 0x2f, 0x2b, 0x2e, 0x82, 0x01, 0x98, 0x2e, 0xf3, 0x03, 0x12, + 0x30, 0x81, 0x90, 0x61, 0x52, 0x08, 0x2f, 0x65, 0x42, 0x65, 0x42, 0x43, 0x80, 0x39, 0x84, 0x82, 0x88, 0x05, 0x42, + 0x45, 0x42, 0x85, 0x42, 0x05, 0x43, 0x00, 0x2e, 0x80, 0x41, 0x00, 0x90, 0x90, 0x2e, 0xe1, 0xb4, 0x65, 0x54, 0xc1, + 0x6f, 0x80, 0x40, 0x00, 0xb2, 0x43, 0x58, 0x69, 0x50, 0x44, 0x2f, 0x55, 0x5c, 0xb7, 0x87, 0x8c, 0x0f, 0x0d, 0x2e, + 0x96, 0x01, 0xc4, 0x40, 0x36, 0x2f, 0x41, 0x56, 0x8b, 0x0e, 0x2a, 0x2f, 0x0b, 0x52, 0xa1, 0x0e, 0x0a, 0x2f, 0x05, + 0x2e, 0x8f, 0x01, 0x14, 0x25, 0x98, 0x2e, 0xfe, 0xc9, 0x4b, 0x54, 0x02, 0x0f, 0x69, 0x50, 0x05, 0x30, 0x65, 0x54, + 0x15, 0x2f, 0x03, 0x2e, 0x8e, 0x01, 0x4d, 0x5c, 0x8e, 0x0f, 0x3a, 0x2f, 0x05, 0x2e, 0x8f, 0x01, 0x98, 0x2e, 0xfe, + 0xc9, 0x4f, 0x54, 0x82, 0x0f, 0x05, 0x30, 0x69, 0x50, 0x65, 0x54, 0x30, 0x2f, 0x6d, 0x52, 0x15, 0x30, 0x42, 0x8c, + 0x45, 0x42, 0x04, 0x30, 0x2b, 0x2c, 0x84, 0x43, 0x6b, 0x52, 0x42, 0x8c, 0x00, 0x2e, 0x85, 0x43, 0x15, 0x30, 0x24, + 0x2c, 0x45, 0x42, 0x8e, 0x0f, 0x20, 0x2f, 0x0d, 0x2e, 0x8e, 0x01, 0xb1, 0x0e, 0x1c, 0x2f, 0x23, 0x2e, 0x8e, 0x01, + 0x1a, 0x2d, 0x0e, 0x0e, 0x17, 0x2f, 0xa1, 0x0f, 0x15, 0x2f, 0x23, 0x2e, 0x8d, 0x01, 0x13, 0x2d, 0x98, 0x2e, 0x74, + 0xc0, 0x43, 0x54, 0xc2, 0x0e, 0x0a, 0x2f, 0x65, 0x50, 0x04, 0x80, 0x0b, 0x30, 0x06, 0x82, 0x0b, 0x42, 0x79, 0x80, + 0x41, 0x40, 0x12, 0x30, 0x25, 0x2e, 0x8c, 0x01, 0x01, 0x42, 0x05, 0x30, 0x69, 0x50, 0x65, 0x54, 0x84, 0x82, 0x43, + 0x84, 0xbe, 0x8c, 0x84, 0x40, 0x86, 0x41, 0x26, 0x29, 0x94, 0x42, 0xbe, 0x8e, 0xd5, 0x7f, 0x19, 0xa1, 0x43, 0x40, + 0x0b, 0x2e, 0x8c, 0x01, 0x84, 0x40, 0xc7, 0x41, 0x5d, 0x29, 0x27, 0x29, 0x45, 0x42, 0x84, 0x42, 0xc2, 0x7f, 0x01, + 0x2f, 0xc0, 0xb3, 0x1d, 0x2f, 0x05, 0x2e, 0x94, 0x01, 0x99, 0xa0, 0x01, 0x2f, 0x80, 0xb3, 0x13, 0x2f, 0x80, 0xb3, + 0x18, 0x2f, 0xc0, 0xb3, 0x16, 0x2f, 0x12, 0x40, 0x01, 0x40, 0x92, 0x7f, 0x98, 0x2e, 0x74, 0xc0, 0x92, 0x6f, 0x10, + 0x0f, 0x20, 0x30, 0x03, 0x2f, 0x10, 0x30, 0x21, 0x2e, 0x7e, 0x01, 0x0a, 0x2d, 0x21, 0x2e, 0x7e, 0x01, 0x07, 0x2d, + 0x20, 0x30, 0x21, 0x2e, 0x7e, 0x01, 0x03, 0x2d, 0x10, 0x30, 0x21, 0x2e, 0x7e, 0x01, 0xc2, 0x6f, 0x01, 0x2e, 0xc9, + 0x00, 0xbc, 0x84, 0x02, 0x80, 0x82, 0x40, 0x00, 0x40, 0x90, 0x0e, 0xd5, 0x6f, 0x02, 0x2f, 0x15, 0x30, 0x98, 0x2e, + 0xf3, 0x03, 0x41, 0x91, 0x05, 0x30, 0x07, 0x2f, 0x67, 0x50, 0x3d, 0x80, 0x2b, 0x2e, 0x8f, 0x01, 0x05, 0x42, 0x04, + 0x80, 0x00, 0x2e, 0x05, 0x42, 0x02, 0x2c, 0x00, 0x30, 0x00, 0x30, 0xa2, 0x6f, 0x98, 0x8a, 0x86, 0x40, 0x80, 0xa7, + 0x05, 0x2f, 0x98, 0x2e, 0xf3, 0x03, 0xc0, 0x30, 0x21, 0x2e, 0x95, 0x01, 0x06, 0x25, 0x1a, 0x25, 0xe2, 0x6f, 0x76, + 0x82, 0x96, 0x40, 0x56, 0x43, 0x51, 0x0e, 0xfb, 0x2f, 0xbb, 0x6f, 0x30, 0x5f, 0xb8, 0x2e, 0x01, 0x2e, 0xb8, 0x00, + 0x01, 0x31, 0x41, 0x08, 0x40, 0xb2, 0x20, 0x50, 0xf2, 0x30, 0x02, 0x08, 0xfb, 0x7f, 0x01, 0x30, 0x10, 0x2f, 0x05, + 0x2e, 0xcc, 0x00, 0x81, 0x90, 0xe0, 0x7f, 0x03, 0x2f, 0x23, 0x2e, 0xcc, 0x00, 0x98, 0x2e, 0x55, 0xb6, 0x98, 0x2e, + 0x1d, 0xb5, 0x10, 0x25, 0xfb, 0x6f, 0xe0, 0x6f, 0xe0, 0x5f, 0x80, 0x2e, 0x95, 0xcf, 0x98, 0x2e, 0x95, 0xcf, 0x10, + 0x30, 0x21, 0x2e, 0xcc, 0x00, 0xfb, 0x6f, 0xe0, 0x5f, 0xb8, 0x2e, 0x00, 0x51, 0x05, 0x58, 0xeb, 0x7f, 0x2a, 0x25, + 0x89, 0x52, 0x6f, 0x5a, 0x89, 0x50, 0x13, 0x41, 0x06, 0x40, 0xb3, 0x01, 0x16, 0x42, 0xcb, 0x16, 0x06, 0x40, 0xf3, + 0x02, 0x13, 0x42, 0x65, 0x0e, 0xf5, 0x2f, 0x05, 0x40, 0x14, 0x30, 0x2c, 0x29, 0x04, 0x42, 0x08, 0xa1, 0x00, 0x30, + 0x90, 0x2e, 0x52, 0xb6, 0xb3, 0x88, 0xb0, 0x8a, 0xb6, 0x84, 0xa4, 0x7f, 0xc4, 0x7f, 0xb5, 0x7f, 0xd5, 0x7f, 0x92, + 0x7f, 0x73, 0x30, 0x04, 0x30, 0x55, 0x40, 0x42, 0x40, 0x8a, 0x17, 0xf3, 0x08, 0x6b, 0x01, 0x90, 0x02, 0x53, 0xb8, + 0x4b, 0x82, 0xad, 0xbe, 0x71, 0x7f, 0x45, 0x0a, 0x09, 0x54, 0x84, 0x7f, 0x98, 0x2e, 0xd9, 0xc0, 0xa3, 0x6f, 0x7b, + 0x54, 0xd0, 0x42, 0xa3, 0x7f, 0xf2, 0x7f, 0x60, 0x7f, 0x20, 0x25, 0x71, 0x6f, 0x75, 0x5a, 0x77, 0x58, 0x79, 0x5c, + 0x75, 0x56, 0x98, 0x2e, 0x67, 0xcc, 0xb1, 0x6f, 0x62, 0x6f, 0x50, 0x42, 0xb1, 0x7f, 0xb3, 0x30, 0x10, 0x25, 0x98, + 0x2e, 0x0f, 0xca, 0x84, 0x6f, 0x20, 0x29, 0x71, 0x6f, 0x92, 0x6f, 0xa5, 0x6f, 0x76, 0x82, 0x6a, 0x0e, 0x73, 0x30, + 0x00, 0x30, 0xd0, 0x2f, 0xd2, 0x6f, 0xd1, 0x7f, 0xb4, 0x7f, 0x98, 0x2e, 0x2b, 0xb7, 0x15, 0xbd, 0x0b, 0xb8, 0x02, + 0x0a, 0xc2, 0x6f, 0xc0, 0x7f, 0x98, 0x2e, 0x2b, 0xb7, 0x15, 0xbd, 0x0b, 0xb8, 0x42, 0x0a, 0xc0, 0x6f, 0x08, 0x17, + 0x41, 0x18, 0x89, 0x16, 0xe1, 0x18, 0xd0, 0x18, 0xa1, 0x7f, 0x27, 0x25, 0x16, 0x25, 0x98, 0x2e, 0x79, 0xc0, 0x8b, + 0x54, 0x90, 0x7f, 0xb3, 0x30, 0x82, 0x40, 0x80, 0x90, 0x0d, 0x2f, 0x7d, 0x52, 0x92, 0x6f, 0x98, 0x2e, 0x0f, 0xca, + 0xb2, 0x6f, 0x90, 0x0e, 0x06, 0x2f, 0x8b, 0x50, 0x14, 0x30, 0x42, 0x6f, 0x51, 0x6f, 0x14, 0x42, 0x12, 0x42, 0x01, + 0x42, 0x00, 0x2e, 0x31, 0x6f, 0x98, 0x2e, 0x74, 0xc0, 0x41, 0x6f, 0x80, 0x7f, 0x98, 0x2e, 0x74, 0xc0, 0x82, 0x6f, + 0x10, 0x04, 0x43, 0x52, 0x01, 0x0f, 0x05, 0x2e, 0xcb, 0x00, 0x00, 0x30, 0x04, 0x30, 0x21, 0x2f, 0x51, 0x6f, 0x43, + 0x58, 0x8c, 0x0e, 0x04, 0x30, 0x1c, 0x2f, 0x85, 0x88, 0x41, 0x6f, 0x04, 0x41, 0x8c, 0x0f, 0x04, 0x30, 0x16, 0x2f, + 0x84, 0x88, 0x00, 0x2e, 0x04, 0x41, 0x04, 0x05, 0x8c, 0x0e, 0x04, 0x30, 0x0f, 0x2f, 0x82, 0x88, 0x31, 0x6f, 0x04, + 0x41, 0x04, 0x05, 0x8c, 0x0e, 0x04, 0x30, 0x08, 0x2f, 0x83, 0x88, 0x00, 0x2e, 0x04, 0x41, 0x8c, 0x0f, 0x04, 0x30, + 0x02, 0x2f, 0x21, 0x2e, 0xad, 0x01, 0x14, 0x30, 0x00, 0x91, 0x14, 0x2f, 0x03, 0x2e, 0xa1, 0x01, 0x41, 0x90, 0x0e, + 0x2f, 0x03, 0x2e, 0xad, 0x01, 0x14, 0x30, 0x4c, 0x28, 0x23, 0x2e, 0xad, 0x01, 0x46, 0xa0, 0x06, 0x2f, 0x81, 0x84, + 0x8d, 0x52, 0x48, 0x82, 0x82, 0x40, 0x21, 0x2e, 0xa1, 0x01, 0x42, 0x42, 0x5c, 0x2c, 0x02, 0x30, 0x05, 0x2e, 0xaa, + 0x01, 0x80, 0xb2, 0x02, 0x30, 0x55, 0x2f, 0x03, 0x2e, 0xa9, 0x01, 0x92, 0x6f, 0xb3, 0x30, 0x98, 0x2e, 0x0f, 0xca, + 0xb2, 0x6f, 0x90, 0x0f, 0x00, 0x30, 0x02, 0x30, 0x4a, 0x2f, 0xa2, 0x6f, 0x87, 0x52, 0x91, 0x00, 0x85, 0x52, 0x51, + 0x0e, 0x02, 0x2f, 0x00, 0x2e, 0x43, 0x2c, 0x02, 0x30, 0xc2, 0x6f, 0x7f, 0x52, 0x91, 0x0e, 0x02, 0x30, 0x3c, 0x2f, + 0x51, 0x6f, 0x81, 0x54, 0x98, 0x2e, 0xfe, 0xc9, 0x10, 0x25, 0xb3, 0x30, 0x21, 0x25, 0x98, 0x2e, 0x0f, 0xca, 0x32, + 0x6f, 0xc0, 0x7f, 0xb3, 0x30, 0x12, 0x25, 0x98, 0x2e, 0x0f, 0xca, 0x42, 0x6f, 0xb0, 0x7f, 0xb3, 0x30, 0x12, 0x25, + 0x98, 0x2e, 0x0f, 0xca, 0xb2, 0x6f, 0x90, 0x28, 0x83, 0x52, 0x98, 0x2e, 0xfe, 0xc9, 0xc2, 0x6f, 0x90, 0x0f, 0x00, + 0x30, 0x02, 0x30, 0x1d, 0x2f, 0x05, 0x2e, 0xa1, 0x01, 0x80, 0xb2, 0x12, 0x30, 0x0f, 0x2f, 0x42, 0x6f, 0x03, 0x2e, + 0xab, 0x01, 0x91, 0x0e, 0x02, 0x30, 0x12, 0x2f, 0x52, 0x6f, 0x03, 0x2e, 0xac, 0x01, 0x91, 0x0f, 0x02, 0x30, 0x0c, + 0x2f, 0x21, 0x2e, 0xaa, 0x01, 0x0a, 0x2c, 0x12, 0x30, 0x03, 0x2e, 0xcb, 0x00, 0x8d, 0x58, 0x08, 0x89, 0x41, 0x40, + 0x11, 0x43, 0x00, 0x43, 0x25, 0x2e, 0xa1, 0x01, 0xd4, 0x6f, 0x8f, 0x52, 0x00, 0x43, 0x3a, 0x89, 0x00, 0x2e, 0x10, + 0x43, 0x10, 0x43, 0x61, 0x0e, 0xfb, 0x2f, 0x03, 0x2e, 0xa0, 0x01, 0x11, 0x1a, 0x02, 0x2f, 0x02, 0x25, 0x21, 0x2e, + 0xa0, 0x01, 0xeb, 0x6f, 0x00, 0x5f, 0xb8, 0x2e, 0x91, 0x52, 0x10, 0x30, 0x02, 0x30, 0x95, 0x56, 0x52, 0x42, 0x4b, + 0x0e, 0xfc, 0x2f, 0x8d, 0x54, 0x88, 0x82, 0x93, 0x56, 0x80, 0x42, 0x53, 0x42, 0x40, 0x42, 0x42, 0x86, 0x83, 0x54, + 0xc0, 0x2e, 0xc2, 0x42, 0x00, 0x2e, 0xa3, 0x52, 0x00, 0x51, 0x52, 0x40, 0x47, 0x40, 0x1a, 0x25, 0x01, 0x2e, 0x97, + 0x00, 0x8f, 0xbe, 0x72, 0x86, 0xfb, 0x7f, 0x0b, 0x30, 0x7c, 0xbf, 0xa5, 0x50, 0x10, 0x08, 0xdf, 0xba, 0x70, 0x88, + 0xf8, 0xbf, 0xcb, 0x42, 0xd3, 0x7f, 0x6c, 0xbb, 0xfc, 0xbb, 0xc5, 0x0a, 0x90, 0x7f, 0x1b, 0x7f, 0x0b, 0x43, 0xc0, + 0xb2, 0xe5, 0x7f, 0xb7, 0x7f, 0xa6, 0x7f, 0xc4, 0x7f, 0x90, 0x2e, 0x1c, 0xb7, 0x07, 0x2e, 0xd2, 0x00, 0xc0, 0xb2, + 0x0b, 0x2f, 0x97, 0x52, 0x01, 0x2e, 0xcd, 0x00, 0x82, 0x7f, 0x98, 0x2e, 0xbb, 0xcc, 0x0b, 0x30, 0x37, 0x2e, 0xd2, + 0x00, 0x82, 0x6f, 0x90, 0x6f, 0x1a, 0x25, 0x00, 0xb2, 0x8b, 0x7f, 0x14, 0x2f, 0xa6, 0xbd, 0x25, 0xbd, 0xb6, 0xb9, + 0x2f, 0xb9, 0x80, 0xb2, 0xd4, 0xb0, 0x0c, 0x2f, 0x99, 0x54, 0x9b, 0x56, 0x0b, 0x30, 0x0b, 0x2e, 0xb1, 0x00, 0xa1, + 0x58, 0x9b, 0x42, 0xdb, 0x42, 0x6c, 0x09, 0x2b, 0x2e, 0xb1, 0x00, 0x8b, 0x42, 0xcb, 0x42, 0x86, 0x7f, 0x73, 0x84, + 0xa7, 0x56, 0xc3, 0x08, 0x39, 0x52, 0x05, 0x50, 0x72, 0x7f, 0x63, 0x7f, 0x98, 0x2e, 0xc2, 0xc0, 0xe1, 0x6f, 0x62, + 0x6f, 0xd1, 0x0a, 0x01, 0x2e, 0xcd, 0x00, 0xd5, 0x6f, 0xc4, 0x6f, 0x72, 0x6f, 0x97, 0x52, 0x9d, 0x5c, 0x98, 0x2e, + 0x06, 0xcd, 0x23, 0x6f, 0x90, 0x6f, 0x99, 0x52, 0xc0, 0xb2, 0x04, 0xbd, 0x54, 0x40, 0xaf, 0xb9, 0x45, 0x40, 0xe1, + 0x7f, 0x02, 0x30, 0x06, 0x2f, 0xc0, 0xb2, 0x02, 0x30, 0x03, 0x2f, 0x9b, 0x5c, 0x12, 0x30, 0x94, 0x43, 0x85, 0x43, + 0x03, 0xbf, 0x6f, 0xbb, 0x80, 0xb3, 0x20, 0x2f, 0x06, 0x6f, 0x26, 0x01, 0x16, 0x6f, 0x6e, 0x03, 0x45, 0x42, 0xc0, + 0x90, 0x29, 0x2e, 0xce, 0x00, 0x9b, 0x52, 0x14, 0x2f, 0x9b, 0x5c, 0x00, 0x2e, 0x93, 0x41, 0x86, 0x41, 0xe3, 0x04, + 0xae, 0x07, 0x80, 0xab, 0x04, 0x2f, 0x80, 0x91, 0x0a, 0x2f, 0x86, 0x6f, 0x73, 0x0f, 0x07, 0x2f, 0x83, 0x6f, 0xc0, + 0xb2, 0x04, 0x2f, 0x54, 0x42, 0x45, 0x42, 0x12, 0x30, 0x04, 0x2c, 0x11, 0x30, 0x02, 0x2c, 0x11, 0x30, 0x11, 0x30, + 0x02, 0xbc, 0x0f, 0xb8, 0xd2, 0x7f, 0x00, 0xb2, 0x0a, 0x2f, 0x01, 0x2e, 0xfc, 0x00, 0x05, 0x2e, 0xc7, 0x01, 0x10, + 0x1a, 0x02, 0x2f, 0x21, 0x2e, 0xc7, 0x01, 0x03, 0x2d, 0x02, 0x2c, 0x01, 0x30, 0x01, 0x30, 0xb0, 0x6f, 0x98, 0x2e, + 0x95, 0xcf, 0xd1, 0x6f, 0xa0, 0x6f, 0x98, 0x2e, 0x95, 0xcf, 0xe2, 0x6f, 0x9f, 0x52, 0x01, 0x2e, 0xce, 0x00, 0x82, + 0x40, 0x50, 0x42, 0x0c, 0x2c, 0x42, 0x42, 0x11, 0x30, 0x23, 0x2e, 0xd2, 0x00, 0x01, 0x30, 0xb0, 0x6f, 0x98, 0x2e, + 0x95, 0xcf, 0xa0, 0x6f, 0x01, 0x30, 0x98, 0x2e, 0x95, 0xcf, 0x00, 0x2e, 0xfb, 0x6f, 0x00, 0x5f, 0xb8, 0x2e, 0x83, + 0x86, 0x01, 0x30, 0x00, 0x30, 0x94, 0x40, 0x24, 0x18, 0x06, 0x00, 0x53, 0x0e, 0x4f, 0x02, 0xf9, 0x2f, 0xb8, 0x2e, + 0xa9, 0x52, 0x00, 0x2e, 0x60, 0x40, 0x41, 0x40, 0x0d, 0xbc, 0x98, 0xbc, 0xc0, 0x2e, 0x01, 0x0a, 0x0f, 0xb8, 0xab, + 0x52, 0x53, 0x3c, 0x52, 0x40, 0x40, 0x40, 0x4b, 0x00, 0x82, 0x16, 0x26, 0xb9, 0x01, 0xb8, 0x41, 0x40, 0x10, 0x08, + 0x97, 0xb8, 0x01, 0x08, 0xc0, 0x2e, 0x11, 0x30, 0x01, 0x08, 0x43, 0x86, 0x25, 0x40, 0x04, 0x40, 0xd8, 0xbe, 0x2c, + 0x0b, 0x22, 0x11, 0x54, 0x42, 0x03, 0x80, 0x4b, 0x0e, 0xf6, 0x2f, 0xb8, 0x2e, 0x9f, 0x50, 0x10, 0x50, 0xad, 0x52, + 0x05, 0x2e, 0xd3, 0x00, 0xfb, 0x7f, 0x00, 0x2e, 0x13, 0x40, 0x93, 0x42, 0x41, 0x0e, 0xfb, 0x2f, 0x98, 0x2e, 0xa5, + 0xb7, 0x98, 0x2e, 0x87, 0xcf, 0x01, 0x2e, 0xd9, 0x00, 0x00, 0xb2, 0xfb, 0x6f, 0x0b, 0x2f, 0x01, 0x2e, 0x69, 0xf7, + 0xb1, 0x3f, 0x01, 0x08, 0x01, 0x30, 0xf0, 0x5f, 0x23, 0x2e, 0xd9, 0x00, 0x21, 0x2e, 0x69, 0xf7, 0x80, 0x2e, 0x7a, + 0xb7, 0xf0, 0x5f, 0xb8, 0x2e, 0x01, 0x2e, 0xc0, 0xf8, 0x03, 0x2e, 0xfc, 0xf5, 0x15, 0x54, 0xaf, 0x56, 0x82, 0x08, + 0x0b, 0x2e, 0x69, 0xf7, 0xcb, 0x0a, 0xb1, 0x58, 0x80, 0x90, 0xdd, 0xbe, 0x4c, 0x08, 0x5f, 0xb9, 0x59, 0x22, 0x80, + 0x90, 0x07, 0x2f, 0x03, 0x34, 0xc3, 0x08, 0xf2, 0x3a, 0x0a, 0x08, 0x02, 0x35, 0xc0, 0x90, 0x4a, 0x0a, 0x48, 0x22, + 0xc0, 0x2e, 0x23, 0x2e, 0xfc, 0xf5, 0x10, 0x50, 0xfb, 0x7f, 0x98, 0x2e, 0x56, 0xc7, 0x98, 0x2e, 0x49, 0xc3, 0x10, + 0x30, 0xfb, 0x6f, 0xf0, 0x5f, 0x21, 0x2e, 0xcc, 0x00, 0x21, 0x2e, 0xca, 0x00, 0xb8, 0x2e, 0x03, 0x2e, 0xd3, 0x00, + 0x16, 0xb8, 0x02, 0x34, 0x4a, 0x0c, 0x21, 0x2e, 0x2d, 0xf5, 0xc0, 0x2e, 0x23, 0x2e, 0xd3, 0x00, 0x03, 0xbc, 0x21, + 0x2e, 0xd5, 0x00, 0x03, 0x2e, 0xd5, 0x00, 0x40, 0xb2, 0x10, 0x30, 0x21, 0x2e, 0x77, 0x00, 0x01, 0x30, 0x05, 0x2f, + 0x05, 0x2e, 0xd8, 0x00, 0x80, 0x90, 0x01, 0x2f, 0x23, 0x2e, 0x6f, 0xf5, 0xc0, 0x2e, 0x21, 0x2e, 0xd9, 0x00, 0x11, + 0x30, 0x81, 0x08, 0x01, 0x2e, 0x6a, 0xf7, 0x71, 0x3f, 0x23, 0xbd, 0x01, 0x08, 0x02, 0x0a, 0xc0, 0x2e, 0x21, 0x2e, + 0x6a, 0xf7, 0x30, 0x25, 0x00, 0x30, 0x21, 0x2e, 0x5a, 0xf5, 0x10, 0x50, 0x21, 0x2e, 0x7b, 0x00, 0x21, 0x2e, 0x7c, + 0x00, 0xfb, 0x7f, 0x98, 0x2e, 0xc3, 0xb7, 0x40, 0x30, 0x21, 0x2e, 0xd4, 0x00, 0xfb, 0x6f, 0xf0, 0x5f, 0x03, 0x25, + 0x80, 0x2e, 0xaf, 0xb7, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, + 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, + 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x01, 0x2e, 0x5d, 0xf7, 0x08, 0xbc, 0x80, 0xac, 0x0e, 0xbb, 0x02, 0x2f, + 0x00, 0x30, 0x41, 0x04, 0x82, 0x06, 0xc0, 0xa4, 0x00, 0x30, 0x11, 0x2f, 0x40, 0xa9, 0x03, 0x2f, 0x40, 0x91, 0x0d, + 0x2f, 0x00, 0xa7, 0x0b, 0x2f, 0x80, 0xb3, 0xb3, 0x58, 0x02, 0x2f, 0x90, 0xa1, 0x26, 0x13, 0x20, 0x23, 0x80, 0x90, + 0x10, 0x30, 0x01, 0x2f, 0xcc, 0x0e, 0x00, 0x2f, 0x00, 0x30, 0xb8, 0x2e, 0xb5, 0x50, 0x18, 0x08, 0x08, 0xbc, 0x88, + 0xb6, 0x0d, 0x17, 0xc6, 0xbd, 0x56, 0xbc, 0xb7, 0x58, 0xda, 0xba, 0x04, 0x01, 0x1d, 0x0a, 0x10, 0x50, 0x05, 0x30, + 0x32, 0x25, 0x45, 0x03, 0xfb, 0x7f, 0xf6, 0x30, 0x21, 0x25, 0x98, 0x2e, 0x37, 0xca, 0x16, 0xb5, 0x9a, 0xbc, 0x06, + 0xb8, 0x80, 0xa8, 0x41, 0x0a, 0x0e, 0x2f, 0x80, 0x90, 0x02, 0x2f, 0x2d, 0x50, 0x48, 0x0f, 0x09, 0x2f, 0xbf, 0xa0, + 0x04, 0x2f, 0xbf, 0x90, 0x06, 0x2f, 0xb7, 0x54, 0xca, 0x0f, 0x03, 0x2f, 0x00, 0x2e, 0x02, 0x2c, 0xb7, 0x52, 0x2d, + 0x52, 0xf2, 0x33, 0x98, 0x2e, 0xd9, 0xc0, 0xfb, 0x6f, 0xf1, 0x37, 0xc0, 0x2e, 0x01, 0x08, 0xf0, 0x5f, 0xbf, 0x56, + 0xb9, 0x54, 0xd0, 0x40, 0xc4, 0x40, 0x0b, 0x2e, 0xfd, 0xf3, 0xbf, 0x52, 0x90, 0x42, 0x94, 0x42, 0x95, 0x42, 0x05, + 0x30, 0xc1, 0x50, 0x0f, 0x88, 0x06, 0x40, 0x04, 0x41, 0x96, 0x42, 0xc5, 0x42, 0x48, 0xbe, 0x73, 0x30, 0x0d, 0x2e, + 0xd8, 0x00, 0x4f, 0xba, 0x84, 0x42, 0x03, 0x42, 0x81, 0xb3, 0x02, 0x2f, 0x2b, 0x2e, 0x6f, 0xf5, 0x06, 0x2d, 0x05, + 0x2e, 0x77, 0xf7, 0xbd, 0x56, 0x93, 0x08, 0x25, 0x2e, 0x77, 0xf7, 0xbb, 0x54, 0x25, 0x2e, 0xc2, 0xf5, 0x07, 0x2e, + 0xfd, 0xf3, 0x42, 0x30, 0xb4, 0x33, 0xda, 0x0a, 0x4c, 0x00, 0x27, 0x2e, 0xfd, 0xf3, 0x43, 0x40, 0xd4, 0x3f, 0xdc, + 0x08, 0x43, 0x42, 0x00, 0x2e, 0x00, 0x2e, 0x43, 0x40, 0x24, 0x30, 0xdc, 0x0a, 0x43, 0x42, 0x04, 0x80, 0x03, 0x2e, + 0xfd, 0xf3, 0x4a, 0x0a, 0x23, 0x2e, 0xfd, 0xf3, 0x61, 0x34, 0xc0, 0x2e, 0x01, 0x42, 0x00, 0x2e, 0x60, 0x50, 0x1a, + 0x25, 0x7a, 0x86, 0xe0, 0x7f, 0xf3, 0x7f, 0x03, 0x25, 0xc3, 0x52, 0x41, 0x84, 0xdb, 0x7f, 0x33, 0x30, 0x98, 0x2e, + 0x16, 0xc2, 0x1a, 0x25, 0x7d, 0x82, 0xf0, 0x6f, 0xe2, 0x6f, 0x32, 0x25, 0x16, 0x40, 0x94, 0x40, 0x26, 0x01, 0x85, + 0x40, 0x8e, 0x17, 0xc4, 0x42, 0x6e, 0x03, 0x95, 0x42, 0x41, 0x0e, 0xf4, 0x2f, 0xdb, 0x6f, 0xa0, 0x5f, 0xb8, 0x2e, + 0xb0, 0x51, 0xfb, 0x7f, 0x98, 0x2e, 0xe8, 0x0d, 0x5a, 0x25, 0x98, 0x2e, 0x0f, 0x0e, 0xcb, 0x58, 0x32, 0x87, 0xc4, + 0x7f, 0x65, 0x89, 0x6b, 0x8d, 0xc5, 0x5a, 0x65, 0x7f, 0xe1, 0x7f, 0x83, 0x7f, 0xa6, 0x7f, 0x74, 0x7f, 0xd0, 0x7f, + 0xb6, 0x7f, 0x94, 0x7f, 0x17, 0x30, 0xc7, 0x52, 0xc9, 0x54, 0x51, 0x7f, 0x00, 0x2e, 0x85, 0x6f, 0x42, 0x7f, 0x00, + 0x2e, 0x51, 0x41, 0x45, 0x81, 0x42, 0x41, 0x13, 0x40, 0x3b, 0x8a, 0x00, 0x40, 0x4b, 0x04, 0xd0, 0x06, 0xc0, 0xac, + 0x85, 0x7f, 0x02, 0x2f, 0x02, 0x30, 0x51, 0x04, 0xd3, 0x06, 0x41, 0x84, 0x05, 0x30, 0x5d, 0x02, 0xc9, 0x16, 0xdf, + 0x08, 0xd3, 0x00, 0x8d, 0x02, 0xaf, 0xbc, 0xb1, 0xb9, 0x59, 0x0a, 0x65, 0x6f, 0x11, 0x43, 0xa1, 0xb4, 0x52, 0x41, + 0x53, 0x41, 0x01, 0x43, 0x34, 0x7f, 0x65, 0x7f, 0x26, 0x31, 0xe5, 0x6f, 0xd4, 0x6f, 0x98, 0x2e, 0x37, 0xca, 0x32, + 0x6f, 0x75, 0x6f, 0x83, 0x40, 0x42, 0x41, 0x23, 0x7f, 0x12, 0x7f, 0xf6, 0x30, 0x40, 0x25, 0x51, 0x25, 0x98, 0x2e, + 0x37, 0xca, 0x14, 0x6f, 0x20, 0x05, 0x70, 0x6f, 0x25, 0x6f, 0x69, 0x07, 0xa2, 0x6f, 0x31, 0x6f, 0x0b, 0x30, 0x04, + 0x42, 0x9b, 0x42, 0x8b, 0x42, 0x55, 0x42, 0x32, 0x7f, 0x40, 0xa9, 0xc3, 0x6f, 0x71, 0x7f, 0x02, 0x30, 0xd0, 0x40, + 0xc3, 0x7f, 0x03, 0x2f, 0x40, 0x91, 0x15, 0x2f, 0x00, 0xa7, 0x13, 0x2f, 0x00, 0xa4, 0x11, 0x2f, 0x84, 0xbd, 0x98, + 0x2e, 0x79, 0xca, 0x55, 0x6f, 0xb7, 0x54, 0x54, 0x41, 0x82, 0x00, 0xf3, 0x3f, 0x45, 0x41, 0xcb, 0x02, 0xf6, 0x30, + 0x98, 0x2e, 0x37, 0xca, 0x35, 0x6f, 0xa4, 0x6f, 0x41, 0x43, 0x03, 0x2c, 0x00, 0x43, 0xa4, 0x6f, 0x35, 0x6f, 0x17, + 0x30, 0x42, 0x6f, 0x51, 0x6f, 0x93, 0x40, 0x42, 0x82, 0x00, 0x41, 0xc3, 0x00, 0x03, 0x43, 0x51, 0x7f, 0x00, 0x2e, + 0x94, 0x40, 0x41, 0x41, 0x4c, 0x02, 0xc4, 0x6f, 0xd1, 0x56, 0x63, 0x0e, 0x74, 0x6f, 0x51, 0x43, 0xa5, 0x7f, 0x8a, + 0x2f, 0x09, 0x2e, 0xd8, 0x00, 0x01, 0xb3, 0x21, 0x2f, 0xcb, 0x58, 0x90, 0x6f, 0x13, 0x41, 0xb6, 0x6f, 0xe4, 0x7f, + 0x00, 0x2e, 0x91, 0x41, 0x14, 0x40, 0x92, 0x41, 0x15, 0x40, 0x17, 0x2e, 0x6f, 0xf5, 0xb6, 0x7f, 0xd0, 0x7f, 0xcb, + 0x7f, 0x98, 0x2e, 0x00, 0x0c, 0x07, 0x15, 0xc2, 0x6f, 0x14, 0x0b, 0x29, 0x2e, 0x6f, 0xf5, 0xc3, 0xa3, 0xc1, 0x8f, + 0xe4, 0x6f, 0xd0, 0x6f, 0xe6, 0x2f, 0x14, 0x30, 0x05, 0x2e, 0x6f, 0xf5, 0x14, 0x0b, 0x29, 0x2e, 0x6f, 0xf5, 0x18, + 0x2d, 0xcd, 0x56, 0x04, 0x32, 0xb5, 0x6f, 0x1c, 0x01, 0x51, 0x41, 0x52, 0x41, 0xc3, 0x40, 0xb5, 0x7f, 0xe4, 0x7f, + 0x98, 0x2e, 0x1f, 0x0c, 0xe4, 0x6f, 0x21, 0x87, 0x00, 0x43, 0x04, 0x32, 0xcf, 0x54, 0x5a, 0x0e, 0xef, 0x2f, 0x15, + 0x54, 0x09, 0x2e, 0x77, 0xf7, 0x22, 0x0b, 0x29, 0x2e, 0x77, 0xf7, 0xfb, 0x6f, 0x50, 0x5e, 0xb8, 0x2e, 0x10, 0x50, + 0x01, 0x2e, 0xd4, 0x00, 0x00, 0xb2, 0xfb, 0x7f, 0x51, 0x2f, 0x01, 0xb2, 0x48, 0x2f, 0x02, 0xb2, 0x42, 0x2f, 0x03, + 0x90, 0x56, 0x2f, 0xd7, 0x52, 0x79, 0x80, 0x42, 0x40, 0x81, 0x84, 0x00, 0x40, 0x42, 0x42, 0x98, 0x2e, 0x93, 0x0c, + 0xd9, 0x54, 0xd7, 0x50, 0xa1, 0x40, 0x98, 0xbd, 0x82, 0x40, 0x3e, 0x82, 0xda, 0x0a, 0x44, 0x40, 0x8b, 0x16, 0xe3, + 0x00, 0x53, 0x42, 0x00, 0x2e, 0x43, 0x40, 0x9a, 0x02, 0x52, 0x42, 0x00, 0x2e, 0x41, 0x40, 0x15, 0x54, 0x4a, 0x0e, + 0x3a, 0x2f, 0x3a, 0x82, 0x00, 0x30, 0x41, 0x40, 0x21, 0x2e, 0x85, 0x0f, 0x40, 0xb2, 0x0a, 0x2f, 0x98, 0x2e, 0xb1, + 0x0c, 0x98, 0x2e, 0x45, 0x0e, 0x98, 0x2e, 0x5b, 0x0e, 0xfb, 0x6f, 0xf0, 0x5f, 0x00, 0x30, 0x80, 0x2e, 0xce, 0xb7, + 0xdd, 0x52, 0xd3, 0x54, 0x42, 0x42, 0x4f, 0x84, 0x73, 0x30, 0xdb, 0x52, 0x83, 0x42, 0x1b, 0x30, 0x6b, 0x42, 0x23, + 0x30, 0x27, 0x2e, 0xd7, 0x00, 0x37, 0x2e, 0xd4, 0x00, 0x21, 0x2e, 0xd6, 0x00, 0x7a, 0x84, 0x17, 0x2c, 0x42, 0x42, + 0x30, 0x30, 0x21, 0x2e, 0xd4, 0x00, 0x12, 0x2d, 0x21, 0x30, 0x00, 0x30, 0x23, 0x2e, 0xd4, 0x00, 0x21, 0x2e, 0x7b, + 0xf7, 0x0b, 0x2d, 0x17, 0x30, 0x98, 0x2e, 0x51, 0x0c, 0xd5, 0x50, 0x0c, 0x82, 0x72, 0x30, 0x2f, 0x2e, 0xd4, 0x00, + 0x25, 0x2e, 0x7b, 0xf7, 0x40, 0x42, 0x00, 0x2e, 0xfb, 0x6f, 0xf0, 0x5f, 0xb8, 0x2e, 0x70, 0x50, 0x0a, 0x25, 0x39, + 0x86, 0xfb, 0x7f, 0xe1, 0x32, 0x62, 0x30, 0x98, 0x2e, 0xc2, 0xc4, 0xb5, 0x56, 0xa5, 0x6f, 0xab, 0x08, 0x91, 0x6f, + 0x4b, 0x08, 0xdf, 0x56, 0xc4, 0x6f, 0x23, 0x09, 0x4d, 0xba, 0x93, 0xbc, 0x8c, 0x0b, 0xd1, 0x6f, 0x0b, 0x09, 0xcb, + 0x52, 0xe1, 0x5e, 0x56, 0x42, 0xaf, 0x09, 0x4d, 0xba, 0x23, 0xbd, 0x94, 0x0a, 0xe5, 0x6f, 0x68, 0xbb, 0xeb, 0x08, + 0xbd, 0xb9, 0x63, 0xbe, 0xfb, 0x6f, 0x52, 0x42, 0xe3, 0x0a, 0xc0, 0x2e, 0x43, 0x42, 0x90, 0x5f, 0xd1, 0x50, 0x03, + 0x2e, 0x25, 0xf3, 0x13, 0x40, 0x00, 0x40, 0x9b, 0xbc, 0x9b, 0xb4, 0x08, 0xbd, 0xb8, 0xb9, 0x98, 0xbc, 0xda, 0x0a, + 0x08, 0xb6, 0x89, 0x16, 0xc0, 0x2e, 0x19, 0x00, 0x62, 0x02, 0x10, 0x50, 0xfb, 0x7f, 0x98, 0x2e, 0x81, 0x0d, 0x01, + 0x2e, 0xd4, 0x00, 0x31, 0x30, 0x08, 0x04, 0xfb, 0x6f, 0x01, 0x30, 0xf0, 0x5f, 0x23, 0x2e, 0xd6, 0x00, 0x21, 0x2e, + 0xd7, 0x00, 0xb8, 0x2e, 0x01, 0x2e, 0xd7, 0x00, 0x03, 0x2e, 0xd6, 0x00, 0x48, 0x0e, 0x01, 0x2f, 0x80, 0x2e, 0x1f, + 0x0e, 0xb8, 0x2e, 0xe3, 0x50, 0x21, 0x34, 0x01, 0x42, 0x82, 0x30, 0xc1, 0x32, 0x25, 0x2e, 0x62, 0xf5, 0x01, 0x00, + 0x22, 0x30, 0x01, 0x40, 0x4a, 0x0a, 0x01, 0x42, 0xb8, 0x2e, 0xe3, 0x54, 0xf0, 0x3b, 0x83, 0x40, 0xd8, 0x08, 0xe5, + 0x52, 0x83, 0x42, 0x00, 0x30, 0x83, 0x30, 0x50, 0x42, 0xc4, 0x32, 0x27, 0x2e, 0x64, 0xf5, 0x94, 0x00, 0x50, 0x42, + 0x40, 0x42, 0xd3, 0x3f, 0x84, 0x40, 0x7d, 0x82, 0xe3, 0x08, 0x40, 0x42, 0x83, 0x42, 0xb8, 0x2e, 0xdd, 0x52, 0x00, + 0x30, 0x40, 0x42, 0x7c, 0x86, 0xb9, 0x52, 0x09, 0x2e, 0x70, 0x0f, 0xbf, 0x54, 0xc4, 0x42, 0xd3, 0x86, 0x54, 0x40, + 0x55, 0x40, 0x94, 0x42, 0x85, 0x42, 0x21, 0x2e, 0xd7, 0x00, 0x42, 0x40, 0x25, 0x2e, 0xfd, 0xf3, 0xc0, 0x42, 0x7e, + 0x82, 0x05, 0x2e, 0x7d, 0x00, 0x80, 0xb2, 0x14, 0x2f, 0x05, 0x2e, 0x89, 0x00, 0x27, 0xbd, 0x2f, 0xb9, 0x80, 0x90, + 0x02, 0x2f, 0x21, 0x2e, 0x6f, 0xf5, 0x0c, 0x2d, 0x07, 0x2e, 0x71, 0x0f, 0x14, 0x30, 0x1c, 0x09, 0x05, 0x2e, 0x77, + 0xf7, 0xbd, 0x56, 0x47, 0xbe, 0x93, 0x08, 0x94, 0x0a, 0x25, 0x2e, 0x77, 0xf7, 0xe7, 0x54, 0x50, 0x42, 0x4a, 0x0e, + 0xfc, 0x2f, 0xb8, 0x2e, 0x50, 0x50, 0x02, 0x30, 0x43, 0x86, 0xe5, 0x50, 0xfb, 0x7f, 0xe3, 0x7f, 0xd2, 0x7f, 0xc0, + 0x7f, 0xb1, 0x7f, 0x00, 0x2e, 0x41, 0x40, 0x00, 0x40, 0x48, 0x04, 0x98, 0x2e, 0x74, 0xc0, 0x1e, 0xaa, 0xd3, 0x6f, + 0x14, 0x30, 0xb1, 0x6f, 0xe3, 0x22, 0xc0, 0x6f, 0x52, 0x40, 0xe4, 0x6f, 0x4c, 0x0e, 0x12, 0x42, 0xd3, 0x7f, 0xeb, + 0x2f, 0x03, 0x2e, 0x86, 0x0f, 0x40, 0x90, 0x11, 0x30, 0x03, 0x2f, 0x23, 0x2e, 0x86, 0x0f, 0x02, 0x2c, 0x00, 0x30, + 0xd0, 0x6f, 0xfb, 0x6f, 0xb0, 0x5f, 0xb8, 0x2e, 0x40, 0x50, 0xf1, 0x7f, 0x0a, 0x25, 0x3c, 0x86, 0xeb, 0x7f, 0x41, + 0x33, 0x22, 0x30, 0x98, 0x2e, 0xc2, 0xc4, 0xd3, 0x6f, 0xf4, 0x30, 0xdc, 0x09, 0x47, 0x58, 0xc2, 0x6f, 0x94, 0x09, + 0xeb, 0x58, 0x6a, 0xbb, 0xdc, 0x08, 0xb4, 0xb9, 0xb1, 0xbd, 0xe9, 0x5a, 0x95, 0x08, 0x21, 0xbd, 0xf6, 0xbf, 0x77, + 0x0b, 0x51, 0xbe, 0xf1, 0x6f, 0xeb, 0x6f, 0x52, 0x42, 0x54, 0x42, 0xc0, 0x2e, 0x43, 0x42, 0xc0, 0x5f, 0x50, 0x50, + 0xf5, 0x50, 0x31, 0x30, 0x11, 0x42, 0xfb, 0x7f, 0x7b, 0x30, 0x0b, 0x42, 0x11, 0x30, 0x02, 0x80, 0x23, 0x33, 0x01, + 0x42, 0x03, 0x00, 0x07, 0x2e, 0x80, 0x03, 0x05, 0x2e, 0xd3, 0x00, 0x23, 0x52, 0xe2, 0x7f, 0xd3, 0x7f, 0xc0, 0x7f, + 0x98, 0x2e, 0xb6, 0x0e, 0xd1, 0x6f, 0x08, 0x0a, 0x1a, 0x25, 0x7b, 0x86, 0xd0, 0x7f, 0x01, 0x33, 0x12, 0x30, 0x98, + 0x2e, 0xc2, 0xc4, 0xd1, 0x6f, 0x08, 0x0a, 0x00, 0xb2, 0x0d, 0x2f, 0xe3, 0x6f, 0x01, 0x2e, 0x80, 0x03, 0x51, 0x30, + 0xc7, 0x86, 0x23, 0x2e, 0x21, 0xf2, 0x08, 0xbc, 0xc0, 0x42, 0x98, 0x2e, 0xa5, 0xb7, 0x00, 0x2e, 0x00, 0x2e, 0xd0, + 0x2e, 0xb0, 0x6f, 0x0b, 0xb8, 0x03, 0x2e, 0x1b, 0x00, 0x08, 0x1a, 0xb0, 0x7f, 0x70, 0x30, 0x04, 0x2f, 0x21, 0x2e, + 0x21, 0xf2, 0x00, 0x2e, 0x00, 0x2e, 0xd0, 0x2e, 0x98, 0x2e, 0x6d, 0xc0, 0x98, 0x2e, 0x5d, 0xc0, 0xed, 0x50, 0x98, + 0x2e, 0x44, 0xcb, 0xef, 0x50, 0x98, 0x2e, 0x46, 0xc3, 0xf1, 0x50, 0x98, 0x2e, 0x53, 0xc7, 0x35, 0x50, 0x98, 0x2e, + 0x64, 0xcf, 0x10, 0x30, 0x98, 0x2e, 0xdc, 0x03, 0x20, 0x26, 0xc0, 0x6f, 0x02, 0x31, 0x12, 0x42, 0xab, 0x33, 0x0b, + 0x42, 0x37, 0x80, 0x01, 0x30, 0x01, 0x42, 0xf3, 0x37, 0xf7, 0x52, 0xfb, 0x50, 0x44, 0x40, 0xa2, 0x0a, 0x42, 0x42, + 0x8b, 0x31, 0x09, 0x2e, 0x5e, 0xf7, 0xf9, 0x54, 0xe3, 0x08, 0x83, 0x42, 0x1b, 0x42, 0x23, 0x33, 0x4b, 0x00, 0xbc, + 0x84, 0x0b, 0x40, 0x33, 0x30, 0x83, 0x42, 0x0b, 0x42, 0xe0, 0x7f, 0xd1, 0x7f, 0x98, 0x2e, 0x58, 0xb7, 0xd1, 0x6f, + 0x80, 0x30, 0x40, 0x42, 0x03, 0x30, 0xe0, 0x6f, 0xf3, 0x54, 0x04, 0x30, 0x00, 0x2e, 0x00, 0x2e, 0x01, 0x89, 0x62, + 0x0e, 0xfa, 0x2f, 0x43, 0x42, 0x11, 0x30, 0xfb, 0x6f, 0xc0, 0x2e, 0x01, 0x42, 0xb0, 0x5f, 0xc1, 0x4a, 0x00, 0x00, + 0x6d, 0x57, 0x00, 0x00, 0x77, 0x8e, 0x00, 0x00, 0xe0, 0xff, 0xff, 0xff, 0xd3, 0xff, 0xff, 0xff, 0xe5, 0xff, 0xff, + 0xff, 0xee, 0xe1, 0xff, 0xff, 0x7c, 0x13, 0x00, 0x00, 0x46, 0xe6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x2e, 0x00, 0xc1, 0x80, + 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, + 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, + 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, + 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, + 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, + 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, + 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, + 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, 0x2e, 0x00, 0xc1, 0x80, + 0x2e, 0x00, 0xc1 +}; diff --git a/Drivers/bmi270-module/source/bmi270.cpp b/Drivers/bmi270-module/source/bmi270.cpp new file mode 100644 index 000000000..fbc6b487b --- /dev/null +++ b/Drivers/bmi270-module/source/bmi270.cpp @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include +#include + +#define TAG "BMI270" + +static constexpr uint8_t REG_CHIP_ID = 0x00; // read: expect 0x24 +static constexpr uint8_t REG_DATA_ACC = 0x0C; // 6 bytes: acc X/Y/Z LSB/MSB +static constexpr uint8_t REG_DATA_GYR = 0x12; // 6 bytes: gyr X/Y/Z LSB/MSB +static constexpr uint8_t REG_INTERNAL_ST = 0x21; // bit0: init done +static constexpr uint8_t REG_ACC_CONF = 0x40; // ODR + BWP + filter_perf +static constexpr uint8_t REG_ACC_RANGE = 0x41; // range selector +static constexpr uint8_t REG_GYR_CONF = 0x42; // ODR + BWP +static constexpr uint8_t REG_GYR_RANGE = 0x43; // range selector +static constexpr uint8_t REG_INIT_CTRL = 0x59; // 0=start upload, 1=done +static constexpr uint8_t REG_INIT_ADDR_0 = 0x5B; // burst address low nibble +// REG_INIT_ADDR_1 = 0x5C written together with 0x5B (2-byte burst) +static constexpr uint8_t REG_INIT_DATA = 0x5E; // config burst write target +static constexpr uint8_t REG_PWR_CONF = 0x7C; // 0=disable adv. power save +static constexpr uint8_t REG_PWR_CTRL = 0x7D; // bit1=gyr_en, bit2=acc_en +static constexpr uint8_t REG_CMD = 0x7E; // 0xB6 = soft reset + +// ACC_CONF: filter_perf=1, bwp=normal(2), odr=100Hz(8) → 0xA8 +static constexpr uint8_t ACC_CONF_VAL = 0xA8; +static constexpr uint8_t ACC_RANGE_VAL = 0x02; // ±8g +// GYR_CONF: filter_perf=1, noise_perf=1, bwp=normal(2), odr=100Hz(8) → 0xE8 +static constexpr uint8_t GYR_CONF_VAL = 0xE8; +static constexpr uint8_t GYR_RANGE_VAL = 0x00; // ±2000°/s + +// Scaling: full-scale / 2^15 +static constexpr float ACCEL_SCALE = 8.0f / 32768.0f; // g per LSB (±8g) +static constexpr float GYRO_SCALE = 2000.0f / 32768.0f; // °/s per LSB (±2000°/s) + +// Config upload chunk size (bytes of config data per I2C transaction) +static constexpr size_t CHUNK_SIZE = 64; + +static constexpr TickType_t I2C_TIMEOUT_TICKS = pdMS_TO_TICKS(10); + +#define GET_CONFIG(device) (static_cast((device)->config)) + +// region Helpers + +static bool configure(Device* i2c_controller, uint8_t address) { + // Disable advanced power save before uploading + if (i2c_controller_register8_set(i2c_controller, address, REG_PWR_CONF, 0x00, I2C_TIMEOUT_TICKS) != ERROR_NONE) return false; + vTaskDelay(pdMS_TO_TICKS(1)); + + // Signal start of config upload + if (i2c_controller_register8_set(i2c_controller, address, REG_INIT_CTRL, 0x00, I2C_TIMEOUT_TICKS) != ERROR_NONE) return false; + + // Upload config in CHUNK_SIZE-byte bursts + // The half-word address (hwAddr) increments by CHUNK_SIZE/2 per chunk + constexpr size_t config_data_size = sizeof(bmi270_config_data); + for (size_t offset = 0; offset < config_data_size; offset += CHUNK_SIZE) { + // Set INIT_ADDR_0 and INIT_ADDR_1 (consecutive registers 0x5B/0x5C) + auto hardware_address = static_cast(offset / 2); + uint8_t address_buffer[2] = { + static_cast(hardware_address & 0x0Fu), // INIT_ADDR_0: bits [3:0] + static_cast((hardware_address >> 4) & 0xFFu) // INIT_ADDR_1: bits [11:4] + }; + + if (i2c_controller_write_register(i2c_controller, address, REG_INIT_ADDR_0, address_buffer, 2, I2C_TIMEOUT_TICKS) != ERROR_NONE) return false; + + // Write chunk to INIT_DATA register. + // Copy to a stack buffer first: the config array may live in DROM (SPI flash) + // which is not DMA-accessible; the I2C driver requires DRAM-backed buffers. + size_t chunk_length = (offset + CHUNK_SIZE <= config_data_size) ? CHUNK_SIZE : (config_data_size - offset); + uint8_t chunk_buffer[CHUNK_SIZE]; + memcpy(chunk_buffer, bmi270_config_data + offset, chunk_length); + if (i2c_controller_write_register(i2c_controller, address, REG_INIT_DATA, chunk_buffer, static_cast(chunk_length), I2C_TIMEOUT_TICKS) != ERROR_NONE) return false; + } + + // Signal end of config upload + if (i2c_controller_register8_set(i2c_controller, address, REG_INIT_CTRL, 0x01, I2C_TIMEOUT_TICKS) != ERROR_NONE) return false; + vTaskDelay(pdMS_TO_TICKS(20)); + + // Verify initialization + uint8_t status = 0; + if (i2c_controller_register8_get(i2c_controller, address, REG_INTERNAL_ST, &status, I2C_TIMEOUT_TICKS) != ERROR_NONE) return false; + return (status & 0x01u) != 0; // bit 0 = init_ok +} + +// endregion + +// region Driver lifecycle + +static error_t start(Device* device) { + auto* i2c_controller = device_get_parent(device); + if (device_get_type(i2c_controller) != &I2C_CONTROLLER_TYPE) { + LOG_E(TAG, "Parent is not an I2C controller"); + return ERROR_RESOURCE; + } + + auto address = GET_CONFIG(device)->address; + + // Verify chip ID + uint8_t chip_id= 0; + if (i2c_controller_register8_get(i2c_controller, address, REG_CHIP_ID, &chip_id, I2C_TIMEOUT_TICKS) != ERROR_NONE || chip_id != 0x24) { + return ERROR_RESOURCE; + } + + // Soft reset — clears all registers; datasheet specifies 2 ms startup time. + // Use 20 ms to be safe and allow the chip to fully re-initialise before + // any further I2C traffic (a second chip-ID read immediately after reset + // is unreliable and not part of the Bosch SensorAPI init flow). + if (i2c_controller_register8_set(i2c_controller, address, REG_CMD, 0xB6, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + vTaskDelay(pdMS_TO_TICKS(20)); + + // Upload 8KB configuration (enables internal feature engine) + if (!configure(i2c_controller, address)) { + return ERROR_RESOURCE; + } + + // Configure accelerometer: ODR=100Hz, normal filter, ±8g + if (i2c_controller_register8_set(i2c_controller, address, REG_ACC_CONF, ACC_CONF_VAL, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + if (i2c_controller_register8_set(i2c_controller, address, REG_ACC_RANGE, ACC_RANGE_VAL, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + + // Configure gyroscope: ODR=100Hz, normal filter, ±2000°/s + if (i2c_controller_register8_set(i2c_controller, address, REG_GYR_CONF, GYR_CONF_VAL, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + if (i2c_controller_register8_set(i2c_controller, address, REG_GYR_RANGE, GYR_RANGE_VAL, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + + // Enable accelerometer and gyroscope (bit1=gyr_en, bit2=acc_en) + if (i2c_controller_register8_set(i2c_controller, address, REG_PWR_CTRL, 0x06, I2C_TIMEOUT_TICKS) != ERROR_NONE) return ERROR_RESOURCE; + vTaskDelay(pdMS_TO_TICKS(2)); + + return ERROR_NONE; +} + +static error_t stop(Device* device) { + return ERROR_NONE; +} + +// endregion + +extern "C" { + +error_t bmi270_read(Device* device, Bmi270Data* data) { + auto* i2c_controller = device_get_parent(device); + + auto address = GET_CONFIG(device)->address; + + // Burst-read 12 bytes: acc X/Y/Z (6 bytes) + gyro X/Y/Z (6 bytes) + // Registers: 0x0C–0x17 are contiguous for acc+gyro in I2C mode (no dummy byte) + uint8_t buffer[12] = {}; + error_t error = i2c_controller_read_register(i2c_controller, address, REG_DATA_ACC, buffer, sizeof(buffer), I2C_TIMEOUT_TICKS); + if (error != ERROR_NONE) return error; + + auto toI16 = [](uint8_t lo, uint8_t hi) -> int16_t { + return static_cast(static_cast(hi) << 8 | lo); + }; + + data->ax = toI16(buffer[0], buffer[1]) * ACCEL_SCALE; + data->ay = toI16(buffer[2], buffer[3]) * ACCEL_SCALE; + data->az = toI16(buffer[4], buffer[5]) * ACCEL_SCALE; + data->gx = toI16(buffer[6], buffer[7]) * GYRO_SCALE; + data->gy = toI16(buffer[8], buffer[9]) * GYRO_SCALE; + data->gz = toI16(buffer[10], buffer[11]) * GYRO_SCALE; + + return ERROR_NONE; +} + +Driver bmi270_driver = { + .name = "bmi270", + .compatible = (const char*[]) { "bosch,bmi270", nullptr}, + .start_device = start, + .stop_device = stop, + .api = nullptr, + .device_type = nullptr, + .owner = &bmi270_module, + .internal = nullptr +}; + +} \ No newline at end of file diff --git a/Drivers/bmi270-module/source/module.cpp b/Drivers/bmi270-module/source/module.cpp new file mode 100644 index 000000000..b77e8510b --- /dev/null +++ b/Drivers/bmi270-module/source/module.cpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include +#include + +extern "C" { + +extern Driver bmi270_driver; + +static error_t start() { + /* We crash when construct fails, because if a single driver fails to construct, + * there is no guarantee that the previously constructed drivers can be destroyed */ + check(driver_construct_add(&bmi270_driver) == ERROR_NONE); + return ERROR_NONE; +} + +static error_t stop() { + /* We crash when destruct fails, because if a single driver fails to destruct, + * there is no guarantee that the previously destroyed drivers can be recovered */ + check(driver_remove_destruct(&bmi270_driver) == ERROR_NONE); + return ERROR_NONE; +} + +extern const ModuleSymbol bmi270_module_symbols[]; + +Module bmi270_module = { + .name = "bmi270", + .start = start, + .stop = stop, + .symbols = bmi270_module_symbols, + .internal = nullptr +}; + +} diff --git a/Drivers/bmi270-module/source/symbols.c b/Drivers/bmi270-module/source/symbols.c new file mode 100644 index 000000000..68dc957ce --- /dev/null +++ b/Drivers/bmi270-module/source/symbols.c @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#include + +const struct ModuleSymbol bmi270_module_symbols[] = { + DEFINE_MODULE_SYMBOL(bmi270_read), + MODULE_SYMBOL_TERMINATOR +}; diff --git a/Drivers/pi4ioe5v6408-module/include/drivers/pi4ioe5v6408.h b/Drivers/pi4ioe5v6408-module/include/drivers/pi4ioe5v6408.h index 93f982746..8ca85a028 100644 --- a/Drivers/pi4ioe5v6408-module/include/drivers/pi4ioe5v6408.h +++ b/Drivers/pi4ioe5v6408-module/include/drivers/pi4ioe5v6408.h @@ -2,40 +2,16 @@ #pragma once #include -#include -#include #ifdef __cplusplus extern "C" { #endif -struct Device; - struct Pi4ioe5v6408Config { /** Address on bus */ uint8_t address; }; -error_t pi4ioe5v6408_set_direction(struct Device* device, uint8_t bits, TickType_t timeout); - -error_t pi4ioe5v6408_set_output_level(struct Device* device, uint8_t bits, TickType_t timeout); - -error_t pi4ioe5v6408_get_output_level(struct Device* device, uint8_t* bits, TickType_t timeout); - -error_t pi4ioe5v6408_set_output_high_impedance(struct Device* device, uint8_t bits, TickType_t timeout); - -error_t pi4ioe5v6408_set_input_default_level(struct Device* device, uint8_t bits, TickType_t timeout); - -error_t pi4ioe5v6408_set_pull_enable(struct Device* device, uint8_t bits, TickType_t timeout); - -error_t pi4ioe5v6408_set_pull_select(struct Device* device, uint8_t bits, TickType_t timeout); - -error_t pi4ioe5v6408_get_input_level(struct Device* device, uint8_t* bits, TickType_t timeout); - -error_t pi4ioe5v6408_set_interrupt_mask(struct Device* device, uint8_t bits, TickType_t timeout); - -error_t pi4ioe5v6408_get_interrupt_level(struct Device* device, uint8_t* bits, TickType_t timeout); - #ifdef __cplusplus } #endif diff --git a/Drivers/pi4ioe5v6408-module/source/module.cpp b/Drivers/pi4ioe5v6408-module/source/module.cpp index d5c31f458..bb744edcb 100644 --- a/Drivers/pi4ioe5v6408-module/source/module.cpp +++ b/Drivers/pi4ioe5v6408-module/source/module.cpp @@ -6,7 +6,6 @@ extern "C" { extern Driver pi4ioe5v6408_driver; -extern const ModuleSymbol pi4ioe5v6408_module_symbols[]; static error_t start() { /* We crash when construct fails, because if a single driver fails to construct, @@ -26,7 +25,7 @@ Module pi4ioe5v6408_module = { .name = "pi4ioe5v6408", .start = start, .stop = stop, - .symbols = pi4ioe5v6408_module_symbols, + .symbols = nullptr, .internal = nullptr }; diff --git a/Drivers/pi4ioe5v6408-module/source/pi4ioe5v6408.cpp b/Drivers/pi4ioe5v6408-module/source/pi4ioe5v6408.cpp index fdc1e94aa..82b765c67 100644 --- a/Drivers/pi4ioe5v6408-module/source/pi4ioe5v6408.cpp +++ b/Drivers/pi4ioe5v6408-module/source/pi4ioe5v6408.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include #include @@ -27,72 +29,181 @@ static error_t start(Device* device) { return ERROR_RESOURCE; } LOG_I(TAG, "Started PI4IOE5V6408 device %s", device->name); - return ERROR_NONE; + + return gpio_controller_init_descriptors(device, 8, nullptr); } static error_t stop(Device* device) { + check(gpio_controller_deinit_descriptors(device) == ERROR_NONE); return ERROR_NONE; } extern "C" { -error_t pi4ioe5v6408_set_direction(Device* device, uint8_t bits, TickType_t timeout) { - auto* parent = device_get_parent(device); - return i2c_controller_register8_set(parent, GET_CONFIG(device)->address, PI4_REGISTER_DIRECTION, bits, timeout); -} -error_t pi4ioe5v6408_set_output_level(Device* device, uint8_t bits, TickType_t timeout) { +static error_t set_level(GpioDescriptor* descriptor, bool high) { + auto* device = descriptor->controller; auto* parent = device_get_parent(device); - return i2c_controller_register8_set(parent, GET_CONFIG(device)->address, PI4_REGISTER_OUTPUT_LEVEL, bits, timeout); + auto address = GET_CONFIG(device)->address; + uint8_t bit = 1 << descriptor->pin; + + if (high) { + return i2c_controller_register8_set_bits(parent, address, PI4_REGISTER_OUTPUT_LEVEL, bit, portMAX_DELAY); + } else { + return i2c_controller_register8_reset_bits(parent, address, PI4_REGISTER_OUTPUT_LEVEL, bit, portMAX_DELAY); + } } -error_t pi4ioe5v6408_get_output_level(Device* device, uint8_t* bits, TickType_t timeout) { +static error_t get_level(GpioDescriptor* descriptor, bool* high) { + auto* device = descriptor->controller; auto* parent = device_get_parent(device); - return i2c_controller_register8_get(parent, GET_CONFIG(device)->address, PI4_REGISTER_OUTPUT_LEVEL, bits, timeout); + auto address = GET_CONFIG(device)->address; + uint8_t bits; + + error_t err = i2c_controller_register8_get(parent, address, PI4_REGISTER_INPUT_LEVEL, &bits, portMAX_DELAY); + if (err != ERROR_NONE) { + return err; + } + + *high = (bits & (1 << descriptor->pin)) != 0; + return ERROR_NONE; } -error_t pi4ioe5v6408_set_output_high_impedance(struct Device* device, uint8_t bits, TickType_t timeout) { +static error_t set_flags(GpioDescriptor* descriptor, gpio_flags_t flags) { + auto* device = descriptor->controller; auto* parent = device_get_parent(device); - return i2c_controller_register8_set(parent, GET_CONFIG(device)->address, PI4_REGISTER_OUTPUT_HIGH_IMPEDANCE, bits, timeout); + auto address = GET_CONFIG(device)->address; + uint8_t bit = 1 << descriptor->pin; + + error_t err; + + // Direction + if (flags & GPIO_FLAG_DIRECTION_OUTPUT) { + err = i2c_controller_register8_set_bits(parent, address, PI4_REGISTER_DIRECTION, bit, portMAX_DELAY); + } else { + err = i2c_controller_register8_reset_bits(parent, address, PI4_REGISTER_DIRECTION, bit, portMAX_DELAY); + } + + if (err != ERROR_NONE) { + return err; + } + + // High Impedance + if (flags & GPIO_FLAG_HIGH_IMPEDANCE) { + err = i2c_controller_register8_set_bits(parent, address, PI4_REGISTER_OUTPUT_HIGH_IMPEDANCE, bit, portMAX_DELAY); + } else { + err = i2c_controller_register8_reset_bits(parent, address, PI4_REGISTER_OUTPUT_HIGH_IMPEDANCE, bit, portMAX_DELAY); + } + + if (err != ERROR_NONE) { + return err; + } + + // Pull-up/down + if (flags & (GPIO_FLAG_PULL_UP | GPIO_FLAG_PULL_DOWN)) { + // Set pull up or pull down + if (flags & GPIO_FLAG_PULL_UP) { + err = i2c_controller_register8_set_bits(parent, address, PI4_REGISTER_PULL_SELECT, bit, portMAX_DELAY); + } else { + err = i2c_controller_register8_reset_bits(parent, address, PI4_REGISTER_PULL_SELECT, bit, portMAX_DELAY); + } + + if (err != ERROR_NONE) { + return err; + } + + // Enable pull-up/down + err = i2c_controller_register8_set_bits(parent, address, PI4_REGISTER_PULL_ENABLE, bit, portMAX_DELAY); + } else { + // Disable pull-up/down + err = i2c_controller_register8_reset_bits(parent, address, PI4_REGISTER_PULL_ENABLE, bit, portMAX_DELAY); + } + + return err; } -error_t pi4ioe5v6408_set_input_default_level(struct Device* device, uint8_t bits, TickType_t timeout) { +static error_t get_flags(GpioDescriptor* descriptor, gpio_flags_t* flags) { + auto* device = descriptor->controller; auto* parent = device_get_parent(device); - return i2c_controller_register8_set(parent, GET_CONFIG(device)->address, PI4_REGISTER_INPUT_DEFAULT_LEVEL, bits, timeout); + auto address = GET_CONFIG(device)->address; + uint8_t bit = 1 << descriptor->pin; + uint8_t val; + error_t err; + + gpio_flags_t f = GPIO_FLAG_NONE; + + // Direction + err = i2c_controller_register8_get(parent, address, PI4_REGISTER_DIRECTION, &val, portMAX_DELAY); + if (err != ERROR_NONE) return err; + if (val & bit) { + f |= GPIO_FLAG_DIRECTION_OUTPUT; + } else { + f |= GPIO_FLAG_DIRECTION_INPUT; + } + + // Pull-up/down + err = i2c_controller_register8_get(parent, address, PI4_REGISTER_PULL_ENABLE, &val, portMAX_DELAY); + if (err != ERROR_NONE) return err; + if (val & bit) { + err = i2c_controller_register8_get(parent, address, PI4_REGISTER_PULL_SELECT, &val, portMAX_DELAY); + if (err != ERROR_NONE) return err; + if (val & bit) { + f |= GPIO_FLAG_PULL_UP; + } else { + f |= GPIO_FLAG_PULL_DOWN; + } + } + + // High Impedance + err = i2c_controller_register8_get(parent, address, PI4_REGISTER_OUTPUT_HIGH_IMPEDANCE, &val, portMAX_DELAY); + if (err != ERROR_NONE) return err; + if (val & bit) { + f |= GPIO_FLAG_HIGH_IMPEDANCE; + } + + *flags = f; + return ERROR_NONE; } -error_t pi4ioe5v6408_set_pull_enable(struct Device* device, uint8_t bits, TickType_t timeout) { - auto* parent = device_get_parent(device); - return i2c_controller_register8_set(parent, GET_CONFIG(device)->address, PI4_REGISTER_PULL_ENABLE, bits, timeout); +static error_t get_native_pin_number(GpioDescriptor* descriptor, void* pin_number) { + return ERROR_NOT_SUPPORTED; } -error_t pi4ioe5v6408_set_pull_select(struct Device* device, uint8_t bits, TickType_t timeout) { - auto* parent = device_get_parent(device); - return i2c_controller_register8_set(parent, GET_CONFIG(device)->address, PI4_REGISTER_PULL_SELECT, bits, timeout); +static error_t add_callback(GpioDescriptor* descriptor, void (*callback)(void*), void* arg) { + return ERROR_NOT_SUPPORTED; } -error_t pi4ioe5v6408_get_input_level(struct Device* device, uint8_t* bits, TickType_t timeout) { - auto* parent = device_get_parent(device); - return i2c_controller_register8_get(parent, GET_CONFIG(device)->address, PI4_REGISTER_INPUT_LEVEL, bits, timeout); +static error_t remove_callback(GpioDescriptor* descriptor) { + return ERROR_NOT_SUPPORTED; } -error_t pi4ioe5v6408_set_interrupt_mask(struct Device* device, uint8_t bits, TickType_t timeout) { - auto* parent = device_get_parent(device); - return i2c_controller_register8_set(parent, GET_CONFIG(device)->address, PI4_REGISTER_INTERRUPT_MASK, bits, timeout); +static error_t enable_interrupt(GpioDescriptor* descriptor) { + return ERROR_NOT_SUPPORTED; } -error_t pi4ioe5v6408_get_interrupt_level(struct Device* device, uint8_t* bits, TickType_t timeout) { - auto* parent = device_get_parent(device); - return i2c_controller_register8_get(parent, GET_CONFIG(device)->address, PI4_REGISTER_INTERRUPT_LEVEL, bits, timeout); +static error_t disable_interrupt(GpioDescriptor* descriptor) { + return ERROR_NOT_SUPPORTED; } +const static GpioControllerApi pi4_gpio_api = { + .set_level = set_level, + .get_level = get_level, + .set_flags = set_flags, + .get_flags = get_flags, + .get_native_pin_number = get_native_pin_number, + .add_callback = add_callback, + .remove_callback = remove_callback, + .enable_interrupt = enable_interrupt, + .disable_interrupt = disable_interrupt +}; + Driver pi4ioe5v6408_driver = { .name = "pi4ioe5v6408", .compatible = (const char*[]) { "diodes,pi4ioe5v6408", nullptr}, .start_device = start, .stop_device = stop, - .api = nullptr, - .device_type = nullptr, + .api = static_cast(&pi4_gpio_api), + .device_type = &GPIO_CONTROLLER_TYPE, .owner = &pi4ioe5v6408_module, .internal = nullptr }; diff --git a/Drivers/pi4ioe5v6408-module/source/symbols.c b/Drivers/pi4ioe5v6408-module/source/symbols.c deleted file mode 100644 index ea1dc7a38..000000000 --- a/Drivers/pi4ioe5v6408-module/source/symbols.c +++ /dev/null @@ -1,15 +0,0 @@ -#include -#include - -const struct ModuleSymbol pi4ioe5v6408_module_symbols[] = { - DEFINE_MODULE_SYMBOL(pi4ioe5v6408_set_direction), - DEFINE_MODULE_SYMBOL(pi4ioe5v6408_set_output_level), - DEFINE_MODULE_SYMBOL(pi4ioe5v6408_set_output_high_impedance), - DEFINE_MODULE_SYMBOL(pi4ioe5v6408_set_input_default_level), - DEFINE_MODULE_SYMBOL(pi4ioe5v6408_set_pull_enable), - DEFINE_MODULE_SYMBOL(pi4ioe5v6408_set_pull_select), - DEFINE_MODULE_SYMBOL(pi4ioe5v6408_get_input_level), - DEFINE_MODULE_SYMBOL(pi4ioe5v6408_set_interrupt_mask), - DEFINE_MODULE_SYMBOL(pi4ioe5v6408_get_interrupt_level), - MODULE_SYMBOL_TERMINATOR -}; diff --git a/Modules/lvgl-module/source/symbols.c b/Modules/lvgl-module/source/symbols.c index 8cd2a04b6..9ad7cb53e 100644 --- a/Modules/lvgl-module/source/symbols.c +++ b/Modules/lvgl-module/source/symbols.c @@ -60,6 +60,8 @@ const struct ModuleSymbol lvgl_module_symbols[] = { DEFINE_MODULE_SYMBOL(lv_obj_get_group), DEFINE_MODULE_SYMBOL(lv_obj_get_user_data), DEFINE_MODULE_SYMBOL(lv_obj_get_state), + DEFINE_MODULE_SYMBOL(lv_obj_get_scroll_bottom), + DEFINE_MODULE_SYMBOL(lv_obj_get_scroll_y), DEFINE_MODULE_SYMBOL(lv_obj_has_flag), DEFINE_MODULE_SYMBOL(lv_obj_has_flag_any), DEFINE_MODULE_SYMBOL(lv_obj_has_state), @@ -158,7 +160,15 @@ const struct ModuleSymbol lvgl_module_symbols[] = { DEFINE_MODULE_SYMBOL(lv_obj_set_style_min_height), DEFINE_MODULE_SYMBOL(lv_obj_set_style_max_height), // lv_font + DEFINE_MODULE_SYMBOL(lv_font_get_bitmap_fmt_txt), DEFINE_MODULE_SYMBOL(lv_font_get_default), + DEFINE_MODULE_SYMBOL(lv_font_get_glyph_bitmap), + DEFINE_MODULE_SYMBOL(lv_font_get_glyph_dsc), + DEFINE_MODULE_SYMBOL(lv_font_get_glyph_dsc_fmt_txt), + DEFINE_MODULE_SYMBOL(lv_font_get_glyph_static_bitmap), + DEFINE_MODULE_SYMBOL(lv_font_get_glyph_width), + DEFINE_MODULE_SYMBOL(lv_font_get_line_height), + DEFINE_MODULE_SYMBOL(lv_font_set_kerning), // lv_theme DEFINE_MODULE_SYMBOL(lv_theme_get_color_primary), DEFINE_MODULE_SYMBOL(lv_theme_get_color_secondary), @@ -422,5 +432,8 @@ const struct ModuleSymbol lvgl_module_symbols[] = { DEFINE_MODULE_SYMBOL(lv_spangroup_refresh), DEFINE_MODULE_SYMBOL(lv_span_get_style), DEFINE_MODULE_SYMBOL(lv_span_set_text), + // lv_binfont + DEFINE_MODULE_SYMBOL(lv_binfont_create), + DEFINE_MODULE_SYMBOL(lv_binfont_destroy), MODULE_SYMBOL_TERMINATOR }; \ No newline at end of file diff --git a/Platforms/platform-esp32/CMakeLists.txt b/Platforms/platform-esp32/CMakeLists.txt index e0445b4d9..ac049c589 100644 --- a/Platforms/platform-esp32/CMakeLists.txt +++ b/Platforms/platform-esp32/CMakeLists.txt @@ -6,5 +6,5 @@ idf_component_register( SRCS ${SOURCES} INCLUDE_DIRS "include/" PRIV_INCLUDE_DIRS "private/" - REQUIRES TactilityKernel driver + REQUIRES TactilityKernel driver vfs fatfs ) diff --git a/Platforms/platform-esp32/bindings/espressif,esp32-sdmmc.yaml b/Platforms/platform-esp32/bindings/espressif,esp32-sdmmc.yaml new file mode 100644 index 000000000..7dbdabef9 --- /dev/null +++ b/Platforms/platform-esp32/bindings/espressif,esp32-sdmmc.yaml @@ -0,0 +1,53 @@ +description: ESP32 SDMMC + +compatible: "espressif,esp32-sdmmc" + +properties: + pin-clk: + type: phandle-array + required: true + pin-cmd: + type: phandle-array + required: true + pin-d0: + type: phandle-array + required: true + pin-d1: + type: phandle-array + default: GPIO_PIN_SPEC_NONE + pin-d2: + type: phandle-array + default: GPIO_PIN_SPEC_NONE + pin-d3: + type: phandle-array + default: GPIO_PIN_SPEC_NONE + pin-d4: + type: phandle-array + default: GPIO_PIN_SPEC_NONE + pin-d5: + type: phandle-array + default: GPIO_PIN_SPEC_NONE + pin-d6: + type: phandle-array + default: GPIO_PIN_SPEC_NONE + pin-d7: + type: phandle-array + default: GPIO_PIN_SPEC_NONE + pin-cd: + type: phandle-array + default: GPIO_PIN_SPEC_NONE + pin-wp: + type: phandle-array + default: GPIO_PIN_SPEC_NONE + bus-width: + type: int + required: true + description: Bus width in bits + wp-active-high: + type: boolean + default: false + description: Whether the WP pin is active high + enable-uhs: + type: boolean + default: false + description: Enable UHS mode \ No newline at end of file diff --git a/Platforms/platform-esp32/include/tactility/bindings/esp32_sdmmc.h b/Platforms/platform-esp32/include/tactility/bindings/esp32_sdmmc.h new file mode 100644 index 000000000..6b045aade --- /dev/null +++ b/Platforms/platform-esp32/include/tactility/bindings/esp32_sdmmc.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +DEFINE_DEVICETREE(esp32_sdmmc, struct Esp32SdmmcConfig) + +#ifdef __cplusplus +} +#endif diff --git a/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h b/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h new file mode 100644 index 000000000..152b42559 --- /dev/null +++ b/Platforms/platform-esp32/include/tactility/drivers/esp32_sdmmc.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once +#include +#if SOC_SDMMC_HOST_SUPPORTED + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct Esp32SdmmcConfig { + struct GpioPinSpec pin_clk; + struct GpioPinSpec pin_cmd; + struct GpioPinSpec pin_d0; + struct GpioPinSpec pin_d1; + struct GpioPinSpec pin_d2; + struct GpioPinSpec pin_d3; + struct GpioPinSpec pin_d4; + struct GpioPinSpec pin_d5; + struct GpioPinSpec pin_d6; + struct GpioPinSpec pin_d7; + struct GpioPinSpec pin_cd; + struct GpioPinSpec pin_wp; + uint8_t bus_width; + bool wp_active_high; + bool enable_uhs; +}; + +/** + * @brief Get the SD card handle for the given device. + * @param[in] device the device to get the card handle for + * @return the SD card handle, or NULL if the device is not mounted + */ +sdmmc_card_t* esp32_sdmmc_get_card(struct Device* device); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/Platforms/platform-esp32/private/tactility/drivers/esp32_sdmmc_fs.h b/Platforms/platform-esp32/private/tactility/drivers/esp32_sdmmc_fs.h new file mode 100644 index 000000000..6f0f5bf08 --- /dev/null +++ b/Platforms/platform-esp32/private/tactility/drivers/esp32_sdmmc_fs.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once +#include +#if SOC_SDMMC_HOST_SUPPORTED +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct Esp32SdmmcConfig; +typedef void* Esp32SdmmcHandle; + +extern Esp32SdmmcHandle esp32_sdmmc_fs_alloc(const struct Esp32SdmmcConfig* config, const char* mount_path); + +extern void esp32_sdmmc_fs_free(Esp32SdmmcHandle handle); + +extern sdmmc_card_t* esp32_sdmmc_fs_get_card(Esp32SdmmcHandle handle); + +extern const FileSystemApi esp32_sdmmc_fs_api; + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/Platforms/platform-esp32/source/drivers/esp32_gpio.cpp b/Platforms/platform-esp32/source/drivers/esp32_gpio.cpp index d0b39a296..d6d5137a9 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_gpio.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_gpio.cpp @@ -12,7 +12,12 @@ #define TAG "esp32_gpio" +struct Esp32GpioInternal { + uint8_t isr_service_ref_count = 0; +}; + #define GET_CONFIG(device) ((struct Esp32GpioConfig*)device->config) +#define GET_INTERNAL_FROM_DESCRIPTOR(gpio_descriptor) ((struct Esp32GpioInternal*)gpio_descriptor->controller_context) extern "C" { @@ -97,15 +102,76 @@ static error_t get_native_pin_number(GpioDescriptor* descriptor, void* pin_numbe return ERROR_NONE; } +static error_t add_callback(GpioDescriptor* descriptor, void (*callback)(void*), void* arg) { + auto* internal = GET_INTERNAL_FROM_DESCRIPTOR(descriptor); + if (internal->isr_service_ref_count == 0) { + auto esp_error = gpio_install_isr_service(0); + if (esp_error != ESP_OK && esp_error != ESP_ERR_INVALID_STATE) { + return esp_err_to_error(esp_error); + } + } + + auto esp_error = gpio_isr_handler_add(static_cast(descriptor->pin), callback, arg); + + if (esp_error == ESP_OK) { + internal->isr_service_ref_count++; + } else if (internal->isr_service_ref_count == 0) { + gpio_uninstall_isr_service(); + } + + return esp_err_to_error(esp_error); +} + +static error_t remove_callback(GpioDescriptor* descriptor) { + auto esp_error = gpio_isr_handler_remove(static_cast(descriptor->pin)); + if (esp_error == ESP_OK) { + auto* internal = GET_INTERNAL_FROM_DESCRIPTOR(descriptor); + check(internal->isr_service_ref_count > 0); + internal->isr_service_ref_count--; + if (internal->isr_service_ref_count == 0) { + gpio_uninstall_isr_service(); + } + } + return esp_err_to_error(esp_error); +} + +static error_t enable_interrupt(GpioDescriptor* descriptor) { + auto esp_error = gpio_intr_enable(static_cast(descriptor->pin)); + return esp_err_to_error(esp_error); +} + +static error_t disable_interrupt(GpioDescriptor* descriptor) { + auto esp_error = gpio_intr_disable(static_cast(descriptor->pin)); + return esp_err_to_error(esp_error); +} + static error_t start(Device* device) { - ESP_LOGI(TAG, "start %s", device->name); - auto pin_count = GET_CONFIG(device)->gpioCount; - return gpio_controller_init_descriptors(device, pin_count, nullptr); + LOG_I(TAG, "start %s", device->name); + const Esp32GpioConfig* config = GET_CONFIG(device); + auto* internal = new Esp32GpioInternal(); + return gpio_controller_init_descriptors(device, config->gpioCount, internal); } static error_t stop(Device* device) { - ESP_LOGI(TAG, "stop %s", device->name); - return gpio_controller_deinit_descriptors(device); + LOG_I(TAG, "stop %s", device->name); + const Esp32GpioConfig* config = GET_CONFIG(device); + auto* internal = static_cast(gpio_controller_get_controller_context(device)); + + // First, remove all ISR handlers to prevent callbacks during cleanup + for (uint8_t i = 0; i < config->gpioCount; ++i) { + gpio_isr_handler_remove(static_cast(i)); + } + + // Then uninstall ISR service + if (internal->isr_service_ref_count > 0) { + gpio_uninstall_isr_service(); + } + + // Now safe to deinit descriptors and delete internal + check(gpio_controller_deinit_descriptors(device) == ERROR_NONE); + delete internal; + + return ERROR_NONE; } const static GpioControllerApi esp32_gpio_api = { @@ -113,17 +179,21 @@ const static GpioControllerApi esp32_gpio_api = { .get_level = get_level, .set_flags = set_flags, .get_flags = get_flags, - .get_native_pin_number = get_native_pin_number + .get_native_pin_number = get_native_pin_number, + .add_callback = add_callback, + .remove_callback = remove_callback, + .enable_interrupt = enable_interrupt, + .disable_interrupt = disable_interrupt }; -extern struct Module platform_esp32_module; +extern Module platform_esp32_module; Driver esp32_gpio_driver = { .name = "esp32_gpio", .compatible = (const char*[]) { "espressif,esp32-gpio", nullptr }, .start_device = start, .stop_device = stop, - .api = (void*)&esp32_gpio_api, + .api = static_cast(&esp32_gpio_api), .device_type = &GPIO_CONTROLLER_TYPE, .owner = &platform_esp32_module, .internal = nullptr diff --git a/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp b/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp new file mode 100644 index 000000000..d36fe5aeb --- /dev/null +++ b/Platforms/platform-esp32/source/drivers/esp32_sdmmc.cpp @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#if SOC_SDMMC_HOST_SUPPORTED + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG "esp32_sdmmc" + +#define GET_CONFIG(device) ((const struct Esp32SdmmcConfig*)device->config) +#define GET_DATA(device) ((struct Esp32SdmmcInternal*)device_get_driver_data(device)) + +extern "C" { + +struct Esp32SdmmcInternal { + RecursiveMutex mutex = {}; + bool initialized = false; + Esp32SdmmcHandle esp32_sdmmc_fs_handle = nullptr; + FileSystem* file_system = nullptr; + + // Pin descriptors + GpioDescriptor* pin_clk_descriptor = nullptr; + GpioDescriptor* pin_cmd_descriptor = nullptr; + GpioDescriptor* pin_d0_descriptor = nullptr; + GpioDescriptor* pin_d1_descriptor = nullptr; + GpioDescriptor* pin_d2_descriptor = nullptr; + GpioDescriptor* pin_d3_descriptor = nullptr; + GpioDescriptor* pin_d4_descriptor = nullptr; + GpioDescriptor* pin_d5_descriptor = nullptr; + GpioDescriptor* pin_d6_descriptor = nullptr; + GpioDescriptor* pin_d7_descriptor = nullptr; + GpioDescriptor* pin_cd_descriptor = nullptr; + GpioDescriptor* pin_wp_descriptor = nullptr; + + explicit Esp32SdmmcInternal() { + recursive_mutex_construct(&mutex); + } + + ~Esp32SdmmcInternal() { + cleanup_pins(); + recursive_mutex_destruct(&mutex); + if (esp32_sdmmc_fs_handle) esp32_sdmmc_fs_free(esp32_sdmmc_fs_handle); + } + + void cleanup_pins() { + release_pin(&pin_clk_descriptor); + release_pin(&pin_cmd_descriptor); + release_pin(&pin_d0_descriptor); + release_pin(&pin_d1_descriptor); + release_pin(&pin_d2_descriptor); + release_pin(&pin_d3_descriptor); + release_pin(&pin_d4_descriptor); + release_pin(&pin_d5_descriptor); + release_pin(&pin_d6_descriptor); + release_pin(&pin_d7_descriptor); + release_pin(&pin_cd_descriptor); + release_pin(&pin_wp_descriptor); + } + + void lock() { recursive_mutex_lock(&mutex); } + + void unlock() { recursive_mutex_unlock(&mutex); } +}; + + +static error_t start(Device* device) { + LOG_I(TAG, "start %s", device->name); + auto* data = new (std::nothrow) Esp32SdmmcInternal(); + if (!data) return ERROR_OUT_OF_MEMORY; + + data->lock(); + device_set_driver_data(device, data); + + auto* sdmmc_config = GET_CONFIG(device); + + // Acquire pins from the specified GPIO pin specs. Optional pins are allowed. + bool pins_ok = + acquire_pin_or_set_null(sdmmc_config->pin_clk, &data->pin_clk_descriptor) && + acquire_pin_or_set_null(sdmmc_config->pin_cmd, &data->pin_cmd_descriptor) && + acquire_pin_or_set_null(sdmmc_config->pin_d0, &data->pin_d0_descriptor) && + acquire_pin_or_set_null(sdmmc_config->pin_d1, &data->pin_d1_descriptor) && + acquire_pin_or_set_null(sdmmc_config->pin_d2, &data->pin_d2_descriptor) && + acquire_pin_or_set_null(sdmmc_config->pin_d3, &data->pin_d3_descriptor) && + acquire_pin_or_set_null(sdmmc_config->pin_d4, &data->pin_d4_descriptor) && + acquire_pin_or_set_null(sdmmc_config->pin_d5, &data->pin_d5_descriptor) && + acquire_pin_or_set_null(sdmmc_config->pin_d6, &data->pin_d6_descriptor) && + acquire_pin_or_set_null(sdmmc_config->pin_d7, &data->pin_d7_descriptor) && + acquire_pin_or_set_null(sdmmc_config->pin_cd, &data->pin_cd_descriptor) && + acquire_pin_or_set_null(sdmmc_config->pin_wp, &data->pin_wp_descriptor); + + if (!pins_ok) { + LOG_E(TAG, "Failed to acquire required one or more pins"); + data->cleanup_pins(); + device_set_driver_data(device, nullptr); + data->unlock(); + delete data; + return ERROR_RESOURCE; + } + + data->esp32_sdmmc_fs_handle = esp32_sdmmc_fs_alloc(sdmmc_config, "/sdcard"); + if (!data->esp32_sdmmc_fs_handle) { + data->cleanup_pins(); + device_set_driver_data(device, nullptr); + data->unlock(); + delete data; + return ERROR_OUT_OF_MEMORY; + } + + data->file_system = file_system_add(&esp32_sdmmc_fs_api, data->esp32_sdmmc_fs_handle); + if (file_system_mount(data->file_system) != ERROR_NONE) { + // Error is not recoverable at the time, but it might be recoverable later, + // so we don't return start() failure. + LOG_E(TAG, "Failed to mount SD card filesystem"); + } + + data->initialized = true; + data->unlock(); + return ERROR_NONE; +} + +static error_t stop(Device* device) { + LOG_I(TAG, "stop %s", device->name); + auto* data = GET_DATA(device); + if (!data) return ERROR_NONE; + + data->lock(); + + if (file_system_is_mounted(data->file_system)) { + if (file_system_unmount(data->file_system) != ERROR_NONE) { + LOG_E(TAG, "Failed to unmount SD card filesystem"); + data->unlock(); + return ERROR_RESOURCE; + } + } + + // Free file system + file_system_remove(data->file_system); + data->file_system = nullptr; + // Free file system data + esp32_sdmmc_fs_free(data->esp32_sdmmc_fs_handle); + data->esp32_sdmmc_fs_handle = nullptr; + + data->cleanup_pins(); + device_set_driver_data(device, nullptr); + + data->unlock(); + delete data; + return ERROR_NONE; +} + +sdmmc_card_t* esp32_sdmmc_get_card(Device* device) { + if (!device_is_ready(device)) return nullptr; + auto* data = GET_DATA(device); + if (!data) return nullptr; + + data->lock(); + auto* card = esp32_sdmmc_fs_get_card(data->esp32_sdmmc_fs_handle); + data->unlock(); + + return card; +} + +extern Module platform_esp32_module; + +Driver esp32_sdmmc_driver = { + .name = "esp32_sdmmc", + .compatible = (const char*[]) { "espressif,esp32-sdmmc", nullptr }, + .start_device = start, + .stop_device = stop, + .api = nullptr, + .device_type = nullptr, + .owner = &platform_esp32_module, + .internal = nullptr +}; + +} // extern "C" +#endif \ No newline at end of file diff --git a/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp b/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp new file mode 100644 index 000000000..9387d7568 --- /dev/null +++ b/Platforms/platform-esp32/source/drivers/esp32_sdmmc_fs.cpp @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: Apache-2.0 +#include +#if SOC_SDMMC_HOST_SUPPORTED +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#if SOC_SD_PWR_CTRL_SUPPORTED +#include +#endif + +#define TAG "esp32_sdmmc_fs" + +#define GET_DATA(data) static_cast(data) + +struct Esp32SdmmcFsData { + const std::string mount_path; + const Esp32SdmmcConfig* config; + sdmmc_card_t* card; +#if SOC_SD_PWR_CTRL_SUPPORTED + sd_pwr_ctrl_handle_t pwr_ctrl_handle; +#endif + + Esp32SdmmcFsData(const Esp32SdmmcConfig* config, const std::string& mount_path) : + mount_path(mount_path), + config(config), + card(nullptr) +#if SOC_SD_PWR_CTRL_SUPPORTED + ,pwr_ctrl_handle(nullptr) +#endif + {} +}; + +static gpio_num_t to_native_pin(GpioPinSpec pin_spec) { + if (pin_spec.gpio_controller == nullptr) { return GPIO_NUM_NC; } + return static_cast(pin_spec.pin); +} + +extern "C" { + +static error_t get_path(void* data, char* out_path, size_t out_path_size); + +Esp32SdmmcHandle esp32_sdmmc_fs_alloc(const Esp32SdmmcConfig* config, const char* mount_path) { + return new(std::nothrow) Esp32SdmmcFsData(config, mount_path); +} + +sdmmc_card_t* esp32_sdmmc_fs_get_card(Esp32SdmmcHandle handle) { + return GET_DATA(handle)->card; +} + +void esp32_sdmmc_fs_free(Esp32SdmmcHandle handle) { + auto* fs_data = GET_DATA(handle); + delete fs_data; +} + +static error_t mount(void* data) { + auto* fs_data = GET_DATA(data); + auto* config = fs_data->config; + + LOG_I(TAG, "Mounting %s", fs_data->mount_path.c_str()); + + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = false, + .max_files = 4, + .allocation_unit_size = 0, // Default is sector size + .disk_status_check_enable = false, + .use_one_fat = false + }; + + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + +#if SOC_SD_PWR_CTRL_SUPPORTED + sd_pwr_ctrl_ldo_config_t ldo_config = { + .ldo_chan_id = 4, // LDO4 is typically used for SDMMC on ESP32-S3 + }; + esp_err_t pwr_err = sd_pwr_ctrl_new_on_chip_ldo(&ldo_config, &fs_data->pwr_ctrl_handle); + if (pwr_err != ESP_OK) { + LOG_E(TAG, "Failed to create SD power control driver, err=0x%x", pwr_err); + return ERROR_NOT_SUPPORTED; + } + host.pwr_ctrl_handle = fs_data->pwr_ctrl_handle; +#endif + + uint32_t slot_config_flags = 0; + if (config->enable_uhs) slot_config_flags |= SDMMC_SLOT_FLAG_UHS1; + if (config->wp_active_high) slot_config_flags |= SDMMC_SLOT_FLAG_WP_ACTIVE_HIGH; + + sdmmc_slot_config_t slot_config = { + .clk = to_native_pin(config->pin_clk), + .cmd = to_native_pin(config->pin_cmd), + .d0 = to_native_pin(config->pin_d0), + .d1 = to_native_pin(config->pin_d1), + .d2 = to_native_pin(config->pin_d2), + .d3 = to_native_pin(config->pin_d3), + .d4 = to_native_pin(config->pin_d4), + .d5 = to_native_pin(config->pin_d5), + .d6 = to_native_pin(config->pin_d6), + .d7 = to_native_pin(config->pin_d7), + .cd = to_native_pin(config->pin_cd), + .wp = to_native_pin(config->pin_wp), + .width = config->bus_width, + .flags = slot_config_flags + }; + + esp_err_t result = esp_vfs_fat_sdmmc_mount(fs_data->mount_path.c_str(), &host, &slot_config, &mount_config, &fs_data->card); + + if (result != ESP_OK || fs_data->card == nullptr) { + if (result == ESP_FAIL) { + LOG_E(TAG, "Mounting failed. Ensure the card is formatted with FAT."); + } else { + LOG_E(TAG, "Mounting failed: %s", esp_err_to_name(result)); + } +#if SOC_SD_PWR_CTRL_SUPPORTED + if (fs_data->pwr_ctrl_handle) { + sd_pwr_ctrl_del_on_chip_ldo(fs_data->pwr_ctrl_handle); + fs_data->pwr_ctrl_handle = nullptr; + } +#endif + return ERROR_UNDEFINED; + } + + LOG_I(TAG, "Mounted %s", fs_data->mount_path.c_str()); + + return ERROR_NONE; +} + +static error_t unmount(void* data) { + auto* fs_data = GET_DATA(data); + LOG_I(TAG, "Unmounting %s", fs_data->mount_path.c_str()); + + if (esp_vfs_fat_sdcard_unmount(fs_data->mount_path.c_str(), fs_data->card) != ESP_OK) { + LOG_E(TAG, "Unmount failed for %s", fs_data->mount_path.c_str()); + return ERROR_UNDEFINED; + } + + fs_data->card = nullptr; + +#if SOC_SD_PWR_CTRL_SUPPORTED + if (fs_data->pwr_ctrl_handle) { + sd_pwr_ctrl_del_on_chip_ldo(fs_data->pwr_ctrl_handle); + fs_data->pwr_ctrl_handle = nullptr; + } +#endif + + LOG_I(TAG, "Unmounted %s", fs_data->mount_path.c_str()); + + return ERROR_NONE; +} + +static bool is_mounted(void* data) { + const auto* fs_data = GET_DATA(data); + if (fs_data->card == nullptr) return false; + return sdmmc_get_status(fs_data->card) == ESP_OK; +} + +static error_t get_path(void* data, char* out_path, size_t out_path_size) { + const auto* fs_data = GET_DATA(data); + if (fs_data->mount_path.size() >= out_path_size) return ERROR_BUFFER_OVERFLOW; + strncpy(out_path, fs_data->mount_path.c_str(), out_path_size); + return ERROR_NONE; +} + +const FileSystemApi esp32_sdmmc_fs_api = { + .mount = mount, + .unmount = unmount, + .is_mounted = is_mounted, + .get_path = get_path +}; + +} // extern "C" +#endif \ No newline at end of file diff --git a/Platforms/platform-esp32/source/drivers/esp32_spi.cpp b/Platforms/platform-esp32/source/drivers/esp32_spi.cpp index e3f5440cf..14551a27c 100644 --- a/Platforms/platform-esp32/source/drivers/esp32_spi.cpp +++ b/Platforms/platform-esp32/source/drivers/esp32_spi.cpp @@ -2,11 +2,11 @@ #include #include #include +#include #include "tactility/drivers/gpio_descriptor.h" #include #include -#include #include #include @@ -84,7 +84,7 @@ static error_t start(Device* device) { acquire_pin_or_set_null(dts_config->pin_hd, &data->hd_descriptor); if (!pins_ok) { - ESP_LOGE(TAG, "Failed to acquire required SPI pins"); + LOG_E(TAG, "Failed to acquire required SPI pins"); data->cleanup_pins(); device_set_driver_data(device, nullptr); delete data; @@ -113,7 +113,7 @@ static error_t start(Device* device) { data->cleanup_pins(); device_set_driver_data(device, nullptr); delete data; - ESP_LOGE(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(ret)); + LOG_E(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(ret)); return ERROR_RESOURCE; } @@ -122,7 +122,7 @@ static error_t start(Device* device) { } static error_t stop(Device* device) { - ESP_LOGI(TAG, "stop %s", device->name); + LOG_I(TAG, "stop %s", device->name); auto* driver_data = GET_DATA(device); auto* dts_config = GET_CONFIG(device); diff --git a/Platforms/platform-esp32/source/module.cpp b/Platforms/platform-esp32/source/module.cpp index f75fbb09e..84a73aa6c 100644 --- a/Platforms/platform-esp32/source/module.cpp +++ b/Platforms/platform-esp32/source/module.cpp @@ -2,11 +2,16 @@ #include #include +#include + extern "C" { extern Driver esp32_gpio_driver; extern Driver esp32_i2c_driver; extern Driver esp32_i2s_driver; +#if SOC_SDMMC_HOST_SUPPORTED +extern Driver esp32_sdmmc_driver; +#endif extern Driver esp32_spi_driver; extern Driver esp32_uart_driver; @@ -16,6 +21,9 @@ static error_t start() { check(driver_construct_add(&esp32_gpio_driver) == ERROR_NONE); check(driver_construct_add(&esp32_i2c_driver) == ERROR_NONE); check(driver_construct_add(&esp32_i2s_driver) == ERROR_NONE); +#if SOC_SDMMC_HOST_SUPPORTED + check(driver_construct_add(&esp32_sdmmc_driver) == ERROR_NONE); +#endif check(driver_construct_add(&esp32_spi_driver) == ERROR_NONE); check(driver_construct_add(&esp32_uart_driver) == ERROR_NONE); return ERROR_NONE; @@ -27,12 +35,15 @@ static error_t stop() { check(driver_remove_destruct(&esp32_gpio_driver) == ERROR_NONE); check(driver_remove_destruct(&esp32_i2c_driver) == ERROR_NONE); check(driver_remove_destruct(&esp32_i2s_driver) == ERROR_NONE); +#if SOC_SDMMC_HOST_SUPPORTED + check(driver_remove_destruct(&esp32_sdmmc_driver) == ERROR_NONE); +#endif check(driver_remove_destruct(&esp32_spi_driver) == ERROR_NONE); check(driver_remove_destruct(&esp32_uart_driver) == ERROR_NONE); return ERROR_NONE; } -struct Module platform_esp32_module = { +Module platform_esp32_module = { .name = "platform-esp32", .start = start, .stop = stop, diff --git a/Tactility/Include/Tactility/MountPoints.h b/Tactility/Include/Tactility/MountPoints.h index d2a3a9a85..809c8bd46 100644 --- a/Tactility/Include/Tactility/MountPoints.h +++ b/Tactility/Include/Tactility/MountPoints.h @@ -21,6 +21,6 @@ constexpr auto* MOUNT_POINT_DATA = "/data"; constexpr auto* MOUNT_POINT_DATA = "data"; #endif -std::vector getMountPoints(); +std::vector getFileSystemDirents(); } // namespace diff --git a/Tactility/Include/Tactility/Paths.h b/Tactility/Include/Tactility/Paths.h index 005bd6e5e..c3930db97 100644 --- a/Tactility/Include/Tactility/Paths.h +++ b/Tactility/Include/Tactility/Paths.h @@ -2,10 +2,15 @@ #include +#include + + namespace tt { bool findFirstMountedSdCardPath(std::string& path); +FileSystem* findSdcardFileSystem(bool mustBeMounted); + std::string getSystemRootPath(); std::string getTempPath(); diff --git a/Tactility/Include/Tactility/hal/sdcard/SdCardDevice.h b/Tactility/Include/Tactility/hal/sdcard/SdCardDevice.h index a9588e242..d57ffdb73 100644 --- a/Tactility/Include/Tactility/hal/sdcard/SdCardDevice.h +++ b/Tactility/Include/Tactility/hal/sdcard/SdCardDevice.h @@ -2,9 +2,11 @@ #include -#include #include +#include + +struct FileSystem; namespace tt::hal::sdcard { /** @@ -31,11 +33,12 @@ class SdCardDevice : public Device { private: MountBehaviour mountBehaviour; + FileSystem* fileSystem; public: - explicit SdCardDevice(MountBehaviour mountBehaviour) : mountBehaviour(mountBehaviour) {} - ~SdCardDevice() override = default; + explicit SdCardDevice(MountBehaviour mountBehaviour); + ~SdCardDevice() override; Type getType() const final { return Type::SdCard; }; diff --git a/Tactility/Include/Tactility/hal/sdcard/SdmmcDevice.h b/Tactility/Include/Tactility/hal/sdcard/SdmmcDevice.h deleted file mode 100644 index dc490678b..000000000 --- a/Tactility/Include/Tactility/hal/sdcard/SdmmcDevice.h +++ /dev/null @@ -1,93 +0,0 @@ -#ifdef ESP_PLATFORM - -#pragma once - -#include "SdCardDevice.h" - -#include -#include -#include -#include -#include -#include - -namespace tt::hal::sdcard { - -/** - * SD card interface for the SDMMC interface. - */ -class SdmmcDevice final : public SdCardDevice { - - std::shared_ptr mutex = std::make_shared(); - -public: - - struct Config { - Config( - gpio_num_t pinClock, - gpio_num_t pinCmd, - gpio_num_t pinD0, - gpio_num_t pinD1, - gpio_num_t pinD2, - gpio_num_t pinD3, - MountBehaviour mountBehaviourAtBoot, - uint8_t busWidth = 4 - ) : - pinClock(pinClock), - pinCmd(pinCmd), - pinD0(pinD0), - pinD1(pinD1), - pinD2(pinD2), - pinD3(pinD3), - mountBehaviourAtBoot(mountBehaviourAtBoot), - busWidth(busWidth) - {} - - int spiFrequencyKhz; - gpio_num_t pinClock; - gpio_num_t pinCmd; - gpio_num_t pinD0; - gpio_num_t pinD1; - gpio_num_t pinD2; - gpio_num_t pinD3; - MountBehaviour mountBehaviourAtBoot; - uint8_t busWidth; - bool formatOnMountFailed = false; - uint16_t maxOpenFiles = 4; - uint16_t allocUnitSize = 16 * 1024; - bool statusCheckEnabled = false; - }; - -private: - - std::string mountPath; - sdmmc_card_t* card = nullptr; - std::shared_ptr config; - - bool applyGpioWorkAround(); - bool mountInternal(const std::string& mountPath); - -public: - - explicit SdmmcDevice(std::unique_ptr config) : SdCardDevice(config->mountBehaviourAtBoot), - config(std::move(config)) - {} - - std::string getName() const override { return "SDMMC"; } - std::string getDescription() const override { return "SD card via SDMMC interface"; } - - bool mount(const std::string& mountPath) override; - bool unmount() override; - std::string getMountPath() const override { return mountPath; } - - std::shared_ptr getLock() const override { return mutex; } - - State getState(TickType_t timeout) const override; - - /** return card when mounted, otherwise return nullptr */ - sdmmc_card_t* getCard() { return card; } -}; - -} - -#endif diff --git a/Tactility/Source/MountPoints.cpp b/Tactility/Source/MountPoints.cpp index ac298fbc2..32a6866bd 100644 --- a/Tactility/Source/MountPoints.cpp +++ b/Tactility/Source/MountPoints.cpp @@ -2,56 +2,36 @@ #include "Tactility/TactilityConfig.h" #include -#include "Tactility/hal/sdcard/SdCardDevice.h" #include #include -#include #include +#include +#include namespace tt::file { -std::vector getMountPoints() { +std::vector getFileSystemDirents() { std::vector dir_entries; - dir_entries.clear(); - - // Data partition - auto data_dirent = dirent{ - .d_ino = 1, - .d_type = TT_DT_DIR, - .d_name = { 0 } - }; - strcpy(data_dirent.d_name, DATA_PARTITION_NAME); - dir_entries.push_back(data_dirent); - - // SD card partitions - auto sdcards = tt::hal::findDevices(hal::Device::Type::SdCard); - for (auto& sdcard : sdcards) { - auto state = sdcard->getState(); - if (state == hal::sdcard::SdCardDevice::State::Mounted) { - auto mount_name = sdcard->getMountPath().substr(1); - auto dir_entry = dirent { - .d_ino = 2, - .d_type = TT_DT_DIR, - .d_name = { 0 } - }; - assert(mount_name.length() < sizeof(dirent::d_name)); - strcpy(dir_entry.d_name, mount_name.c_str()); - dir_entries.push_back(dir_entry); - } - } - - if (config::SHOW_SYSTEM_PARTITION) { - // System partition - auto system_dirent = dirent{ - .d_ino = 0, + + file_system_for_each(&dir_entries, [](auto* fs, void* context) { + if (!file_system_is_mounted(fs)) return true; + char path[128]; + if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true; + auto mount_name = std::string(path).substr(1); + if (!config::SHOW_SYSTEM_PARTITION && mount_name.starts_with(SYSTEM_PARTITION_NAME)) return true; + auto dir_entry = dirent { + .d_ino = 2, .d_type = TT_DT_DIR, .d_name = { 0 } }; - strcpy(system_dirent.d_name, SYSTEM_PARTITION_NAME); - dir_entries.push_back(system_dirent); - } + auto& dir_entries = *static_cast*>(context); + strcpy(dir_entry.d_name, mount_name.c_str()); + dir_entries.push_back(dir_entry); + + return true; + }); return dir_entries; } diff --git a/Tactility/Source/PartitionsEsp.cpp b/Tactility/Source/PartitionsEsp.cpp index b1b79a5aa..73f0f11c8 100644 --- a/Tactility/Source/PartitionsEsp.cpp +++ b/Tactility/Source/PartitionsEsp.cpp @@ -5,11 +5,47 @@ #include #include +#include +#include namespace tt { static const auto LOGGER = Logger("Partitions"); +// region file_system stub + +struct PartitionFsData { + const char* path; +}; + +static error_t mount(void* data) { + return ERROR_NOT_SUPPORTED; +} + +static error_t unmount(void* data) { + return ERROR_NOT_SUPPORTED; +} + +static bool is_mounted(void* data) { + return true; +} + +static error_t get_path(void* data, char* out_path, size_t out_path_size) { + auto* fs_data = static_cast(data); + if (strlen(fs_data->path) >= out_path_size) return ERROR_BUFFER_OVERFLOW; + strncpy(out_path, fs_data->path, out_path_size); + return ERROR_NONE; +} + +FileSystemApi partition_fs_api = { + .mount = mount, + .unmount = unmount, + .is_mounted = is_mounted, + .get_path = get_path +}; + +// endregion file_system stub + static esp_err_t initNvsFlashSafely() { esp_err_t result = nvs_flash_init(); if (result == ESP_ERR_NVS_NO_FREE_PAGES || result == ESP_ERR_NVS_NEW_VERSION_FOUND) { @@ -56,6 +92,8 @@ esp_err_t initPartitionsEsp() { LOGGER.error("Failed to mount /system ({})", esp_err_to_name(system_result)); } else { LOGGER.info("Mounted /system"); + static auto system_fs_data = PartitionFsData("/system"); + file_system_add(&partition_fs_api, &system_fs_data); } auto data_result = esp_vfs_fat_spiflash_mount_rw_wl("/data", "data", &mount_config, &data_wl_handle); @@ -63,6 +101,8 @@ esp_err_t initPartitionsEsp() { LOGGER.error("Failed to mount /data ({})", esp_err_to_name(data_result)); } else { LOGGER.info("Mounted /data"); + static auto data_fs_data = PartitionFsData("/data"); + file_system_add(&partition_fs_api, &data_fs_data); } return system_result == ESP_OK && data_result == ESP_OK; diff --git a/Tactility/Source/Paths.cpp b/Tactility/Source/Paths.cpp index b35a847b2..8affa3c0f 100644 --- a/Tactility/Source/Paths.cpp +++ b/Tactility/Source/Paths.cpp @@ -2,25 +2,37 @@ #include #include -#include #include +#include namespace tt { bool findFirstMountedSdCardPath(std::string& path) { - // const auto sdcards = hal::findDevices(hal::Device::Type::SdCard); - bool is_set = false; - hal::findDevices(hal::Device::Type::SdCard, [&is_set, &path](const auto& device) { - if (device->isMounted()) { - path = device->getMountPath(); - is_set = true; - return false; // stop iterating - } else { - return true; + auto* fs = findSdcardFileSystem(true); + if (fs == nullptr) return false; + char found_path[128]; + if (file_system_get_path(fs, found_path, sizeof(found_path)) != ERROR_NONE) return false; + path = found_path; + return true; +} + +FileSystem* findSdcardFileSystem(bool mustBeMounted) { + FileSystem* found = nullptr; + file_system_for_each(&found, [](auto* fs, void* context) { + char path[128]; + if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true; + // TODO: Find a better way to identify SD card paths + if (std::string(path).starts_with("/sdcard")) { + *static_cast(context) = fs; + return false; } + return true; }); - return is_set; + if (found && mustBeMounted && !file_system_is_mounted(found)) { + return nullptr; + } + return found; } std::string getSystemRootPath() { diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index 85b81afe1..52ff1ef4f 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -8,19 +8,19 @@ #include #include -#include -#include #include #include +#include +#include +#include +#include +#include +#include #include #include #include #include -#include -#include -#include #include -#include #include #include #include @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -49,7 +50,6 @@ namespace service { // Primary namespace gps { extern const ServiceManifest manifest; } namespace wifi { extern const ServiceManifest manifest; } - namespace sdcard { extern const ServiceManifest manifest; } #ifdef ESP_PLATFORM namespace development { extern const ServiceManifest manifest; } #endif @@ -228,22 +228,18 @@ static void registerInstalledApps(const std::string& path) { }); } -static void registerInstalledAppsFromSdCard(const std::shared_ptr& sdcard) { - auto sdcard_root_path = sdcard->getMountPath(); - auto app_path = std::format("{}/app", sdcard_root_path); - if (file::isDirectory(app_path)) { - registerInstalledApps(app_path); - } -} - -static void registerInstalledAppsFromSdCards() { - auto sdcard_devices = hal::findDevices(hal::Device::Type::SdCard); - for (const auto& sdcard : sdcard_devices) { - if (sdcard->isMounted()) { - LOGGER.info("Registering apps from {}", sdcard->getMountPath()); - registerInstalledAppsFromSdCard(sdcard); +static void registerInstalledAppsFromFileSystems() { + file_system_for_each(nullptr, [](auto* fs, void* context) { + if (!file_system_is_mounted(fs)) return true; + char path[128]; + if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true; + const auto app_path = std::format("{}/app", path); + if (!app_path.starts_with(file::MOUNT_POINT_SYSTEM) && file::isDirectory(app_path)) { + LOGGER.info("Registering apps from {}", app_path); + registerInstalledApps(app_path); } - } + return true; + }); } static void registerAndStartSecondaryServices() { @@ -266,9 +262,6 @@ static void registerAndStartSecondaryServices() { static void registerAndStartPrimaryServices() { LOGGER.info("Registering and starting primary system services"); addService(service::gps::manifest); - if (hal::hasDevice(hal::Device::Type::SdCard)) { - addService(service::sdcard::manifest); - } addService(service::wifi::manifest); #ifdef ESP_PLATFORM addService(service::development::manifest); @@ -301,26 +294,19 @@ void createTempDirectory(const std::string& rootPath) { } void prepareFileSystems() { - // Temporary directories for SD cards - auto sdcard_devices = hal::findDevices(hal::Device::Type::SdCard); - for (const auto& sdcard : sdcard_devices) { - if (sdcard->isMounted()) { - createTempDirectory(sdcard->getMountPath()); - } - } - // Temporary directory for /data - if (file::isDirectory(file::MOUNT_POINT_DATA)) { - createTempDirectory(file::MOUNT_POINT_DATA); - } + file_system_for_each(nullptr, [](auto* fs, void* context) { + if (!file_system_is_mounted(fs)) return true; + char path[128]; + if (file_system_get_path(fs, path, sizeof(path)) != ERROR_NONE) return true; + if (std::string(path) == file::MOUNT_POINT_SYSTEM) return true; + createTempDirectory(path); + return true; + }); } void registerApps() { registerInternalApps(); - auto data_apps_path = std::format("{}/app", file::MOUNT_POINT_DATA); - if (file::isDirectory(data_apps_path)) { - registerInstalledApps(data_apps_path); - } - registerInstalledAppsFromSdCards(); + registerInstalledAppsFromFileSystems(); } void run(const Configuration& config, Module* dtsModules[], DtsDevice dtsDevices[]) { diff --git a/Tactility/Source/app/files/FilesApp.cpp b/Tactility/Source/app/files/FilesApp.cpp index ee2637744..5f353cbf7 100644 --- a/Tactility/Source/app/files/FilesApp.cpp +++ b/Tactility/Source/app/files/FilesApp.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include diff --git a/Tactility/Source/app/files/State.cpp b/Tactility/Source/app/files/State.cpp index 8a7bb4853..86f88a594 100644 --- a/Tactility/Source/app/files/State.cpp +++ b/Tactility/Source/app/files/State.cpp @@ -51,7 +51,7 @@ bool State::setEntriesForPath(const std::string& path) { bool get_mount_points = (kernel::getPlatform() == kernel::PlatformEsp) && (path == "/"); if (get_mount_points) { LOGGER.info("Setting custom root"); - dir_entries = file::getMountPoints(); + dir_entries = file::getFileSystemDirents(); current_path = path; selected_child_entry = ""; action = ActionNone; diff --git a/Tactility/Source/app/fileselection/State.cpp b/Tactility/Source/app/fileselection/State.cpp index 78b997477..7dfa38ca8 100644 --- a/Tactility/Source/app/fileselection/State.cpp +++ b/Tactility/Source/app/fileselection/State.cpp @@ -6,10 +6,11 @@ #include #include +#include #include +#include #include #include -#include namespace tt::app::fileselection { @@ -36,6 +37,12 @@ std::string State::getSelectedChildPath() const { bool State::setEntriesForPath(const std::string& path) { LOGGER.info("Changing path: {} -> {}", current_path, path); + auto lock = mutex.asScopedLock(); + if (!lock.lock(100)) { + LOGGER.error(LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "setEntriesForPath"); + return false; + } + /** * ESP32 does not have a root directory, so we have to create it manually. * We'll add the NVS Flash partitions and the binding for the sdcard. @@ -43,7 +50,7 @@ bool State::setEntriesForPath(const std::string& path) { bool show_custom_root = (kernel::getPlatform() == kernel::PlatformEsp) && (path == "/"); if (show_custom_root) { LOGGER.info("Setting custom root"); - dir_entries = file::getMountPoints(); + dir_entries = file::getFileSystemDirents(); current_path = path; selected_child_entry = ""; return true; diff --git a/Tactility/Source/app/screenshot/Screenshot.cpp b/Tactility/Source/app/screenshot/Screenshot.cpp index 8394b80da..5ec43faec 100644 --- a/Tactility/Source/app/screenshot/Screenshot.cpp +++ b/Tactility/Source/app/screenshot/Screenshot.cpp @@ -1,18 +1,18 @@ #include #include - #if TT_FEATURE_SCREENSHOT_ENABLED #include #include -#include #include #include #include #include #include #include + +#include #include #include @@ -204,12 +204,9 @@ void ScreenshotApp::createFilePathWidgets(lv_obj_t* parent) { lv_textarea_set_one_line(pathTextArea, true); lv_obj_set_flex_grow(pathTextArea, 1); if (kernel::getPlatform() == kernel::PlatformEsp) { - auto sdcard_devices = tt::hal::findDevices(tt::hal::Device::Type::SdCard); - if (sdcard_devices.size() > 1) { - LOGGER.warn("Found multiple SD card devices - picking first"); - } - if (!sdcard_devices.empty() && sdcard_devices.front()->isMounted()) { - std::string lvgl_mount_path = lvgl::PATH_PREFIX + sdcard_devices.front()->getMountPath(); + std::string sdcard_path; + if (findFirstMountedSdCardPath(sdcard_path)) { + std::string lvgl_mount_path = lvgl::PATH_PREFIX + sdcard_path + "/screenshots"; lv_textarea_set_text(pathTextArea, lvgl_mount_path.c_str()); } else { lv_textarea_set_text(pathTextArea, "Error: no SD card"); diff --git a/Tactility/Source/app/systeminfo/SystemInfo.cpp b/Tactility/Source/app/systeminfo/SystemInfo.cpp index 6d5c3171f..8e7f25d39 100644 --- a/Tactility/Source/app/systeminfo/SystemInfo.cpp +++ b/Tactility/Source/app/systeminfo/SystemInfo.cpp @@ -1,10 +1,10 @@ #include #include #include -#include #include #include +#include #include #include #include @@ -321,7 +321,6 @@ class SystemInfoApp final : public App { bool hasExternalMem = false; bool hasDataStorage = false; - bool hasSdcardStorage = false; bool hasSystemStorage = false; void updateMemory() { @@ -343,14 +342,9 @@ class SystemInfoApp final : public App { } } - if (hasSdcardStorage) { - const auto sdcard_devices = hal::findDevices(hal::Device::Type::SdCard); - for (const auto& sdcard : sdcard_devices) { - if (sdcard->isMounted() && esp_vfs_fat_info(sdcard->getMountPath().c_str(), &storage_total, &storage_free) == ESP_OK) { - updateMemoryBar(sdcardStorageBar, storage_free, storage_total); - break; // Only update first SD card - } - } + std::string sdcard_path; + if (findFirstMountedSdCardPath(sdcard_path) && esp_vfs_fat_info(sdcard_path.c_str(), &storage_total, &storage_free) == ESP_OK) { + updateMemoryBar(sdcardStorageBar, storage_free, storage_total); } if (hasSystemStorage) { @@ -624,13 +618,9 @@ class SystemInfoApp final : public App { dataStorageBar = createMemoryBar(storage_tab, file::MOUNT_POINT_DATA); } - const auto sdcard_devices = hal::findDevices(hal::Device::Type::SdCard); - for (const auto& sdcard : sdcard_devices) { - if (sdcard->isMounted() && esp_vfs_fat_info(sdcard->getMountPath().c_str(), &storage_total, &storage_free) == ESP_OK) { - hasSdcardStorage = true; - sdcardStorageBar = createMemoryBar(storage_tab, sdcard->getMountPath().c_str()); - break; // Only show first SD card - } + std::string sdcard_path; + if (findFirstMountedSdCardPath(sdcard_path) && esp_vfs_fat_info(sdcard_path.c_str(), &storage_total, &storage_free) == ESP_OK) { + sdcardStorageBar = createMemoryBar(storage_tab, sdcard_path.c_str()); } if (config::SHOW_SYSTEM_PARTITION) { diff --git a/Tactility/Source/hal/sdcard/SdCardDevice.cpp b/Tactility/Source/hal/sdcard/SdCardDevice.cpp new file mode 100644 index 000000000..44fbd2843 --- /dev/null +++ b/Tactility/Source/hal/sdcard/SdCardDevice.cpp @@ -0,0 +1,52 @@ +#include + +#include + +#include + +namespace tt::hal::sdcard { + +static error_t mount(void* data) { + auto* device = static_cast(data); + auto path = device->getMountPath(); + if (!device->mount(path)) return ERROR_UNDEFINED; + return ERROR_NONE; +} + +static error_t unmount(void* data) { + auto* device = static_cast(data); + if (!device->unmount()) return ERROR_UNDEFINED; + return ERROR_NONE; +} + +static bool is_mounted(void* data) { + auto* device = static_cast(data); + return device->isMounted(); +} + +static error_t get_path(void* data, char* out_path, size_t out_path_size) { + auto* device = static_cast(data); + const auto mount_path = device->getMountPath(); + if (mount_path.size() >= out_path_size) return ERROR_BUFFER_OVERFLOW; + if (mount_path.empty()) return ERROR_INVALID_STATE; + strncpy(out_path, mount_path.c_str(), out_path_size); + return ERROR_NONE; +} + +FileSystemApi sdCardDeviceApi = { + .mount = mount, + .unmount = unmount, + .is_mounted = is_mounted, + .get_path = get_path +}; + +SdCardDevice::SdCardDevice(MountBehaviour mountBehaviour) : mountBehaviour(mountBehaviour) { + fileSystem = file_system_add(&sdCardDeviceApi, this); + check(fileSystem != nullptr); +} + +SdCardDevice::~SdCardDevice() { + file_system_remove(fileSystem); +} + +} diff --git a/Tactility/Source/hal/sdcard/SdmmcDevice.cpp b/Tactility/Source/hal/sdcard/SdmmcDevice.cpp deleted file mode 100644 index ae74e6ce4..000000000 --- a/Tactility/Source/hal/sdcard/SdmmcDevice.cpp +++ /dev/null @@ -1,123 +0,0 @@ -#ifdef ESP_PLATFORM -#include -#endif - -#if defined(ESP_PLATFORM) && defined(SOC_SDMMC_HOST_SUPPORTED) - -#include -#include - -#include -#include -#include - -namespace tt::hal::sdcard { - -static const auto LOGGER = Logger("SdmmcDevice"); - -bool SdmmcDevice::mountInternal(const std::string& newMountPath) { - LOGGER.info("Mounting {}", newMountPath); - - esp_vfs_fat_sdmmc_mount_config_t mount_config = { - .format_if_mount_failed = config->formatOnMountFailed, - .max_files = config->maxOpenFiles, - .allocation_unit_size = config->allocUnitSize, - .disk_status_check_enable = config->statusCheckEnabled, - .use_one_fat = false - }; - - sdmmc_host_t host = SDMMC_HOST_DEFAULT(); - - sdmmc_slot_config_t slot_config = { - .clk = config->pinClock, - .cmd = config->pinCmd, - .d0 = config->pinD0, - .d1 = config->pinD1, - .d2 = config->pinD2, - .d3 = config->pinD3, - .d4 = static_cast(0), - .d5 = static_cast(0), - .d6 = static_cast(0), - .d7 = static_cast(0), - .cd = GPIO_NUM_NC, - .wp = GPIO_NUM_NC, - .width = config->busWidth, - .flags = 0 - }; - - esp_err_t result = esp_vfs_fat_sdmmc_mount(newMountPath.c_str(), &host, &slot_config, &mount_config, &card); - - if (result != ESP_OK || card == nullptr) { - if (result == ESP_FAIL) { - LOGGER.error("Mounting failed. Ensure the card is formatted with FAT."); - } else { - LOGGER.error("Mounting failed ({})", esp_err_to_name(result)); - } - return false; - } - - mountPath = newMountPath; - - return true; -} - -bool SdmmcDevice::mount(const std::string& newMountPath) { - auto lock = getLock()->asScopedLock(); - lock.lock(); - - if (mountInternal(newMountPath)) { - LOGGER.info("Mounted at {}", newMountPath); - sdmmc_card_print_info(stdout, card); - return true; - } else { - LOGGER.error("Mount failed for {}", newMountPath); - return false; - } -} - -bool SdmmcDevice::unmount() { - auto lock = getLock()->asScopedLock(); - lock.lock(); - - if (card == nullptr) { - LOGGER.error("Can't unmount: not mounted"); - return false; - } - - if (esp_vfs_fat_sdcard_unmount(mountPath.c_str(), card) != ESP_OK) { - LOGGER.error("Unmount failed for {}", mountPath); - return false; - } - - LOGGER.info("Unmounted {}", mountPath); - mountPath = ""; - card = nullptr; - return true; -} - -SdmmcDevice::State SdmmcDevice::getState(TickType_t timeout) const { - if (card == nullptr) { - return State::Unmounted; - } - - /** - * The SD card and the screen are on the same SPI bus. - * Writing and reading to the bus from 2 devices at the same time causes crashes. - * This work-around ensures that this check is only happening when LVGL isn't rendering. - */ - auto lock = getLock()->asScopedLock(); - bool locked = lock.lock(timeout); - if (!locked) { - return State::Timeout; - } - - if (sdmmc_get_status(card) != ESP_OK) { - return State::Error; - } - - return State::Mounted; -} - -} - -#endif diff --git a/Tactility/Source/hal/usb/Usb.cpp b/Tactility/Source/hal/usb/Usb.cpp index 6138ff3c7..c52a0c52a 100644 --- a/Tactility/Source/hal/usb/Usb.cpp +++ b/Tactility/Source/hal/usb/Usb.cpp @@ -1,10 +1,13 @@ #ifdef ESP_PLATFORM +#include + #include #include #include #include +#include namespace tt::hal::usb { @@ -21,29 +24,41 @@ static Mode currentMode = Mode::Default; static RTC_NOINIT_ATTR BootModeData bootModeData; sdmmc_card_t* getCard() { - auto sdcards = findDevices(Device::Type::SdCard); + sdmmc_card_t* sdcard = nullptr; - std::shared_ptr usable_sdcard; - for (auto& sdcard : sdcards) { - auto sdcard_candidate = std::static_pointer_cast(sdcard); - if (sdcard_candidate != nullptr && sdcard_candidate->isMounted() && sdcard_candidate->getCard() != nullptr) { - usable_sdcard = sdcard_candidate; + // Find old HAL SD card device: + auto sdcards = findDevices(Device::Type::SdCard); + for (auto& device : sdcards) { + auto sdcard_device= std::static_pointer_cast(device); + if (sdcard_device != nullptr && sdcard_device->isMounted() && sdcard_device->getCard() != nullptr) { + sdcard = sdcard_device->getCard(); break; } } - if (usable_sdcard == nullptr) { - LOGGER.warn("Couldn't find a mounted SpiSdCard"); - return nullptr; +#if SOC_SDMMC_HOST_SUPPORTED + // Find ESP32 SDMMC device: + if (sdcard == nullptr) { + device_for_each(&sdcard, [](auto* device, void* context) { + if (device_is_ready(device) && device_is_compatible(device, "espressif,esp32-sdmmc")) { + auto** sdcard = static_cast(context); + auto* sdmmc_card = esp32_sdmmc_get_card(device); + if (sdmmc_card) { + *sdcard = sdmmc_card; + return false; + } + return true; + } + return true; + }); } +#endif - auto* sdmmc_card = usable_sdcard->getCard(); - if (sdmmc_card == nullptr) { - LOGGER.warn("SD card has no card object available"); - return nullptr; + if (sdcard == nullptr) { + LOGGER.warn("Couldn't find a mounted SD card"); } - return sdmmc_card; + return sdcard; } static bool canStartNewMode() { diff --git a/Tactility/Source/service/sdcard/Sdcard.cpp b/Tactility/Source/service/sdcard/Sdcard.cpp deleted file mode 100644 index 1cbddb821..000000000 --- a/Tactility/Source/service/sdcard/Sdcard.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -namespace tt::service::sdcard { - -static const auto LOGGER = Logger("SdcardService"); - -extern const ServiceManifest manifest; - -class SdCardService final : public Service { - - Mutex mutex; - std::unique_ptr updateTimer; - hal::sdcard::SdCardDevice::State lastState = hal::sdcard::SdCardDevice::State::Unmounted; - - bool lock(TickType_t timeout) const { - return mutex.lock(timeout); - } - - void unlock() const { - mutex.unlock(); - } - - void update() { - // TODO: Support multiple SD cards - auto sdcard = hal::findFirstDevice(hal::Device::Type::SdCard); - if (sdcard == nullptr) { - return; - } - - if (lock(50)) { - auto new_state = sdcard->getState(); - - if (new_state == hal::sdcard::SdCardDevice::State::Error) { - LOGGER.error("Sdcard error - unmounting. Did you eject the card in an unsafe manner?"); - sdcard->unmount(); - } - - if (new_state != lastState) { - lastState = new_state; - } - - unlock(); - } else { - LOGGER.warn(LOG_MESSAGE_MUTEX_LOCK_FAILED); - } - } - -public: - - bool onStart(ServiceContext& serviceContext) override { - if (hal::findFirstDevice(hal::Device::Type::SdCard) == nullptr) { - LOGGER.warn("No SD card device found - not starting Service"); - return false; - } - - auto service = findServiceById(manifest.id); - updateTimer = std::make_unique(Timer::Type::Periodic, 1000, [service] { - service->update(); - }); - - // We want to try and scan more often in case of startup or scan lock failure - updateTimer->start(); - - return true; - } - - void onStop(ServiceContext& serviceContext) override { - if (updateTimer != nullptr) { - // Stop thread - updateTimer->stop(); - updateTimer = nullptr; - } - } -}; - -extern const ServiceManifest manifest = { - .id = "sdcard", - .createService = create -}; - -} // namespace diff --git a/Tactility/Source/service/statusbar/Statusbar.cpp b/Tactility/Source/service/statusbar/Statusbar.cpp index 23c440731..0af4c2007 100644 --- a/Tactility/Source/service/statusbar/Statusbar.cpp +++ b/Tactility/Source/service/statusbar/Statusbar.cpp @@ -2,8 +2,8 @@ #include #include +#include #include -#include #include #include #include @@ -13,6 +13,7 @@ #include #include #include +#include #include @@ -56,18 +57,9 @@ static const char* getWifiStatusIcon(wifi::RadioState state) { } } -static const char* getSdCardStatusIcon(hal::sdcard::SdCardDevice::State state) { - switch (state) { - using enum hal::sdcard::SdCardDevice::State; - case Mounted: - return LVGL_ICON_STATUSBAR_SD_CARD; - case Error: - case Unmounted: - case Timeout: - return LVGL_ICON_STATUSBAR_SD_CARD_ALERT; - default: - check(false, "Unhandled SdCard state"); - } +static const char* getSdCardStatusIcon(bool mounted) { + if (mounted) return LVGL_ICON_STATUSBAR_SD_CARD; + return LVGL_ICON_STATUSBAR_SD_CARD_ALERT; } static const char* getPowerStatusIcon() { @@ -172,20 +164,21 @@ class StatusbarService final : public Service { } void updateSdCardIcon() { - auto sdcards = hal::findDevices(hal::Device::Type::SdCard); + auto* sdcard_fs = findSdcardFileSystem(false); // TODO: Support multiple SD cards - auto sdcard = sdcards.empty() ? nullptr : sdcards[0]; - if (sdcard != nullptr) { - auto state = sdcard->getState(50 / portTICK_PERIOD_MS); - if (state != hal::sdcard::SdCardDevice::State::Timeout) { - auto* desired_icon = getSdCardStatusIcon(state); - if (sdcard_last_icon != desired_icon) { - lvgl::statusbar_icon_set_image(sdcard_icon_id, desired_icon); - lvgl::statusbar_icon_set_visibility(sdcard_icon_id, true); - sdcard_last_icon = desired_icon; - } + if (sdcard_fs != nullptr) { + auto mounted = file_system_is_mounted(sdcard_fs); + auto* desired_icon = getSdCardStatusIcon(mounted); + if (sdcard_last_icon != desired_icon) { + lvgl::statusbar_icon_set_image(sdcard_icon_id, desired_icon); + lvgl::statusbar_icon_set_visibility(sdcard_icon_id, true); + sdcard_last_icon = desired_icon; + } + } else { + if (sdcard_last_icon != nullptr) { + lvgl::statusbar_icon_set_visibility(sdcard_icon_id, false); + sdcard_last_icon = nullptr; } - // TODO: Consider tracking how long the SD card has been in unknown status and then show error } } diff --git a/Tactility/Source/service/webserver/WebServerService.cpp b/Tactility/Source/service/webserver/WebServerService.cpp index 6aa3335f7..5a2f5523b 100644 --- a/Tactility/Source/service/webserver/WebServerService.cpp +++ b/Tactility/Source/service/webserver/WebServerService.cpp @@ -27,6 +27,7 @@ #include #include +#include #if TT_FEATURE_SCREENSHOT_ENABLED #include @@ -764,20 +765,24 @@ esp_err_t WebServerService::handleFsList(httpd_req_t* request) { std::ostringstream json; json << "{\"path\":\"" << norm << "\",\"entries\":["; - + struct FsIterContext { + std::ostringstream& json; + uint16_t count = 0; + }; + FsIterContext fs_iter_context { json }; // Special handling for root: show available mount points if (norm == "/") { - // Always show /data - json << "{\"name\":\"data\",\"type\":\"dir\",\"size\":0}"; - - // Show /sdcard if mounted - const auto sdcard_devices = hal::findDevices(hal::Device::Type::SdCard); - for (const auto& sdcard : sdcard_devices) { - if (sdcard->isMounted()) { - json << ",{\"name\":\"sdcard\",\"type\":\"dir\",\"size\":0}"; - break; + file_system_for_each(&fs_iter_context, [] (auto* fs, void* context) { + auto* fs_iter_context = static_cast(context); + char path[128]; + if (file_system_is_mounted(fs) && file_system_get_path(fs, path, sizeof(path)) == ERROR_NONE && strcmp(path, "/system") != 0) { + fs_iter_context->count++; + if (fs_iter_context->count != 1) fs_iter_context->json << ","; // add separator between json array entries + fs_iter_context->json << "{\"name\":\"" << path << "\",\"type\":\"dir\",\"size\":0}"; } - } + return true; + }); + json << "]}"; } else { std::vector entries; @@ -1160,34 +1165,38 @@ esp_err_t WebServerService::handleApiSysinfo(httpd_req_t* request) { json << "\"storage\":{"; uint64_t storage_total = 0, storage_free = 0; - // Data partition - json << "\"data\":{"; - if (esp_vfs_fat_info(file::MOUNT_POINT_DATA, &storage_total, &storage_free) == ESP_OK) { - json << "\"free\":" << storage_free << ","; - json << "\"total\":" << storage_total << ","; - json << "\"mounted\":true"; - } else { - json << "\"mounted\":false"; - } - json << "},"; - - // SD card - check all sdcard devices - json << "\"sdcard\":{"; - bool sdcard_found = false; - const auto sdcard_devices = hal::findDevices(hal::Device::Type::SdCard); - for (const auto& sdcard : sdcard_devices) { - if (sdcard->isMounted() && esp_vfs_fat_info(sdcard->getMountPath().c_str(), &storage_total, &storage_free) == ESP_OK) { - json << "\"free\":" << storage_free << ","; - json << "\"total\":" << storage_total << ","; - json << "\"mounted\":true"; - sdcard_found = true; - break; + struct FsIterContext { + std::ostringstream& json; + uint16_t count = 0; + }; + FsIterContext fs_iter_context { json }; + file_system_for_each(&fs_iter_context, [] (auto* fs, void* context) { + char mount_path[128] = ""; + if (file_system_get_path(fs, mount_path, sizeof(mount_path)) != ERROR_NONE) return true; + if (strcmp(mount_path, "/system") == 0) return true; // Hide system partition + + bool mounted = file_system_is_mounted(fs); + auto* fs_iter_context = static_cast(context); + auto& json_context = fs_iter_context->json; + std::string mount_path_cpp = mount_path; + + fs_iter_context->count++; + if (fs_iter_context->count != 1) json_context << ","; // add separator between json array entries + json_context << "\"" << mount_path_cpp.substr(1) << "\":{"; + + uint64_t storage_total = 0, storage_free = 0; + if (esp_vfs_fat_info(mount_path, &storage_total, &storage_free) == ESP_OK) { + json_context << "\"free\":" << storage_free << ","; + json_context << "\"total\":" << storage_total << ","; + } else { + json_context << "\"free\":0,"; + json_context << "\"total\":0,"; } - } - if (!sdcard_found) { - json << "\"mounted\":false"; - } - json << "}"; + + json_context << "\"mounted\":" << (mounted ? "true" : "false") << ""; + json_context << "}"; + return true; + }); json << "},"; // end storage @@ -1459,14 +1468,7 @@ esp_err_t WebServerService::handleApiScreenshot(httpd_req_t* request) { #if TT_FEATURE_SCREENSHOT_ENABLED // Determine save location: prefer SD card root if mounted, otherwise /data std::string save_path; - auto sdcard_devices = hal::findDevices(hal::Device::Type::SdCard); - for (const auto& sdcard : sdcard_devices) { - if (sdcard->isMounted()) { - save_path = sdcard->getMountPath(); - break; - } - } - if (save_path.empty()) { + if (!findFirstMountedSdCardPath(save_path)) { save_path = file::MOUNT_POINT_DATA; } @@ -1543,7 +1545,7 @@ esp_err_t WebServerService::handleFsTree(httpd_req_t* request) { std::ostringstream json; json << "{"; // Gather mount points - auto mounts = file::getMountPoints(); + auto mounts = file::getFileSystemDirents(); json << "\"mounts\": ["; bool firstMount = true; for (auto& m : mounts) { diff --git a/Tactility/Source/service/wifi/WifiBootSplashInit.cpp b/Tactility/Source/service/wifi/WifiBootSplashInit.cpp index 04bd0417a..86625684c 100644 --- a/Tactility/Source/service/wifi/WifiBootSplashInit.cpp +++ b/Tactility/Source/service/wifi/WifiBootSplashInit.cpp @@ -6,13 +6,14 @@ #include #include +#include +#include +#include #include #include #include #include #include -#include -#include namespace tt::service::wifi { @@ -118,18 +119,14 @@ static void importWifiApSettingsFromDir(const std::string& path) { void bootSplashInit() { getMainDispatcher().dispatch([] { // First import any provisioning files placed on the system data partition. - const std::string settings_path = file::getChildPath(file::MOUNT_POINT_DATA, "settings"); - importWifiApSettingsFromDir(settings_path); + const std::string data_settings_path = file::getChildPath(file::MOUNT_POINT_DATA, "settings"); + importWifiApSettingsFromDir(data_settings_path); // Then scan attached SD cards as before. - const auto sdcards = hal::findDevices(hal::Device::Type::SdCard); - for (auto& sdcard : sdcards) { - if (sdcard->isMounted()) { - const std::string settings_path = file::getChildPath(sdcard->getMountPath(), "settings"); - importWifiApSettingsFromDir(settings_path); - } else { - LOGGER.warn("Skipping unmounted SD card {}", sdcard->getMountPath()); - } + std::string sdcard_path; + if (findFirstMountedSdCardPath((sdcard_path))) { + const std::string sd_settings_path = file::getChildPath(sdcard_path, "settings"); + importWifiApSettingsFromDir(sd_settings_path); } }); } diff --git a/Tactility/Source/settings/BootSettings.cpp b/Tactility/Source/settings/BootSettings.cpp index 1b30ba36f..29cdbcf42 100644 --- a/Tactility/Source/settings/BootSettings.cpp +++ b/Tactility/Source/settings/BootSettings.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -18,9 +19,9 @@ constexpr auto* PROPERTIES_KEY_LAUNCHER_APP_ID = "launcherAppId"; constexpr auto* PROPERTIES_KEY_AUTO_START_APP_ID = "autoStartAppId"; static std::string getPropertiesFilePath() { - const auto sdcards = hal::findDevices(hal::Device::Type::SdCard); - for (auto& sdcard : sdcards) { - std::string path = std::format(PROPERTIES_FILE_FORMAT, sdcard->getMountPath()); + std::string sdcard_path; + if (findFirstMountedSdCardPath(sdcard_path)) { + std::string path = std::format(PROPERTIES_FILE_FORMAT, sdcard_path); if (file::isFile(path)) { return path; } diff --git a/TactilityC/Source/symbols/stl.cpp b/TactilityC/Source/symbols/stl.cpp index 56426854d..7bc96f60d 100644 --- a/TactilityC/Source/symbols/stl.cpp +++ b/TactilityC/Source/symbols/stl.cpp @@ -5,6 +5,19 @@ #include +#include + +extern "C" { + +// std::map / std::set red-black tree non-template helpers. +// We use the mangled names directly (same pattern as string.cpp) to avoid +// ambiguity from the overloaded const/non-const variants in stl_tree.h. +void* _ZSt18_Rb_tree_decrementPSt18_Rb_tree_node_base(void*); +void* _ZSt18_Rb_tree_incrementPSt18_Rb_tree_node_base(void*); +void _ZSt29_Rb_tree_insert_and_rebalancebPSt18_Rb_tree_node_baseS0_RS_(bool, void*, void*, void*); + +} + const esp_elfsym stl_symbols[] = { // Note: You have to use the mangled names here { "_ZSt17__throw_bad_allocv", (void*)&(std::__throw_bad_alloc) }, @@ -12,7 +25,11 @@ const esp_elfsym stl_symbols[] = { { "_ZSt25__throw_bad_function_callv", (void*)&(std::__throw_bad_function_call) }, { "_ZSt20__throw_length_errorPKc", (void*)&(std::__throw_length_error) }, { "_ZSt19__throw_logic_errorPKc", (void*)&std::__throw_logic_error }, - // { "", (void*)&(std::) }, + { "_ZSt24__throw_out_of_range_fmtPKcz", (void*)&std::__throw_out_of_range_fmt }, + // std::map / std::set (red-black tree internals) + DEFINE_MODULE_SYMBOL(_ZSt18_Rb_tree_decrementPSt18_Rb_tree_node_base), + DEFINE_MODULE_SYMBOL(_ZSt18_Rb_tree_incrementPSt18_Rb_tree_node_base), + DEFINE_MODULE_SYMBOL(_ZSt29_Rb_tree_insert_and_rebalancebPSt18_Rb_tree_node_baseS0_RS_), // delimiter ESP_ELFSYM_END }; diff --git a/TactilityC/Source/tt_init.cpp b/TactilityC/Source/tt_init.cpp index 3dc5079bc..04072a2f8 100644 --- a/TactilityC/Source/tt_init.cpp +++ b/TactilityC/Source/tt_init.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -221,6 +223,7 @@ const esp_elfsym main_symbols[] { ESP_ELFSYM_EXPORT(strtod), ESP_ELFSYM_EXPORT(strrchr), ESP_ELFSYM_EXPORT(strtol), + ESP_ELFSYM_EXPORT(strtoul), ESP_ELFSYM_EXPORT(strcspn), ESP_ELFSYM_EXPORT(strncat), ESP_ELFSYM_EXPORT(strpbrk), @@ -393,6 +396,7 @@ const esp_elfsym main_symbols[] { ESP_ELFSYM_EXPORT(gpio_config), ESP_ELFSYM_EXPORT(gpio_get_level), ESP_ELFSYM_EXPORT(gpio_set_level), + ESP_ELFSYM_EXPORT(gpio_reset_pin), // driver/i2s_common.h ESP_ELFSYM_EXPORT(i2s_new_channel), ESP_ELFSYM_EXPORT(i2s_del_channel), @@ -409,8 +413,30 @@ const esp_elfsym main_symbols[] { ESP_ELFSYM_EXPORT(i2s_channel_reconfig_std_clock), ESP_ELFSYM_EXPORT(i2s_channel_reconfig_std_slot), ESP_ELFSYM_EXPORT(i2s_channel_reconfig_std_gpio), + // miniz.h + ESP_ELFSYM_EXPORT(tinfl_decompress), + ESP_ELFSYM_EXPORT(tinfl_decompress_mem_to_callback), + ESP_ELFSYM_EXPORT(tinfl_decompress_mem_to_mem), + // ledc + ESP_ELFSYM_EXPORT(ledc_update_duty), + ESP_ELFSYM_EXPORT(ledc_set_freq), + ESP_ELFSYM_EXPORT(ledc_channel_config), + ESP_ELFSYM_EXPORT(ledc_set_duty), + ESP_ELFSYM_EXPORT(ledc_set_fade), + ESP_ELFSYM_EXPORT(ledc_set_fade_with_step), + ESP_ELFSYM_EXPORT(ledc_set_fade_with_time), + ESP_ELFSYM_EXPORT(ledc_set_fade_step_and_start), + ESP_ELFSYM_EXPORT(ledc_set_fade_time_and_start), + ESP_ELFSYM_EXPORT(ledc_set_pin), + ESP_ELFSYM_EXPORT(ledc_timer_config), + ESP_ELFSYM_EXPORT(ledc_timer_pause), + ESP_ELFSYM_EXPORT(ledc_timer_resume), + ESP_ELFSYM_EXPORT(ledc_timer_rst), // esp_heap_caps.h ESP_ELFSYM_EXPORT(heap_caps_get_total_size), + ESP_ELFSYM_EXPORT(heap_caps_get_allocated_size), + ESP_ELFSYM_EXPORT(heap_caps_get_free_size), + ESP_ELFSYM_EXPORT(heap_caps_get_largest_free_block), // delimiter ESP_ELFSYM_END }; diff --git a/TactilityKernel/include/tactility/drivers/gpio.h b/TactilityKernel/include/tactility/drivers/gpio.h index 835ff5736..2250725fc 100644 --- a/TactilityKernel/include/tactility/drivers/gpio.h +++ b/TactilityKernel/include/tactility/drivers/gpio.h @@ -7,10 +7,9 @@ extern "C" { #endif #include -#include #include -#define GPIO_FLAGS_MASK 0x1f +#define GPIO_FLAGS_MASK 0xff #define GPIO_PIN_NONE -1 @@ -27,6 +26,7 @@ extern "C" { #define GPIO_FLAG_INTERRUPT_BITMASK (0b111 << 5) // 3 bits to hold the values [0, 5] #define GPIO_FLAG_INTERRUPT_FROM_OPTIONS(options) (gpio_int_type_t)((options & GPIO_FLAG_INTERRUPT_BITMASK) >> 5) #define GPIO_FLAG_INTERRUPT_TO_OPTIONS(options, interrupt) (options | (interrupt << 5)) +#define GPIO_FLAG_HIGH_IMPEDANCE (1 << 8) typedef enum { GPIO_INTERRUPT_DISABLE = 0, diff --git a/TactilityKernel/include/tactility/drivers/gpio_controller.h b/TactilityKernel/include/tactility/drivers/gpio_controller.h index 2251e2f07..44365a968 100644 --- a/TactilityKernel/include/tactility/drivers/gpio_controller.h +++ b/TactilityKernel/include/tactility/drivers/gpio_controller.h @@ -52,6 +52,36 @@ struct GpioControllerApi { * @return ERROR_NONE if successful */ error_t (*get_native_pin_number)(struct GpioDescriptor* descriptor, void* pin_number); + + /** + * @brief Adds a callback to be called when a GPIO interrupt occurs. + * @param[in] descriptor the pin descriptor + * @param[in] callback the callback function + * @param[in] arg the argument to pass to the callback + * @return ERROR_NONE if successful, ERROR_NOT_SUPPORTED if not implemented + */ + error_t (*add_callback)(struct GpioDescriptor* descriptor, void (*callback)(void*), void* arg); + + /** + * @brief Removes a callback from a GPIO interrupt. + * @param[in] descriptor the pin descriptor + * @return ERROR_NONE if successful, ERROR_NOT_SUPPORTED if not implemented + */ + error_t (*remove_callback)(struct GpioDescriptor* descriptor); + + /** + * @brief Enables the interrupt for a GPIO pin. + * @param[in] descriptor the pin descriptor + * @return ERROR_NONE if successful, ERROR_NOT_SUPPORTED if not implemented + */ + error_t (*enable_interrupt)(struct GpioDescriptor* descriptor); + + /** + * @brief Disables the interrupt for a GPIO pin. + * @param[in] descriptor the pin descriptor + * @return ERROR_NONE if successful, ERROR_NOT_SUPPORTED if not implemented + */ + error_t (*disable_interrupt)(struct GpioDescriptor* descriptor); }; struct GpioDescriptor* gpio_descriptor_acquire( @@ -118,6 +148,36 @@ error_t gpio_descriptor_get_flags(struct GpioDescriptor* descriptor, gpio_flags_ */ error_t gpio_descriptor_get_native_pin_number(struct GpioDescriptor* descriptor, void* pin_number); +/** + * @brief Adds a callback to be called when a GPIO interrupt occurs. + * @param[in] descriptor the pin descriptor + * @param[in] callback the callback function + * @param[in] arg the argument to pass to the callback + * @return ERROR_NONE if successful, ERROR_NOT_SUPPORTED if not implemented + */ +error_t gpio_descriptor_add_callback(struct GpioDescriptor* descriptor, void (*callback)(void*), void* arg); + +/** + * @brief Removes a callback from a GPIO interrupt. + * @param[in] descriptor the pin descriptor + * @return ERROR_NONE if successful, ERROR_NOT_SUPPORTED if not implemented + */ +error_t gpio_descriptor_remove_callback(struct GpioDescriptor* descriptor); + +/** + * @brief Enables the interrupt for a GPIO pin. + * @param[in] descriptor the pin descriptor + * @return ERROR_NONE if successful, ERROR_NOT_SUPPORTED if not implemented + */ +error_t gpio_descriptor_enable_interrupt(struct GpioDescriptor* descriptor); + +/** + * @brief Disables the interrupt for a GPIO pin. + * @param[in] descriptor the pin descriptor + * @return ERROR_NONE if successful, ERROR_NOT_SUPPORTED if not implemented + */ +error_t gpio_descriptor_disable_interrupt(struct GpioDescriptor* descriptor); + /** * @brief Gets the number of pins supported by the controller. * @param[in] device the GPIO controller device @@ -141,6 +201,15 @@ error_t gpio_controller_init_descriptors(struct Device* device, uint32_t pin_cou */ error_t gpio_controller_deinit_descriptors(struct Device* device); +/** + * Unlike other drivers, a GPIO controller's internal data is created and set by gpio_controller_init_descriptors() + * This means that the specific controller implementation cannot set the device's driver data, as it's already set by the GPIO controller base coded + * When calling init descriptors, the caller can pass a controller_context, which is an optional pointer that holds the implementation's internal data. + * @param device the GPIO controller device + * @return the context void pointer + */ +void* gpio_controller_get_controller_context(struct Device* device); + extern const struct DeviceType GPIO_CONTROLLER_TYPE; #ifdef __cplusplus diff --git a/TactilityKernel/include/tactility/drivers/gpio_descriptor.h b/TactilityKernel/include/tactility/drivers/gpio_descriptor.h index 383961aad..80d4d52b3 100644 --- a/TactilityKernel/include/tactility/drivers/gpio_descriptor.h +++ b/TactilityKernel/include/tactility/drivers/gpio_descriptor.h @@ -11,6 +11,11 @@ struct GpioDescriptor { gpio_pin_t pin; /** @brief Current owner */ enum GpioOwnerType owner_type; - /** @brief Implementation-specific context (e.g. from esp32 controller internally) */ + /** + * @brief Implementation-specific context (e.g. from esp32 controller internally) + * Unlike other drivers, a GPIO controller's internal data is created and set by gpio_controller_init_descriptors() + * This means that the specific controller implementation cannot set the device's driver data, as it's already set by the GPIO controller base coded. + * When calling init descriptors, the caller can pass a controller_context, which is an optional pointer that holds the implementation's internal data. + */ void* controller_context; }; diff --git a/TactilityKernel/include/tactility/drivers/wifi.h b/TactilityKernel/include/tactility/drivers/wifi.h new file mode 100644 index 000000000..dafc3e84b --- /dev/null +++ b/TactilityKernel/include/tactility/drivers/wifi.h @@ -0,0 +1,205 @@ +#pragma once + +#include +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct Device; + +enum WifiAuthenticationType { + WIFI_AUTHENTICATION_TYPE_OPEN = 0, + WIFI_AUTHENTICATION_TYPE_WEP, + WIFI_AUTHENTICATION_TYPE_WPA_PSK, + WIFI_AUTHENTICATION_TYPE_WPA2_PSK, + WIFI_AUTHENTICATION_TYPE_WPA_WPA2_PSK, + WIFI_AUTHENTICATION_TYPE_WPA2_ENTERPRISE, + WIFI_AUTHENTICATION_TYPE_WPA3_PSK, + WIFI_AUTHENTICATION_TYPE_WPA2_WPA3_PSK, + WIFI_AUTHENTICATION_TYPE_WAPI_PSK, + WIFI_AUTHENTICATION_TYPE_OWE, + WIFI_AUTHENTICATION_TYPE_WPA3_ENT_192, + WIFI_AUTHENTICATION_TYPE_WPA3_EXT_PSK, + WIFI_AUTHENTICATION_TYPE_WPA3_EXT_PSK_MIXED_MODE, + WIFI_AUTHENTICATION_TYPE_MAX +}; + +struct WifiApRecord { + char ssid[33]; // 32 bytes + null terminator + int8_t rssi; + int32_t channel; + enum WifiAuthenticationType authentication_type; +}; + +enum WifiRadioState { + WIFI_RADIO_STATE_OFF, + WIFI_RADIO_STATE_ON_PENDING, + WIFI_RADIO_STATE_ON, + WIFI_RADIO_STATE_OFF_PENDING, +}; + +enum WifiStationState { + WIFI_STATION_STATE_DISCONNECTED, + WIFI_STATION_STATE_CONNECTION_PENDING, + WIFI_STATION_STATE_CONNECTED +}; + +enum WifiAccessPointState { + WIFI_ACCESS_POINT_STATE_STARTED, + WIFI_ACCESS_POINT_STATE_STOPPED, +}; + +enum WifiEventType { + /** Radio state changed */ + WIFI_EVENT_TYPE_RADIO_STATE_CHANGED, + /** WifiStationState changed */ + WIFI_EVENT_TYPE_STATION_STATE_CHANGED, + /** WifiAccessPointState changed */ + WIFI_EVENT_TYPE_STATION_CONNECTION_RESULT, + /** WifiAccessPointState changed */ + WIFI_EVENT_TYPE_ACCESS_POINT_STATE_CHANGED, + /** Started scanning for access points */ + WIFI_EVENT_TYPE_SCAN_STARTED, + /** Finished scanning for access points */ + WIFI_EVENT_TYPE_SCAN_FINISHED, +}; + +enum WifiStationConnectionError { + WIFI_STATION_CONNECTION_ERROR_NONE, + /** Wrong password */ + WIFI_STATION_CONNECTION_ERROR_WRONG_CREDENTIALS, + /** Failed to connect in a timely manner */ + WIFI_STATION_CONNECTION_ERROR_TIMEOUT, + /** SSID not found */ + WIFI_STATION_CONNECTION_ERROR_TARGET_NOT_FOUND, +}; + +struct WifiEvent { + enum WifiEventType type; + union { + enum WifiRadioState radio_state; + enum WifiStationState station_state; + enum WifiAccessPointState access_point_state; + enum WifiStationConnectionError connection_error; + }; +}; + +typedef void (*WifiEventCallback)(struct Device* device, void* callback_context, struct WifiEvent event); + +struct WifiApi { + /** + * Get the radio state of the device. + * @param[in] device the wifi device + * @param[out] state the radio state + * @return ERROR_NONE on success + */ + error_t (*get_radio_state)(struct Device* device, enum WifiRadioState* state); + + /** + * Get the station state of the device. + * @param[in] device the wifi device + * @param[out] state the station state + * @return ERROR_NONE on success + */ + error_t (*get_station_state)(struct Device* device, enum WifiStationState* state); + + /** + * Get the access point state of the device. + * @param[in] device the wifi device + * @param[out] state the access point state + * @return ERROR_NONE on success + */ + error_t (*get_access_point_state)(struct Device* device, enum WifiAccessPointState* state); + + /** + * Check if the device is currently scanning for access points. + * @param[in] device the wifi device + * @return true when scanning + */ + bool (*is_scanning)(struct Device* device); + + /** + * Start a scan for access points. + * @param[in] device the wifi device + * @return ERROR_NONE on success + */ + error_t (*scan)(struct Device* device); + + /** + * Get the scan results of the device. + * @param[in] device the wifi device + * @param[out] results the buffer to store the scan results + * @param[in, out] num_results the number of scan results: it's first used as input to determine the size of the buffer, and then as output to get the actual number of results + * @return ERROR_NONE on success + */ + error_t (*get_scan_results)(struct Device* device, struct WifiApRecord* results, size_t* num_results); + + /** + * Get the IPv4 address of the device. + * @param[in] device the device + * @param[out] ipv4 the buffer to store the IPv4 address (must be at least 16 bytes, will be null-terminated) + * @return ERROR_NONE on success + */ + error_t (*station_get_ipv4_address)(struct Device* device, char* ipv4); + + /** + * Get the SSID of the access point the device is currently connected to. + * @param[in] device the wifi device + * @param[out] ssid the buffer to store the SSID (must be at least 33 bytes, will be null-terminated) + * @return ERROR_NONE on success + */ + error_t (*station_get_target_ssid)(struct Device* device, char* ssid); + + /** + * Connect to an access point. + * @param[in] device the wifi device + * @param[in] ssid the SSID of the access point + * @param[in] password the password of the access point + * @param[in] channel the Wi-Fi channel to connect to (0 means "any" / no preference) + * @return ERROR_NONE on success + */ + error_t (*station_connect)(struct Device* device, const char* ssid, const char* password, int32_t channel); + + /** + * Disconnect from the current access point. + * @param[in] device the wifi device + * @return ERROR_NONE on success + */ + error_t (*station_disconnect)(struct Device* device); + + /** + * Get the RSSI of the current access point. + * @param[in] device the wifi device + * @param[out] rssi the buffer to store the RSSI + * @return ERROR_NONE on success + */ + error_t (*station_get_rssi)(struct Device* device, int32_t* rssi); + + /** + * Add a WifiEvent callback. + * @param[in] device the wifi device + * @param[in] callback_context the context to pass to the callback + * @param[in] callback the callback function + * @return ERROR_NONE on success + */ + error_t (*add_event_callback)(struct Device* device, void* callback_context, WifiEventCallback callback); + + /** + * Remove a WifiEvent callback. + * @param[in] device the wifi device + * @param[in] callback the callback function + * @return ERROR_NONE on success + */ + error_t (*remove_event_callback)(struct Device* device, WifiEventCallback callback); +}; + +extern const struct DeviceType WIFI_TYPE; + +#ifdef __cplusplus +} +#endif diff --git a/TactilityKernel/include/tactility/filesystem/file_system.h b/TactilityKernel/include/tactility/filesystem/file_system.h new file mode 100644 index 000000000..40a6bec75 --- /dev/null +++ b/TactilityKernel/include/tactility/filesystem/file_system.h @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct FileSystem; + +/** + * @brief File system API. + * + * Provides a set of function pointers to interact with a specific file system implementation. + */ +struct FileSystemApi { + /** + * @brief Mounts the file system. + * @param[in] data file system private data + * @return ERROR_NONE on success, or an error code + */ + error_t (*mount)(void* data); + + /** + * @brief Unmounts the file system. + * @param[in] data file system private data + * @return ERROR_NONE on success, or an error code + */ + error_t (*unmount)(void* data); + + /** + * @brief Checks if the file system is mounted. + * @param[in] data file system private data + * @return true if mounted, false otherwise + */ + bool (*is_mounted)(void* data); + + /** + * @brief Gets the mount path. + * @param[in] data file system private data + * @param[out] out_path buffer to store the path + * @param[in] out_path_size size of the output buffer + * @return ERROR_NONE on success, or an error code + */ + error_t (*get_path)(void* data, char* out_path, size_t out_path_size); +}; + +/** + * @brief Registers a new file system. + * @param[in] fs_api the file system API implementation + * @param[in] data private data for the file system + * @return the registered FileSystem object + */ +struct FileSystem* file_system_add(const struct FileSystemApi* fs_api, void* data); + +/** + * @brief Removes a registered file system. + * @note The file system must be unmounted before removal. + * @param[in] fs the FileSystem object to remove + */ +void file_system_remove(struct FileSystem* fs); + +/** + * @brief Iterates over all registered file systems. + * @param[in] callback_context context passed to the callback + * @param[in] callback function called for each file system. Return true to continue, false to stop. + */ +void file_system_for_each(void* callback_context, bool (*callback)(struct FileSystem* fs, void* context)); + +/** + * @brief Mounts the file system. + * @param[in] fs the FileSystem object + * @return ERROR_NONE on success, or an error code + */ +error_t file_system_mount(struct FileSystem* fs); + +/** + * @warning Unmounting can fail (e.g. when the device is busy), so you might need to retry it. + * @brief Unmounts the file system. + * @param[in] fs the FileSystem object + * @return ERROR_NONE on success, or an error code + */ +error_t file_system_unmount(struct FileSystem* fs); + +/** + * @brief Checks if the file system is mounted. + * @param[in] fs the FileSystem object + * @return true if mounted, false otherwise + */ +bool file_system_is_mounted(struct FileSystem* fs); + +/** + * @brief Gets the mount path. + * @param[in] fs the FileSystem object + * @param[out] out_path buffer to store the path + * @param[in] out_path_size size of the output buffer + * @return ERROR_NONE on success, or an error code + */ +error_t file_system_get_path(struct FileSystem* fs, char* out_path, size_t out_path_size); + +#ifdef __cplusplus +} +#endif diff --git a/TactilityKernel/include/tactility/module.h b/TactilityKernel/include/tactility/module.h index 345a762c4..910f29b45 100644 --- a/TactilityKernel/include/tactility/module.h +++ b/TactilityKernel/include/tactility/module.h @@ -118,6 +118,7 @@ error_t module_construct_add_start(struct Module* module); /** * @brief Check if the module is started. + * Can be used when module isn't constructed yet. * @param module module to check * @return true if the module is started, false otherwise */ diff --git a/TactilityKernel/source/drivers/gpio_controller.cpp b/TactilityKernel/source/drivers/gpio_controller.cpp index 336183a15..922aad6ac 100644 --- a/TactilityKernel/source/drivers/gpio_controller.cpp +++ b/TactilityKernel/source/drivers/gpio_controller.cpp @@ -14,22 +14,25 @@ extern "C" { struct GpioControllerData { - struct Mutex mutex {}; + Mutex mutex {}; uint32_t pin_count; - struct GpioDescriptor* descriptors = nullptr; + GpioDescriptor* descriptors = nullptr; + void* controller_context; - explicit GpioControllerData(uint32_t pin_count) : pin_count(pin_count) { + explicit GpioControllerData( + uint32_t pin_count, void* controller_context + ) : pin_count(pin_count), controller_context(controller_context) { mutex_construct(&mutex); } - error_t init_descriptors(Device* device, void* controller_context) { - descriptors = (struct GpioDescriptor*)calloc(pin_count, sizeof(struct GpioDescriptor)); + error_t init_descriptors(Device* device) { + descriptors = static_cast(calloc(pin_count, sizeof(GpioDescriptor))); if (!descriptors) return ERROR_OUT_OF_MEMORY; for (uint32_t i = 0; i < pin_count; ++i) { descriptors[i].controller = device; - descriptors[i].pin = (gpio_pin_t)i; + descriptors[i].pin = static_cast(i); descriptors[i].owner_type = GPIO_OWNER_NONE; - descriptors[i].controller_context = controller_context; + descriptors[i].controller_context = this->controller_context; } return ERROR_NONE; } @@ -42,14 +45,14 @@ struct GpioControllerData { } }; -struct GpioDescriptor* gpio_descriptor_acquire( - struct Device* controller, +GpioDescriptor* gpio_descriptor_acquire( + Device* controller, gpio_pin_t pin_number, - enum GpioOwnerType owner + GpioOwnerType owner ) { check(owner != GPIO_OWNER_NONE); - auto* data = (struct GpioControllerData*)device_get_driver_data(controller); + auto* data = static_cast(device_get_driver_data(controller)); mutex_lock(&data->mutex); if (pin_number >= data->pin_count) { @@ -57,7 +60,7 @@ struct GpioDescriptor* gpio_descriptor_acquire( return nullptr; } - struct GpioDescriptor* desc = &data->descriptors[pin_number]; + GpioDescriptor* desc = &data->descriptors[pin_number]; if (desc->owner_type != GPIO_OWNER_NONE) { mutex_unlock(&data->mutex); return nullptr; @@ -69,22 +72,27 @@ struct GpioDescriptor* gpio_descriptor_acquire( return desc; } -error_t gpio_descriptor_release(struct GpioDescriptor* descriptor) { +error_t gpio_descriptor_release(GpioDescriptor* descriptor) { + auto* data = static_cast(device_get_driver_data(descriptor->controller)); + mutex_lock(&data->mutex); descriptor->owner_type = GPIO_OWNER_NONE; + mutex_unlock(&data->mutex); return ERROR_NONE; } -error_t gpio_controller_get_pin_count(struct Device* device, uint32_t* count) { - auto* data = (struct GpioControllerData*)device_get_driver_data(device); +error_t gpio_controller_get_pin_count(Device* device, uint32_t* count) { + auto* data = static_cast(device_get_driver_data(device)); + mutex_lock(&data->mutex); *count = data->pin_count; + mutex_unlock(&data->mutex); return ERROR_NONE; } -error_t gpio_controller_init_descriptors(struct Device* device, uint32_t pin_count, void* controller_context) { - auto* data = new(std::nothrow) GpioControllerData(pin_count); +error_t gpio_controller_init_descriptors(Device* device, uint32_t pin_count, void* controller_context) { + auto* data = new(std::nothrow) GpioControllerData(pin_count, controller_context); if (!data) return ERROR_OUT_OF_MEMORY; - if (data->init_descriptors(device, controller_context) != ERROR_NONE) { + if (data->init_descriptors(device) != ERROR_NONE) { delete data; return ERROR_OUT_OF_MEMORY; } @@ -93,49 +101,82 @@ error_t gpio_controller_init_descriptors(struct Device* device, uint32_t pin_cou return ERROR_NONE; } -error_t gpio_controller_deinit_descriptors(struct Device* device) { +error_t gpio_controller_deinit_descriptors(Device* device) { auto* data = static_cast(device_get_driver_data(device)); - delete data; device_set_driver_data(device, nullptr); + delete data; return ERROR_NONE; } -error_t gpio_descriptor_set_level(struct GpioDescriptor* descriptor, bool high) { +void* gpio_controller_get_controller_context(Device* device) { + auto* data = static_cast(device_get_driver_data(device)); + return data->controller_context; +} + +error_t gpio_descriptor_set_level(GpioDescriptor* descriptor, bool high) { const auto* driver = device_get_driver(descriptor->controller); return GPIO_INTERNAL_API(driver)->set_level(descriptor, high); } -error_t gpio_descriptor_get_level(struct GpioDescriptor* descriptor, bool* high) { +error_t gpio_descriptor_get_level(GpioDescriptor* descriptor, bool* high) { const auto* driver = device_get_driver(descriptor->controller); return GPIO_INTERNAL_API(driver)->get_level(descriptor, high); } -error_t gpio_descriptor_set_flags(struct GpioDescriptor* descriptor, gpio_flags_t flags) { +error_t gpio_descriptor_set_flags(GpioDescriptor* descriptor, gpio_flags_t flags) { const auto* driver = device_get_driver(descriptor->controller); return GPIO_INTERNAL_API(driver)->set_flags(descriptor, flags); } -error_t gpio_descriptor_get_flags(struct GpioDescriptor* descriptor, gpio_flags_t* flags) { +error_t gpio_descriptor_get_flags(GpioDescriptor* descriptor, gpio_flags_t* flags) { const auto* driver = device_get_driver(descriptor->controller); return GPIO_INTERNAL_API(driver)->get_flags(descriptor, flags); } -error_t gpio_descriptor_get_pin_number(struct GpioDescriptor* descriptor, gpio_pin_t* pin) { +error_t gpio_descriptor_get_pin_number(GpioDescriptor* descriptor, gpio_pin_t* pin) { *pin = descriptor->pin; return ERROR_NONE; } -error_t gpio_descriptor_get_native_pin_number(struct GpioDescriptor* descriptor, void* pin_number) { +error_t gpio_descriptor_get_native_pin_number(GpioDescriptor* descriptor, void* pin_number) { const auto* driver = device_get_driver(descriptor->controller); return GPIO_INTERNAL_API(driver)->get_native_pin_number(descriptor, pin_number); } -error_t gpio_descriptor_get_owner_type(struct GpioDescriptor* descriptor, GpioOwnerType* owner_type) { +error_t gpio_descriptor_add_callback(GpioDescriptor* descriptor, void (*callback)(void*), void* arg) { + const auto* driver = device_get_driver(descriptor->controller); + auto* api = GPIO_INTERNAL_API(driver); + if (!api->add_callback) return ERROR_NOT_SUPPORTED; + return api->add_callback(descriptor, callback, arg); +} + +error_t gpio_descriptor_remove_callback(GpioDescriptor* descriptor) { + const auto* driver = device_get_driver(descriptor->controller); + auto* api = GPIO_INTERNAL_API(driver); + if (!api->remove_callback) return ERROR_NOT_SUPPORTED; + return api->remove_callback(descriptor); +} + +error_t gpio_descriptor_enable_interrupt(GpioDescriptor* descriptor) { + const auto* driver = device_get_driver(descriptor->controller); + auto* api = GPIO_INTERNAL_API(driver); + if (!api->enable_interrupt) return ERROR_NOT_SUPPORTED; + return api->enable_interrupt(descriptor); +} + +error_t gpio_descriptor_disable_interrupt(GpioDescriptor* descriptor) { + const auto* driver = device_get_driver(descriptor->controller); + auto* api = GPIO_INTERNAL_API(driver); + if (!api->disable_interrupt) return ERROR_NOT_SUPPORTED; + return api->disable_interrupt(descriptor); +} + +error_t gpio_descriptor_get_owner_type(GpioDescriptor* descriptor, GpioOwnerType* owner_type) { *owner_type = descriptor->owner_type; return ERROR_NONE; } -const struct DeviceType GPIO_CONTROLLER_TYPE { +const DeviceType GPIO_CONTROLLER_TYPE { .name = "gpio-controller" }; diff --git a/TactilityKernel/source/filesystem/file_system.cpp b/TactilityKernel/source/filesystem/file_system.cpp new file mode 100644 index 000000000..d22dd31a7 --- /dev/null +++ b/TactilityKernel/source/filesystem/file_system.cpp @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include +#include + +// Define the internal FileSystem structure +struct FileSystem { + const FileSystemApi* api; + void* data; +}; + +// Global list of file systems and its mutex +struct FileSystemsLedger { + std::vector file_systems; + // Use recursive mutex so that file_system_for_each() can lock within the callback + RecursiveMutex mutex {}; + + FileSystemsLedger() { recursive_mutex_construct(&mutex); } + ~FileSystemsLedger() { recursive_mutex_destruct(&mutex); } + + void lock() { recursive_mutex_lock(&mutex); } + bool is_locked() { return recursive_mutex_is_locked(&mutex); } + void unlock() { recursive_mutex_unlock(&mutex); } +}; + +static FileSystemsLedger& get_ledger() { + static FileSystemsLedger ledger; + return ledger; +} + +extern "C" { + +FileSystem* file_system_add(const FileSystemApi* fs_api, void* data) { + auto& ledger = get_ledger(); + check(!ledger.is_locked()); // ensure file_system_for_each() doesn't add a filesystem while iterating + ledger.lock(); + + auto* fs = new(std::nothrow) struct FileSystem(); + check(fs != nullptr); + fs->api = fs_api; + fs->data = data; + ledger.file_systems.push_back(fs); + + ledger.unlock(); + return fs; +} + +void file_system_remove(FileSystem* fs) { + check(!file_system_is_mounted(fs)); + auto& ledger = get_ledger(); + check(!ledger.is_locked()); // ensure file_system_for_each() doesn't remove a filesystem while iterating + ledger.lock(); + + auto it = std::ranges::find(ledger.file_systems, fs); + if (it != ledger.file_systems.end()) { + ledger.file_systems.erase(it); + delete fs; + } + + ledger.unlock(); +} + +void file_system_for_each(void* callback_context, bool (*callback)(FileSystem* fs, void* context)) { + auto& ledger = get_ledger(); + ledger.lock(); + for (auto* fs : ledger.file_systems) { + if (!callback(fs, callback_context)) break; + } + ledger.unlock(); +} + +error_t file_system_mount(FileSystem* fs) { + // Assuming 'device' is accessible or passed via a different mechanism + // as it's required by the FileSystemApi signatures. + return fs->api->mount(fs->data); +} + +error_t file_system_unmount(FileSystem* fs) { + return fs->api->unmount(fs->data); +} + +bool file_system_is_mounted(FileSystem* fs) { + return fs->api->is_mounted(fs->data); +} + +error_t file_system_get_path(FileSystem* fs, char* out_path, size_t out_path_size) { + return fs->api->get_path(fs->data, out_path, out_path_size); +} + +} diff --git a/TactilityKernel/source/kernel_symbols.c b/TactilityKernel/source/kernel_symbols.c index 094271247..7ed2a8eb4 100644 --- a/TactilityKernel/source/kernel_symbols.c +++ b/TactilityKernel/source/kernel_symbols.c @@ -1,3 +1,7 @@ +#include +#include +#include +#include #include #include #include @@ -6,14 +10,14 @@ #include #include #include -#include -#include -#include -#include #include -#include +#include #include +#ifndef ESP_PLATFORM +#include +#endif + /** * This file is a C file instead of C++, so we can import all headers as C code. * The intent is to catch errors that only show up when compiling as C and not as C++. @@ -153,6 +157,11 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = { DEFINE_MODULE_SYMBOL(timer_set_callback_priority), // error DEFINE_MODULE_SYMBOL(error_to_string), + // file system + DEFINE_MODULE_SYMBOL(file_system_mount), + DEFINE_MODULE_SYMBOL(file_system_unmount), + DEFINE_MODULE_SYMBOL(file_system_is_mounted), + DEFINE_MODULE_SYMBOL(file_system_get_path), // log #ifndef ESP_PLATFORM DEFINE_MODULE_SYMBOL(log_generic), diff --git a/TactilityKernel/source/module.cpp b/TactilityKernel/source/module.cpp index 51200f433..5275709dd 100644 --- a/TactilityKernel/source/module.cpp +++ b/TactilityKernel/source/module.cpp @@ -12,8 +12,8 @@ struct ModuleInternal { }; struct ModuleLedger { - std::vector modules; - struct Mutex mutex {}; + std::vector modules; + Mutex mutex {}; ModuleLedger() { mutex_construct(&mutex); } ~ModuleLedger() { mutex_destruct(&mutex); } @@ -23,36 +23,37 @@ static ModuleLedger ledger; extern "C" { -error_t module_construct(struct Module* module) { +error_t module_construct(Module* module) { module->internal = new (std::nothrow) ModuleInternal(); if (module->internal == nullptr) return ERROR_OUT_OF_MEMORY; return ERROR_NONE; } -error_t module_destruct(struct Module* module) { +error_t module_destruct(Module* module) { delete static_cast(module->internal); module->internal = nullptr; return ERROR_NONE; } -error_t module_add(struct Module* module) { +error_t module_add(Module* module) { mutex_lock(&ledger.mutex); ledger.modules.push_back(module); mutex_unlock(&ledger.mutex); return ERROR_NONE; } -error_t module_remove(struct Module* module) { +error_t module_remove(Module* module) { mutex_lock(&ledger.mutex); ledger.modules.erase(std::remove(ledger.modules.begin(), ledger.modules.end(), module), ledger.modules.end()); mutex_unlock(&ledger.mutex); return ERROR_NONE; } -error_t module_start(struct Module* module) { +error_t module_start(Module* module) { LOG_I(TAG, "start %s", module->name); - auto* internal = static_cast(module->internal); + auto* internal = module->internal; + if (internal == nullptr) return ERROR_INVALID_STATE; if (internal->started) return ERROR_NONE; error_t error = module->start(); @@ -61,13 +62,15 @@ error_t module_start(struct Module* module) { } bool module_is_started(struct Module* module) { - return static_cast(module->internal)->started; + auto* internal = module->internal; + return internal != nullptr && internal->started; } error_t module_stop(struct Module* module) { LOG_I(TAG, "stop %s", module->name); - auto* internal = static_cast(module->internal); + auto* internal = module->internal; + if (internal == nullptr) return ERROR_INVALID_STATE; if (!internal->started) return ERROR_NONE; error_t error = module->stop();