Ken Van Hoeylandt cb4ce5246d
USB host refactored (#684)
- Create `USB_HOST_TYPE` base driver in TactilityKernel
- Refactor esp32_usbhost dts format, and create child devices dynamically
- Use PSRAM for task stacks where possible: saving 20 kB from just the task stacks, but roughly 30 kB when nothing is connected.
- Allow the user to disable USB host interface in the USB settings app, saving an additionaly 20 kB of IRAM when nothing was connected.
- Change listener-based APIs to subscribe and poll/await pattern. This avoids callstacks of apps and services leaking into the USB API

This results in big IRAM savings: 30 kB when USB host is on and nothing is connected, and 50 kB total when USB host is disabled.
This brings the total free IRAM after boot from roughly 170 kB to 200 kB (host enabled) or 220 kB (host disabled).
2026-10-10 13:33:32 +02:00

302 lines
8.7 KiB
C++

#include <Tactility/app/boot/BootInit.h>
#include "Tactility/bluetooth/Bluetooth.h"
#include "Tactility/hal/SdCard.h"
#include "Tactility/network/NtpPrivate.h"
#include "Tactility/settings/TimePrivate.h"
#include <tactility/delay.h>
#include <tactility/drivers/backlight.h>
#include <tactility/drivers/display.h>
#include <tactility/log.h>
#include <tactility/system_event.h>
#include <tactility/time.h>
#include <app/manager.h>
#include <app/manifest.h>
#include <app/start.h>
#include <Tactility/DeprecatedPaths.h>
#include <Tactility/MountPoints.h>
#include <Tactility/TactilityPrivate.h>
#include <Tactility/app/boot/BootScreen.h>
#include <Tactility/hal/usb/Usb.h>
#include <Tactility/hal/usb/UsbHost.h>
#include <Tactility/lvgl/Fonts.h>
#include <Tactility/lvgl/Lvgl.h>
#include <Tactility/lvgl/Theme.h>
#include <Tactility/settings/AppearanceSettings.h>
#include <Tactility/settings/BootSettings.h>
#include <Tactility/settings/DisplaySettings.h>
#include <format>
#ifdef ESP_PLATFORM
#include <Tactility/app/crashdiagnostics/CrashDiagnostics.h>
#include <esp_system.h>
#include <sdkconfig.h>
#else
#define CONFIG_TT_SPLASH_DURATION 0
#endif
namespace tt::app::boot {
constexpr auto* TAG = "Boot";
constexpr auto* DEFAULT_LAUNCHER_ID = "tactility.launcher";
namespace {
#ifdef ESP_PLATFORM
constexpr auto PARTITION_PREFIX = std::string("/");
#else
constexpr auto PARTITION_PREFIX = std::string("");
#endif
// Equivalent of AppPaths::getAssetsPath() for the internal "Boot" app id, without needing a
// live AppContext (which this app no longer has under the new app-module model).
std::string getBootAssetsPath(const std::string& childPath) {
return std::format("{}{}/app/Boot/assets/{}", PARTITION_PREFIX, file::SYSTEM_PARTITION_NAME, childPath);
}
// TODO: Replace with automatic asset buckets like on Android
std::string getSizedAssetPath(const BootScreen& screen, const char* small, const char* medium, const char* large) {
const int smallest_dimension = screen.getSmallestDimension();
if (smallest_dimension < 150) { // e.g. Cardputer
return getBootAssetsPath(small);
} else if (smallest_dimension >= 320) {
return getBootAssetsPath(large);
} else {
return getBootAssetsPath(medium);
}
}
std::string getLogoPath(const BootScreen& screen) {
return getSizedAssetPath(screen, "logo_small.png", "logo.png", "logo_large.png");
}
std::string getUsbLogoPath(const BootScreen& screen) {
return getSizedAssetPath(screen, "logo_usb_small.png", "logo_usb.png", "logo_usb_large.png");
}
void setupDisplay() {
// TODO: Support for multiple displays
Device* display = nullptr;
if (device_get_first_by_type(&DISPLAY_TYPE, &display) != ERROR_NONE) {
LOG_I(TAG, "No kernel display");
return;
}
// Set backlight brightness
Device* backlight;
if (display_get_backlight(display, &backlight) == ERROR_NONE) {
if (!device_is_ready(backlight)) {
if (device_start(backlight) != ERROR_NONE) {
LOG_E(TAG, "Failed to start %s", backlight->name);
}
}
settings::display::DisplaySettings settings;
if (settings::display::load(settings)) {
} else {
settings = settings::display::getDefault();
}
if (backlight_set_brightness(backlight, settings.backlightDuty) == ERROR_NONE) {
LOG_I(TAG, "Backlight for %s set to %d", display->name, settings.backlightDuty);
} else {
LOG_E(TAG, "Failed to set brightness of %s", backlight->name);
}
device_put(backlight);
} else {
LOG_I(TAG, "No backlight for %s", display->name);
}
device_put(display);
}
enum class UsbBootResult {
/** Not booting into USB mass storage mode */
None,
Started,
Failed
};
UsbBootResult setupUsbBootMode() {
if (!hal::usb::isUsbBootMode()) {
return UsbBootResult::None;
}
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) {
started = hal::usb::startMassStorageWithFlash(true);
} else if (mode == hal::usb::BootMode::Sdmmc) {
started = hal::usb::startMassStorageWithSdmmc(true);
}
return started ? UsbBootResult::Started : UsbBootResult::Failed;
}
void waitForMinimalSplashDuration(TickType_t startTime) {
const auto end_time = get_ticks();
const auto ticks_passed = end_time - startTime;
constexpr auto minimum_ticks = (CONFIG_TT_SPLASH_DURATION / portTICK_PERIOD_MS);
if (minimum_ticks > ticks_passed) {
delay_ticks(minimum_ticks - ticks_passed);
}
}
std::string getLauncherAppId() {
settings::BootSettings boot_properties;
// When boot.properties hasn't been overridden, return default
if (!settings::loadBootSettings(boot_properties)) {
return DEFAULT_LAUNCHER_ID;
}
// When boot properties didn't specify an override, return default
if (boot_properties.launcherAppId.empty()) {
LOG_E(TAG, "Failed to load launcher configuration, or launcher not configured");
return DEFAULT_LAUNCHER_ID;
}
// If the app in the boot.properties does not exist, return default
AppManifest manifest;
if (app_manager_find_manifest(boot_properties.launcherAppId.c_str(), &manifest) != ERROR_NONE) {
LOG_E(TAG, "Launcher app %s not found", boot_properties.launcherAppId.c_str());
return DEFAULT_LAUNCHER_ID;
}
// The boot.properties launcher app id is valid
return boot_properties.launcherAppId;
}
void reboot() {
#ifdef ESP_PLATFORM
esp_restart();
#else
LOG_W(TAG, "Reboot is not supported on this platform");
#endif
}
void waitForInputAndReboot() {
waitForInput();
reboot();
}
void startNextApp() {
auto launcher_app_id = getLauncherAppId();
uint32_t launcher_instance_id = 0;
AppStartContext context;
if (app_start_context_from_id(launcher_app_id.c_str(), &context) == ERROR_NONE) {
app_start_with_context(&context, &launcher_instance_id);
}
}
} // namespace
bool bootInit(TickType_t startTime) {
LOG_I(TAG, "Starting boot sequence");
const bool is_usb_boot = hal::usb::isUsbBootMode();
auto start_time = get_millis();
BootScreen screen;
if (screen.begin()) {
screen.show(is_usb_boot ? getUsbLogoPath(screen) : getLogoPath(screen), {});
}
LOG_I(TAG, "Init display");
setupDisplay();
LOG_I(TAG, "Init timezone");
settings::initTimeZone();
// Attempt to start all disabled SD cards (some require delayed init)
LOG_I(TAG, "Init SDMMC");
hal::sdcard::startAll();
LOG_I(TAG, "Init NTP");
network::ntp::init();
LOG_I(TAG, "Init BLE");
bluetooth::systemStart();
LOG_I(TAG, "Init Services");
registerAndStartServices();
LOG_I(TAG, "Prepare file systems");
prepareFileSystems();
LOG_I(TAG, "Init USB host");
hal::usbhost::systemStart();
bool sd_card_missing = false;
#ifdef CONFIG_TT_USER_DATA_LOCATION_SD
std::string sd_path;
if (!findFirstMountedSdCardPath(sd_path)) {
LOG_E(TAG, "SD card not found");
sd_card_missing = true;
}
#endif
switch (setupUsbBootMode()) {
case UsbBootResult::None:
break;
case UsbBootResult::Started:
#ifdef ESP_PLATFORM
screen.show(getUsbLogoPath(screen), { getInputPrompt("return to OS") });
waitForInput();
hal::usb::stop();
esp_restart();
#endif
return false;
case UsbBootResult::Failed:
screen.show("", { "Failed to start USB mass storage:", hal::usb::getLastError(), getInputPrompt("reboot") });
waitForInputAndReboot();
return false;
}
registerApps();
if (sd_card_missing) {
screen.show("", { "SD card not found.", "Please insert one and reboot.", getInputPrompt("reboot") });
waitForInputAndReboot();
return false;
}
#ifdef ESP_PLATFORM
if (esp_reset_reason() == ESP_RST_PANIC) {
crashdiagnostics::showCrashScreen(screen);
}
#endif
LOG_I(TAG, "Loading fonts");
lvgl::loadFonts(lvgl::loadFontConfiguration());
lvgl::configureTheme(settings::appearance::loadOrGetDefault());
screen.end();
size_t total_time = get_millis() - start_time;
LOG_I(TAG, "Finished in %lu ms", static_cast<uint32_t>(total_time));
waitForMinimalSplashDuration(startTime);
LOG_I(TAG, "Starting LVGL");
lvgl::start();
startNextApp();
// This event will likely block as other systems are initialized
// e.g. Wi-Fi reads AP configs from SD card
LOG_I(TAG, "Publish event");
system_event_emit(KERNEL_EVENT_BOOT_COMPLETED, nullptr, 0);
return true;
}
}