diff --git a/Platforms/platform-esp32/source/drivers/usb/esp32_usb_cdc_device.cpp b/Platforms/platform-esp32/source/drivers/usb/esp32_usb_cdc_device.cpp index f4ba8b109..b83548161 100644 --- a/Platforms/platform-esp32/source/drivers/usb/esp32_usb_cdc_device.cpp +++ b/Platforms/platform-esp32/source/drivers/usb/esp32_usb_cdc_device.cpp @@ -9,7 +9,7 @@ #include #include -#include +#include #include #include @@ -112,15 +112,14 @@ static error_t cdc_device_build_contribution(struct Device* device, struct Devic static error_t cdc_device_start_console(struct Device* device) { (void)device; const tinyusb_config_cdcacm_t acm_cfg = { - .usb_dev = TINYUSB_USBDEV_0, .cdc_port = TINYUSB_CDC_ACM_0, .callback_rx = nullptr, .callback_rx_wanted_char = nullptr, .callback_line_state_changed = nullptr, .callback_line_coding_changed = nullptr, }; - if (tusb_cdc_acm_init(&acm_cfg) != ESP_OK) { - LOG_E(TAG, "tusb_cdc_acm_init failed - CDC console unavailable"); + if (tinyusb_cdcacm_init(&acm_cfg) != ESP_OK) { + LOG_E(TAG, "tinyusb_cdcacm_init failed - CDC console unavailable"); return ERROR_RESOURCE; } previous_vprintf = esp_log_set_vprintf(cdc_mirroring_vprintf); @@ -135,7 +134,7 @@ static error_t cdc_device_stop_console(struct Device* device) { } esp_log_set_vprintf(previous_vprintf ? previous_vprintf : vprintf); previous_vprintf = nullptr; - tusb_cdc_acm_deinit(TINYUSB_CDC_ACM_0); + tinyusb_cdcacm_deinit(TINYUSB_CDC_ACM_0); cdc_console_installed = false; return ERROR_NONE; } diff --git a/Platforms/platform-esp32/source/drivers/usb/esp32_usb_device_controller.cpp b/Platforms/platform-esp32/source/drivers/usb/esp32_usb_device_controller.cpp index 344919c63..06703af50 100644 --- a/Platforms/platform-esp32/source/drivers/usb/esp32_usb_device_controller.cpp +++ b/Platforms/platform-esp32/source/drivers/usb/esp32_usb_device_controller.cpp @@ -12,8 +12,10 @@ #include #include +#include #include +#include #include #if CONFIG_IDF_TARGET_ESP32P4 @@ -33,6 +35,15 @@ static constexpr size_t COMPOSITE_CONFIG_DESCRIPTOR_MAX = TUD_CONFIG_DESC_LEN + // Worst case: primary's own table (lang/mfr/product/serial/interface = 5) + CDC's own interface string. static constexpr size_t COMPOSITE_STRING_DESCRIPTOR_MAX = 6; +// Detail for the most recent claim() failure - the console can be unreachable by the time this +// fails (route_phy_for_device_mode() already reassigned the PHY away from it), so callers read +// this instead of relying on the LOG_E above having been visible. +static char last_claim_error[48] = ""; + +extern "C" const char* usb_device_controller_get_last_error() { + return last_claim_error; +} + // ---- Controller state ---- // One TinyUSB device-mode slot, shared by every USB device class (each a separate child device // under usbdevice0 in the devicetree; \see esp32_usb_hid_device.cpp, esp32_usb_device_msc.cpp, @@ -256,13 +267,14 @@ static error_t claim(struct Device* device, enum UsbDeviceClass usb_class, const } ctx->composite_tusb_cfg = {}; - ctx->composite_tusb_cfg.device_descriptor = &ctx->composite_device_descriptor; - ctx->composite_tusb_cfg.string_descriptor = ctx->composite_string_descriptor; - ctx->composite_tusb_cfg.string_descriptor_count = string_count; - ctx->composite_tusb_cfg.external_phy = false; + ctx->composite_tusb_cfg.descriptor.device = &ctx->composite_device_descriptor; + ctx->composite_tusb_cfg.descriptor.string = ctx->composite_string_descriptor; + ctx->composite_tusb_cfg.descriptor.string_count = (int)string_count; + ctx->composite_tusb_cfg.phy.skip_setup = false; + ctx->composite_tusb_cfg.task = TINYUSB_TASK_DEFAULT(); // task.size/priority can't be left 0 - rejected by tinyusb_task_check_config() #if (TUD_OPT_HIGH_SPEED) - ctx->composite_tusb_cfg.fs_configuration_descriptor = ctx->composite_fs_config_descriptor; - ctx->composite_tusb_cfg.hs_configuration_descriptor = ctx->composite_hs_config_descriptor; + ctx->composite_tusb_cfg.descriptor.full_speed_config = ctx->composite_fs_config_descriptor; + ctx->composite_tusb_cfg.descriptor.high_speed_config = ctx->composite_hs_config_descriptor; // The qualifier descriptor's class triad must mirror the device descriptor's (same IAD // reasoning). Built here rather than supplied per-primary since its content is boilerplate // (same bMaxPacketSize0/bNumConfigurations for every class) and the controller already knows @@ -278,18 +290,20 @@ static error_t claim(struct Device* device, enum UsbDeviceClass usb_class, const .bNumConfigurations = 0x01, .bReserved = 0x00, }; - ctx->composite_tusb_cfg.qualifier_descriptor = &ctx->composite_device_qualifier; + ctx->composite_tusb_cfg.descriptor.qualifier = &ctx->composite_device_qualifier; #else - ctx->composite_tusb_cfg.configuration_descriptor = ctx->composite_fs_config_descriptor; + ctx->composite_tusb_cfg.descriptor.full_speed_config = ctx->composite_fs_config_descriptor; #endif - ctx->composite_tusb_cfg.self_powered = false; - ctx->composite_tusb_cfg.vbus_monitor_io = 0; + ctx->composite_tusb_cfg.phy.self_powered = false; + ctx->composite_tusb_cfg.phy.vbus_monitor_io = 0; route_phy_for_device_mode(); ctx->phy_routed = true; - if (tinyusb_driver_install(&ctx->composite_tusb_cfg) != ESP_OK) { - LOG_E(TAG, "claim: tinyusb_driver_install failed for class %d", usb_class); + esp_err_t install_result = tinyusb_driver_install(&ctx->composite_tusb_cfg); + if (install_result != ESP_OK) { + LOG_E(TAG, "claim: tinyusb_driver_install failed for class %d: %s", usb_class, esp_err_to_name(install_result)); + snprintf(last_claim_error, sizeof(last_claim_error), "tinyusb_driver_install: %s", esp_err_to_name(install_result)); if (ctx->phy_routed) { restore_default_phy_route(); ctx->phy_routed = false; diff --git a/Platforms/platform-esp32/source/drivers/usb/esp32_usb_device_msc.cpp b/Platforms/platform-esp32/source/drivers/usb/esp32_usb_device_msc.cpp index e9e7d5ce8..2e68aa44d 100644 --- a/Platforms/platform-esp32/source/drivers/usb/esp32_usb_device_msc.cpp +++ b/Platforms/platform-esp32/source/drivers/usb/esp32_usb_device_msc.cpp @@ -9,14 +9,24 @@ #include #include -#include +#include #include +#include #include #define TAG "esp32_usb_device_msc" #define GET_CONFIG(device) ((const Esp32UsbDeviceChildConfig*)(device)->config) +// Detail for the most recent msc_device_start() failure - the console can be unreachable by the +// time this fails (the controller already reassigned the PHY away from it), so callers read this +// instead of relying on the LOG_E calls below having been visible. +static char last_msc_error[48] = ""; + +extern "C" const char* usb_msc_device_get_last_error() { + return last_msc_error; +} + // ---- MSC device descriptor set ---- // Mutable, matching the other primary classes' descriptors, even though MSC's own @@ -62,6 +72,7 @@ static uint8_t msc_hs_configuration_descriptor[TUD_MSC_DESC_LEN]; struct UsbMscDeviceCtx { UsbMscDeviceMountChangedCallback mount_changed_cb = nullptr; void* mount_changed_context = nullptr; + tinyusb_msc_storage_handle_t storage_handle = nullptr; bool storage_active = false; }; @@ -70,12 +81,20 @@ struct UsbMscDeviceCtx { // device-mode slot the controller already enforces. static struct Device* active_msc_device = nullptr; -static void storage_mount_changed_cb(tinyusb_msc_event_t* event) { +// esp_tinyusb 2.x's MOUNT_COMPLETE event fires for either owner taking the storage. Kernel-facing +// UsbMscDeviceMountChangedCallback's `mounted` (\see usb_msc_device.h) means "mounted into our own +// filesystem" (the pre-2.x meaning, kept for callers like the boot-into-MSC flow that reboot back +// to normal OS once mounted==true) - i.e. ownership has passed to TINYUSB_MSC_STORAGE_MOUNT_APP, +// the opposite of the initial USB-owned state this driver starts storage in. +static void storage_mount_changed_cb(tinyusb_msc_storage_handle_t handle, tinyusb_msc_event_t* event, void* arg) { + (void)handle; + (void)arg; + if (event->id != TINYUSB_MSC_EVENT_MOUNT_COMPLETE) return; if (active_msc_device == nullptr) return; auto* ctx = static_cast(device_get_driver_data(active_msc_device)); if (ctx == nullptr) return; - const bool mounted = event->mount_changed_data.is_mounted; + const bool mounted = event->mount_point == TINYUSB_MSC_STORAGE_MOUNT_APP; LOG_I(TAG, "%s", mounted ? "MSC mounted" : "MSC unmounted"); if (ctx->mount_changed_cb != nullptr) { ctx->mount_changed_cb(mounted, ctx->mount_changed_context); @@ -131,6 +150,7 @@ static error_t msc_device_start(struct Device* device, enum UsbMscDeviceSource s error_t claim_result = usb_device_controller_claim(controller, USB_DEVICE_CLASS_MSC, &claim_config); if (claim_result != ERROR_NONE) { + snprintf(last_msc_error, sizeof(last_msc_error), "claim: %s", usb_device_controller_get_last_error()); return claim_result; } @@ -139,44 +159,66 @@ static error_t msc_device_start(struct Device* device, enum UsbMscDeviceSource s ctx->mount_changed_context = context; active_msc_device = device; - esp_err_t result; - if (source == USB_MSC_DEVICE_SOURCE_SDMMC) { - const tinyusb_msc_sdmmc_config_t config_sdmmc = { - .card = static_cast(source_handle), - .callback_mount_changed = storage_mount_changed_cb, - .callback_premount_changed = nullptr, - .mount_config = { - .format_if_mount_failed = false, - .max_files = 5, - .allocation_unit_size = 0, - .disk_status_check_enable = false, - .use_one_fat = false, - }, - }; - result = tinyusb_msc_storage_init_sdmmc(&config_sdmmc); - } else { - const tinyusb_msc_spiflash_config_t config_flash = { - .wl_handle = *static_cast(source_handle), - .callback_mount_changed = storage_mount_changed_cb, - .callback_premount_changed = nullptr, - .mount_config = { - .format_if_mount_failed = false, - .max_files = 5, - .allocation_unit_size = 0, - .disk_status_check_enable = false, - .use_one_fat = false, - }, - }; - result = tinyusb_msc_storage_init_spiflash(&config_flash); - } - + // esp_tinyusb 2.x auto-remounts storage on every tud_mount_cb()/tud_umount_cb(), including + // the bus resets that happen as a normal part of USB enumeration - not just a genuine host + // eject. Left enabled, that fires a spurious "mounted into our own fs" transition almost + // immediately after claim(), which callers like the boot-into-MSC flow read as "the host is + // done with it" and act on right away. Disabling it restores the old (pre-2.x) behavior: + // mount_point only changes via an explicit host SCSI eject or our own start/stop calls. + const tinyusb_msc_driver_config_t driver_cfg = { + .user_flags = { .val = 1 }, // auto_mount_off + .callback = storage_mount_changed_cb, + .callback_arg = nullptr, + }; + esp_err_t result = tinyusb_msc_install_driver(&driver_cfg); if (result != ESP_OK) { - LOG_E(TAG, "storage init failed: %s", esp_err_to_name(result)); + LOG_E(TAG, "MSC driver install failed: %s", esp_err_to_name(result)); + snprintf(last_msc_error, sizeof(last_msc_error), "install_driver: %s", esp_err_to_name(result)); active_msc_device = nullptr; usb_device_controller_release(controller, USB_DEVICE_CLASS_MSC); return ERROR_RESOURCE; } + const tinyusb_msc_fatfs_config_t fat_fs_config = { + .base_path = nullptr, // use CONFIG_TINYUSB_MSC_MOUNT_PATH default + .config = { + .format_if_mount_failed = false, + .max_files = 5, + .allocation_unit_size = 0, + .disk_status_check_enable = false, + .use_one_fat = false, + }, + .do_not_format = false, + .format_flags = 0, // FM_ANY + }; + + tinyusb_msc_storage_handle_t handle = nullptr; + if (source == USB_MSC_DEVICE_SOURCE_SDMMC) { + const tinyusb_msc_storage_config_t config_sdmmc = { + .medium = { .card = static_cast(source_handle) }, + .fat_fs = fat_fs_config, + .mount_point = TINYUSB_MSC_STORAGE_MOUNT_USB, + }; + result = tinyusb_msc_new_storage_sdmmc(&config_sdmmc, &handle); + } else { + const tinyusb_msc_storage_config_t config_flash = { + .medium = { .wl_handle = *static_cast(source_handle) }, + .fat_fs = fat_fs_config, + .mount_point = TINYUSB_MSC_STORAGE_MOUNT_USB, + }; + result = tinyusb_msc_new_storage_spiflash(&config_flash, &handle); + } + + if (result != ESP_OK) { + LOG_E(TAG, "storage init failed: %s", esp_err_to_name(result)); + snprintf(last_msc_error, sizeof(last_msc_error), "new_storage: %s", esp_err_to_name(result)); + tinyusb_msc_uninstall_driver(); + active_msc_device = nullptr; + usb_device_controller_release(controller, USB_DEVICE_CLASS_MSC); + return ERROR_RESOURCE; + } + + ctx->storage_handle = handle; ctx->storage_active = true; return ERROR_NONE; } @@ -194,8 +236,10 @@ static error_t msc_device_stop(struct Device* device) { auto* controller = device_get_parent(device); usb_device_controller_release(controller, USB_DEVICE_CLASS_MSC); - tinyusb_msc_storage_deinit(); + tinyusb_msc_delete_storage(ctx->storage_handle); + tinyusb_msc_uninstall_driver(); + ctx->storage_handle = nullptr; ctx->storage_active = false; ctx->mount_changed_cb = nullptr; ctx->mount_changed_context = nullptr; diff --git a/Tactility/Include/Tactility/hal/usb/Usb.h b/Tactility/Include/Tactility/hal/usb/Usb.h index 9cc3d461f..64a37afb5 100644 --- a/Tactility/Include/Tactility/hal/usb/Usb.h +++ b/Tactility/Include/Tactility/hal/usb/Usb.h @@ -16,6 +16,8 @@ enum class BootMode { }; bool startMassStorageWithSdmmc(bool fromBootMode = false); +// Reason the most recent startMassStorageWith*() call failed, or "" if it succeeded. +const char* getLastError(); void stop(); Mode getMode(); bool isSupported(); diff --git a/Tactility/Private/Tactility/hal/usb/UsbTusb.h b/Tactility/Private/Tactility/hal/usb/UsbTusb.h index 68cda7623..5c9b225f9 100644 --- a/Tactility/Private/Tactility/hal/usb/UsbTusb.h +++ b/Tactility/Private/Tactility/hal/usb/UsbTusb.h @@ -4,4 +4,7 @@ bool tusbIsSupported(); bool tusbStartMassStorageWithSdmmc(bool fromBootMode = false); bool tusbStartMassStorageWithFlash(bool fromBootMode = false); void tusbStop(); -bool tusbCanStartMassStorageWithFlash(); \ No newline at end of file +bool tusbCanStartMassStorageWithFlash(); + +// Reason the most recent tusbStartMassStorageWith*() call failed, or "" if it succeeded. +const char* tusbGetLastError(); \ No newline at end of file diff --git a/Tactility/Source/app/boot/Boot.cpp b/Tactility/Source/app/boot/Boot.cpp index 911751f02..8c5c7db8e 100644 --- a/Tactility/Source/app/boot/Boot.cpp +++ b/Tactility/Source/app/boot/Boot.cpp @@ -1,3 +1,4 @@ +#include "Tactility/app/alertdialog/AlertDialog.h" #include "tactility/memory.h" #include "tactility/system_event.h" @@ -56,6 +57,10 @@ std::atomic isUsbBootSplash = false; // window to an error screen and halts before starting the next app. std::atomic sdCardMissing = false; +// Set when setupUsbBootMode() fails +std::atomic usbBootModeFailed = false; +std::string usbBootModeFailedReason; + uint32_t bootAppInstanceId = 0; WindowId bootWindowId = 0; @@ -108,6 +113,8 @@ void setupDisplay() { device_put(display); } +// Returns true if either the MSC screen or the failure screen now owns the display, in which +// case the caller must not proceed to the normal boot path (registerApps()/startNextApp()). bool setupUsbBootMode() { if (!hal::usb::isUsbBootMode()) { return false; @@ -116,16 +123,18 @@ bool setupUsbBootMode() { LOG_I(TAG, "Rebooting into mass storage device mode"); auto mode = hal::usb::getUsbBootMode(); // Get mode before reset hal::usb::resetUsbBootMode(); + delay_millis(3000); + + bool started = false; if (mode == hal::usb::BootMode::Flash) { - if (!hal::usb::startMassStorageWithFlash(true)) { - LOG_E(TAG, "Unable to start flash mass storage"); - return false; - } + started = hal::usb::startMassStorageWithFlash(true); } else if (mode == hal::usb::BootMode::Sdmmc) { - if (!hal::usb::startMassStorageWithSdmmc(true)) { - LOG_E(TAG, "Unable to start SD mass storage"); - return false; - } + started = hal::usb::startMassStorageWithSdmmc(true); + } + + if (!started) { + usbBootModeFailedReason = hal::usb::getLastError(); + usbBootModeFailed = true; } return true; @@ -237,6 +246,36 @@ void showSdCardMissingScreen() { bootWindowId = window_manager_create(bootAppInstanceId, createSdCardMissingWidgets, nullptr); } +void createUsbBootModeFailedWidgets(lv_obj_t* root, void*) { + lvgl::obj_set_style_bg_blacken(root); + lv_obj_set_style_border_width(root, 0, LV_STATE_DEFAULT); + lv_obj_set_style_radius(root, 0, LV_STATE_DEFAULT); + lv_obj_set_flex_flow(root, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(root, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + + auto* label = lv_label_create(root); + lv_label_set_text(label, std::format("Failed to start USB mass storage:\n{}", usbBootModeFailedReason).c_str()); + lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, LV_STATE_DEFAULT); + lv_obj_set_style_text_color(label, lv_color_white(), LV_STATE_DEFAULT); + + auto* button = lv_button_create(root); + lv_obj_set_style_margin_top(button, 16, LV_STATE_DEFAULT); + auto* button_label = lv_label_create(button); + lv_label_set_text(button_label, "Reboot"); + lv_obj_add_event_cb(button, [](lv_event_t*) { +#ifdef ESP_PLATFORM + esp_restart(); +#endif + }, LV_EVENT_SHORT_CLICKED, nullptr); +} + +void showUsbBootModeFailedScreen() { + if (bootWindowId != 0) { + window_manager_remove(bootWindowId); + } + bootWindowId = window_manager_create(bootAppInstanceId, createUsbBootModeFailedWidgets, nullptr); +} + void startNextApp() { if (sdCardMissing) { showSdCardMissingScreen(); @@ -278,8 +317,10 @@ void runBootSequence(TickType_t startTime) { #endif if (setupUsbBootMode()) { - // Stay open: the splash's "Return to OS" button is this app's only way to leave mass - // storage mode, so it must not self-close here like the normal boot path does below. + if (usbBootModeFailed) { + showUsbBootModeFailedScreen(); + } + // Stay open: the splash's "Return to OS" button or the failure screen's "Reboot" button. return; } diff --git a/Tactility/Source/hal/usb/Usb.cpp b/Tactility/Source/hal/usb/Usb.cpp index f02a0b866..4c5bcf86b 100644 --- a/Tactility/Source/hal/usb/Usb.cpp +++ b/Tactility/Source/hal/usb/Usb.cpp @@ -67,6 +67,10 @@ bool startMassStorageWithSdmmc(bool fromBootMode) { } } +const char* getLastError() { + return tusbGetLastError(); +} + void stop() { if (canStartNewMode()) { return; diff --git a/Tactility/Source/hal/usb/UsbMock.cpp b/Tactility/Source/hal/usb/UsbMock.cpp index d18dddb57..9738ad31a 100644 --- a/Tactility/Source/hal/usb/UsbMock.cpp +++ b/Tactility/Source/hal/usb/UsbMock.cpp @@ -5,6 +5,7 @@ namespace tt::hal::usb { bool startMassStorageWithSdmmc(bool /*fromBootMode*/) { return false; } +const char* getLastError() { return ""; } void stop() {} Mode getMode() { return Mode::Default; } BootMode getUsbBootMode() { return BootMode::None; } diff --git a/Tactility/Source/hal/usb/UsbTusb.cpp b/Tactility/Source/hal/usb/UsbTusb.cpp index ed2fc796c..06a831194 100644 --- a/Tactility/Source/hal/usb/UsbTusb.cpp +++ b/Tactility/Source/hal/usb/UsbTusb.cpp @@ -12,6 +12,8 @@ #include #include +#include + #include #include #include @@ -25,6 +27,8 @@ namespace tt::hal::usb { // Used to decide whether ejecting the volume should automatically reboot back to normal OS. static bool startedFromBootMode = false; +static char lastErrorMessage[96] = ""; + static sdmmc_card_t* getCard() { sdmmc_card_t* card = nullptr; @@ -70,12 +74,14 @@ bool tusbStartMassStorageWithSdmmc(bool fromBootMode) { auto* device = usb_msc_device_get(); if (device == nullptr) { LOG_E(TAG, "USB MSC device not available"); + snprintf(tt::hal::usb::lastErrorMessage, sizeof(tt::hal::usb::lastErrorMessage), "USB MSC device not available"); return false; } auto* card = tt::hal::usb::getCard(); if (card == nullptr) { LOG_E(TAG, "SD card not mounted"); + snprintf(tt::hal::usb::lastErrorMessage, sizeof(tt::hal::usb::lastErrorMessage), "SD card not mounted"); device_put(device); return false; } @@ -86,6 +92,7 @@ bool tusbStartMassStorageWithSdmmc(bool fromBootMode) { tt::hal::usb::onMountChanged, nullptr); if (result != ERROR_NONE) { LOG_E(TAG, "TinyUSB SDMMC init failed: %s", error_to_string(result)); + snprintf(tt::hal::usb::lastErrorMessage, sizeof(tt::hal::usb::lastErrorMessage), "SDMMC init failed: %s (%s)", error_to_string(result), usb_msc_device_get_last_error()); } else { LOG_I(TAG, "TinyUSB SDMMC init success"); } @@ -100,12 +107,14 @@ bool tusbStartMassStorageWithFlash(bool fromBootMode) { auto* device = usb_msc_device_get(); if (device == nullptr) { LOG_E(TAG, "USB MSC device not available"); + snprintf(tt::hal::usb::lastErrorMessage, sizeof(tt::hal::usb::lastErrorMessage), "USB MSC device not available"); return false; } wl_handle_t handle = tt::getDataPartitionWlHandle(); if (handle == WL_INVALID_HANDLE) { LOG_E(TAG, "WL not mounted for /data"); + snprintf(tt::hal::usb::lastErrorMessage, sizeof(tt::hal::usb::lastErrorMessage), "WL not mounted for /data"); device_put(device); return false; } @@ -116,6 +125,7 @@ bool tusbStartMassStorageWithFlash(bool fromBootMode) { &handle, tt::hal::usb::onMountChanged, nullptr); if (result != ERROR_NONE) { LOG_E(TAG, "TinyUSB flash init failed: %s", error_to_string(result)); + snprintf(tt::hal::usb::lastErrorMessage, sizeof(tt::hal::usb::lastErrorMessage), "Flash init failed: %s (%s)", error_to_string(result), usb_msc_device_get_last_error()); } else { LOG_I(TAG, "TinyUSB flash init success"); } @@ -123,6 +133,10 @@ bool tusbStartMassStorageWithFlash(bool fromBootMode) { return result == ERROR_NONE; } +const char* tusbGetLastError() { + return tt::hal::usb::lastErrorMessage; +} + void tusbStop() { auto* device = usb_msc_device_get(); if (device != nullptr) { @@ -142,6 +156,7 @@ bool tusbStartMassStorageWithSdmmc(bool /*fromBootMode*/) { return false; } bool tusbStartMassStorageWithFlash(bool /*fromBootMode*/) { return false; } void tusbStop() {} bool tusbCanStartMassStorageWithFlash() { return false; } +const char* tusbGetLastError() { return ""; } #endif // CONFIG_TINYUSB_MSC_ENABLED diff --git a/Tactility/idf_component.yml b/Tactility/idf_component.yml index 962baf3ed..50347201d 100644 --- a/Tactility/idf_component.yml +++ b/Tactility/idf_component.yml @@ -71,7 +71,7 @@ dependencies: - if: "target in [esp32p4]" espressif/esp_lcd_panel_io_additions: "1.0.1" espressif/esp_tinyusb: - version: "1.7.6~1" + version: "2.2.1" rules: - if: "target in [esp32s3, esp32p4]" espressif/esp_lvgl_port: "2.7.2" diff --git a/TactilityKernel/include/tactility/drivers/usb_device_controller.h b/TactilityKernel/include/tactility/drivers/usb_device_controller.h index e91c9b0b7..7782bb0c4 100644 --- a/TactilityKernel/include/tactility/drivers/usb_device_controller.h +++ b/TactilityKernel/include/tactility/drivers/usb_device_controller.h @@ -183,6 +183,9 @@ error_t usb_device_controller_allocate_interfaces(struct Device* device, uint8_t struct UsbInterfaceAllocation* out_allocation); error_t usb_device_controller_claim(struct Device* device, enum UsbDeviceClass usb_class, const struct UsbDeviceClaimConfig* config); + +/** Detail string (e.g. an esp_err_t name) for the most recent claim() failure, or "" if none. */ +const char* usb_device_controller_get_last_error(void); error_t usb_device_controller_release(struct Device* device, enum UsbDeviceClass usb_class); enum UsbDeviceClass usb_device_controller_get_active_class(struct Device* device); bool usb_device_controller_is_cdc_enabled(struct Device* device); diff --git a/TactilityKernel/include/tactility/drivers/usb_msc_device.h b/TactilityKernel/include/tactility/drivers/usb_msc_device.h index d9805549b..4bb410ffc 100644 --- a/TactilityKernel/include/tactility/drivers/usb_msc_device.h +++ b/TactilityKernel/include/tactility/drivers/usb_msc_device.h @@ -85,6 +85,9 @@ error_t usb_msc_device_start(struct Device* device, enum UsbMscDeviceSource sour error_t usb_msc_device_stop(struct Device* device); bool usb_msc_device_is_connected(struct Device* device); +/** Detail string for the most recent usb_msc_device_start() failure, or "" if none. */ +const char* usb_msc_device_get_last_error(void); + #ifdef __cplusplus } #endif