diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1dad215f..a74cf3e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,12 +13,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Install clang-format run: pip install clang-format==22.1.3 - name: Run format check - run: bash tools/format.sh --check + run: bash tools/format.sh --check-changed "${{ github.event.pull_request.base.sha }}" commitlint: name: Validate Commits @@ -50,3 +52,25 @@ jobs: - name: Build run: bash tools/build.sh shell: bash + + hle-tests: + name: HLE Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y libsdl2-dev + + - name: Configure + run: cmake -B build -S tools/hle + env: + SDL_VIDEODRIVER: dummy + + - name: Build tests + run: cmake --build build --target hle_tests -j$(nproc) + + - name: Run tests + run: ./build/hle_tests diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..d2f416f0 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,120 @@ +# TentacleOS — Agent Instructions + +## Project Overview + +Embedded firmware for the High Boy platform (ESP32-S3/P4/C5). Dual-firmware architecture: ESP32-C5 is the co-processor, ESP32-P4 is the master that embeds the C5 binary at build time. + +## Build & Flash + +ESP-IDF v5.5.3 is the pinned version. Source the environment before any build command: + +```bash +. $HOME/esp/v5.5.3/esp-idf/export.sh # or rely on $IDF_PATH +``` + +Full build (C5 then P4, in order — P4 embeds the C5 binary): + +```bash +./tools/build.sh +``` + +Build a single target: + +```bash +cd firmware_c5 && idf.py -DIDF_TARGET=esp32c5 build +cd firmware_p4 && idf.py -DIDF_TARGET=esp32p4 build +``` + +Flash (P4 only, which also programs C5): + +```bash +./tools/flash.sh +``` + +## Formatting + +Formatting is enforced by CI and the pre-commit hook. Run manually before committing: + +```bash +./tools/format.sh # fix all firmware sources +./tools/format.sh --check # verify all firmware sources +./tools/format.sh --changed # fix files changed from a base +./tools/format.sh --check-changed # verify changed files (CI uses this) +``` + +Config: `.clang-format` — LLVM base, 2-space indent, 100-column limit, pointers right-aligned, no sorted includes. Only `.c` and `.h` files under `firmware_*/` are formatted. + +## Commit Messages + +Conventional Commits enforced by git hook and CI. Format: `(): ` + +Types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `chore`, `ci`, `build`, `revert`, `delete`, `deleted`, `remove`, `removed` + +Breaking changes use `!` before the colon. Header max length: 200 chars. + +## Repo Structure + +``` +firmware_c5/ # ESP32-C5 co-processor firmware +firmware_p4/ # ESP32-P4 master firmware (embeds C5 binary) + components/ + Drivers/ # Hardware drivers (SPI, buttons, display, radio, USB) + Service/ # Support services (OTA, WiFi, console, storage, SPI bridge) + Core/ # System core and main managers + Applications/ # User-facing apps (UI, bad_usb, SubGhz) + Drivers/spi_bridge_phy/ # SPI bridge physical layer (P4 side) + main/main.c # Entry point +common/metadata/ # Shared metadata +tools/ # Build, flash, format, setup scripts +``` + +Each target is a standalone ESP-IDF project with its own `CMakeLists.txt`, `partitions.csv`, and `sdkconfig.defaults`. + +## Coding Standards + +All rules are in `CODING_STANDARDS.md`. Key points an agent must not miss: + +- **Public functions** are prefixed with module name (`cc1101_set_frequency()`). **Static functions** drop the prefix (`process_pulse()`). +- **Variables**: local `snake_case`, static `s_` prefix, global `g_` prefix, bools `is_`/`has_`/`can_`, output params `out_`. +- **Types**: `module_name_t` with `_cb_t` for callbacks. +- **Constants**: `UPPER_SNAKE_CASE`. Every literal with domain meaning must be a named `#define` (except `0`, `1`, `NULL`, `true`, `false`). +- **Enums**: `UPPER_SNAKE_CASE` with module prefix, include `_COUNT` sentinel when iterable. +- **Fixed-width types** from `` for all hardware code. Never rely on implicit `int`. +- **Error handling**: public functions return `esp_err_t`. Every `malloc` must be checked for `NULL`, logged with `ESP_LOGE`, and handled via `goto cleanup`. +- **Logging**: every `.c` file defines `static const char *TAG = "MODULE_NAME";`. Use `ESP_LOGx` macros, never `printf`. +- **File layout order**: license, own header, C stdlib, ESP-IDF/FreeRTOS, project headers, defines, static types, static vars, forward decls, public funcs, static funcs. +- **Headers**: `#ifndef` guards, `extern "C"` blocks, include order separated by blank lines. +- **Source file layout**: `^[0-9a-z_]+\.[ch]$`, filename is a prefix for its content. + +## Hook Setup + +Run once after cloning: + +```bash +./tools/setup.sh +``` + +This sets `core.hooksPath` to `.githooks/` (pre-commit: clang-format, commit-msg: conventional commits). + +## CI + +GitHub Actions runs on PRs to `main`/`dev`: +1. **Format check** — `./tools/format.sh --check` +2. **Commitlint** — validates all commits in PR +3. **Build** — both targets inside `espressif/idf:v5.5.3` container via `./tools/build.sh` + +All must pass before merge. + +## Generated Files (do not edit) + +- `managed_components/` — ESP-IDF component manager (gitignored) +- `sdkconfig` / `sdkconfig.old` — ESP-IDF build config (gitignored); use `sdkconfig.defaults` instead +- `dependencies.lock` — ESP-IDF component manager lockfile (gitignored) +- Build outputs in `build/` directories (gitignored) + +## Versioning + +Automated via semantic-release on `main`. Do not manage versions manually — commit messages determine bumps. Version is written to: +- `firmware_p4/assets/config/OTA/firmware.json` +- `firmware_c5/assets/config/OTA/firmware.json` +- `firmware_p4/components/Service/ota/include/ota_version.h` diff --git a/README.md b/README.md index 66343520..36f6cb9b 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,140 @@ Example layout: ``` +## Native HLE simulator + +The host-level emulation (HLE) target runs the P4 UI, LVGL, host-backed storage, +and a simulated C5 SPI bridge on Linux. It is intended for UI and firmware-flow +development without a connected High Boy. + +

+ TentacleOS HLE emulator boot screen +
+ Boot screen rendered by the native SDL simulator. +

+ +### Requirements + +- Linux +- CMake 3.16 or newer +- A C11/C++17 toolchain +- Git and the SDL2 development headers +- Internet access during the first configure, which downloads LVGL, cJSON, and + GoogleTest + +On Ubuntu or Debian: + +```bash +sudo apt update +sudo apt install build-essential cmake git libsdl2-dev +``` + +ESP-IDF, an ESP32 toolchain, and connected High Boy hardware are not required +for the native simulator. + +### Build and run + +Run these commands from the repository root: + +```bash +cmake -S tools/hle -B build +cmake --build build --target hle_interactive -j +./build/hle_interactive +``` + +The first build also converts the assets under `firmware_p4/assets`. After UI +or firmware changes, rerun the `cmake --build` command and restart the +simulator; reconfiguration is only needed after CMake or source-layout changes. + +### Controls + +| High Boy input | Keyboard | +| :--- | :--- | +| Directional buttons | Arrow keys or W/A/S/D | +| OK | Enter, keypad Enter, or Space | +| Back | Backspace or Escape | +| Exit simulator | Ctrl+Q or close the window | + +### Storage + +The simulator stores `/sdcard` data under `/tmp/hle_storage` by default. +Override the location with `HLE_STORAGE_PATH`: + +```bash +HLE_STORAGE_PATH="$HOME/.local/state/tentacleos-hle" ./build/hle_interactive +``` + +Point `HLE_STORAGE_PATH` at a new empty directory to exercise the firmware's +first-boot flow again. + +### Headless snapshots + +For deterministic, headless UI snapshots: + +```bash +SDL_VIDEODRIVER=dummy \ +HLE_SNAPSHOT_PATH=/tmp/high-boy.ppm \ +HLE_SNAPSHOT_MS=6500 \ +./build/hle_interactive +``` + +The snapshot example renders for 6500 ms, writes a PPM image, and exits. It is +also suitable for CI or SSH sessions without a display server. + +### Tests + +Run the native regression suite with: + +```bash +cmake --build build --target hle_tests -j +ctest --test-dir build --output-on-failure +``` + +#### Example: testing display output + +Every `*.cpp` file under `tools/hle/tests` is compiled into `hle_tests` and +automatically registered with GoogleTest. For example, create +`tools/hle/tests/test_my_ui.cpp`: + +```cpp +#include +#include + +#include + +#include "hle/hle_display.h" + +TEST(MyUIScreen, DrawsExpectedPixel) { + auto &display = hle::Display::instance(); + display.fill_screen(0); + + constexpr uint16_t expected_color = 0xF81F; + display.draw_bitmap(12, 20, 13, 21, &expected_color); + + std::array framebuffer{}; + ASSERT_TRUE(display.copy_pixels_if_dirty( + framebuffer.data(), hle::LCD_H_RES * sizeof(uint16_t))); + EXPECT_EQ(framebuffer[(20 * hle::LCD_H_RES) + 12], expected_color); +} +``` + +Build and run only that test: + +```bash +cmake --build build --target hle_tests -j +./build/hle_tests --gtest_filter=MyUIScreen.DrawsExpectedPixel +``` + +Use the same pattern for NVS, SPI bridge, input, and other host-emulated +contracts. Tests that include C firmware headers should place those includes +inside an `extern "C"` block. + +### Scope and limitations + +The HLE covers UI and host-emulated firmware flows. Wi-Fi, Bluetooth, radio, +and other physical-hardware behavior still require target testing. + + ## How to Contribute Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. diff --git a/README.pt.md b/README.pt.md index f6643680..a98a519b 100644 --- a/README.pt.md +++ b/README.pt.md @@ -70,6 +70,142 @@ Exemplo de layout: └── README.md ``` +## Simulador HLE nativo + +O alvo de emulação de alto nível (HLE) executa a interface do P4, o LVGL, o +armazenamento no host e uma ponte SPI simulada para o C5 no Linux. Ele permite +desenvolver a interface e os fluxos do firmware sem conectar um High Boy. + +

+ Tela de inicialização do emulador HLE do TentacleOS +
+ Tela de inicialização renderizada pelo simulador SDL nativo. +

+ +### Requisitos + +- Linux +- CMake 3.16 ou mais recente +- Um compilador compatível com C11/C++17 +- Git e os cabeçalhos de desenvolvimento do SDL2 +- Acesso à internet durante a primeira configuração, que baixa LVGL, cJSON e + GoogleTest + +No Ubuntu ou Debian: + +```bash +sudo apt update +sudo apt install build-essential cmake git libsdl2-dev +``` + +O simulador nativo não exige ESP-IDF, um toolchain ESP32 ou um High Boy +conectado. + +### Compilar e executar + +Execute estes comandos a partir da raiz do repositório: + +```bash +cmake -S tools/hle -B build +cmake --build build --target hle_interactive -j +./build/hle_interactive +``` + +A primeira compilação também converte os assets em `firmware_p4/assets`. Após +alterações na interface ou no firmware, execute novamente o comando +`cmake --build` e reinicie o simulador. Só é necessário reconfigurar após +alterações no CMake ou na estrutura dos arquivos-fonte. + +### Controles + +| Entrada do High Boy | Teclado | +| :--- | :--- | +| Botões direcionais | Setas ou W/A/S/D | +| OK | Enter, Enter do teclado numérico ou Espaço | +| Voltar | Backspace ou Escape | +| Sair do simulador | Ctrl+Q ou fechar a janela | + +### Armazenamento + +Por padrão, o simulador armazena os dados de `/sdcard` em `/tmp/hle_storage`. +Use `HLE_STORAGE_PATH` para escolher outro local: + +```bash +HLE_STORAGE_PATH="$HOME/.local/state/tentacleos-hle" ./build/hle_interactive +``` + +Use um diretório novo e vazio em `HLE_STORAGE_PATH` para executar novamente o +fluxo de primeira inicialização do firmware. + +### Capturas sem interface gráfica + +Para gerar capturas determinísticas da interface sem abrir uma janela: + +```bash +SDL_VIDEODRIVER=dummy \ +HLE_SNAPSHOT_PATH=/tmp/high-boy.ppm \ +HLE_SNAPSHOT_MS=6500 \ +./build/hle_interactive +``` + +O exemplo gera a interface por 6500 ms, grava uma imagem PPM e encerra. Ele +também pode ser usado em CI ou em sessões SSH sem servidor gráfico. + +### Testes + +Execute os testes nativos com: + +```bash +cmake --build build --target hle_tests -j +ctest --test-dir build --output-on-failure +``` + +#### Exemplo: testar a saída do display + +Cada arquivo `*.cpp` em `tools/hle/tests` é compilado no executável `hle_tests` +e registrado automaticamente no GoogleTest. Por exemplo, crie +`tools/hle/tests/test_my_ui.cpp`: + +```cpp +#include +#include + +#include + +#include "hle/hle_display.h" + +TEST(MyUIScreen, DrawsExpectedPixel) { + auto &display = hle::Display::instance(); + display.fill_screen(0); + + constexpr uint16_t expected_color = 0xF81F; + display.draw_bitmap(12, 20, 13, 21, &expected_color); + + std::array framebuffer{}; + ASSERT_TRUE(display.copy_pixels_if_dirty( + framebuffer.data(), hle::LCD_H_RES * sizeof(uint16_t))); + EXPECT_EQ(framebuffer[(20 * hle::LCD_H_RES) + 12], expected_color); +} +``` + +Compile e execute somente esse teste: + +```bash +cmake --build build --target hle_tests -j +./build/hle_tests --gtest_filter=MyUIScreen.DrawsExpectedPixel +``` + +Use o mesmo padrão para NVS, ponte SPI, entrada e outros contratos emulados no +host. Testes que incluem cabeçalhos C do firmware devem colocar essas inclusões +dentro de um bloco `extern "C"`. + +### Escopo e limitações + +O HLE cobre a interface e os fluxos emulados do firmware. Wi-Fi, Bluetooth, +rádio e outros comportamentos de hardware físico ainda exigem testes no +dispositivo. + + --- ## Como Contribuir diff --git a/firmware_c5/components/Drivers/bq25896/bq25896.c b/firmware_c5/components/Drivers/bq25896/bq25896.c index 48b7d265..3d4c3a3c 100644 --- a/firmware_c5/components/Drivers/bq25896/bq25896.c +++ b/firmware_c5/components/Drivers/bq25896/bq25896.c @@ -19,6 +19,7 @@ #include "driver/i2c.h" #include "esp_log.h" +#include "freertos/FreeRTOS.h" static const char *TAG = "BQ25896"; diff --git a/firmware_c5/components/Drivers/spi_slave/spi_slave_driver.c b/firmware_c5/components/Drivers/spi_slave/spi_slave_driver.c index ef55b71b..c2fa23f4 100644 --- a/firmware_c5/components/Drivers/spi_slave/spi_slave_driver.c +++ b/firmware_c5/components/Drivers/spi_slave/spi_slave_driver.c @@ -20,6 +20,7 @@ #include "driver/gpio.h" #include "driver/spi_slave.h" #include "esp_log.h" +#include "freertos/FreeRTOS.h" #include "pin_def.h" diff --git a/firmware_p4/components/Applications/ui/assets_manager.c b/firmware_p4/components/Applications/ui/assets_manager.c index 80d47358..bdab25c7 100644 --- a/firmware_p4/components/Applications/ui/assets_manager.c +++ b/firmware_p4/components/Applications/ui/assets_manager.c @@ -128,21 +128,23 @@ static void scan_and_load_recursive(const char *base_path) { char path[512]; while ((ent = readdir(dir)) != NULL) { - if (ent->d_type == DT_DIR) { - if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) - continue; - snprintf(path, sizeof(path), "%s/%s", base_path, ent->d_name); + if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) + continue; + + snprintf(path, sizeof(path), "%s/%s", base_path, ent->d_name); + + struct stat st; + if (stat(path, &st) != 0) + continue; + + if (S_ISDIR(st.st_mode)) { scan_and_load_recursive(path); - } else if (ent->d_type == DT_REG) { + } else if (S_ISREG(st.st_mode)) { size_t len = strlen(ent->d_name); - if (len > 4 && strcmp(ent->d_name + len - 4, ".bin") == 0) { - snprintf(path, sizeof(path), "%s/%s", base_path, ent->d_name); - - if (assets_get(path) == NULL) { - lv_image_dsc_t *dsc = load_asset_from_file(path); - if (dsc) { - add_asset_to_list(path, dsc, false); - } + if (len > 4 && strcmp(ent->d_name + len - 4, ".bin") == 0 && assets_get(path) == NULL) { + lv_image_dsc_t *dsc = load_asset_from_file(path); + if (dsc) { + add_asset_to_list(path, dsc, false); } } } @@ -229,16 +231,17 @@ int assets_load_from_sd(const char *sd_dir, const char *flash_prefix) { char cache_key[512]; while ((ent = readdir(dir)) != NULL) { - if (ent->d_type != DT_REG) + snprintf(sd_path, sizeof(sd_path), "%s/%s", sd_dir, ent->d_name); + snprintf(cache_key, sizeof(cache_key), "%s/%s", flash_prefix, ent->d_name); + + struct stat st; + if (stat(sd_path, &st) != 0 || !S_ISREG(st.st_mode)) continue; size_t len = strlen(ent->d_name); if (len <= 4 || strcmp(ent->d_name + len - 4, ".bin") != 0) continue; - snprintf(sd_path, sizeof(sd_path), "%s/%s", sd_dir, ent->d_name); - snprintf(cache_key, sizeof(cache_key), "%s/%s", flash_prefix, ent->d_name); - lv_image_dsc_t *dsc = load_asset_from_file(sd_path); if (dsc == NULL) continue; @@ -275,4 +278,4 @@ void assets_unload_sd(void) { } ESP_LOGI(TAG, "Unloaded %d SD asset(s) from cache", removed); -} \ No newline at end of file +} diff --git a/firmware_p4/components/Drivers/bq25896/bq25896.c b/firmware_p4/components/Drivers/bq25896/bq25896.c index bd1d2f03..150c7b40 100644 --- a/firmware_p4/components/Drivers/bq25896/bq25896.c +++ b/firmware_p4/components/Drivers/bq25896/bq25896.c @@ -19,6 +19,7 @@ #include "driver/i2c.h" #include "esp_log.h" +#include "freertos/FreeRTOS.h" #define I2C_PORT I2C_NUM_0 #define I2C_TIMEOUT_MS 100 diff --git a/pics/hle-emulator.png b/pics/hle-emulator.png new file mode 100644 index 00000000..a51b51cc Binary files /dev/null and b/pics/hle-emulator.png differ diff --git a/tools/format.sh b/tools/format.sh index 30e59788..5837aac8 100755 --- a/tools/format.sh +++ b/tools/format.sh @@ -15,6 +15,48 @@ # You should have received a copy of the GNU General Public License # along with TentacleOS. If not, see . +set -o pipefail + +usage() { + echo "Usage: $0 [--check | --changed | --check-changed ]" +} + +MODE="format" +BASE_REF="" +case "${1:-}" in + "") + if [ "$#" -ne 0 ]; then + usage + exit 2 + fi + ;; + --check) + if [ "$#" -ne 1 ]; then + usage + exit 2 + fi + MODE="check" + ;; + --changed) + if [ "$#" -ne 2 ] || [ -z "$2" ]; then + usage + exit 2 + fi + BASE_REF="$2" + ;; + --check-changed) + if [ "$#" -ne 2 ] || [ -z "$2" ]; then + usage + exit 2 + fi + MODE="check" + BASE_REF="$2" + ;; + *) + usage + exit 2 + ;; +esac REPO_ROOT="$(git rev-parse --show-toplevel)" @@ -25,47 +67,64 @@ TARGETS=( "$REPO_ROOT/firmware_c5/main" ) -EXCLUDE_DIRS=( - "managed_components" - "build" +DIFF_TARGETS=( + "firmware_p4/components" + "firmware_p4/main" + "firmware_c5/components" + "firmware_c5/main" ) -EXCLUDE_ARGS=() -for dir in "${EXCLUDE_DIRS[@]}"; do - EXCLUDE_ARGS+=(-not -path "*/$dir/*") -done +FILES=() +if [ -n "$BASE_REF" ]; then + if ! git -C "$REPO_ROOT" rev-parse --verify "${BASE_REF}^{commit}" >/dev/null 2>&1; then + echo "Unknown base ref: $BASE_REF" + exit 2 + fi + + while IFS= read -r -d '' path; do + case "$path" in + *.c|*.h) FILES+=("$REPO_ROOT/$path") ;; + esac + done < <(git -C "$REPO_ROOT" diff --name-only -z --diff-filter=ACMR \ + "$BASE_REF"...HEAD -- "${DIFF_TARGETS[@]}") +else + EXISTING_TARGETS=() + for target in "${TARGETS[@]}"; do + if [ -d "$target" ]; then + EXISTING_TARGETS+=("$target") + fi + done -EXISTING_TARGETS=() -for target in "${TARGETS[@]}"; do - if [ -d "$target" ]; then - EXISTING_TARGETS+=("$target") + if [ "${#EXISTING_TARGETS[@]}" -eq 0 ]; then + echo "No target directories found." + exit 0 fi -done -if [ ${#EXISTING_TARGETS[@]} -eq 0 ]; then - echo "No target directories found." - exit 0 + while IFS= read -r -d '' path; do + FILES+=("$path") + done < <(find "${EXISTING_TARGETS[@]}" -type f \( -name "*.c" -o -name "*.h" \) \ + -not -path "*/managed_components/*" -not -path "*/build/*" -print0) fi -FILES=$(find "${EXISTING_TARGETS[@]}" \( -name "*.c" -o -name "*.h" \) "${EXCLUDE_ARGS[@]}") - -if [ -z "$FILES" ]; then +COUNT="${#FILES[@]}" +if [ "$COUNT" -eq 0 ]; then echo "No files to format." exit 0 fi -COUNT=$(echo "$FILES" | wc -l) - -if [ "$1" = "--check" ]; then +if [ "$MODE" = "check" ]; then echo "Checking $COUNT files..." - echo "$FILES" | xargs clang-format --dry-run --Werror 2>&1 - if [ $? -ne 0 ]; then - echo "Formatting errors found. Run ./tools/format.sh to fix." + if ! printf '%s\0' "${FILES[@]}" | xargs -0 clang-format --dry-run --Werror; then + if [ -n "$BASE_REF" ]; then + echo "Formatting errors found. Run ./tools/format.sh --changed \"$BASE_REF\" to fix." + else + echo "Formatting errors found. Run ./tools/format.sh to fix." + fi exit 1 fi echo "All files formatted correctly." else echo "Formatting $COUNT files..." - echo "$FILES" | xargs clang-format -i + printf '%s\0' "${FILES[@]}" | xargs -0 clang-format -i echo "Done." fi diff --git a/tools/hle/CMakeLists.txt b/tools/hle/CMakeLists.txt new file mode 100644 index 00000000..8d41e08a --- /dev/null +++ b/tools/hle/CMakeLists.txt @@ -0,0 +1,209 @@ +cmake_minimum_required(VERSION 3.16) +project(TentacleOS_HLE C CXX) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +set(FIRMWARE_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../..") +set(HLE_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include") +set(HLE_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src") +set(HLE_ASSETS_DIR "${FIRMWARE_ROOT}/firmware_p4/temp") + +# ── Collect firmware component include dirs ─────────────────────────────────── +function(collect_firmware_includes TARGET_DIR OUTPUT_VAR) + set(_includes "") + file(GLOB_RECURSE _dirs CONFIGURE_DEPENDS LIST_DIRECTORIES true + "${TARGET_DIR}/components/*/include") + foreach(_dir ${_dirs}) + list(APPEND _includes ${_dir}) + endforeach() + set(${OUTPUT_VAR} ${_includes} PARENT_SCOPE) +endfunction() +collect_firmware_includes("${FIRMWARE_ROOT}/firmware_c5" C5_INCLUDES) +collect_firmware_includes("${FIRMWARE_ROOT}/firmware_p4" P4_INCLUDES) + +function(collect_firmware_sources TARGET_DIR PATTERN OUTPUT_VAR) + file(GLOB_RECURSE _sources CONFIGURE_DEPENDS "${TARGET_DIR}/${PATTERN}") + set(${OUTPUT_VAR} ${_sources} PARENT_SCOPE) +endfunction() + +# ── C5 firmware sources ────────────────────────────────────────────────────── +collect_firmware_sources("${FIRMWARE_ROOT}/firmware_c5" "components/Core/*.c" C5_CORE_SRC) +collect_firmware_sources("${FIRMWARE_ROOT}/firmware_c5" "main/*.c" C5_MAIN_SRC) +collect_firmware_sources("${FIRMWARE_ROOT}/firmware_c5" "components/Drivers/*.c" C5_DRV) +collect_firmware_sources("${FIRMWARE_ROOT}/firmware_c5" "components/Service/*.c" C5_SVC) +collect_firmware_sources("${FIRMWARE_ROOT}/firmware_c5" "components/Drivers/*/*.c" C5_DRV_DEEP) +collect_firmware_sources("${FIRMWARE_ROOT}/firmware_c5" "components/Service/*/*.c" C5_SVC_DEEP) + +# ── P4 firmware sources ────────────────────────────────────────────────────── +collect_firmware_sources("${FIRMWARE_ROOT}/firmware_p4" "components/Core/*.c" P4_CORE_SRC) +collect_firmware_sources("${FIRMWARE_ROOT}/firmware_p4" "main/*.c" P4_MAIN_SRC) +collect_firmware_sources("${FIRMWARE_ROOT}/firmware_p4" "components/Drivers/*.c" P4_DRV) +collect_firmware_sources("${FIRMWARE_ROOT}/firmware_p4" "components/Drivers/*/*.c" P4_DRV_DEEP) +collect_firmware_sources("${FIRMWARE_ROOT}/firmware_p4" "components/Service/*.c" P4_SVC) +collect_firmware_sources("${FIRMWARE_ROOT}/firmware_p4" "components/Service/*/*.c" P4_SVC_DEEP) +collect_firmware_sources("${FIRMWARE_ROOT}/firmware_p4" "components/Applications/*.c" P4_APP) +collect_firmware_sources("${FIRMWARE_ROOT}/firmware_p4" "components/Applications/*/*.c" P4_APP_DEEP) +collect_firmware_sources("${FIRMWARE_ROOT}/firmware_p4" "components/Applications/*/*/*.c" P4_APP_DEEP2) + +# ── Dependencies ───────────────────────────────────────────────────────────── +include(FetchContent) + +file(GLOB_RECURSE HLE_ASSET_SOURCES CONFIGURE_DEPENDS "${FIRMWARE_ROOT}/firmware_p4/assets/*") +set(HLE_ASSETS_STAMP "${HLE_ASSETS_DIR}/.hle_assets_stamp") +add_custom_command( + OUTPUT ${HLE_ASSETS_STAMP} + COMMAND bash "${FIRMWARE_ROOT}/tools/png_to_bin/png_conversor_to_bin.sh" + COMMAND ${CMAKE_COMMAND} -E touch ${HLE_ASSETS_STAMP} + WORKING_DIRECTORY "${FIRMWARE_ROOT}/firmware_p4" + DEPENDS ${HLE_ASSET_SOURCES} "${FIRMWARE_ROOT}/tools/png_to_bin/png_conversor_to_bin.sh" + COMMENT "Generating HLE LVGL assets" +) +add_custom_target(hle_assets DEPENDS ${HLE_ASSETS_STAMP}) + +# cJSON +set(ENABLE_CJSON_TEST OFF CACHE BOOL "Build cJSON tests" FORCE) +set(ENABLE_CJSON_UNINSTALL OFF CACHE BOOL "Create cJSON uninstall target" FORCE) +set(ENABLE_TARGET_EXPORT OFF CACHE BOOL "Export cJSON CMake targets" FORCE) +set(CJSON_OVERRIDE_BUILD_SHARED_LIBS ON CACHE BOOL "Override cJSON library type" FORCE) +set(CJSON_BUILD_SHARED_LIBS OFF CACHE BOOL "Build cJSON as a shared library" FORCE) +FetchContent_Declare(cjson GIT_REPOSITORY https://github.com/DaveGamble/cJSON.git GIT_TAG v1.7.18) +FetchContent_MakeAvailable(cjson) + +FetchContent_Declare(googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG v1.14.0) +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) + +set(LV_BUILD_CONF_DIR "${HLE_INCLUDE_DIR}" CACHE PATH "Directory containing the HLE lv_conf.h" FORCE) +set(CONFIG_LV_BUILD_DEMOS OFF CACHE BOOL "Build LVGL demos" FORCE) +set(CONFIG_LV_BUILD_EXAMPLES OFF CACHE BOOL "Build LVGL examples" FORCE) +set(CONFIG_LV_USE_THORVG_INTERNAL OFF CACHE BOOL "Build LVGL's internal ThorVG" FORCE) +FetchContent_Declare(lvgl GIT_REPOSITORY https://github.com/lvgl/lvgl.git GIT_TAG v9.4.0) +FetchContent_MakeAvailable(lvgl) + +option(HLE_BUILD_INTERACTIVE "Build the SDL2 High Boy simulator" ON) +if(HLE_BUILD_INTERACTIVE) + find_package(SDL2 REQUIRED) +endif() + +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type" FORCE) +endif() +add_compile_definitions(HLE_BUILD) +add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-unused-variable + -Wno-sign-compare -Wno-missing-field-initializers -fno-strict-aliasing) +set(HLE_FS_WRAP_OPTIONS + -Wl,--wrap=fopen + -Wl,--wrap=stat + -Wl,--wrap=opendir + -Wl,--wrap=mkdir + -Wl,--wrap=access + -Wl,--wrap=remove + -Wl,--wrap=rename + -Wl,--wrap=unlink +) + +# ── Libraries ──────────────────────────────────────────────────────────────── +file(GLOB_RECURSE HLE_SRC CONFIGURE_DEPENDS "${HLE_SRC_DIR}/*.cpp") +list(FILTER HLE_SRC EXCLUDE REGEX ".*main\\.cpp$") # main is for interactive only +list(FILTER HLE_SRC EXCLUDE REGEX ".*lv_port.*\\.cpp$") # lv_port needs display context +list(FILTER HLE_SRC EXCLUDE REGEX ".*hle_kernel.*\\.cpp$") # kernel needs LVGL + bridge +add_library(hle_shims STATIC ${HLE_SRC}) +target_include_directories(hle_shims PUBLIC ${HLE_INCLUDE_DIR} ${P4_INCLUDES}) +target_compile_options(hle_shims PRIVATE -Wno-pmf-conversions) +target_compile_definitions(hle_shims PRIVATE HLE_ASSETS_DIR="${HLE_ASSETS_DIR}") + +add_library(firmware_c5 STATIC + ${C5_CORE_SRC} ${C5_MAIN_SRC} ${C5_DRV} ${C5_DRV_DEEP} ${C5_SVC} ${C5_SVC_DEEP} +) + +# C5 hardware transports and network services require ESP-IDF/NimBLE/lwIP stacks. +# HLE provides its own in-memory SPI bridge and compiles P4 proxy clients instead. +get_target_property(_c5_srcs firmware_c5 SOURCES) +list(FILTER _c5_srcs EXCLUDE REGEX ".*bluetooth_service\\.c$") +list(FILTER _c5_srcs EXCLUDE REGEX ".*wifi_service\\.c$") +list(FILTER _c5_srcs EXCLUDE REGEX ".*/host_link/host_link_gatt\\.c$") +list(FILTER _c5_srcs EXCLUDE REGEX ".*/spi_bridge/spi_bridge\\.c$") +list(FILTER _c5_srcs EXCLUDE REGEX ".*/ota/.*\\.c$") +list(FILTER _c5_srcs EXCLUDE REGEX ".*/console/.*\\.c$") +list(FILTER _c5_srcs EXCLUDE REGEX ".*/dns_server/.*\\.c$") +list(FILTER _c5_srcs EXCLUDE REGEX ".*/esp_now/.*\\.c$") +list(FILTER _c5_srcs EXCLUDE REGEX ".*/http_server/.*\\.c$") +list(FILTER _c5_srcs EXCLUDE REGEX ".*/meshtastic/.*\\.c$") +list(FILTER _c5_srcs EXCLUDE REGEX ".*/meshcore/.*\\.c$") +list(FILTER _c5_srcs EXCLUDE REGEX ".*/sd_card/.*\\.c$") +list(FILTER _c5_srcs EXCLUDE REGEX ".*/storage_vfs/vfs_sdcard\\.c$") +set_target_properties(firmware_c5 PROPERTIES SOURCES "${_c5_srcs}") +target_include_directories(firmware_c5 SYSTEM PRIVATE ${HLE_INCLUDE_DIR} ${C5_INCLUDES} ${cjson_SOURCE_DIR}) +target_compile_options(firmware_c5 PRIVATE -Wno-implicit-function-declaration) + +add_library(firmware_p4 STATIC + ${P4_CORE_SRC} ${P4_MAIN_SRC} + ${P4_DRV} ${P4_DRV_DEEP} ${P4_SVC} ${P4_SVC_DEEP} + ${P4_APP} ${P4_APP_DEEP} ${P4_APP_DEEP2} +) +target_include_directories(firmware_p4 SYSTEM PRIVATE ${HLE_INCLUDE_DIR} ${P4_INCLUDES}) +target_include_directories(firmware_p4 SYSTEM PRIVATE ${cjson_SOURCE_DIR} ${lvgl_SOURCE_DIR}/src) +target_compile_options(firmware_p4 PRIVATE -Wno-implicit-function-declaration) +target_link_libraries(firmware_p4 PUBLIC lvgl) + +# P4: exclude hardware drivers and transports that need deep external stacks +get_target_property(_p4_srcs firmware_p4 SOURCES) +list(FILTER _p4_srcs EXCLUDE REGEX ".*spi_bridge_phy.*\\.c$") +list(FILTER _p4_srcs EXCLUDE REGEX ".*c5_flasher.*\\.c$") +list(FILTER _p4_srcs EXCLUDE REGEX ".*bad_usb.*\\.c$") +list(FILTER _p4_srcs EXCLUDE REGEX ".*LoRa/.*\\.c$") +list(FILTER _p4_srcs EXCLUDE REGEX ".*/Service/host_link/.*\\.c$") +# WiFi application backends (need esp_wifi/lwIP) and their UI screens +list(FILTER _p4_srcs EXCLUDE REGEX ".*/Applications/wifi/.*\\.c$") +list(FILTER _p4_srcs EXCLUDE REGEX ".*/ui/screens/wifi/.*\\.c$") +list(FILTER _p4_srcs EXCLUDE REGEX ".*/ui/screens/connect_wifi/.*\\.c$") +list(FILTER _p4_srcs EXCLUDE REGEX ".*tusb.*\\.c$") +list(FILTER _p4_srcs EXCLUDE REGEX ".*ys_rfid2.*hal.*\\.c$") +list(FILTER _p4_srcs EXCLUDE REGEX ".*/nfc/.*/nfc_crypto\\.c$") +list(FILTER _p4_srcs EXCLUDE REGEX ".*/nfc/.*/mf_desfire\\.c$") +list(FILTER _p4_srcs EXCLUDE REGEX ".*/nfc/.*/mf_ultralight\\.c$") +list(FILTER _p4_srcs EXCLUDE REGEX ".*/nfc/.*/t2t_emu\\.c$") +list(FILTER _p4_srcs EXCLUDE REGEX ".*storage_vfs/vfs_sdcard.*\\.c$") +list(FILTER _p4_srcs EXCLUDE REGEX ".*storage_vfs/vfs_spiffs.*\\.c$") +list(FILTER _p4_srcs EXCLUDE REGEX ".*storage_vfs/vfs_littlefs.*\\.c$") +list(FILTER _p4_srcs EXCLUDE REGEX ".*storage_vfs/vfs_ramfs.*\\.c$") +list(FILTER _p4_srcs EXCLUDE REGEX ".*console/.*\\.c$") +list(FILTER _p4_srcs EXCLUDE REGEX ".*ota/.*\\.c$") +list(FILTER _p4_srcs EXCLUDE REGEX ".*sd_card/.*\\.c$") +list(FILTER _p4_srcs EXCLUDE REGEX ".*lv_port_disp\\.c$") +list(FILTER _p4_srcs EXCLUDE REGEX ".*lv_port_indev\\.c$") +list(FILTER _p4_srcs EXCLUDE REGEX ".*lv_port_touch\\.c$") +set_target_properties(firmware_p4 PROPERTIES SOURCES "${_p4_srcs}") + +# ── Tests ──────────────────────────────────────────────────────────────────── +enable_testing() +file(GLOB_RECURSE TEST_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/tests/*.cpp") +add_executable(hle_tests ${TEST_SOURCES}) +target_link_libraries(hle_tests PRIVATE hle_shims GTest::gtest_main pthread) +target_include_directories(hle_tests PRIVATE ${HLE_INCLUDE_DIR}) +target_link_options(hle_tests PRIVATE ${HLE_FS_WRAP_OPTIONS}) +include(GoogleTest) +gtest_discover_tests(hle_tests) + +# ── Interactive ────────────────────────────────────────────────────────────── +if(HLE_BUILD_INTERACTIVE) + set(INTERACTIVE_SRC + "${HLE_SRC_DIR}/main.cpp" + "${HLE_SRC_DIR}/hal/lv_port_disp_hle.cpp" + "${HLE_SRC_DIR}/hal/lv_port_indev_hle.cpp" + "${HLE_SRC_DIR}/hal/hle_kernel.cpp" + "${HLE_SRC_DIR}/hal/hle_stub_system.cpp" + "${HLE_SRC_DIR}/hal/hle_stub_ui_wifi.cpp" + "${HLE_SRC_DIR}/hal/hle_stub_dispatchers.cpp" + ) + add_executable(hle_interactive ${INTERACTIVE_SRC}) + target_include_directories(hle_interactive PRIVATE ${HLE_INCLUDE_DIR}) + target_include_directories(hle_interactive SYSTEM PRIVATE ${P4_INCLUDES} ${lvgl_SOURCE_DIR} ${lvgl_SOURCE_DIR}/src) + target_compile_definitions(hle_interactive PRIVATE HLE_ASSETS_DIR="${HLE_ASSETS_DIR}") + target_link_options(hle_interactive PRIVATE ${HLE_FS_WRAP_OPTIONS}) + target_link_libraries(hle_interactive PRIVATE hle_shims firmware_p4 firmware_c5 lvgl SDL2::SDL2 pthread cjson) + add_dependencies(hle_interactive hle_assets) + message(STATUS "SDL2 found — hle_interactive enabled") +endif() diff --git a/tools/hle/include/argtable3/argtable3.h b/tools/hle/include/argtable3/argtable3.h new file mode 100644 index 00000000..35057c37 --- /dev/null +++ b/tools/hle/include/argtable3/argtable3.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct arg_int { int count; int *ival; const char *shortopts; const char *longopts; const char *datatype; const char *glossary; } arg_int_t; +typedef struct arg_str { int count; const char **sval; const char *shortopts; const char *longopts; const char *datatype; const char *glossary; } arg_str_t; +typedef struct arg_lit { int count; const char *shortopts; const char *longopts; const char *glossary; } arg_lit_t; +typedef struct arg_file { int count; const char **filename; const char *shortopts; const char *longopts; const char *datatype; const char *glossary; } arg_file_t; +typedef struct arg_end { int count; const char *shortopts; const char *longopts; const char *datatype; const char *glossary; } arg_end_t; + +arg_end_t *arg_end_n(int n); +arg_int_t *arg_int0(const char *s, const char *l, const char *d, const char *g); +arg_int_t *arg_int1(const char *s, const char *l, const char *d, const char *g); +arg_str_t *arg_str0(const char *s, const char *l, const char *d, const char *g); +arg_str_t *arg_str1(const char *s, const char *l, const char *d, const char *g); +arg_lit_t *arg_lit0(const char *s, const char *l, const char *g); +arg_file_t *arg_file0(const char *s, const char *l, const char *d, const char *g); +int arg_parse(int argc, char **argv, void **table); +void arg_freetable(void **table, int n); +void arg_print_errors(FILE *fp, const arg_end_t *end, const char *prog); + +#define arg_end(n) arg_end_n(n) +#define arg_int0(s,l,d,g) arg_int0(s,l,d,g) +#define arg_int1(s,l,d,g) arg_int1(s,l,d,g) +#define arg_str0(s,l,d,g) arg_str0(s,l,d,g) +#define arg_str1(s,l,d,g) arg_str1(s,l,d,g) +#define arg_lit0(s,l,g) arg_lit0(s,l,g) +#define arg_file0(s,l,d,g) arg_file0(s,l,d,g) + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/driver/gpio.h b/tools/hle/include/driver/gpio.h new file mode 100644 index 00000000..a35203a2 --- /dev/null +++ b/tools/hle/include/driver/gpio.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef uint8_t gpio_num_t; + +#define GPIO_NUM_NC (-1) +#define GPIO_NUM_0 0 +#define GPIO_NUM_1 1 +#define GPIO_NUM_2 2 +#define GPIO_NUM_3 3 +#define GPIO_NUM_4 4 +#define GPIO_NUM_5 5 +#define GPIO_NUM_6 6 +#define GPIO_NUM_7 7 +#define GPIO_NUM_8 8 +#define GPIO_NUM_9 9 +#define GPIO_NUM_10 10 +#define GPIO_NUM_11 11 +#define GPIO_NUM_12 12 +#define GPIO_NUM_13 13 +#define GPIO_NUM_14 14 +#define GPIO_NUM_15 15 +#define GPIO_NUM_16 16 +#define GPIO_NUM_17 17 +#define GPIO_NUM_18 18 +#define GPIO_NUM_19 19 +#define GPIO_NUM_20 20 +#define GPIO_NUM_21 21 +#define GPIO_NUM_22 22 +#define GPIO_NUM_23 23 +#define GPIO_NUM_24 24 +#define GPIO_NUM_25 25 +#define GPIO_NUM_26 26 +#define GPIO_NUM_27 27 +#define GPIO_NUM_32 32 +#define GPIO_NUM_33 33 +#define GPIO_NUM_38 38 +#define GPIO_NUM_39 39 +#define GPIO_NUM_40 40 +#define GPIO_NUM_41 41 +#define GPIO_NUM_42 42 +#define GPIO_NUM_43 43 +#define GPIO_NUM_44 44 +#define GPIO_NUM_45 45 +#define GPIO_NUM_46 46 +#define GPIO_NUM_47 47 +#define GPIO_NUM_48 48 +#define GPIO_NUM_54 54 + +typedef struct { + uint64_t pin_bit_mask; + int mode; + int pull_up_en; + int pull_down_en; + int intr_type; +} gpio_config_t; + +#define GPIO_INTR_DISABLE 0 +#define GPIO_INTR_POSEDGE 1 +#define GPIO_INTR_NEGEDGE 2 +#define GPIO_INTR_ANYEDGE 3 +#define GPIO_MODE_INPUT 1 +#define GPIO_MODE_OUTPUT 2 +#define GPIO_MODE_INPUT_OUTPUT 3 +#define GPIO_MODE_INPUT_OUTPUT_OD 4 +#define GPIO_PULLUP_ENABLE 1 +#define GPIO_PULLUP_DISABLE 0 +#define GPIO_PULLDOWN_ENABLE 1 +#define GPIO_PULLDOWN_DISABLE 0 + +esp_err_t gpio_config(const gpio_config_t *cfg); +esp_err_t gpio_set_level(gpio_num_t pin, uint32_t level); +int gpio_get_level(gpio_num_t pin); +esp_err_t gpio_install_isr_service(int flags); +esp_err_t gpio_isr_handler_add(gpio_num_t pin, void (*handler)(void *), void *arg); +void gpio_reset_pin(gpio_num_t pin); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/driver/i2c.h b/tools/hle/include/driver/i2c.h new file mode 100644 index 00000000..a2316447 --- /dev/null +++ b/tools/hle/include/driver/i2c.h @@ -0,0 +1,47 @@ +#pragma once + +#include + +#include "esp_err.h" +#include "driver/gpio.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int i2c_port_t; +#define I2C_NUM_0 0 + +typedef int i2c_cmd_handle_t; + +typedef struct { + int mode; + int sda_io_num; + int scl_io_num; + bool sda_pullup_en; + bool scl_pullup_en; + struct { + uint32_t clk_speed; + } master; +} i2c_config_t; + +#define I2C_MODE_MASTER 1 +#define I2C_MODE_SLAVE 2 +#define ACK_CHECK_EN 1 +#define I2C_MASTER_WRITE 0 +#define I2C_MASTER_READ 1 +#define I2C_MASTER_LAST_NACK 1 + +esp_err_t i2c_param_config(i2c_port_t port, const i2c_config_t *cfg); +esp_err_t i2c_driver_install(i2c_port_t port, int mode, int rx_buf, int tx_buf, int flags); +i2c_cmd_handle_t i2c_cmd_link_create(void); +esp_err_t i2c_master_start(i2c_cmd_handle_t cmd); +esp_err_t i2c_master_write_byte(i2c_cmd_handle_t cmd, uint8_t data, bool ack_en); +esp_err_t i2c_master_read_byte(i2c_cmd_handle_t cmd, uint8_t *data, int ack); +esp_err_t i2c_master_stop(i2c_cmd_handle_t cmd); +esp_err_t i2c_master_cmd_begin(i2c_port_t port, i2c_cmd_handle_t cmd, int timeout_ms); +void i2c_cmd_link_delete(i2c_cmd_handle_t cmd); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/driver/ledc.h b/tools/hle/include/driver/ledc.h new file mode 100644 index 00000000..3c2f102e --- /dev/null +++ b/tools/hle/include/driver/ledc.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int ledc_timer_t; +typedef int ledc_channel_t; +typedef int ledc_mode_t; +#define LEDC_LOW_SPEED_MODE 0 +#define LEDC_TIMER_0 0 +#define LEDC_CHANNEL_0 0 +#define LEDC_TIMER_10_BIT 10 +#define LEDC_TIMER_13_BIT 13 +#define LEDC_AUTO_CLK 0 +#define LEDC_INTR_DISABLE 0 + +typedef struct { int speed_mode; int timer_num; int freq_hz; int duty_resolution; int clk_cfg; } ledc_timer_config_t; +typedef struct { int gpio_num; int speed_mode; int channel; int timer_sel; int duty; int hpoint; int intr_type; } ledc_channel_config_t; + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/driver/rmt.h b/tools/hle/include/driver/rmt.h new file mode 100644 index 00000000..5188f9b2 --- /dev/null +++ b/tools/hle/include/driver/rmt.h @@ -0,0 +1,34 @@ +#pragma once + +/* Minimal ESP-IDF 5.5.3-compatible RMT API declarations for HLE build. + * Behavioral semantics are intentionally incomplete — do not use for ABI interop. */ + +#include +#include +#include + +#include "esp_err.h" +#include "driver/rmt_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define RMT_CLK_SRC_DEFAULT 0 + +esp_err_t rmt_new_rx_channel(const rmt_rx_channel_config_t *config, rmt_channel_handle_t *ret_chan); +esp_err_t rmt_new_tx_channel(const rmt_tx_channel_config_t *config, rmt_channel_handle_t *ret_chan); +esp_err_t rmt_new_copy_encoder(const rmt_copy_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder); +esp_err_t rmt_rx_register_event_callbacks(rmt_channel_handle_t rx_chan, const rmt_rx_event_callbacks_t *cbs, void *user_data); +esp_err_t rmt_enable(rmt_channel_handle_t chan); +esp_err_t rmt_disable(rmt_channel_handle_t chan); +esp_err_t rmt_receive(rmt_channel_handle_t rx_chan, rmt_symbol_word_t *buffer, size_t buffer_size, const rmt_receive_config_t *config); +esp_err_t rmt_transmit(rmt_channel_handle_t tx_chan, rmt_encoder_handle_t encoder, const void *payload, size_t payload_bytes, const rmt_transmit_config_t *config); +esp_err_t rmt_tx_wait_all_done(rmt_channel_handle_t tx_chan, int timeout_ms); +esp_err_t rmt_del_channel(rmt_channel_handle_t chan); +esp_err_t rmt_del_encoder(rmt_encoder_handle_t encoder); +esp_err_t rmt_apply_carrier(rmt_channel_handle_t tx_chan, const rmt_carrier_config_t *config); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/driver/rmt_encoder.h b/tools/hle/include/driver/rmt_encoder.h new file mode 100644 index 00000000..549f75b6 --- /dev/null +++ b/tools/hle/include/driver/rmt_encoder.h @@ -0,0 +1,3 @@ +#pragma once +/* Passthrough — ESP-IDF organizes RMT encoder API in its own header. */ +#include "driver/rmt.h" diff --git a/tools/hle/include/driver/rmt_rx.h b/tools/hle/include/driver/rmt_rx.h new file mode 100644 index 00000000..4e2f0f10 --- /dev/null +++ b/tools/hle/include/driver/rmt_rx.h @@ -0,0 +1,3 @@ +#pragma once +/* Passthrough — ESP-IDF organizes RMT headers by direction. */ +#include "driver/rmt.h" diff --git a/tools/hle/include/driver/rmt_tx.h b/tools/hle/include/driver/rmt_tx.h new file mode 100644 index 00000000..4e2f0f10 --- /dev/null +++ b/tools/hle/include/driver/rmt_tx.h @@ -0,0 +1,3 @@ +#pragma once +/* Passthrough — ESP-IDF organizes RMT headers by direction. */ +#include "driver/rmt.h" diff --git a/tools/hle/include/driver/rmt_types.h b/tools/hle/include/driver/rmt_types.h new file mode 100644 index 00000000..fd275799 --- /dev/null +++ b/tools/hle/include/driver/rmt_types.h @@ -0,0 +1,107 @@ +#pragma once + +/* Minimal ESP-IDF 5.5.3-compatible RMT type declarations for HLE build. + * Behavioral semantics are intentionally incomplete — do not use for ABI interop. + * Layout-sensitive types mirror upstream bitfield patterns and are guarded by + * static assertions where data integrity matters for protocol encode/decode. */ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef union { + struct { + uint16_t duration0 : 15; + uint16_t level0 : 1; + uint16_t duration1 : 15; + uint16_t level1 : 1; + }; + uint32_t val; +} rmt_symbol_word_t; + +#ifdef __cplusplus +static_assert(sizeof(rmt_symbol_word_t) == sizeof(uint32_t), + "rmt_symbol_word_t must be 4 bytes"); +#else +_Static_assert(sizeof(rmt_symbol_word_t) == sizeof(uint32_t), + "rmt_symbol_word_t must be 4 bytes"); +#endif + +typedef void *rmt_channel_handle_t; +typedef void *rmt_encoder_handle_t; + +typedef struct { + struct { + uint32_t invert_in : 1; + uint32_t with_dma : 1; + uint32_t io_loop_back : 1; + } flags; + uint32_t resolution_hz; + int gpio_num; + int clk_src; + int mem_block_symbols; +} rmt_rx_channel_config_t; + +typedef struct { + struct { + uint32_t invert_out : 1; + uint32_t with_dma : 1; + uint32_t io_loop_back : 1; + uint32_t io_od_mode : 1; + } flags; + uint32_t resolution_hz; + int gpio_num; + int clk_src; + int mem_block_symbols; + int trans_queue_depth; +} rmt_tx_channel_config_t; + +typedef struct { + rmt_symbol_word_t *buffer; + size_t num_symbols; + uint32_t signal_range_min_ns; + uint32_t signal_range_max_ns; +} rmt_receive_config_t; + +typedef struct { + struct { + uint32_t invert_out : 1; + } flags; +} rmt_copy_encoder_config_t; + +typedef struct { + rmt_channel_handle_t channel; + rmt_symbol_word_t *received_symbols; + size_t num_received_symbols; + size_t num_symbols; +} rmt_rx_done_event_data_t; + +typedef bool (*rmt_rx_event_callback_t)(rmt_channel_handle_t channel, + const rmt_rx_done_event_data_t *edata, + void *user_ctx); + +typedef struct { + rmt_rx_event_callback_t on_recv_done; + void *user_ctx; +} rmt_rx_event_callbacks_t; + +typedef struct { + uint32_t loop_count; + uint32_t flags; +} rmt_transmit_config_t; + +typedef struct { + struct { + uint32_t polarity_active_low : 1; + } flags; + uint32_t duty_cycle; + uint32_t frequency_hz; +} rmt_carrier_config_t; + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/driver/sdmmc_defs.h b/tools/hle/include/driver/sdmmc_defs.h new file mode 100644 index 00000000..85e9e4e8 --- /dev/null +++ b/tools/hle/include/driver/sdmmc_defs.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define SDMMC_SLOT_FLAG_INTERNAL_PULLUP 1 + +typedef int sdmmc_csd_t; +typedef int sdmmc_cid_t; + +esp_err_t sdmmc_card_init(const void *host, sdmmc_csd_t *csd, sdmmc_cid_t *cid, sdmmc_csd_t *scr, sdmmc_card_t *card); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/driver/sdspi_host.h b/tools/hle/include/driver/sdspi_host.h new file mode 100644 index 00000000..46f90fe2 --- /dev/null +++ b/tools/hle/include/driver/sdspi_host.h @@ -0,0 +1,16 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "driver/spi_master.h" + +esp_err_t sdspi_host_init(void); +esp_err_t sdspi_host_init_device(const void *cfg, void **handle); +esp_err_t sdspi_host_remove_device(void *handle); +esp_err_t sdspi_host_deinit(void); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/driver/spi_common.h b/tools/hle/include/driver/spi_common.h new file mode 100644 index 00000000..9d3f2e73 --- /dev/null +++ b/tools/hle/include/driver/spi_common.h @@ -0,0 +1,16 @@ +#pragma once + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +esp_err_t spi_bus_remove_device(int handle); +esp_err_t spi_bus_get_max_transaction_len(int host, size_t *max_len); + +#define ESP_SPI_DMA_BUF_ALIGN 4 + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/driver/spi_master.h b/tools/hle/include/driver/spi_master.h new file mode 100644 index 00000000..8310981e --- /dev/null +++ b/tools/hle/include/driver/spi_master.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include + +#include "esp_err.h" +#include "driver/gpio.h" +#include "soc/spi_periph.h" +#include "esp_heap_caps.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int spi_host_device_t; +typedef void *spi_device_handle_t; + +#define SPI2_HOST 1 +#define SPI3_HOST 2 +#define SPI_DMA_CH_AUTO 0 + +typedef struct { + int command_bits; + int address_bits; + int dummy_bits; + int mode; + int clock_speed_hz; + int spics_io_num; + int queue_size; + int flags; + int cs_ena_pretrans; + int cs_ena_posttrans; +} spi_device_interface_config_t; + +typedef struct { + int mosi_io_num; + int miso_io_num; + int sclk_io_num; + int quadwp_io_num; + int quadhd_io_num; + int max_transfer_sz; + int flags; +} spi_bus_config_t; + +typedef struct { + size_t length; + const void *tx_buffer; + void *rx_buffer; + uint32_t flags; + uint8_t tx_data[4]; + uint8_t rx_data[4]; +} spi_transaction_t; + +#define SPI_TRANS_USE_TXDATA (1 << 0) +#define SPI_TRANS_USE_RXDATA (1 << 1) + +esp_err_t spi_bus_initialize(spi_host_device_t host, const spi_bus_config_t *bus, int dma); +esp_err_t spi_bus_add_device(spi_host_device_t host, const spi_device_interface_config_t *dev, spi_device_handle_t *handle); +esp_err_t spi_device_transmit(spi_device_handle_t handle, const void *trans); +esp_err_t spi_device_polling_transmit(spi_device_handle_t handle, const void *trans); +esp_err_t spi_bus_remove_device(spi_device_handle_t handle); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/driver/spi_slave.h b/tools/hle/include/driver/spi_slave.h new file mode 100644 index 00000000..822bc555 --- /dev/null +++ b/tools/hle/include/driver/spi_slave.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +#include "esp_err.h" +#include "driver/spi_master.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// spi_slave_transaction_t matches the real ESP-IDF struct +typedef struct { + size_t length; + void *trans_len; + const void *tx_buffer; + void *rx_buffer; +} spi_slave_transaction_t; + +typedef struct { + int spics_io_num; + int queue_size; + int mode; + int flags; +} spi_slave_interface_config_t; + +esp_err_t spi_slave_initialize(int host, const spi_bus_config_t *bus, const spi_slave_interface_config_t *cfg, int dma); +esp_err_t spi_slave_transmit(int host, spi_slave_transaction_t *trans, int timeout); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/driver/uart.h b/tools/hle/include/driver/uart.h new file mode 100644 index 00000000..636508b7 --- /dev/null +++ b/tools/hle/include/driver/uart.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int uart_port_t; +#define UART_NUM_0 0 +#define UART_NUM_1 1 +#define UART_NUM_2 2 + +typedef struct { int baud_rate; int data_bits; int parity; int stop_bits; } uart_config_t; + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/esp_bt.h b/tools/hle/include/esp_bt.h new file mode 100644 index 00000000..8774b532 --- /dev/null +++ b/tools/hle/include/esp_bt.h @@ -0,0 +1,17 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#define ESP_BT_MODE_BTDM 0 +#define ESP_BT_MODE_BLE 1 +void esp_bt_controller_mem_release(int mode); +esp_err_t esp_bt_controller_enable(int mode); +esp_err_t esp_bt_controller_disable(void); +esp_err_t esp_bt_controller_init(void *cfg); +void esp_bt_controller_deinit(void); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/esp_console.h b/tools/hle/include/esp_console.h new file mode 100644 index 00000000..00aaf3c8 --- /dev/null +++ b/tools/hle/include/esp_console.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + const char *command; + const char *help; + const char *hint; + int (*func)(int argc, char **argv); + void *argtable; +} esp_console_cmd_t; + +esp_err_t esp_console_cmd_register(const esp_console_cmd_t *cmd); +esp_err_t esp_console_init(const void *config); +esp_err_t esp_console_start(void); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/esp_err.h b/tools/hle/include/esp_err.h new file mode 100644 index 00000000..20da9fac --- /dev/null +++ b/tools/hle/include/esp_err.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int32_t esp_err_t; + +#define ESP_OK 0 +#define ESP_FAIL (-1) +#define ESP_ERR_NO_MEM 0x101 +#define ESP_ERR_INVALID_ARG 0x102 +#define ESP_ERR_INVALID_STATE 0x103 +#define ESP_ERR_INVALID_SIZE 0x104 +#define ESP_ERR_NOT_FOUND 0x105 +#define ESP_ERR_NOT_SUPPORTED 0x106 +#define ESP_ERR_TIMEOUT 0x107 +#define ESP_ERR_INVALID_RESPONSE 0x108 +#define ESP_ERR_INVALID_CRC 0x109 +#define ESP_ERR_INVALID_VERSION 0x10A +#define ESP_ERR_INVALID_MAC 0x10B +#define ESP_ERR_NOT_FINISHED 0x10C +#define ESP_ERR_NOT_ALLOWED 0x10D + +#define ESP_ERR_NVS_BASE 0x1100 +#define ESP_ERR_NVS_NOT_INITIALIZED (ESP_ERR_NVS_BASE + 0x01) +#define ESP_ERR_NVS_NOT_FOUND (ESP_ERR_NVS_BASE + 0x02) +#define ESP_ERR_NVS_TYPE_MISMATCH (ESP_ERR_NVS_BASE + 0x03) +#define ESP_ERR_NVS_READ_ONLY (ESP_ERR_NVS_BASE + 0x04) +#define ESP_ERR_NVS_NOT_ENOUGH_SPACE (ESP_ERR_NVS_BASE + 0x05) +#define ESP_ERR_NVS_INVALID_NAME (ESP_ERR_NVS_BASE + 0x06) +#define ESP_ERR_NVS_INVALID_HANDLE (ESP_ERR_NVS_BASE + 0x07) +#define ESP_ERR_NVS_REMOVE_FAILED (ESP_ERR_NVS_BASE + 0x08) +#define ESP_ERR_NVS_KEY_TOO_LONG (ESP_ERR_NVS_BASE + 0x09) +#define ESP_ERR_NVS_INVALID_LENGTH (ESP_ERR_NVS_BASE + 0x0C) +#define ESP_ERR_NVS_NO_FREE_PAGES (ESP_ERR_NVS_BASE + 0x0D) +#define ESP_ERR_NVS_VALUE_TOO_LONG (ESP_ERR_NVS_BASE + 0x0E) +#define ESP_ERR_NVS_NEW_VERSION_FOUND (ESP_ERR_NVS_BASE + 0x10) + +#define ESP_OK_CHECK(x) do { esp_err_t _err = (x); if (_err != ESP_OK) return _err; } while(0) + +#define ESP_ERROR_CHECK(x) do { \ + esp_err_t _esp_err_rc = (x); \ + if (_esp_err_rc != ESP_OK) { \ + _esp_error_check_failed(_esp_err_rc, __FILE__, __LINE__, \ + __FUNCTION__, #x); \ + } \ + } while(0) + +void _esp_error_check_failed(esp_err_t rc, const char *file, int line, + const char *func, const char *expr); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/esp_event.h b/tools/hle/include/esp_event.h new file mode 100644 index 00000000..69c1d894 --- /dev/null +++ b/tools/hle/include/esp_event.h @@ -0,0 +1,27 @@ +#pragma once + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int esp_event_base_t; +typedef int esp_event_loop_handle_t; +typedef int esp_event_handler_instance_t; + +typedef void (*esp_event_handler_t)(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data); + +#define ESP_EVENT_ANY_ID (-1) + +esp_err_t esp_event_loop_create_default(void); +esp_err_t esp_event_handler_register(esp_event_base_t base, int32_t id, esp_event_handler_t handler, void *ctx); +esp_err_t esp_event_handler_unregister(esp_event_base_t base, int32_t id, esp_event_handler_t handler); +esp_err_t esp_event_post(esp_event_base_t base, int32_t id, void *data, size_t len, unsigned int timeout); + +#define WIFI_EVENT ((esp_event_base_t)0x01) +#define IP_EVENT ((esp_event_base_t)0x02) + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/esp_heap_caps.h b/tools/hle/include/esp_heap_caps.h new file mode 100644 index 00000000..993b42e5 --- /dev/null +++ b/tools/hle/include/esp_heap_caps.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static inline void *heap_caps_malloc(size_t size, uint32_t caps) { + (void)caps; + void *p = malloc(size); + if (p) memset(p, 0, size); + return p; +} + +static inline void *heap_caps_aligned_alloc(size_t alignment, size_t size, uint32_t caps) { + (void)alignment; (void)caps; + void *p = malloc(size); + if (p) memset(p, 0, size); + return p; +} + +static inline size_t heap_caps_get_free_size(uint32_t caps) { (void)caps; return 8 * 1024 * 1024; } + +#define MALLOC_CAP_DEFAULT 0 +#define MALLOC_CAP_8BIT 0 +#define MALLOC_CAP_SPIRAM 0 +#define MALLOC_CAP_DMA 0 +#define MALLOC_CAP_INTERNAL 0 + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/esp_http_server.h b/tools/hle/include/esp_http_server.h new file mode 100644 index 00000000..fa2f6acd --- /dev/null +++ b/tools/hle/include/esp_http_server.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int httpd_handle_t; +typedef int httpd_req_t; +typedef int httpd_config_t; +typedef int httpd_uri_t; +typedef int httpd_method_t; +typedef int ssize_t; + +#define HTTPD_DEFAULT_CONFIG() ((httpd_config_t){0}) +#define HTTPD_200 "200 OK" +#define HTTPD_400 "400 Bad Request" +#define HTTPD_500 "500 Internal Server Error" +#define HTTPD_TYPE_TEXT "text/plain" +#define HTTPD_TYPE_HTML "text/html" +#define ESP_ERR_HTTPD_BASE 0x8000 + +esp_err_t httpd_start(httpd_handle_t *h, const httpd_config_t *cfg); +esp_err_t httpd_stop(httpd_handle_t h); +esp_err_t httpd_register_uri_handler(httpd_handle_t h, const httpd_uri_t *uri); +esp_err_t httpd_resp_send(httpd_req_t *req, const char *buf, ssize_t len); +esp_err_t httpd_req_get_url_query_str(httpd_req_t *req, char *buf, size_t len); +esp_err_t httpd_query_key_value(const char *query, const char *key, char *val, size_t len); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/esp_lcd_panel_io.h b/tools/hle/include/esp_lcd_panel_io.h new file mode 100644 index 00000000..9798070e --- /dev/null +++ b/tools/hle/include/esp_lcd_panel_io.h @@ -0,0 +1,3 @@ +#pragma once + +#include "esp_lcd_panel_ops.h" diff --git a/tools/hle/include/esp_lcd_panel_ops.h b/tools/hle/include/esp_lcd_panel_ops.h new file mode 100644 index 00000000..5ff94421 --- /dev/null +++ b/tools/hle/include/esp_lcd_panel_ops.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void *esp_lcd_panel_io_handle_t; +typedef void *esp_lcd_panel_handle_t; + +typedef struct { + int cs_gpio_num; + int dc_gpio_num; + int spi_mode; + int pclk_hz; + int trans_queue_depth; + int lcd_cmd_bits; + int lcd_param_bits; + int flags; +} esp_lcd_panel_io_spi_config_t; + +typedef struct { + int reset_gpio_num; + int x_gap; + int y_gap; + int color_space; + int bits_per_pixel; + int flags; + int rgb_ele_order; +} esp_lcd_panel_dev_config_t; + +#define LCD_RGB_ELEMENT_ORDER_RGB 0 + +typedef enum { + ESP_LCD_COLOR_SPACE_RGB = 0, + ESP_LCD_COLOR_SPACE_BGR, + ESP_LCD_COLOR_SPACE_MONOCHROME, +} esp_lcd_color_space_t; + +typedef struct { + int dummy; +} esp_lcd_panel_io_event_data_t; + +typedef bool (*esp_lcd_panel_io_color_trans_done_cb_t)(esp_lcd_panel_io_handle_t, esp_lcd_panel_io_event_data_t *, void *); + +typedef struct { + esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; +} esp_lcd_panel_io_callbacks_t; + +esp_err_t esp_lcd_new_panel_io_spi(int bus, const esp_lcd_panel_io_spi_config_t *cfg, esp_lcd_panel_io_handle_t *out_io); +esp_err_t esp_lcd_new_panel_st7789(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *cfg, esp_lcd_panel_handle_t *out_panel); + +// Panel ops +esp_err_t esp_lcd_panel_reset(esp_lcd_panel_handle_t panel); +esp_err_t esp_lcd_panel_init(esp_lcd_panel_handle_t panel); +esp_err_t esp_lcd_panel_invert_color(esp_lcd_panel_handle_t panel, bool invert); +esp_err_t esp_lcd_panel_disp_on_off(esp_lcd_panel_handle_t panel, bool on); +esp_err_t esp_lcd_panel_mirror(esp_lcd_panel_handle_t panel, bool mx, bool my); +esp_err_t esp_lcd_panel_swap_xy(esp_lcd_panel_handle_t panel, bool swap); +esp_err_t esp_lcd_panel_set_gap(esp_lcd_panel_handle_t panel, int x, int y); +esp_err_t esp_lcd_panel_draw_bitmap(esp_lcd_panel_handle_t panel, int x0, int y0, int x1, int y1, const void *data); + +// IO ops +esp_err_t esp_lcd_panel_io_register_event_callbacks(esp_lcd_panel_io_handle_t io, + const esp_lcd_panel_io_callbacks_t *cbs, void *ctx); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/esp_lcd_panel_vendor.h b/tools/hle/include/esp_lcd_panel_vendor.h new file mode 100644 index 00000000..9798070e --- /dev/null +++ b/tools/hle/include/esp_lcd_panel_vendor.h @@ -0,0 +1,3 @@ +#pragma once + +#include "esp_lcd_panel_ops.h" diff --git a/tools/hle/include/esp_littlefs.h b/tools/hle/include/esp_littlefs.h new file mode 100644 index 00000000..bb8ecdd1 --- /dev/null +++ b/tools/hle/include/esp_littlefs.h @@ -0,0 +1,25 @@ +#pragma once + +#include "esp_err.h" +#include "esp_partition.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + const char *base_path; + const char *partition_label; + int partition; + bool format_if_mount_failed; + bool dont_mount; + int dummy; +} esp_vfs_littlefs_conf_t; + +esp_err_t esp_vfs_littlefs_register(const esp_vfs_littlefs_conf_t *conf); +esp_err_t esp_vfs_littlefs_unregister(const char *label); +esp_err_t esp_littlefs_info(const char *label, size_t *total, size_t *used); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/esp_log.h b/tools/hle/include/esp_log.h new file mode 100644 index 00000000..320e812f --- /dev/null +++ b/tools/hle/include/esp_log.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +/* Transitive includes mirroring ESP-IDF behavior: real esp_log.h indirectly + * exposes heap_caps and inttypes through its dependency chain. Firmware source + * files rely on this without explicit includes. */ +#include "esp_heap_caps.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ESP_LOG_NONE 0 +#define ESP_LOG_ERROR 1 +#define ESP_LOG_WARN 2 +#define ESP_LOG_INFO 3 +#define ESP_LOG_DEBUG 4 +#define ESP_LOG_VERBOSE 5 + +#define ESP_LOGE(tag, fmt, ...) fprintf(stderr, "E [%s] " fmt "\n", tag, ##__VA_ARGS__) +#define ESP_LOGW(tag, fmt, ...) fprintf(stderr, "W [%s] " fmt "\n", tag, ##__VA_ARGS__) +#define ESP_LOGI(tag, fmt, ...) fprintf(stderr, "I [%s] " fmt "\n", tag, ##__VA_ARGS__) +#define ESP_LOGD(tag, fmt, ...) fprintf(stderr, "D [%s] " fmt "\n", tag, ##__VA_ARGS__) +#define ESP_LOGV(tag, fmt, ...) fprintf(stderr, "V [%s] " fmt "\n", tag, ##__VA_ARGS__) +#define ESP_LOG_BUFFER_HEX_LEVEL(tag, buf, len, level) ((void)0) +#define ESP_LOG_BUFFER_HEXDUMP(tag, buf, len, level) ((void)0) + +typedef int (*vprintf_like_t)(const char *fmt, va_list ap); + +vprintf_like_t esp_log_set_vprintf(vprintf_like_t func); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/esp_mac.h b/tools/hle/include/esp_mac.h new file mode 100644 index 00000000..7ca910fd --- /dev/null +++ b/tools/hle/include/esp_mac.h @@ -0,0 +1,19 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int esp_mac_type_t; +#define ESP_MAC_WIFI_STA 0 +#define ESP_MAC_WIFI_SOFTAP 1 +#define ESP_MAC_BT 2 +#define ESP_MAC_ETH 3 + +esp_err_t esp_read_mac(uint8_t *mac, int type); +esp_err_t esp_efuse_mac_get_default(uint8_t *mac); +esp_err_t esp_base_mac_addr_get(uint8_t *mac); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/esp_netif.h b/tools/hle/include/esp_netif.h new file mode 100644 index 00000000..25d7adc3 --- /dev/null +++ b/tools/hle/include/esp_netif.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int esp_netif_t; +typedef int esp_netif_config_t; + +typedef struct { + uint32_t ip; + uint32_t netmask; + uint32_t gw; +} esp_netif_ip_info_t; + +#define IP2STR(ip) ((ip) & 0xFF), (((ip)>>8) & 0xFF), (((ip)>>16) & 0xFF), (((ip)>>24) & 0xFF) +#define IPSTR "%d.%d.%d.%d" +#define MAC2STR(m) (m)[0], (m)[1], (m)[2], (m)[3], (m)[4], (m)[5] +#define MACSTR "%02x:%02x:%02x:%02x:%02x:%02x" + +esp_netif_t *esp_netif_create_default_wifi_sta(void); +esp_netif_t *esp_netif_create_default_wifi_ap(void); +void esp_netif_destroy(esp_netif_t *netif); +esp_err_t esp_netif_get_ip_info(esp_netif_t *netif, esp_netif_ip_info_t *ip_info); +esp_err_t esp_netif_set_ip_info(esp_netif_t *netif, const esp_netif_ip_info_t *ip_info); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/esp_now.h b/tools/hle/include/esp_now.h new file mode 100644 index 00000000..7be8502f --- /dev/null +++ b/tools/hle/include/esp_now.h @@ -0,0 +1,24 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_err.h" + +esp_err_t esp_now_init(void); +esp_err_t esp_now_deinit(void); +esp_err_t esp_now_register_send_cb(void *cb); +esp_err_t esp_now_register_recv_cb(void *cb); +esp_err_t esp_now_send(const uint8_t *peer, const uint8_t *data, size_t len); + +typedef struct { uint8_t mac[6]; } esp_now_peer_info_t; +typedef void (*service_esp_now_send_cb_t)(const uint8_t *mac, int status); +typedef void (*service_esp_now_recv_cb_t)(const uint8_t *mac, const uint8_t *data, int len, int rssi); + +#define ESP_IF_WIFI_STA 0 +#define ESP_NOW_ETH_ALEN 6 + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/esp_ota_ops.h b/tools/hle/include/esp_ota_ops.h new file mode 100644 index 00000000..321121c3 --- /dev/null +++ b/tools/hle/include/esp_ota_ops.h @@ -0,0 +1,15 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_err.h" + +esp_err_t esp_ota_get_boot_partition(void *partition); +esp_err_t esp_ota_mark_app_valid_cancel_rollback(void); +esp_err_t esp_ota_set_boot_partition(void *partition); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/esp_partition.h b/tools/hle/include/esp_partition.h new file mode 100644 index 00000000..f8b86b08 --- /dev/null +++ b/tools/hle/include/esp_partition.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + uint32_t address; + size_t size; + char label[16]; +} esp_partition_t; + +typedef int esp_partition_subtype_t; +typedef int esp_partition_type_t; + +esp_partition_t *esp_partition_find_first(int type, int subtype, const char *label); +const esp_partition_t *esp_ota_get_running_partition(void); +void esp_partition_get_sha256(const esp_partition_t *p, uint8_t *sha); + +#define ESP_PARTITION_SUBTYPE_ANY (-1) +#define ESP_PARTITION_TYPE_APP 0 + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/esp_random.h b/tools/hle/include/esp_random.h new file mode 100644 index 00000000..1158758e --- /dev/null +++ b/tools/hle/include/esp_random.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static inline uint32_t esp_random(void) { + return (uint32_t)rand(); +} + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/esp_rom_sys.h b/tools/hle/include/esp_rom_sys.h new file mode 100644 index 00000000..95f53753 --- /dev/null +++ b/tools/hle/include/esp_rom_sys.h @@ -0,0 +1,12 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +void esp_rom_delay_us(uint32_t us); +void esp_rom_gpio_pad_select_gpio(uint32_t gpio_num); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/esp_system.h b/tools/hle/include/esp_system.h new file mode 100644 index 00000000..9f78815c --- /dev/null +++ b/tools/hle/include/esp_system.h @@ -0,0 +1,13 @@ +#pragma once + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void esp_restart(void); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/esp_timer.h b/tools/hle/include/esp_timer.h new file mode 100644 index 00000000..44e82822 --- /dev/null +++ b/tools/hle/include/esp_timer.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +uint64_t esp_timer_get_time(void); + +typedef struct { + const char *name; + void (*callback)(void *arg); + void *arg; + uint64_t alarm; + bool dispatch; +} esp_timer_create_args_t; + +typedef void *esp_timer_handle_t; + +esp_err_t esp_timer_create(const esp_timer_create_args_t *args, esp_timer_handle_t *handle); +esp_err_t esp_timer_start_periodic(esp_timer_handle_t handle, uint64_t period_us); +esp_err_t esp_timer_stop(esp_timer_handle_t handle); +esp_err_t esp_timer_delete(esp_timer_handle_t handle); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/esp_vfs_fat.h b/tools/hle/include/esp_vfs_fat.h new file mode 100644 index 00000000..00804795 --- /dev/null +++ b/tools/hle/include/esp_vfs_fat.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +#include "esp_err.h" +#include "sdmmc_cmd.h" + +#ifdef __cplusplus +extern "C" { +#endif + +esp_err_t esp_vfs_fat_sdmmc_mount(const char *base_path, const sdmmc_host_t *host, const void *slot, + const void *mount, sdmmc_card_t **out_card); +esp_err_t esp_vfs_fat_sdmmc_unmount(void); +esp_err_t esp_vfs_fat_sdcard_unmount(const char *base_path, sdmmc_card_t *card); +esp_err_t esp_vfs_fat_spiflash_mount(const char *base, const char *part_label, const void *mount, void *wl_handle); +esp_err_t esp_vfs_fat_spiflash_unmount(const char *base, void *wl_handle); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/esp_wifi.h b/tools/hle/include/esp_wifi.h new file mode 100644 index 00000000..601e32d2 --- /dev/null +++ b/tools/hle/include/esp_wifi.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +#include "esp_wifi_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int wifi_interface_t; +#define WIFI_IF_STA 0 +#define WIFI_IF_AP 1 + +int esp_wifi_init(const void *cfg); +int esp_wifi_start(void); +int esp_wifi_stop(void); +int esp_wifi_disconnect(void); +int esp_wifi_scan_start(const void *cfg, bool block); +int esp_wifi_scan_stop(void); +int esp_wifi_scan_get_ap_num(uint16_t *num); +int esp_wifi_scan_get_ap_records(uint16_t *num, wifi_ap_record_t *rec); +int esp_wifi_set_promiscuous(bool enable); +int esp_wifi_set_promiscuous_filter(const wifi_promiscuous_filter_t *filter); +int esp_wifi_set_promiscuous_rx_cb(wifi_promiscuous_cb_t cb); +int esp_wifi_set_channel(uint8_t primary, int secondary); +int esp_wifi_get_channel(uint8_t *primary, int *secondary); + +#define WIFI_PROTOCOL_11B 1 +#define WIFI_PROTOCOL_11G 2 + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/esp_wifi_types.h b/tools/hle/include/esp_wifi_types.h new file mode 100644 index 00000000..e2c4baeb --- /dev/null +++ b/tools/hle/include/esp_wifi_types.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + WIFI_AUTH_OPEN = 0, + WIFI_AUTH_WEP, + WIFI_AUTH_WPA_PSK, + WIFI_AUTH_WPA2_PSK, + WIFI_AUTH_WPA_WPA2_PSK, + WIFI_AUTH_WPA2_ENTERPRISE, + WIFI_AUTH_WPA3_PSK, + WIFI_AUTH_WPA2_WPA3_PSK, + WIFI_AUTH_WAPI_PSK, + WIFI_AUTH_MAX, +} wifi_auth_mode_t; + +typedef enum { + WIFI_CIPHER_TYPE_NONE = 0, + WIFI_CIPHER_TYPE_WEP40, + WIFI_CIPHER_TYPE_WEP104, + WIFI_CIPHER_TYPE_TKIP, + WIFI_CIPHER_TYPE_CCMP, + WIFI_CIPHER_TYPE_TKIP_CCMP, + WIFI_CIPHER_TYPE_AES_CMAC128, + WIFI_CIPHER_TYPE_SMS4, + WIFI_CIPHER_TYPE_GCMP, + WIFI_CIPHER_TYPE_GCMP256, +} wifi_cipher_type_t; + +typedef struct { + uint8_t bssid[6]; + uint8_t ssid[33]; + uint8_t primary; + int8_t rssi; + wifi_auth_mode_t authmode; + wifi_cipher_type_t pairwise_cipher; + wifi_cipher_type_t group_cipher; + uint16_t count; +} wifi_ap_record_t; + +typedef enum { + WIFI_FILTER_BEACON = 0, + WIFI_FILTER_PROBE_REQ, + WIFI_FILTER_MGMT_MASK, + WIFI_FILTER_DATA_MASK, + WIFI_FILTER_CTRL_MASK, +} wifi_promiscuous_filter_type_t; + +typedef enum { + WIFI_PKT_MGMT = 0, + WIFI_PKT_CTRL, + WIFI_PKT_DATA, + WIFI_PKT_MISC, +} wifi_promiscuous_pkt_type_t; + +typedef struct { + uint32_t filter_mask; +} wifi_promiscuous_filter_t; + +typedef void (*wifi_promiscuous_cb_t)(void *buf, wifi_promiscuous_pkt_type_t type); + +typedef struct { + int dummy; +} wifi_init_config_t; + +#define WIFI_INIT_CONFIG_DEFAULT() ((wifi_init_config_t){0}) +#define WIFI_PROMISC_FILTER_MASK_ALL 0xFFFFFFFF +#define WIFI_PROMIS_FILTER_MASK_ALL 0xFFFFFFFF + +typedef struct { int dummy; } wifi_tx_info_t; + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/ff.h b/tools/hle/include/ff.h new file mode 100644 index 00000000..7de64104 --- /dev/null +++ b/tools/hle/include/ff.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef uint8_t BYTE; +typedef uint16_t WORD; +typedef uint32_t DWORD; +typedef int FRESULT; +typedef int FATFS; +typedef int FIL; +typedef int DIR; + +#define FR_OK 0 +#define FA_READ 0x01 +#define FA_WRITE 0x02 +#define FA_CREATE_NEW 0x04 + +FRESULT f_mount(FATFS *fs, const char *path, int opt); +FRESULT f_open(FIL *fp, const char *path, BYTE mode); +FRESULT f_close(FIL *fp); +FRESULT f_read(FIL *fp, void *buff, unsigned int btr, unsigned int *br); +FRESULT f_write(FIL *fp, const void *buff, unsigned int btw, unsigned int *bw); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/freertos/FreeRTOS.h b/tools/hle/include/freertos/FreeRTOS.h new file mode 100644 index 00000000..16cf8dda --- /dev/null +++ b/tools/hle/include/freertos/FreeRTOS.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include + +#include "FreeRTOSConfig.h" +#include "portmacro.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*TaskFunction_t)(void *); +typedef void *TaskHandle_t; +typedef void *QueueHandle_t; +typedef void *SemaphoreHandle_t; +typedef void *TimerHandle_t; +typedef void *EventGroupHandle_t; + +typedef int32_t BaseType_t; +typedef uint32_t UBaseType_t; +typedef uint32_t TickType_t; +typedef uint32_t EventBits_t; + +typedef uint8_t StackType_t; +typedef struct { union { void *opaque[8]; double _align; }; } StaticTask_t; +typedef struct { union { void *opaque[8]; double _align; }; } StaticQueue_t; + +#define pdFALSE ((BaseType_t)0) +#define pdTRUE ((BaseType_t)1) +#define pdPASS pdTRUE +#define pdFAIL pdFALSE + +#define portMAX_DELAY ((TickType_t)0xFFFFFFFF) +#define portTICK_PERIOD_MS 1 + +static inline TickType_t pdMS_TO_TICKS(uint32_t ms) { return ms / portTICK_PERIOD_MS; } + +BaseType_t xTaskCreatePinnedToCore(TaskFunction_t func, const char *name, uint32_t stack_depth, + void *params, UBaseType_t prio, TaskHandle_t *handle, + BaseType_t core_id); +#define xTaskCreate(func, name, stack, params, prio, handle) \ + xTaskCreatePinnedToCore(func, name, stack, params, prio, handle, 0) + +void vTaskDelete(TaskHandle_t handle); +void vTaskDelay(TickType_t ticks); +TickType_t xTaskGetTickCount(void); +void vTaskSuspend(TaskHandle_t handle); +void vTaskResume(TaskHandle_t handle); +UBaseType_t uxTaskGetStackHighWaterMark(TaskHandle_t handle); +char *pcTaskGetName(TaskHandle_t handle); +TaskHandle_t xTaskGetCurrentTaskHandle(void); + +TaskHandle_t xTaskCreateStatic(TaskFunction_t func, const char *name, uint32_t stack_depth, + void *params, UBaseType_t prio, StackType_t *stack_buf, + StaticTask_t *task_buf); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/freertos/FreeRTOSConfig.h b/tools/hle/include/freertos/FreeRTOSConfig.h new file mode 100644 index 00000000..0625a586 --- /dev/null +++ b/tools/hle/include/freertos/FreeRTOSConfig.h @@ -0,0 +1,17 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#define configMINIMAL_STACK_SIZE 1024 +#define configTICK_RATE_HZ 1000 +#define configMAX_PRIORITIES 25 +#define configUSE_PREEMPTION 1 +#define configUSE_TIME_SLICING 1 +#define portSHORT short +#define tskIDLE_PRIORITY 0 + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/freertos/event_groups.h b/tools/hle/include/freertos/event_groups.h new file mode 100644 index 00000000..c91a769f --- /dev/null +++ b/tools/hle/include/freertos/event_groups.h @@ -0,0 +1,17 @@ +#pragma once + +#include "FreeRTOS.h" + +#ifdef __cplusplus +extern "C" { +#endif + +EventGroupHandle_t xEventGroupCreate(void); +EventBits_t xEventGroupSetBits(EventGroupHandle_t eg, EventBits_t bits); +EventBits_t xEventGroupClearBits(EventGroupHandle_t eg, EventBits_t bits); +EventBits_t xEventGroupWaitBits(EventGroupHandle_t eg, EventBits_t bits, BaseType_t clear, + BaseType_t wait_all, TickType_t timeout); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/freertos/portmacro.h b/tools/hle/include/freertos/portmacro.h new file mode 100644 index 00000000..a9d29c24 --- /dev/null +++ b/tools/hle/include/freertos/portmacro.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define portTICK_PERIOD_MS 1 +#define portSHORT short + +typedef struct { int owner; int count; } portMUX_TYPE; +#define portMUX_INITIALIZER_UNLOCKED { -1, 0 } +static inline void portENTER_CRITICAL(portMUX_TYPE *mux) { (void)mux; } +static inline void portEXIT_CRITICAL(portMUX_TYPE *mux) { (void)mux; } +static inline void portENTER_CRITICAL_ISR(portMUX_TYPE *mux) { (void)mux; } +static inline void portEXIT_CRITICAL_ISR(portMUX_TYPE *mux) { (void)mux; } + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/freertos/queue.h b/tools/hle/include/freertos/queue.h new file mode 100644 index 00000000..23e85401 --- /dev/null +++ b/tools/hle/include/freertos/queue.h @@ -0,0 +1,19 @@ +#pragma once + +#include "FreeRTOS.h" + +#ifdef __cplusplus +extern "C" { +#endif + +QueueHandle_t xQueueCreate(UBaseType_t queue_len, UBaseType_t item_size); +QueueHandle_t xQueueCreateStatic(UBaseType_t queue_len, UBaseType_t item_size, + uint8_t *storage, StaticQueue_t *queue_buf); +BaseType_t xQueueSend(QueueHandle_t queue, const void *item, TickType_t timeout); +BaseType_t xQueueSendFromISR(QueueHandle_t queue, const void *item, BaseType_t *higher_prio_woken); +BaseType_t xQueueReceive(QueueHandle_t queue, void *item, TickType_t timeout); +void vQueueDelete(QueueHandle_t queue); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/freertos/semphr.h b/tools/hle/include/freertos/semphr.h new file mode 100644 index 00000000..3fa1c98a --- /dev/null +++ b/tools/hle/include/freertos/semphr.h @@ -0,0 +1,25 @@ +#pragma once + +#include "FreeRTOS.h" + +#ifdef __cplusplus +extern "C" { +#endif + +SemaphoreHandle_t xSemaphoreCreateMutex(void); +SemaphoreHandle_t xSemaphoreCreateBinary(void); +SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t max, UBaseType_t initial); + +BaseType_t xSemaphoreTake(SemaphoreHandle_t sem, TickType_t timeout); +BaseType_t xSemaphoreGive(SemaphoreHandle_t sem); +BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t sem, BaseType_t *woken); +void vSemaphoreDelete(SemaphoreHandle_t sem); + +// ESP-IDF recursive mutex extensions +SemaphoreHandle_t xSemaphoreCreateRecursiveMutex(void); +BaseType_t xSemaphoreTakeRecursive(SemaphoreHandle_t sem, TickType_t timeout); +BaseType_t xSemaphoreGiveRecursive(SemaphoreHandle_t sem); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/freertos/task.h b/tools/hle/include/freertos/task.h new file mode 100644 index 00000000..31f44a63 --- /dev/null +++ b/tools/hle/include/freertos/task.h @@ -0,0 +1,34 @@ +#pragma once + +#include "FreeRTOS.h" +#include "semphr.h" +#include "queue.h" + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define taskYIELD() sched_yield() + +#define pvPortMalloc(size) malloc(size) +#define vPortFree(p) free(p) + +typedef struct { + TaskHandle_t xHandle; + const char *pcTaskName; + UBaseType_t uxCurrentPriority; + uint32_t usStackHighWaterMark; +} TaskStatus_t; + +UBaseType_t uxTaskGetNumberOfTasks(void); +void vTaskPrioritySet(TaskHandle_t handle, UBaseType_t prio); +void vTaskList(char *buffer); +void vTaskGetRunTimeStats(char *buffer); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/freertos/timers.h b/tools/hle/include/freertos/timers.h new file mode 100644 index 00000000..09d670e1 --- /dev/null +++ b/tools/hle/include/freertos/timers.h @@ -0,0 +1,17 @@ +#pragma once + +#include "FreeRTOS.h" + +#ifdef __cplusplus +extern "C" { +#endif + +TimerHandle_t xTimerCreate(const char *name, TickType_t period, UBaseType_t auto_reload, + void *timer_id, void (*callback)(TimerHandle_t)); +BaseType_t xTimerStart(TimerHandle_t timer, TickType_t timeout); +BaseType_t xTimerStop(TimerHandle_t timer, TickType_t timeout); +BaseType_t xTimerReset(TimerHandle_t timer, TickType_t timeout); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/hal/spi_types.h b/tools/hle/include/hal/spi_types.h new file mode 100644 index 00000000..bc65cce8 --- /dev/null +++ b/tools/hle/include/hal/spi_types.h @@ -0,0 +1,12 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#define SPI_DEVICE_HALFDUPLEX 1 +#define SPI3_HOST 3 + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/hle/cc1101_emu.h b/tools/hle/include/hle/cc1101_emu.h new file mode 100644 index 00000000..be4a0ba6 --- /dev/null +++ b/tools/hle/include/hle/cc1101_emu.h @@ -0,0 +1,141 @@ +#pragma once + +#include +#include +#include +#include + +namespace hle { + +enum CC1101State { CC_IDLE = 0, CC_RX, CC_TX, CC_FSTXON, CC_CALIBRATE, CC_SETTLING }; + +class CC1101Emulator { +public: + CC1101Emulator() { reset(); } + + void reset() { + memset(m_regs, 0, sizeof(m_regs)); + m_tx_fifo.clear(); + m_rx_fifo.clear(); + m_state = CC_IDLE; + m_gdo0 = false; + m_gdo2 = false; + m_chip_status = 0x0F; // IDLE + init_defaults(); + } + + // SPI read: returns chip status byte + data byte (burst = multiple) + uint8_t read_register(uint8_t addr) { + uint8_t burst = (addr & 0xC0) >> 6; + addr &= 0x3F; + + if (addr == 0x3E) { // PATABLE (burst read will iterate) + return m_patable[0]; + } + if (addr == 0x3F) { // TX FIFO read + if (!m_tx_fifo.empty()) { + uint8_t v = m_tx_fifo.front(); + m_tx_fifo.pop_front(); + return v; + } + return 0; + } + if (addr <= 0x2E) return m_regs[addr]; + if (addr >= 0x30) return m_chip_status; // status regs return chip status + return 0; + } + + // SPI write: addr & 0x3F with optional burst bit + void write_register(uint8_t addr, uint8_t data) { + bool burst = (addr & 0x40) != 0; + addr &= 0x3F; + + if (addr == 0x3E) { // PATABLE + m_patable[0] = data; + return; + } + if (addr == 0x3F) { // TX FIFO + if (m_tx_fifo.size() < 64) m_tx_fifo.push_back(data); + return; + } + if (addr <= 0x2E) m_regs[addr] = data; + + // Handle burst writes + (void)burst; + } + + // Command strobe + void strobe(uint8_t addr) { + addr &= 0x3F; + switch (addr) { + case 0x30: m_state = CC_IDLE; break; + case 0x34: m_state = CC_TX; m_chip_status = 0x02; break; + case 0x36: m_state = CC_RX; m_chip_status = 0x01; break; + case 0x3A: flush_rx_fifo(); break; + case 0x3B: flush_tx_fifo(); break; + default: break; + } + } + + uint8_t chip_status() const { return m_chip_status; } + CC1101State state() const { return m_state; } + bool gdo0() const { return m_gdo0; } + bool gdo2() const { return m_gdo2; } + + void set_gdo0(bool v) { m_gdo0 = v; } + void set_gdo2(bool v) { m_gdo2 = v; } + + // For testing: inject received data into RX FIFO + void inject_rx_data(const uint8_t *data, size_t len) { + for (size_t i = 0; i < len && m_rx_fifo.size() < 64; i++) + m_rx_fifo.push_back(data[i]); + } + + const uint8_t *registers() const { return m_regs; } + + // Preset configuration + void apply_preset(const char *preset) { + (void)preset; + // Store preset-like values in regs 0x00-0x0B + m_regs[0x00] = 0x0C; // IOCFG2 + m_regs[0x02] = 0x06; // IOCFG0 + m_regs[0x06] = 0x20; // PKTCTRL0 + m_regs[0x07] = 0x04; // PKTCTRL1 + m_regs[0x0B] = 0x06; // FSCTRL1 + } + + // Frequency set via registers 0x0D-0x0F (FREQ2, FREQ1, FREQ0) + void set_frequency_mhz(float mhz) { + uint32_t f = (uint32_t)((mhz / 26.0f) * 65536.0f); + m_regs[0x0D] = (f >> 16) & 0xFF; + m_regs[0x0E] = (f >> 8) & 0xFF; + m_regs[0x0F] = f & 0xFF; + } + + float frequency_mhz() const { + uint32_t f = ((uint32_t)m_regs[0x0D] << 16) | ((uint32_t)m_regs[0x0E] << 8) | m_regs[0x0F]; + return (float)f * 26.0f / 65536.0f; + } + +private: + void init_defaults() { + m_regs[0x0D] = 0x10; // FREQ2 + m_regs[0x0E] = 0xB1; // FREQ1 + m_regs[0x0F] = 0x3B; // FREQ0 (~433.92 MHz) + m_patable[0] = 0xC0; + } + + void flush_rx_fifo() { m_rx_fifo.clear(); } + void flush_tx_fifo() { m_tx_fifo.clear(); } + + uint8_t m_regs[0x30]; + uint8_t m_patable[8]; + std::deque m_tx_fifo; + std::deque m_rx_fifo; + CC1101State m_state; + uint8_t m_chip_status; + bool m_gdo0; + bool m_gdo2; +}; + +} // namespace hle diff --git a/tools/hle/include/hle/hle_display.h b/tools/hle/include/hle/hle_display.h new file mode 100644 index 00000000..e89e9d5e --- /dev/null +++ b/tools/hle/include/hle/hle_display.h @@ -0,0 +1,135 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace hle { + +static constexpr int LCD_H_RES = 240; +static constexpr int LCD_V_RES = 320; + +class Display { +public: + void draw_bitmap(int x0, int y0, int x1, int y1, const uint16_t *data) { + const int width = x1 - x0; + const int height = y1 - y0; + if (data == nullptr || width <= 0 || height <= 0) { + return; + } + + std::lock_guard lock(m_mutex); + for (int row = 0; row < height; ++row) { + const int y = y0 + row; + if (y < 0 || y >= LCD_V_RES) { + continue; + } + for (int col = 0; col < width; ++col) { + const int x = x0 + col; + if (x >= 0 && x < LCD_H_RES) { + m_pixels[static_cast(y * LCD_H_RES + x)] = + data[static_cast(row * width + col)]; + } + } + } + m_dirty = true; + } + + void draw_bitmap_strided(int x0, int y0, int x1, int y1, const uint8_t *data, + size_t src_stride_bytes) { + const int width = x1 - x0; + const int height = y1 - y0; + if (data == nullptr || width <= 0 || height <= 0) { + return; + } + + std::lock_guard lock(m_mutex); + for (int row = 0; row < height; ++row) { + const int y = y0 + row; + if (y < 0 || y >= LCD_V_RES) { + continue; + } + + const auto *src_row = + reinterpret_cast(data + (row * src_stride_bytes)); + for (int col = 0; col < width; ++col) { + const int x = x0 + col; + if (x >= 0 && x < LCD_H_RES) { + m_pixels[static_cast(y * LCD_H_RES + x)] = src_row[col]; + } + } + } + m_dirty = true; + } + + void fill_screen(uint16_t color) { + std::lock_guard lock(m_mutex); + m_pixels.fill(color); + m_dirty = true; + } + + bool copy_pixels_if_dirty(void *out_pixels, size_t out_stride_bytes) { + constexpr size_t row_bytes = LCD_H_RES * sizeof(uint16_t); + if (out_pixels == nullptr || out_stride_bytes < row_bytes) { + return false; + } + + std::lock_guard lock(m_mutex); + if (!m_dirty) { + return false; + } + + auto *dst = static_cast(out_pixels); + const auto *src = reinterpret_cast(m_pixels.data()); + for (int y = 0; y < LCD_V_RES; ++y) { + memcpy(dst + (static_cast(y) * out_stride_bytes), + src + (static_cast(y) * row_bytes), row_bytes); + } + m_dirty = false; + return true; + } + + int width() const { return LCD_H_RES; } + int height() const { return LCD_V_RES; } + + bool save_ppm(const char *path) { + if (path == nullptr) { + return false; + } + + std::lock_guard lock(m_mutex); + FILE *file = fopen(path, "wb"); + if (file == nullptr) { + return false; + } + + fprintf(file, "P6\n%d %d\n255\n", LCD_H_RES, LCD_V_RES); + for (const uint16_t pixel : m_pixels) { + const uint8_t red = static_cast(((pixel >> 11) & 0x1F) * 255 / 31); + const uint8_t green = static_cast(((pixel >> 5) & 0x3F) * 255 / 63); + const uint8_t blue = static_cast((pixel & 0x1F) * 255 / 31); + fputc(red, file); + fputc(green, file); + fputc(blue, file); + } + + const bool is_write_ok = ferror(file) == 0; + const bool is_close_ok = fclose(file) == 0; + return is_write_ok && is_close_ok; + } + + static Display &instance() { + static Display s_instance; + return s_instance; + } + +private: + std::array m_pixels{}; + bool m_dirty = true; + std::mutex m_mutex; +}; + +} // namespace hle diff --git a/tools/hle/include/hle/hle_kernel.h b/tools/hle/include/hle/hle_kernel.h new file mode 100644 index 00000000..5d477054 --- /dev/null +++ b/tools/hle/include/hle/hle_kernel.h @@ -0,0 +1,15 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +void hle_kernel_init(void); +void hle_kernel_shutdown(void); +void hle_set_button_mask(unsigned char mask); +void hle_set_storage_path(const char *path); +const char *hle_get_storage_path(void); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/hle/lv_port_hle.h b/tools/hle/include/hle/lv_port_hle.h new file mode 100644 index 00000000..2cedb773 --- /dev/null +++ b/tools/hle/include/hle/lv_port_hle.h @@ -0,0 +1,15 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +void hle_lv_port_disp_init(void); +void hle_lv_port_indev_init(void); + +extern void *indev_keypad; +extern void *main_group; + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/hle/sdl_renderer.h b/tools/hle/include/hle/sdl_renderer.h new file mode 100644 index 00000000..a86ae693 --- /dev/null +++ b/tools/hle/include/hle/sdl_renderer.h @@ -0,0 +1,163 @@ +#pragma once + +#include + +#include +#include +#include + +#include "hle/hle_display.h" + +namespace hle { + +class SDLRenderer { +public: + explicit SDLRenderer(int scale = 3) : m_scale(scale > 0 ? scale : 1) {} + + ~SDLRenderer() { shutdown(); } + + bool init() { + if (SDL_Init(SDL_INIT_VIDEO) != 0) { + fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError()); + return false; + } + m_is_sdl_initialized = true; + + m_window = SDL_CreateWindow("TentacleOS HLE", SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, LCD_H_RES * m_scale, + LCD_V_RES * m_scale, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE); + if (m_window == nullptr) { + fprintf(stderr, "SDL_CreateWindow failed: %s\n", SDL_GetError()); + shutdown(); + return false; + } + + m_renderer = SDL_CreateRenderer(m_window, -1, SDL_RENDERER_ACCELERATED); + if (m_renderer == nullptr) { + m_renderer = SDL_CreateRenderer(m_window, -1, SDL_RENDERER_SOFTWARE); + } + if (m_renderer == nullptr) { + fprintf(stderr, "SDL_CreateRenderer failed: %s\n", SDL_GetError()); + shutdown(); + return false; + } + + m_texture = SDL_CreateTexture(m_renderer, SDL_PIXELFORMAT_RGB565, + SDL_TEXTUREACCESS_STREAMING, LCD_H_RES, LCD_V_RES); + if (m_texture == nullptr) { + fprintf(stderr, "SDL_CreateTexture failed: %s\n", SDL_GetError()); + shutdown(); + return false; + } + + if (SDL_RenderSetLogicalSize(m_renderer, LCD_H_RES, LCD_V_RES) != 0) { + fprintf(stderr, "SDL_RenderSetLogicalSize failed: %s\n", SDL_GetError()); + shutdown(); + return false; + } + return true; + } + + void handle_events(uint8_t &keys_held, bool &quit) { + quit = false; + SDL_Event event; + while (SDL_PollEvent(&event)) { + if (event.type == SDL_QUIT) { + quit = true; + } else if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_q && + (event.key.keysym.mod & KMOD_CTRL) != 0) { + quit = true; + } else if (event.type == SDL_WINDOWEVENT && + (event.window.event == SDL_WINDOWEVENT_EXPOSED || + event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)) { + m_needs_redraw = true; + } + } + + const uint8_t *state = SDL_GetKeyboardState(nullptr); + m_held_keys = 0; + if (state[SDL_SCANCODE_UP] || state[SDL_SCANCODE_W]) { + m_held_keys |= KEY_UP; + } + if (state[SDL_SCANCODE_DOWN] || state[SDL_SCANCODE_S]) { + m_held_keys |= KEY_DOWN; + } + if (state[SDL_SCANCODE_LEFT] || state[SDL_SCANCODE_A]) { + m_held_keys |= KEY_LEFT; + } + if (state[SDL_SCANCODE_RIGHT] || state[SDL_SCANCODE_D]) { + m_held_keys |= KEY_RIGHT; + } + if (state[SDL_SCANCODE_RETURN] || state[SDL_SCANCODE_KP_ENTER] || + state[SDL_SCANCODE_SPACE]) { + m_held_keys |= KEY_OK; + } + if (state[SDL_SCANCODE_BACKSPACE] || state[SDL_SCANCODE_ESCAPE]) { + m_held_keys |= KEY_BACK; + } + keys_held = m_held_keys; + } + + void render() { + if (m_texture == nullptr || m_renderer == nullptr) { + return; + } + + void *pixels = nullptr; + int pitch = 0; + if (SDL_LockTexture(m_texture, nullptr, &pixels, &pitch) != 0) { + fprintf(stderr, "SDL_LockTexture failed: %s\n", SDL_GetError()); + return; + } + const bool is_updated = + Display::instance().copy_pixels_if_dirty(pixels, static_cast(pitch)); + SDL_UnlockTexture(m_texture); + + if (!is_updated && !m_needs_redraw) { + return; + } + SDL_RenderClear(m_renderer); + SDL_RenderCopy(m_renderer, m_texture, nullptr, nullptr); + SDL_RenderPresent(m_renderer); + m_needs_redraw = false; + } + + void shutdown() { + if (m_texture != nullptr) { + SDL_DestroyTexture(m_texture); + m_texture = nullptr; + } + if (m_renderer != nullptr) { + SDL_DestroyRenderer(m_renderer); + m_renderer = nullptr; + } + if (m_window != nullptr) { + SDL_DestroyWindow(m_window); + m_window = nullptr; + } + if (m_is_sdl_initialized) { + SDL_Quit(); + m_is_sdl_initialized = false; + } + } + + uint8_t held_keys() const { return m_held_keys; } + + static constexpr uint8_t KEY_UP = 1 << 0; + static constexpr uint8_t KEY_DOWN = 1 << 1; + static constexpr uint8_t KEY_LEFT = 1 << 2; + static constexpr uint8_t KEY_RIGHT = 1 << 3; + static constexpr uint8_t KEY_OK = 1 << 4; + static constexpr uint8_t KEY_BACK = 1 << 5; + +private: + SDL_Window *m_window = nullptr; + SDL_Renderer *m_renderer = nullptr; + SDL_Texture *m_texture = nullptr; + int m_scale; + uint8_t m_held_keys = 0; + bool m_needs_redraw = true; + bool m_is_sdl_initialized = false; +}; + +} // namespace hle diff --git a/tools/hle/include/hle/spi_bridge_channel.h b/tools/hle/include/hle/spi_bridge_channel.h new file mode 100644 index 00000000..ecde20be --- /dev/null +++ b/tools/hle/include/hle/spi_bridge_channel.h @@ -0,0 +1,120 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace hle { + +static constexpr size_t SPI_HEADER_SIZE = 5; +static constexpr size_t SPI_MAX_PAYLOAD = 256; +static constexpr size_t SPI_FRAME_SIZE = + (SPI_HEADER_SIZE + SPI_MAX_PAYLOAD + 3) & ~static_cast(3); +static constexpr uint8_t SPI_SYNC_BYTE = 0xAA; +static constexpr size_t STREAM_QUEUE_LEN = 8; +static constexpr uint8_t SPI_MAX_RESPONSE_DATA = 254; + +struct SPIFrame { + uint8_t sync; + uint8_t type; + uint8_t category; + uint8_t op; + uint8_t length; + uint8_t payload[SPI_MAX_PAYLOAD]; + + SPIFrame() : sync(0), type(0), category(0), op(0), length(0) { + memset(payload, 0, sizeof(payload)); + } + + bool is_valid() const { return sync == SPI_SYNC_BYTE; } + + uint16_t command_id() const { + return static_cast((static_cast(category) << 8) | op); + } + + void build_header(uint8_t frame_type, uint16_t command_id, uint8_t payload_len) { + sync = SPI_SYNC_BYTE; + type = frame_type; + category = static_cast(command_id >> 8); + op = static_cast(command_id); + length = payload_len; + } +}; + +class SPIBridgeChannel { +public: + SPIBridgeChannel(); + ~SPIBridgeChannel(); + + // ── Master (P4) side ───────────────────────────────────────────────── + + void master_send_command(uint16_t command_id, const uint8_t *payload, uint8_t payload_len); + bool master_receive_response(uint16_t &out_command_id, uint8_t *out_payload, + uint8_t &out_len, uint32_t timeout_ms); + + // ── Slave (C5) side ────────────────────────────────────────────────── + + bool slave_wait_command(uint16_t &out_command_id, uint8_t *out_payload, + uint8_t &out_len); + void slave_send_response(uint16_t command_id, uint8_t status, const uint8_t *payload, + uint8_t payload_len); + void slave_send_stream(uint16_t stream_id, const uint8_t *payload, uint8_t payload_len); + + // ── Stream queue (C5 → P4) ─────────────────────────────────────────── + + bool stream_push(uint16_t stream_id, const uint8_t *data, size_t len); + bool stream_pop(uint16_t &out_stream_id, uint8_t *out_data, size_t &out_len); + + // ── IRQ simulation ─────────────────────────────────────────────────── + + void slave_notify_irq(); + bool master_wait_irq(uint32_t timeout_ms); + + // ── Management ─────────────────────────────────────────────────────── + + void reset(); + void close(); + bool is_closed() const { return s_closed.load(std::memory_order_acquire); } + +private: + std::atomic s_closed{false}; + + // Master → Slave command queue + SPIFrame s_command_frame; + bool s_has_command = false; + std::mutex s_cmd_mutex; + std::condition_variable s_cmd_cv; + + // Slave → Master response + SPIFrame s_response_frame; + bool s_has_response = false; + std::mutex s_resp_mutex; + std::condition_variable s_resp_cv; + + // IRQ flag + bool s_irq_raised = false; + std::mutex s_irq_mutex; + std::condition_variable s_irq_cv; + + // Stream queue (C5 pushes, P4 pops) + struct StreamItem { + uint16_t stream_id; + uint8_t data[SPI_MAX_PAYLOAD]; + size_t len; + }; + std::deque s_stream_queue; + std::mutex s_stream_mutex; +}; + +} // namespace hle + +// Global bridge channel pointer for firmware shims +extern "C" { +void hle_set_bridge_channel(hle::SPIBridgeChannel *ch); +hle::SPIBridgeChannel *hle_get_bridge_channel(void); +} diff --git a/tools/hle/include/host/ble_gap.h b/tools/hle/include/host/ble_gap.h new file mode 100644 index 00000000..54d446b2 --- /dev/null +++ b/tools/hle/include/host/ble_gap.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int ble_gap_event; +typedef int ble_gap_conn_desc; + +#define BLE_OWN_ADDR_RANDOM 1 +#define BLE_OWN_ADDR_PUBLIC 0 +#define BLE_HS_ADV_F_DISC_GEN 0x01 +#define BLE_HS_ADV_F_BREDR_UNSUP 0x20 +#define BLE_HS_ADV_TX_PWR_LVL_AUTO 127 +#define BLE_ERR_REM_USER_CONN_TERM 0x13 +#define BLE_GAP_INITIAL_CONN_ITVL_MIN 6 +#define BLE_GAP_INITIAL_CONN_ITVL_MAX 12 + +typedef struct { + int dummy; +} ble_hs_adv_fields; + +typedef int ble_store_util_status_rr_t; + +void ble_store_util_status_rr(const void *peer, void *out, int *num); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/host/ble_gatt.h b/tools/hle/include/host/ble_gatt.h new file mode 100644 index 00000000..35d1cfe2 --- /dev/null +++ b/tools/hle/include/host/ble_gatt.h @@ -0,0 +1,14 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int ble_gatt_svc; +typedef int ble_gatt_chr; +typedef int ble_gatt_dsc; +typedef int ble_gatt_attr; + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/host/ble_hs.h b/tools/hle/include/host/ble_hs.h new file mode 100644 index 00000000..0c74ff2f --- /dev/null +++ b/tools/hle/include/host/ble_hs.h @@ -0,0 +1,15 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int ble_hs_conn_handle; +typedef int ble_gatt_svc; +typedef int ble_gatt_chr; +typedef int ble_gatt_dsc; +typedef int ble_gatt_attr; + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/host/ble_hs_id.h b/tools/hle/include/host/ble_hs_id.h new file mode 100644 index 00000000..28da3012 --- /dev/null +++ b/tools/hle/include/host/ble_hs_id.h @@ -0,0 +1,12 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int ble_hs_id_key; +typedef int ble_hs_id_addr; + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/host/ble_hs_stop.h b/tools/hle/include/host/ble_hs_stop.h new file mode 100644 index 00000000..f8eb2271 --- /dev/null +++ b/tools/hle/include/host/ble_hs_stop.h @@ -0,0 +1,11 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#define BLE_HS_FOREVER 0 + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/host/ble_uuid.h b/tools/hle/include/host/ble_uuid.h new file mode 100644 index 00000000..203a78b5 --- /dev/null +++ b/tools/hle/include/host/ble_uuid.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int ble_uuid_any_t; +typedef int ble_uuid16_t; +#define BLE_UUID16_DECLARE(x) ((x) & 0xFFFF) +#define BLE_UUID16(x) ((x) & 0xFFFF) + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/host/util/util.h b/tools/hle/include/host/util/util.h new file mode 100644 index 00000000..192ec746 --- /dev/null +++ b/tools/hle/include/host/util/util.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int ble_addr_t; +#define BLE_ADDR_PUBLIC 0 +#define BLE_ADDR_RANDOM 1 +#define BLE_ADDR_TYPE_PUBLIC 0 +#define BLE_ADDR_TYPE_RANDOM 1 + +typedef struct { + uint8_t val[6]; + uint8_t type; +} ble_addr_le_t; + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/led_strip.h b/tools/hle/include/led_strip.h new file mode 100644 index 00000000..9b1e7f55 --- /dev/null +++ b/tools/hle/include/led_strip.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +#include "esp_err.h" +#include "driver/rmt.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + int strip_gpio_num; + int led_count; + int max_leds; +} led_strip_config_t; + +typedef struct { + int clk_src; + int resolution_hz; + struct { + bool with_dma; + } flags; +} led_strip_rmt_config_t; + +typedef void *led_strip_handle_t; + +esp_err_t led_strip_new_rmt_device(const led_strip_config_t *strip_cfg, + const led_strip_rmt_config_t *rmt_cfg, + led_strip_handle_t *handle); +esp_err_t led_strip_set_pixel(led_strip_handle_t handle, uint32_t index, uint32_t r, uint32_t g, uint32_t b); +esp_err_t led_strip_refresh(led_strip_handle_t handle); +esp_err_t led_strip_clear(led_strip_handle_t handle); +esp_err_t led_strip_del(led_strip_handle_t handle); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/linenoise/linenoise.h b/tools/hle/include/linenoise/linenoise.h new file mode 100644 index 00000000..41bdfeaa --- /dev/null +++ b/tools/hle/include/linenoise/linenoise.h @@ -0,0 +1,17 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int linenoiseCompletions; +void linenoiseSetCompletionCallback(void (*cb)(const char *, void *), void *ctx); +char *linenoise(const char *prompt); +void linenoiseHistoryLoad(const char *filename); +void linenoiseHistorySave(const char *filename); +void linenoiseHistoryAdd(const char *line); +void linenoiseFree(void *ptr); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/lv_conf.h b/tools/hle/include/lv_conf.h new file mode 100644 index 00000000..96da3757 --- /dev/null +++ b/tools/hle/include/lv_conf.h @@ -0,0 +1,24 @@ +#ifndef LV_CONF_H +#define LV_CONF_H + +#define LV_USE_SDL 0 +#define LV_USE_DRAW_SW 1 +#define LV_DRAW_BUF_STRIDE_ALIGN 4 +#define LV_USE_OS LV_OS_PTHREAD +#define LV_OS_TIMER_HANDLE pthread_t +#define LV_USE_LOG 1 +#define LV_LOG_LEVEL LV_LOG_LEVEL_WARN +#define LV_USE_QRCODE 1 + +#define LV_FONT_MONTSERRAT_12 1 +#define LV_FONT_MONTSERRAT_14 1 +#define LV_FONT_MONTSERRAT_16 1 +#define LV_FONT_MONTSERRAT_20 1 +#define LV_FONT_MONTSERRAT_24 1 +#define LV_FONT_MONTSERRAT_32 1 +#define LV_FONT_MONTSERRAT_48 1 + +#define LV_USE_ASSERT_NULL 0 +#define LV_USE_ASSERT_MALLOC 0 + +#endif // LV_CONF_H diff --git a/tools/hle/include/lwip/inet.h b/tools/hle/include/lwip/inet.h new file mode 100644 index 00000000..c5b09ee4 --- /dev/null +++ b/tools/hle/include/lwip/inet.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static inline uint32_t lwip_ntohl(uint32_t x) { return __builtin_bswap32(x); } +static inline uint16_t lwip_ntohs(uint16_t x) { return __builtin_bswap16(x); } +static inline uint32_t lwip_htonl(uint32_t x) { return __builtin_bswap32(x); } +static inline uint16_t lwip_htons(uint16_t x) { return __builtin_bswap16(x); } + +char *lwip_inet_ntoa(uint32_t addr); +int lwip_inet_aton(const char *cp, uint32_t *addr); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/lwip/netdb.h b/tools/hle/include/lwip/netdb.h new file mode 100644 index 00000000..d3aab9d2 --- /dev/null +++ b/tools/hle/include/lwip/netdb.h @@ -0,0 +1,15 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int netconn; +typedef int ip_addr_t; +typedef int err_t; +#define ERR_OK 0 +#define ERR_MEM (-1) + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/lwip/sockets.h b/tools/hle/include/lwip/sockets.h new file mode 100644 index 00000000..ca6d66e5 --- /dev/null +++ b/tools/hle/include/lwip/sockets.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int socklen_t; + +#define AF_INET 2 +#define SOCK_STREAM 1 +#define SOCK_DGRAM 2 +#define IPPROTO_TCP 6 +#define IPPROTO_UDP 17 +#define IPPROTO_IP 0 +#define SOL_SOCKET 0x01 +#define SO_RCVTIMEO 0x02 +#define INADDR_ANY 0 + +struct sockaddr_in { + uint16_t sin_family; + uint16_t sin_port; + union { + uint32_t s_addr; + struct { uint8_t b1, b2, b3, b4; }; + } sin_addr; + uint8_t sin_zero[8]; +}; + +typedef struct { + uint32_t s_addr; +} in_addr_t; + +int lwip_socket(int domain, int type, int protocol); +int lwip_bind(int s, const struct sockaddr_in *addr, socklen_t len); +int lwip_listen(int s, int backlog); +int lwip_accept(int s, struct sockaddr_in *addr, socklen_t *len); +int lwip_send(int s, const void *data, size_t len, int flags); +int lwip_recv(int s, void *data, size_t len, int flags); +int lwip_close(int s); +int lwip_setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen); + +#define htons(x) ((((x) & 0xFF) << 8) | (((x) >> 8) & 0xFF)) +#define htonl(x) ((((x) & 0xFF) << 24) | (((x) & 0xFF00) << 8) | (((x) >> 8) & 0xFF00) | (((x) >> 24) & 0xFF)) + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/mdns.h b/tools/hle/include/mdns.h new file mode 100644 index 00000000..a98fab32 --- /dev/null +++ b/tools/hle/include/mdns.h @@ -0,0 +1,18 @@ +#pragma once + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +esp_err_t mdns_init(void); +esp_err_t mdns_free(void); +esp_err_t mdns_hostname_set(const char *hostname); +esp_err_t mdns_instance_name_set(const char *name); +esp_err_t mdns_service_add(const char *instance, const char *service, const char *proto, uint16_t port, const void *txt, size_t txt_len); +esp_err_t mdns_service_txt_set(const char *service, const char *proto, char **txt, uint8_t txt_count); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/nimble/ble.h b/tools/hle/include/nimble/ble.h new file mode 100644 index 00000000..d66118e1 --- /dev/null +++ b/tools/hle/include/nimble/ble.h @@ -0,0 +1,22 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int ble_npl_event_fn; +typedef int ble_npl_event; +typedef int ble_npl_callout; +typedef int ble_npl_mutex; +typedef int ble_npl_sem; +typedef int ble_addr_t; +typedef int ble_uuid_any_t; +typedef int ble_gap_event; +typedef int os_mempool; +typedef int os_mbuf; + +#define MYNEWT_VAL(x) x + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/nimble/nimble_port.h b/tools/hle/include/nimble/nimble_port.h new file mode 100644 index 00000000..10d55878 --- /dev/null +++ b/tools/hle/include/nimble/nimble_port.h @@ -0,0 +1,11 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int ble_npl_event_fn; + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/nimble/nimble_port_freertos.h b/tools/hle/include/nimble/nimble_port_freertos.h new file mode 100644 index 00000000..276f2bf0 --- /dev/null +++ b/tools/hle/include/nimble/nimble_port_freertos.h @@ -0,0 +1,12 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int ble_npl_time_t; +typedef int ble_npl_event_fn; + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/nvs.h b/tools/hle/include/nvs.h new file mode 100644 index 00000000..a3851a2a --- /dev/null +++ b/tools/hle/include/nvs.h @@ -0,0 +1,3 @@ +#pragma once + +#include "nvs_flash.h" diff --git a/tools/hle/include/nvs_flash.h b/tools/hle/include/nvs_flash.h new file mode 100644 index 00000000..a4d5e6f9 --- /dev/null +++ b/tools/hle/include/nvs_flash.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include +#include + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef uint32_t nvs_handle_t; +typedef enum { NVS_READONLY, NVS_READWRITE } nvs_open_mode_t; + +esp_err_t nvs_flash_init(void); +esp_err_t nvs_flash_erase(void); +void nvs_flash_init_partition(const char *name); +void nvs_flash_init_partition_ptr(const char *name); + +esp_err_t nvs_open(const char *ns, nvs_open_mode_t mode, nvs_handle_t *out_handle); +void nvs_close(nvs_handle_t handle); +esp_err_t nvs_erase_key(nvs_handle_t handle, const char *key); +esp_err_t nvs_erase_all(nvs_handle_t handle); +esp_err_t nvs_commit(nvs_handle_t handle); + +esp_err_t nvs_get_i8(nvs_handle_t h, const char *key, int8_t *val); +esp_err_t nvs_get_u8(nvs_handle_t h, const char *key, uint8_t *val); +esp_err_t nvs_get_i16(nvs_handle_t h, const char *key, int16_t *val); +esp_err_t nvs_get_u16(nvs_handle_t h, const char *key, uint16_t *val); +esp_err_t nvs_get_i32(nvs_handle_t h, const char *key, int32_t *val); +esp_err_t nvs_get_u32(nvs_handle_t h, const char *key, uint32_t *val); +esp_err_t nvs_get_i64(nvs_handle_t h, const char *key, int64_t *val); +esp_err_t nvs_get_u64(nvs_handle_t h, const char *key, uint64_t *val); +esp_err_t nvs_get_str(nvs_handle_t h, const char *key, char *out, size_t *len); +esp_err_t nvs_get_blob(nvs_handle_t h, const char *key, void *out, size_t *len); + +esp_err_t nvs_set_i8(nvs_handle_t h, const char *key, int8_t v); +esp_err_t nvs_set_u8(nvs_handle_t h, const char *key, uint8_t v); +esp_err_t nvs_set_i16(nvs_handle_t h, const char *key, int16_t v); +esp_err_t nvs_set_u16(nvs_handle_t h, const char *key, uint16_t v); +esp_err_t nvs_set_i32(nvs_handle_t h, const char *key, int32_t v); +esp_err_t nvs_set_u32(nvs_handle_t h, const char *key, uint32_t v); +esp_err_t nvs_set_i64(nvs_handle_t h, const char *key, int64_t v); +esp_err_t nvs_set_u64(nvs_handle_t h, const char *key, uint64_t v); +esp_err_t nvs_set_str(nvs_handle_t h, const char *key, const char *v); +esp_err_t nvs_set_blob(nvs_handle_t h, const char *key, const void *v, size_t len); + + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/rom/ets_sys.h b/tools/hle/include/rom/ets_sys.h new file mode 100644 index 00000000..b9ad07e7 --- /dev/null +++ b/tools/hle/include/rom/ets_sys.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static inline void ets_delay_us(uint32_t us) { (void)us; } + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/sdmmc_cmd.h b/tools/hle/include/sdmmc_cmd.h new file mode 100644 index 00000000..6dde0388 --- /dev/null +++ b/tools/hle/include/sdmmc_cmd.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include + +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef uint8_t sdmmc_slot_t; +#define SDMMC_SLOT_0 0 +#define SDMMC_SLOT_1 1 + +typedef struct sdmmc_host_t { int flags; int slot; int max_freq_khz; bool io_voltage; } sdmmc_host_t; +typedef struct sdmmc_card_t { int dummy; } sdmmc_card_t; +typedef struct { sdmmc_host_t host; sdmmc_slot_t slot; int cd; int wp; } sdmmc_slot_config_t; + +typedef struct { int host; int slot; } sdmmc_init_slot_config_t; + +#define SDMMC_HOST_DEFAULT() (sdmmc_host_t){0} +#define SDMMC_SLOT_CONFIG_DEFAULT() (sdmmc_slot_config_t){0} +#define SDMMC_HOST_FLAG_4BIT 1 +#define SDMMC_FREQ_DEFAULT 20000 + +esp_err_t sdmmc_host_init(void); +esp_err_t sdmmc_host_init_slot(int slot, const void *config); +void sdmmc_card_print_info(FILE *f, const sdmmc_card_t *card); +void sdmmc_host_deinit(void); + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/services/dis/ble_svc_dis.h b/tools/hle/include/services/dis/ble_svc_dis.h new file mode 100644 index 00000000..6ae655df --- /dev/null +++ b/tools/hle/include/services/dis/ble_svc_dis.h @@ -0,0 +1,11 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +// Minimal NimBLE DIS service stub + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/services/gap/ble_svc_gap.h b/tools/hle/include/services/gap/ble_svc_gap.h new file mode 100644 index 00000000..0ecde067 --- /dev/null +++ b/tools/hle/include/services/gap/ble_svc_gap.h @@ -0,0 +1,12 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int ble_uuid128_t; +#define BLE_UUID128_DECLARE(u0, u1, u2, u3, u4, u5, u6, u7, u8, u9, ua, ub, uc, ud, ue, uf) 0 + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/services/gatt/ble_svc_gatt.h b/tools/hle/include/services/gatt/ble_svc_gatt.h new file mode 100644 index 00000000..8d7e2485 --- /dev/null +++ b/tools/hle/include/services/gatt/ble_svc_gatt.h @@ -0,0 +1,11 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +// Minimal NimBLE GATT service stub + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/include/soc/spi_periph.h b/tools/hle/include/soc/spi_periph.h new file mode 100644 index 00000000..46fa96d6 --- /dev/null +++ b/tools/hle/include/soc/spi_periph.h @@ -0,0 +1,3 @@ +#pragma once + +#define SOC_SPI_PERIPH_NUM 3 diff --git a/tools/hle/include/tinyusb.h b/tools/hle/include/tinyusb.h new file mode 100644 index 00000000..18a7267d --- /dev/null +++ b/tools/hle/include/tinyusb.h @@ -0,0 +1,13 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#define TUSB_DESC_TOTAL_LEN 256 + +typedef int tusb_desc_device_t; + +#ifdef __cplusplus +} +#endif diff --git a/tools/hle/src/esp_shim.cpp b/tools/hle/src/esp_shim.cpp new file mode 100644 index 00000000..2f9a3bc4 --- /dev/null +++ b/tools/hle/src/esp_shim.cpp @@ -0,0 +1,1347 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "esp_err.h" +#include "esp_log.h" +#include "esp_rom_sys.h" +#include "esp_system.h" +#include "esp_timer.h" +#include "esp_wifi.h" +#include "nvs_flash.h" +#include "driver/gpio.h" +#include "driver/i2c.h" +#include "driver/spi_master.h" +#include "driver/spi_slave.h" +#include "driver/rmt.h" +#include "led_strip.h" +#include "sdmmc_cmd.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/queue.h" +#include "hle/spi_bridge_channel.h" +#include "hle/hle_display.h" +#include "hle/hle_kernel.h" +#include "esp_lcd_panel_ops.h" +#include "esp_vfs_fat.h" +#include "esp_littlefs.h" +#include "esp_partition.h" + +#include +#include +#include + +// ═══════════════════════════════════════════════════════════════════════════════ +// ESP System +// ═══════════════════════════════════════════════════════════════════════════════ + +void esp_restart(void) { + fprintf(stderr, "I [SYSTEM] esp_restart() called — exiting\n"); + exit(0); +} + +static int hle_default_vlog(const char *fmt, va_list ap) { return vprintf(fmt, ap); } +static vprintf_like_t s_log_vprintf = hle_default_vlog; + +vprintf_like_t esp_log_set_vprintf(vprintf_like_t func) { + vprintf_like_t previous = s_log_vprintf; + if (func) s_log_vprintf = func; + return previous; +} + +uint64_t esp_timer_get_time(void) { + auto now = std::chrono::steady_clock::now().time_since_epoch(); + return std::chrono::duration_cast(now).count(); +} + +void esp_rom_delay_us(uint32_t us) { + std::this_thread::sleep_for(std::chrono::microseconds(us)); +} + +esp_err_t esp_timer_create(const esp_timer_create_args_t *args, esp_timer_handle_t *handle) { + (void)args; + if (handle) *handle = (void *)0x1; + return ESP_OK; +} +esp_err_t esp_timer_start_periodic(esp_timer_handle_t handle, uint64_t period_us) { + (void)handle; (void)period_us; return ESP_OK; +} +esp_err_t esp_timer_stop(esp_timer_handle_t handle) { (void)handle; return ESP_OK; } +esp_err_t esp_timer_delete(esp_timer_handle_t handle) { (void)handle; return ESP_OK; } + +// ═══════════════════════════════════════════════════════════════════════════════ +// FreeRTOS Implementation (pthread-based) +// ═══════════════════════════════════════════════════════════════════════════════ + +struct TaskInfo { + pthread_t thread; + TaskFunction_t func; + std::string name; + volatile bool running = true; + volatile bool suspended = false; + void *params; + SemaphoreHandle_t suspend_sem; +}; + +static std::mutex s_tasks_mutex; +static std::map s_tasks; +static TickType_t s_tick_start_us = 0; + +static TickType_t get_tick_count(void) { + auto now = std::chrono::steady_clock::now().time_since_epoch(); + uint64_t us = std::chrono::duration_cast(now).count(); + if (s_tick_start_us == 0) s_tick_start_us = us; + return (us - s_tick_start_us) / 1000; // ms ticks +} + +static void *task_wrapper(void *arg) { + TaskInfo *info = static_cast(arg); + info->func(info->params); + info->running = false; + return nullptr; +} + +BaseType_t xTaskCreatePinnedToCore(TaskFunction_t func, const char *name, uint32_t stack_depth, + void *params, UBaseType_t prio, TaskHandle_t *out_handle, + BaseType_t core_id) { + (void)stack_depth; (void)prio; (void)core_id; + auto *info = new TaskInfo{}; + info->func = func; + info->name = name ? name : "unnamed"; + info->params = params; + info->suspend_sem = xSemaphoreCreateBinary(); + + pthread_t thread; + pthread_attr_t attr; + pthread_attr_init(&attr); + // firmware tasks request stack in words; on host use 4KB minimum or 2x requested + size_t stack_bytes = stack_depth * sizeof(void *); + if (stack_bytes < 65536) stack_bytes = 65536; // 64KB minimum for host threads + pthread_attr_setstacksize(&attr, stack_bytes); + pthread_create(&thread, &attr, task_wrapper, info); + info->thread = thread; + + { + std::lock_guard lock(s_tasks_mutex); + s_tasks[info] = *info; + } + + if (out_handle) *out_handle = info; + return pdPASS; +} + +void vTaskDelete(TaskHandle_t handle) { + if (!handle) { + pthread_exit(nullptr); + } + auto *info = static_cast(handle); + { + std::lock_guard lock(s_tasks_mutex); + info->running = false; + s_tasks.erase(handle); + } + pthread_detach(info->thread); + delete info; +} + +void vTaskDelay(TickType_t ticks) { + std::this_thread::sleep_for(std::chrono::milliseconds(ticks * portTICK_PERIOD_MS)); +} + +TickType_t xTaskGetTickCount(void) { return get_tick_count(); } +char *pcTaskGetName(TaskHandle_t handle) { (void)handle; return (char *)"shim_task"; } +TaskHandle_t xTaskGetCurrentTaskHandle(void) { return nullptr; } + +void vTaskSuspend(TaskHandle_t handle) { + if (!handle) return; + auto *info = static_cast(handle); + info->suspended = true; + xSemaphoreTake(info->suspend_sem, portMAX_DELAY); +} + +void vTaskResume(TaskHandle_t handle) { + if (!handle) return; + auto *info = static_cast(handle); + info->suspended = false; + xSemaphoreGive(info->suspend_sem); +} + +UBaseType_t uxTaskGetStackHighWaterMark(TaskHandle_t handle) { + (void)handle; + return 4096; // enough for any shim task +} + +TaskHandle_t xTaskCreateStatic(TaskFunction_t func, const char *name, uint32_t stack_depth, + void *params, UBaseType_t prio, StackType_t *stack_buf, + StaticTask_t *task_buf) { + if (!func || !stack_buf || !task_buf || stack_depth == 0) return nullptr; + TaskHandle_t handle = nullptr; + xTaskCreatePinnedToCore(func, name, stack_depth, params, prio, &handle, 0); + return handle; +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// FreeRTOS Semaphores +// ═══════════════════════════════════════════════════════════════════════════════ + +struct SemaphoreInfo { + pthread_mutex_t mutex; + pthread_cond_t cond; + int count; + int max_count; + bool is_mutex; +}; + +SemaphoreHandle_t xSemaphoreCreateMutex(void) { + auto *s = new SemaphoreInfo{}; + s->is_mutex = true; + s->max_count = 1; + s->count = 1; + pthread_mutex_init(&s->mutex, nullptr); + pthread_cond_init(&s->cond, nullptr); + return s; +} + +SemaphoreHandle_t xSemaphoreCreateBinary(void) { + auto *s = new SemaphoreInfo{}; + s->is_mutex = false; + s->max_count = 1; + s->count = 0; + pthread_mutex_init(&s->mutex, nullptr); + pthread_cond_init(&s->cond, nullptr); + return s; +} + +SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t max, UBaseType_t initial) { + auto *s = new SemaphoreInfo{}; + s->is_mutex = false; + s->max_count = max; + s->count = initial; + pthread_mutex_init(&s->mutex, nullptr); + pthread_cond_init(&s->cond, nullptr); + return s; +} + +BaseType_t xSemaphoreTake(SemaphoreHandle_t sem, TickType_t timeout) { + if (!sem) return pdFALSE; + auto *s = static_cast(sem); + pthread_mutex_lock(&s->mutex); + int result = 0; + while (s->count == 0 && result == 0) { + if (timeout == portMAX_DELAY) { + pthread_cond_wait(&s->cond, &s->mutex); + } else { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += timeout / 1000; + ts.tv_nsec += (timeout % 1000) * 1000000; + if (ts.tv_nsec >= 1000000000) { ts.tv_sec++; ts.tv_nsec -= 1000000000; } + result = pthread_cond_timedwait(&s->cond, &s->mutex, &ts); + } + } + if (result == 0 && s->count > 0) { + s->count--; + pthread_mutex_unlock(&s->mutex); + return pdTRUE; + } + pthread_mutex_unlock(&s->mutex); + return pdFALSE; +} + +BaseType_t xSemaphoreGive(SemaphoreHandle_t sem) { + if (!sem) return pdFALSE; + auto *s = static_cast(sem); + pthread_mutex_lock(&s->mutex); + if (s->count < s->max_count) { + s->count++; + } + pthread_cond_signal(&s->cond); + pthread_mutex_unlock(&s->mutex); + return pdTRUE; +} + +BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t sem, BaseType_t *woken) { + (void)woken; + return xSemaphoreGive(sem); +} + +void vSemaphoreDelete(SemaphoreHandle_t sem) { + if (!sem) return; + auto *s = static_cast(sem); + pthread_mutex_destroy(&s->mutex); + pthread_cond_destroy(&s->cond); + delete s; +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// FreeRTOS Queues +// ═══════════════════════════════════════════════════════════════════════════════ + +struct QueueInfo { + std::queue> items; + size_t item_size; + size_t max_items; + pthread_mutex_t mutex; + pthread_cond_t cond; +}; + +QueueHandle_t xQueueCreate(UBaseType_t queue_len, UBaseType_t item_size) { + auto *q = new QueueInfo{}; + q->max_items = queue_len; + q->item_size = item_size; + pthread_mutex_init(&q->mutex, nullptr); + pthread_cond_init(&q->cond, nullptr); + return q; +} + +QueueHandle_t xQueueCreateStatic(UBaseType_t queue_len, UBaseType_t item_size, + uint8_t *storage, StaticQueue_t *queue_buf) { + if (!queue_len || !item_size || !storage || !queue_buf) return nullptr; + return xQueueCreate(queue_len, item_size); +} + +BaseType_t xQueueSend(QueueHandle_t queue, const void *item, TickType_t timeout) { + if (!queue) return pdFALSE; + auto *q = static_cast(queue); + pthread_mutex_lock(&q->mutex); + int result = 0; + while (q->items.size() >= q->max_items && result == 0) { + if (timeout == portMAX_DELAY) { + pthread_cond_wait(&q->cond, &q->mutex); + } else { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += timeout / 1000; + ts.tv_nsec += (timeout % 1000) * 1000000; + result = pthread_cond_timedwait(&q->cond, &q->mutex, &ts); + } + } + if (q->items.size() < q->max_items) { + std::vector buf((const uint8_t *)item, (const uint8_t *)item + q->item_size); + q->items.push(std::move(buf)); + pthread_cond_signal(&q->cond); + pthread_mutex_unlock(&q->mutex); + return pdTRUE; + } + pthread_mutex_unlock(&q->mutex); + return pdFALSE; +} + +BaseType_t xQueueSendFromISR(QueueHandle_t queue, const void *item, BaseType_t *higher_prio_woken) { + if (higher_prio_woken) *higher_prio_woken = pdFALSE; + if (!queue || !item) return pdFALSE; + auto *q = static_cast(queue); + if (pthread_mutex_trylock(&q->mutex) != 0) return pdFALSE; + if (q->items.size() >= q->max_items) { + pthread_mutex_unlock(&q->mutex); + return pdFALSE; + } + std::vector buf((const uint8_t *)item, (const uint8_t *)item + q->item_size); + q->items.push(std::move(buf)); + pthread_cond_signal(&q->cond); + pthread_mutex_unlock(&q->mutex); + return pdTRUE; +} + +BaseType_t xQueueReceive(QueueHandle_t queue, void *item, TickType_t timeout) { + if (!queue || !item) return pdFALSE; + auto *q = static_cast(queue); + pthread_mutex_lock(&q->mutex); + int result = 0; + while (q->items.empty() && result == 0) { + if (timeout == portMAX_DELAY) { + pthread_cond_wait(&q->cond, &q->mutex); + } else { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += timeout / 1000; + ts.tv_nsec += (timeout % 1000) * 1000000; + result = pthread_cond_timedwait(&q->cond, &q->mutex, &ts); + } + } + if (!q->items.empty()) { + memcpy(item, q->items.front().data(), q->item_size); + q->items.pop(); + pthread_cond_signal(&q->cond); + pthread_mutex_unlock(&q->mutex); + return pdTRUE; + } + pthread_mutex_unlock(&q->mutex); + return pdFALSE; +} + +void vQueueDelete(QueueHandle_t queue) { + if (!queue) return; + auto *q = static_cast(queue); + pthread_mutex_destroy(&q->mutex); + pthread_cond_destroy(&q->cond); + delete q; +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// NVS Flash Mock +// ═══════════════════════════════════════════════════════════════════════════════ + +enum class NVSValueType { + I8, + U8, + I16, + U16, + I32, + U32, + I64, + U64, + String, + Blob, +}; + +struct NVSValue { + NVSValueType type; + std::vector data; +}; + +struct NVSNamespace { + std::map keys; +}; + +struct NVSHandleInfo { + std::string namespace_name; + nvs_open_mode_t mode; +}; + +static constexpr size_t NVS_NAME_MAX_LENGTH = 15; +static std::mutex s_nvs_mutex; +static std::map s_nvs_storage; +static uint32_t s_nvs_next_handle = 1; +static std::map s_nvs_handles; +static bool s_nvs_initialized = false; + +static esp_err_t nvs_validate_namespace(const char *name) { + if (name == nullptr) { + return ESP_ERR_INVALID_ARG; + } + const size_t length = strlen(name); + if (length == 0 || length > NVS_NAME_MAX_LENGTH) { + return ESP_ERR_NVS_INVALID_NAME; + } + return ESP_OK; +} + +static esp_err_t nvs_validate_key(const char *key) { + if (key == nullptr) { + return ESP_ERR_INVALID_ARG; + } + const size_t length = strlen(key); + if (length == 0) { + return ESP_ERR_NVS_INVALID_NAME; + } + if (length > NVS_NAME_MAX_LENGTH) { + return ESP_ERR_NVS_KEY_TOO_LONG; + } + return ESP_OK; +} + +static std::map::iterator nvs_find_handle(nvs_handle_t handle) { + return s_nvs_handles.find(handle); +} + +static bool nvs_handle_is_writable(const NVSHandleInfo &handle) { + return handle.mode == NVS_READWRITE; +} + +esp_err_t nvs_flash_init(void) { + std::lock_guard lock(s_nvs_mutex); + s_nvs_initialized = true; + return ESP_OK; +} + +esp_err_t nvs_flash_erase(void) { + std::lock_guard lock(s_nvs_mutex); + s_nvs_storage.clear(); + s_nvs_handles.clear(); + s_nvs_next_handle = 1; + s_nvs_initialized = false; + return ESP_OK; +} + +esp_err_t nvs_open(const char *namespace_name, nvs_open_mode_t mode, + nvs_handle_t *out_handle) { + const esp_err_t name_error = nvs_validate_namespace(namespace_name); + if (name_error != ESP_OK || out_handle == nullptr) { + return name_error != ESP_OK ? name_error : ESP_ERR_INVALID_ARG; + } + if (mode != NVS_READONLY && mode != NVS_READWRITE) { + return ESP_ERR_INVALID_ARG; + } + + std::lock_guard lock(s_nvs_mutex); + if (!s_nvs_initialized) { + return ESP_ERR_NVS_NOT_INITIALIZED; + } + + auto namespace_it = s_nvs_storage.find(namespace_name); + if (namespace_it == s_nvs_storage.end()) { + if (mode == NVS_READONLY) { + return ESP_ERR_NVS_NOT_FOUND; + } + s_nvs_storage.emplace(namespace_name, NVSNamespace{}); + } + + const uint32_t handle = s_nvs_next_handle++; + s_nvs_handles.emplace(handle, NVSHandleInfo{namespace_name, mode}); + *out_handle = handle; + return ESP_OK; +} + +void nvs_close(nvs_handle_t handle) { + std::lock_guard lock(s_nvs_mutex); + s_nvs_handles.erase(handle); +} + +esp_err_t nvs_erase_key(nvs_handle_t handle, const char *key) { + const esp_err_t key_error = nvs_validate_key(key); + if (key_error != ESP_OK) { + return key_error; + } + + std::lock_guard lock(s_nvs_mutex); + auto handle_it = nvs_find_handle(handle); + if (handle_it == s_nvs_handles.end()) { + return ESP_ERR_NVS_INVALID_HANDLE; + } + if (!nvs_handle_is_writable(handle_it->second)) { + return ESP_ERR_NVS_READ_ONLY; + } + + auto &keys = s_nvs_storage[handle_it->second.namespace_name].keys; + auto key_it = keys.find(key); + if (key_it == keys.end()) { + return ESP_ERR_NVS_NOT_FOUND; + } + keys.erase(key_it); + return ESP_OK; +} + +esp_err_t nvs_erase_all(nvs_handle_t handle) { + std::lock_guard lock(s_nvs_mutex); + auto handle_it = nvs_find_handle(handle); + if (handle_it == s_nvs_handles.end()) { + return ESP_ERR_NVS_INVALID_HANDLE; + } + if (!nvs_handle_is_writable(handle_it->second)) { + return ESP_ERR_NVS_READ_ONLY; + } + s_nvs_storage[handle_it->second.namespace_name].keys.clear(); + return ESP_OK; +} + +esp_err_t nvs_commit(nvs_handle_t handle) { + std::lock_guard lock(s_nvs_mutex); + return nvs_find_handle(handle) != s_nvs_handles.end() ? ESP_OK + : ESP_ERR_NVS_INVALID_HANDLE; +} + +template +static esp_err_t nvs_get_value(nvs_handle_t handle, const char *key, T *out_value, + NVSValueType expected_type) { + const esp_err_t key_error = nvs_validate_key(key); + if (key_error != ESP_OK || out_value == nullptr) { + return key_error != ESP_OK ? key_error : ESP_ERR_INVALID_ARG; + } + + std::lock_guard lock(s_nvs_mutex); + auto handle_it = nvs_find_handle(handle); + if (handle_it == s_nvs_handles.end()) { + return ESP_ERR_NVS_INVALID_HANDLE; + } + + auto &keys = s_nvs_storage[handle_it->second.namespace_name].keys; + auto key_it = keys.find(key); + if (key_it == keys.end()) { + return ESP_ERR_NVS_NOT_FOUND; + } + if (key_it->second.type != expected_type) { + return ESP_ERR_NVS_TYPE_MISMATCH; + } + memcpy(out_value, key_it->second.data.data(), sizeof(T)); + return ESP_OK; +} + +template +static esp_err_t nvs_set_value(nvs_handle_t handle, const char *key, T value, + NVSValueType type) { + const esp_err_t key_error = nvs_validate_key(key); + if (key_error != ESP_OK) { + return key_error; + } + + std::lock_guard lock(s_nvs_mutex); + auto handle_it = nvs_find_handle(handle); + if (handle_it == s_nvs_handles.end()) { + return ESP_ERR_NVS_INVALID_HANDLE; + } + if (!nvs_handle_is_writable(handle_it->second)) { + return ESP_ERR_NVS_READ_ONLY; + } + + NVSValue stored_value{type, std::vector(sizeof(T))}; + memcpy(stored_value.data.data(), &value, sizeof(T)); + s_nvs_storage[handle_it->second.namespace_name].keys[key] = std::move(stored_value); + return ESP_OK; +} + +esp_err_t nvs_get_i8(nvs_handle_t handle, const char *key, int8_t *out_value) { + return nvs_get_value(handle, key, out_value, NVSValueType::I8); +} +esp_err_t nvs_get_u8(nvs_handle_t handle, const char *key, uint8_t *out_value) { + return nvs_get_value(handle, key, out_value, NVSValueType::U8); +} +esp_err_t nvs_get_i16(nvs_handle_t handle, const char *key, int16_t *out_value) { + return nvs_get_value(handle, key, out_value, NVSValueType::I16); +} +esp_err_t nvs_get_u16(nvs_handle_t handle, const char *key, uint16_t *out_value) { + return nvs_get_value(handle, key, out_value, NVSValueType::U16); +} +esp_err_t nvs_get_i32(nvs_handle_t handle, const char *key, int32_t *out_value) { + return nvs_get_value(handle, key, out_value, NVSValueType::I32); +} +esp_err_t nvs_get_u32(nvs_handle_t handle, const char *key, uint32_t *out_value) { + return nvs_get_value(handle, key, out_value, NVSValueType::U32); +} +esp_err_t nvs_get_i64(nvs_handle_t handle, const char *key, int64_t *out_value) { + return nvs_get_value(handle, key, out_value, NVSValueType::I64); +} +esp_err_t nvs_get_u64(nvs_handle_t handle, const char *key, uint64_t *out_value) { + return nvs_get_value(handle, key, out_value, NVSValueType::U64); +} + +esp_err_t nvs_set_i8(nvs_handle_t handle, const char *key, int8_t value) { + return nvs_set_value(handle, key, value, NVSValueType::I8); +} +esp_err_t nvs_set_u8(nvs_handle_t handle, const char *key, uint8_t value) { + return nvs_set_value(handle, key, value, NVSValueType::U8); +} +esp_err_t nvs_set_i16(nvs_handle_t handle, const char *key, int16_t value) { + return nvs_set_value(handle, key, value, NVSValueType::I16); +} +esp_err_t nvs_set_u16(nvs_handle_t handle, const char *key, uint16_t value) { + return nvs_set_value(handle, key, value, NVSValueType::U16); +} +esp_err_t nvs_set_i32(nvs_handle_t handle, const char *key, int32_t value) { + return nvs_set_value(handle, key, value, NVSValueType::I32); +} +esp_err_t nvs_set_u32(nvs_handle_t handle, const char *key, uint32_t value) { + return nvs_set_value(handle, key, value, NVSValueType::U32); +} +esp_err_t nvs_set_i64(nvs_handle_t handle, const char *key, int64_t value) { + return nvs_set_value(handle, key, value, NVSValueType::I64); +} +esp_err_t nvs_set_u64(nvs_handle_t handle, const char *key, uint64_t value) { + return nvs_set_value(handle, key, value, NVSValueType::U64); +} + +static esp_err_t nvs_get_buffer(nvs_handle_t handle, const char *key, void *out_value, + size_t *in_out_length, NVSValueType expected_type) { + const esp_err_t key_error = nvs_validate_key(key); + if (key_error != ESP_OK || in_out_length == nullptr) { + return key_error != ESP_OK ? key_error : ESP_ERR_INVALID_ARG; + } + + std::lock_guard lock(s_nvs_mutex); + auto handle_it = nvs_find_handle(handle); + if (handle_it == s_nvs_handles.end()) { + return ESP_ERR_NVS_INVALID_HANDLE; + } + + auto &keys = s_nvs_storage[handle_it->second.namespace_name].keys; + auto key_it = keys.find(key); + if (key_it == keys.end()) { + return ESP_ERR_NVS_NOT_FOUND; + } + if (key_it->second.type != expected_type) { + return ESP_ERR_NVS_TYPE_MISMATCH; + } + + const size_t required_length = key_it->second.data.size(); + if (out_value == nullptr) { + *in_out_length = required_length; + return ESP_OK; + } + if (*in_out_length < required_length) { + *in_out_length = required_length; + return ESP_ERR_NVS_INVALID_LENGTH; + } + if (required_length > 0) { + memcpy(out_value, key_it->second.data.data(), required_length); + } + *in_out_length = required_length; + return ESP_OK; +} + +static esp_err_t nvs_set_buffer(nvs_handle_t handle, const char *key, const void *value, + size_t length, NVSValueType type) { + const esp_err_t key_error = nvs_validate_key(key); + if (key_error != ESP_OK || (value == nullptr && length > 0)) { + return key_error != ESP_OK ? key_error : ESP_ERR_INVALID_ARG; + } + + std::lock_guard lock(s_nvs_mutex); + auto handle_it = nvs_find_handle(handle); + if (handle_it == s_nvs_handles.end()) { + return ESP_ERR_NVS_INVALID_HANDLE; + } + if (!nvs_handle_is_writable(handle_it->second)) { + return ESP_ERR_NVS_READ_ONLY; + } + + NVSValue stored_value{type, {}}; + if (length > 0) { + const auto *bytes = static_cast(value); + stored_value.data.assign(bytes, bytes + length); + } + s_nvs_storage[handle_it->second.namespace_name].keys[key] = std::move(stored_value); + return ESP_OK; +} + +esp_err_t nvs_get_str(nvs_handle_t handle, const char *key, char *out_value, + size_t *in_out_length) { + return nvs_get_buffer(handle, key, out_value, in_out_length, NVSValueType::String); +} + +esp_err_t nvs_set_str(nvs_handle_t handle, const char *key, const char *value) { + if (value == nullptr) { + return ESP_ERR_INVALID_ARG; + } + return nvs_set_buffer(handle, key, value, strlen(value) + 1, NVSValueType::String); +} + +esp_err_t nvs_get_blob(nvs_handle_t handle, const char *key, void *out_value, + size_t *in_out_length) { + return nvs_get_buffer(handle, key, out_value, in_out_length, NVSValueType::Blob); +} + +esp_err_t nvs_set_blob(nvs_handle_t handle, const char *key, const void *value, + size_t length) { + return nvs_set_buffer(handle, key, value, length, NVSValueType::Blob); +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// WiFi Stubs +// ═══════════════════════════════════════════════════════════════════════════════ + +int esp_wifi_init(const void *cfg) { (void)cfg; return ESP_OK; } +int esp_wifi_start(void) { return ESP_OK; } +int esp_wifi_stop(void) { return ESP_OK; } +int esp_wifi_disconnect(void) { return ESP_OK; } +int esp_wifi_scan_start(const void *cfg, bool block) { (void)cfg; (void)block; return ESP_OK; } +int esp_wifi_scan_stop(void) { return ESP_OK; } +int esp_wifi_scan_get_ap_num(uint16_t *num) { if (num) *num = 0; return ESP_OK; } +int esp_wifi_scan_get_ap_records(uint16_t *num, wifi_ap_record_t *rec) { (void)num; (void)rec; return ESP_OK; } +int esp_wifi_set_promiscuous(bool enable) { (void)enable; return ESP_OK; } +int esp_wifi_set_promiscuous_filter(const wifi_promiscuous_filter_t *filter) { (void)filter; return ESP_OK; } +int esp_wifi_set_promiscuous_rx_cb(wifi_promiscuous_cb_t cb) { (void)cb; return ESP_OK; } +int esp_wifi_set_channel(uint8_t primary, int secondary) { (void)primary; (void)secondary; return ESP_OK; } +int esp_wifi_get_channel(uint8_t *primary, int *secondary) { if (primary) *primary = 6; if (secondary) *secondary = 0; return ESP_OK; } + +// ═══════════════════════════════════════════════════════════════════════════════ +// GPIO Stubs +// ═══════════════════════════════════════════════════════════════════════════════ + +static std::mutex s_gpio_mutex; +static std::map s_gpio_levels; + +static constexpr gpio_num_t HLE_GPIO_BTN_LEFT_PIN = 5; +static constexpr gpio_num_t HLE_GPIO_BTN_BACK_PIN = 7; +static constexpr gpio_num_t HLE_GPIO_BTN_UP_PIN = 15; +static constexpr gpio_num_t HLE_GPIO_BTN_DOWN_PIN = 6; +static constexpr gpio_num_t HLE_GPIO_BTN_OK_PIN = 4; +static constexpr gpio_num_t HLE_GPIO_BTN_RIGHT_PIN = 16; + +esp_err_t gpio_config(const gpio_config_t *cfg) { + if (!cfg) return ESP_ERR_INVALID_ARG; + std::lock_guard lock(s_gpio_mutex); + for (gpio_num_t pin = 0; pin < 64; ++pin) { + if ((cfg->pin_bit_mask & (1ULL << pin)) == 0) continue; + if ((cfg->mode == GPIO_MODE_INPUT || cfg->mode == GPIO_MODE_INPUT_OUTPUT) && cfg->pull_up_en) { + s_gpio_levels[pin] = true; + } else if ((cfg->mode == GPIO_MODE_INPUT || cfg->mode == GPIO_MODE_INPUT_OUTPUT) && cfg->pull_down_en) { + s_gpio_levels[pin] = false; + } + } + return ESP_OK; +} +esp_err_t gpio_set_level(gpio_num_t pin, uint32_t level) { + std::lock_guard lock(s_gpio_mutex); + s_gpio_levels[pin] = (level != 0); + return ESP_OK; +} +int gpio_get_level(gpio_num_t pin) { + std::lock_guard lock(s_gpio_mutex); + auto it = s_gpio_levels.find(pin); + return (it != s_gpio_levels.end() && it->second) ? 1 : 0; +} +esp_err_t gpio_install_isr_service(int flags) { (void)flags; return ESP_OK; } +esp_err_t gpio_isr_handler_add(gpio_num_t pin, void (*handler)(void *), void *arg) { + (void)pin; (void)handler; (void)arg; return ESP_OK; +} +void gpio_reset_pin(gpio_num_t pin) { (void)pin; } + +extern "C" void hle_set_button_mask(unsigned char mask) { + std::lock_guard lock(s_gpio_mutex); + + s_gpio_levels[HLE_GPIO_BTN_UP_PIN] = (mask & (1u << 0)) == 0; + s_gpio_levels[HLE_GPIO_BTN_DOWN_PIN] = (mask & (1u << 1)) == 0; + s_gpio_levels[HLE_GPIO_BTN_LEFT_PIN] = (mask & (1u << 2)) == 0; + s_gpio_levels[HLE_GPIO_BTN_RIGHT_PIN] = (mask & (1u << 3)) == 0; + s_gpio_levels[HLE_GPIO_BTN_OK_PIN] = (mask & (1u << 4)) == 0; + s_gpio_levels[HLE_GPIO_BTN_BACK_PIN] = (mask & (1u << 5)) == 0; +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// I2C Stubs +// ═══════════════════════════════════════════════════════════════════════════════ + +esp_err_t i2c_param_config(i2c_port_t port, const i2c_config_t *cfg) { (void)port; (void)cfg; return ESP_OK; } +esp_err_t i2c_driver_install(i2c_port_t port, int mode, int rx_buf, int tx_buf, int flags) { + (void)port; (void)mode; (void)rx_buf; (void)tx_buf; (void)flags; return ESP_OK; +} +i2c_cmd_handle_t i2c_cmd_link_create(void) { return 0; } +esp_err_t i2c_master_start(i2c_cmd_handle_t cmd) { (void)cmd; return ESP_OK; } +esp_err_t i2c_master_write_byte(i2c_cmd_handle_t cmd, uint8_t data, bool ack_en) { + (void)cmd; (void)data; (void)ack_en; return ESP_OK; +} +esp_err_t i2c_master_read_byte(i2c_cmd_handle_t cmd, uint8_t *data, int ack) { + (void)cmd; (void)ack; if (data) *data = 0; return ESP_OK; +} +esp_err_t i2c_master_stop(i2c_cmd_handle_t cmd) { (void)cmd; return ESP_OK; } +esp_err_t i2c_master_cmd_begin(i2c_port_t port, i2c_cmd_handle_t cmd, int timeout_ms) { + (void)port; (void)cmd; (void)timeout_ms; return ESP_OK; +} +void i2c_cmd_link_delete(i2c_cmd_handle_t cmd) { (void)cmd; } + +// ═══════════════════════════════════════════════════════════════════════════════ +// SPI Stubs +// ═══════════════════════════════════════════════════════════════════════════════ + +esp_err_t spi_bus_initialize(spi_host_device_t host, const spi_bus_config_t *bus, int dma) { + (void)host; (void)bus; (void)dma; return ESP_OK; +} +esp_err_t spi_bus_add_device(spi_host_device_t host, const spi_device_interface_config_t *dev, spi_device_handle_t *handle) { + (void)host; (void)dev; + if (handle) *handle = (void *)(uintptr_t)1; // non-NULL to pass checks + return ESP_OK; +} +esp_err_t spi_device_transmit(spi_device_handle_t handle, const void *trans) { (void)handle; (void)trans; return ESP_OK; } +esp_err_t spi_device_polling_transmit(spi_device_handle_t handle, const void *trans) { + (void)handle; (void)trans; return ESP_OK; +} +esp_err_t spi_bus_remove_device(spi_device_handle_t handle) { (void)handle; return ESP_OK; } + +// SPI Bridge PHY — implemented after SPIBridgeChannel is declared (see below). + +// ═══════════════════════════════════════════════════════════════════════════════ +// RMT (Remote Control TX/RX) — opaque-handle stubs with honest degradation +// ═══════════════════════════════════════════════════════════════════════════════ +namespace { + +struct HleRmtHandle { + enum class Kind { RxChannel, TxChannel } kind; + bool enabled = false; + rmt_rx_event_callbacks_t rx_cbs = {}; + void *rx_user_data = nullptr; +}; + +struct HleRmtEncoder { + enum class Kind { Copy } kind; +}; + +} // namespace + +esp_err_t rmt_new_rx_channel(const rmt_rx_channel_config_t *config, rmt_channel_handle_t *ret_chan) { + if (!config || !ret_chan) return ESP_ERR_INVALID_ARG; + *ret_chan = new HleRmtHandle{HleRmtHandle::Kind::RxChannel}; + return ESP_OK; +} +esp_err_t rmt_new_tx_channel(const rmt_tx_channel_config_t *config, rmt_channel_handle_t *ret_chan) { + if (!config || !ret_chan) return ESP_ERR_INVALID_ARG; + *ret_chan = new HleRmtHandle{HleRmtHandle::Kind::TxChannel}; + return ESP_OK; +} +esp_err_t rmt_new_copy_encoder(const rmt_copy_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder) { + if (!config || !ret_encoder) return ESP_ERR_INVALID_ARG; + *ret_encoder = new HleRmtEncoder{HleRmtEncoder::Kind::Copy}; + return ESP_OK; +} +esp_err_t rmt_rx_register_event_callbacks(rmt_channel_handle_t rx_chan, const rmt_rx_event_callbacks_t *cbs, void *user_data) { + if (!rx_chan || !cbs) return ESP_ERR_INVALID_ARG; + auto *h = static_cast(rx_chan); + if (h->kind != HleRmtHandle::Kind::RxChannel) return ESP_ERR_INVALID_ARG; + h->rx_cbs = *cbs; + h->rx_user_data = user_data; + return ESP_OK; +} +esp_err_t rmt_enable(rmt_channel_handle_t chan) { + if (!chan) return ESP_ERR_INVALID_ARG; + static_cast(chan)->enabled = true; + return ESP_OK; +} +esp_err_t rmt_disable(rmt_channel_handle_t chan) { + if (!chan) return ESP_ERR_INVALID_ARG; + static_cast(chan)->enabled = false; + return ESP_OK; +} +esp_err_t rmt_receive(rmt_channel_handle_t rx_chan, rmt_symbol_word_t *buffer, size_t buffer_size, const rmt_receive_config_t *config) { + if (!rx_chan || !buffer || !config) return ESP_ERR_INVALID_ARG; + auto *h = static_cast(rx_chan); + if (h->kind != HleRmtHandle::Kind::RxChannel) return ESP_ERR_INVALID_ARG; + return ESP_ERR_NOT_SUPPORTED; +} +esp_err_t rmt_transmit(rmt_channel_handle_t tx_chan, rmt_encoder_handle_t encoder, const void *payload, size_t payload_bytes, const rmt_transmit_config_t *config) { + if (!tx_chan || !encoder || !payload || !config) return ESP_ERR_INVALID_ARG; + return ESP_ERR_NOT_SUPPORTED; +} +esp_err_t rmt_tx_wait_all_done(rmt_channel_handle_t tx_chan, int timeout_ms) { + if (!tx_chan) return ESP_ERR_INVALID_ARG; + return ESP_OK; +} +esp_err_t rmt_del_channel(rmt_channel_handle_t chan) { + if (!chan) return ESP_ERR_INVALID_ARG; + delete static_cast(chan); + return ESP_OK; +} +esp_err_t rmt_del_encoder(rmt_encoder_handle_t encoder) { + if (!encoder) return ESP_ERR_INVALID_ARG; + delete static_cast(encoder); + return ESP_OK; +} +esp_err_t rmt_apply_carrier(rmt_channel_handle_t tx_chan, const rmt_carrier_config_t *config) { + if (!tx_chan || !config) return ESP_ERR_INVALID_ARG; + return ESP_OK; +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// SPI Bridge Channel Integration +// ═══════════════════════════════════════════════════════════════════════════════ + +static std::atomic s_bridge_channel{nullptr}; + +void hle_set_bridge_channel(hle::SPIBridgeChannel *channel) { + s_bridge_channel.store(channel, std::memory_order_release); +} + +hle::SPIBridgeChannel *hle_get_bridge_channel(void) { + return s_bridge_channel.load(std::memory_order_acquire); +} + +// ── SPI Bridge PHY — wired to in-memory channel ──────────────────────────────── +// spi_bridge.c calls: transmit(cmd,NULL) → wait_irq → transmit(empty,resp) +// We track the pending command across these calls. + +#include "spi_protocol.h" + +static std::recursive_mutex s_phy_mutex; +static spi_header_t s_pending_cmd_header; +static uint8_t s_pending_cmd_payload[SPI_MAX_PAYLOAD]; +static bool s_has_pending_cmd = false; +static uint32_t s_pending_timeout_ms = 0; +static bool s_phy_uses_irq = true; + +extern "C" { + +esp_err_t spi_bridge_phy_init_ex(bool setup_irq) { + std::lock_guard lock(s_phy_mutex); + if (hle_get_bridge_channel() == nullptr) { + return ESP_ERR_INVALID_STATE; + } + s_has_pending_cmd = false; + s_pending_timeout_ms = 0; + s_phy_uses_irq = setup_irq; + ESP_LOGI("SPI_PHY", "PHY init (%s)", setup_irq ? "IRQ" : "poll"); + return ESP_OK; +} + +esp_err_t spi_bridge_phy_init(void) { + return spi_bridge_phy_init_ex(true); +} +esp_err_t spi_bridge_phy_transmit(const uint8_t *tx_data, uint8_t *rx_data, size_t len) { + std::lock_guard lock(s_phy_mutex); + auto *channel = hle_get_bridge_channel(); + if (channel == nullptr || channel->is_closed()) { + return ESP_ERR_INVALID_STATE; + } + + if (rx_data == nullptr) { + if (tx_data == nullptr || len < sizeof(spi_header_t)) { + return ESP_ERR_INVALID_ARG; + } + + const auto *header = reinterpret_cast(tx_data); + if (len < sizeof(spi_header_t) + header->length) { + return ESP_ERR_INVALID_SIZE; + } + + memcpy(&s_pending_cmd_header, header, sizeof(spi_header_t)); + memset(s_pending_cmd_payload, 0, sizeof(s_pending_cmd_payload)); + if (header->length > 0) { + memcpy(s_pending_cmd_payload, tx_data + sizeof(spi_header_t), header->length); + } + const uint16_t command_id = spi_header_cmd(header); + s_has_pending_cmd = true; + ESP_LOGI("SPI_PHY", "TX cmd 0x%04X len=%d", + static_cast(command_id), header->length); + channel->master_send_command(command_id, s_pending_cmd_payload, header->length); + return ESP_OK; + } + + if (!s_has_pending_cmd) { + memset(rx_data, 0, len); + return ESP_ERR_INVALID_STATE; + } + + uint16_t resp_id = 0; + uint8_t resp_payload[SPI_MAX_PAYLOAD]; + uint8_t resp_len = 0; + bool is_ok = + channel->master_receive_response(resp_id, resp_payload, resp_len, s_pending_timeout_ms); + memset(rx_data, 0, len); + if (!is_ok) { + if (!s_phy_uses_irq) { + return ESP_OK; + } + s_has_pending_cmd = false; + return ESP_ERR_TIMEOUT; + } + + ESP_LOGI("SPI_PHY", "RX response id=0x%04X len=%d", + static_cast(resp_id), resp_len); + s_has_pending_cmd = false; + + const size_t total = sizeof(spi_header_t) + resp_len; + if (len < total) { + return ESP_ERR_INVALID_SIZE; + } + + spi_header_t resp_header{}; + resp_header.sync = SPI_SYNC_BYTE; + resp_header.type = SPI_TYPE_RESP; + spi_header_set_cmd(&resp_header, resp_id); + resp_header.length = resp_len; + memcpy(rx_data, &resp_header, sizeof(resp_header)); + if (resp_len > 0) { + memcpy(rx_data + sizeof(resp_header), resp_payload, resp_len); + } + return ESP_OK; +} +esp_err_t spi_bridge_phy_wait_irq(uint32_t timeout_ms) { + std::lock_guard lock(s_phy_mutex); + auto *channel = hle_get_bridge_channel(); + if (channel == nullptr || channel->is_closed()) { + return ESP_ERR_INVALID_STATE; + } + + s_pending_timeout_ms = timeout_ms; + ESP_LOGI("SPI_PHY", "Wait IRQ %" PRIu32 " ms", timeout_ms); + const bool is_ready = channel->master_wait_irq(timeout_ms); + ESP_LOGI("SPI_PHY", "IRQ result=%d", is_ready); + return is_ready ? ESP_OK : ESP_ERR_TIMEOUT; +} + +} // extern "C" + +static const size_t B_FRAME = SPI_FRAME_SIZE; + +// SPI slave stubs — wired to in-memory channel +esp_err_t spi_slave_initialize(int host, const spi_bus_config_t *bus, + const spi_slave_interface_config_t *cfg, int dma) { + (void)host; (void)bus; (void)cfg; (void)dma; return ESP_OK; +} + +esp_err_t spi_slave_transmit(int host, spi_slave_transaction_t *trans, int timeout) { + (void)host; + (void)timeout; + auto *channel = hle_get_bridge_channel(); + if (channel == nullptr || channel->is_closed() || trans == nullptr) { + return ESP_ERR_INVALID_STATE; + } + + if (trans->tx_buffer == nullptr && trans->rx_buffer != nullptr) { + uint16_t command_id; + uint8_t payload[SPI_MAX_PAYLOAD]; + uint8_t payload_len; + if (!channel->slave_wait_command(command_id, payload, payload_len)) { + return ESP_FAIL; + } + + auto *header = static_cast(trans->rx_buffer); + memset(header, 0, B_FRAME); + header->sync = SPI_SYNC_BYTE; + header->type = SPI_TYPE_CMD; + spi_header_set_cmd(header, command_id); + header->length = payload_len; + if (payload_len > 0) { + memcpy(reinterpret_cast(header) + sizeof(*header), payload, payload_len); + } + } else if (trans->tx_buffer != nullptr && trans->rx_buffer == nullptr) { + const auto *header = static_cast(trans->tx_buffer); + if (header->sync != SPI_SYNC_BYTE) { + return ESP_FAIL; + } + + const uint16_t command_id = spi_header_cmd(header); + const auto *payload = + reinterpret_cast(header) + sizeof(spi_header_t); + if (header->type == SPI_TYPE_RESP) { + const uint8_t status = header->length > 0 ? payload[0] : 0; + const uint8_t data_len = header->length > 0 ? header->length - 1 : 0; + channel->slave_send_response(command_id, status, payload + 1, data_len); + channel->slave_notify_irq(); + } else if (header->type == SPI_TYPE_STREAM) { + channel->slave_send_stream(command_id, payload, header->length); + } else { + return ESP_ERR_INVALID_ARG; + } + } else { + return ESP_ERR_INVALID_ARG; + } + + return ESP_OK; +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// LED Strip Stubs +// ═══════════════════════════════════════════════════════════════════════════════ + +esp_err_t led_strip_new_rmt_device(const led_strip_config_t *strip_cfg, + const led_strip_rmt_config_t *rmt_cfg, + led_strip_handle_t *handle) { + (void)strip_cfg; (void)rmt_cfg; + if (handle) *handle = (void *)(uintptr_t)1; + return ESP_OK; +} +esp_err_t led_strip_set_pixel(led_strip_handle_t handle, uint32_t index, uint32_t r, uint32_t g, uint32_t b) { + (void)handle; (void)index; (void)r; (void)g; (void)b; return ESP_OK; +} +esp_err_t led_strip_refresh(led_strip_handle_t handle) { (void)handle; return ESP_OK; } +esp_err_t led_strip_clear(led_strip_handle_t handle) { (void)handle; return ESP_OK; } +esp_err_t led_strip_del(led_strip_handle_t handle) { (void)handle; return ESP_OK; } + +// ═══════════════════════════════════════════════════════════════════════════════ +// RMT (new API) stubs — implemented above +// ═══════════════════════════════════════════════════════════════════════════════ + +// SD card stubs +esp_err_t sdmmc_host_init(void) { return ESP_OK; } +esp_err_t sdmmc_host_init_slot(int slot, const void *config) { (void)slot; (void)config; return ESP_OK; } +void sdmmc_card_print_info(FILE *f, const sdmmc_card_t *card) { + (void)f; (void)card; fprintf(stderr, "I [SDMMC] Mock SD card\n"); +} +void sdmmc_host_deinit(void) {} + +// ═══════════════════════════════════════════════════════════════════════════════ +// ESP LCD Panel Stubs (wired to HLE Display) +// ═══════════════════════════════════════════════════════════════════════════════ + +struct HLEPanel { + esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; + void *ctx; +}; + +esp_err_t esp_lcd_new_panel_io_spi(int bus, const esp_lcd_panel_io_spi_config_t *cfg, + esp_lcd_panel_io_handle_t *out_io) { + (void)bus; (void)cfg; + auto *io = new HLEPanel{}; + if (out_io) *out_io = io; + return ESP_OK; +} + +esp_err_t esp_lcd_new_panel_st7789(esp_lcd_panel_io_handle_t io, + const esp_lcd_panel_dev_config_t *cfg, + esp_lcd_panel_handle_t *out_panel) { + (void)io; (void)cfg; + if (out_panel) *out_panel = io; + return ESP_OK; +} + +esp_err_t esp_lcd_panel_reset(esp_lcd_panel_handle_t panel) { (void)panel; return ESP_OK; } +esp_err_t esp_lcd_panel_init(esp_lcd_panel_handle_t panel) { (void)panel; return ESP_OK; } +esp_err_t esp_lcd_panel_invert_color(esp_lcd_panel_handle_t panel, bool invert) { (void)panel; (void)invert; return ESP_OK; } +esp_err_t esp_lcd_panel_disp_on_off(esp_lcd_panel_handle_t panel, bool on) { (void)panel; (void)on; return ESP_OK; } +esp_err_t esp_lcd_panel_mirror(esp_lcd_panel_handle_t panel, bool mx, bool my) { (void)panel; (void)mx; (void)my; return ESP_OK; } +esp_err_t esp_lcd_panel_swap_xy(esp_lcd_panel_handle_t panel, bool swap) { (void)panel; (void)swap; return ESP_OK; } +esp_err_t esp_lcd_panel_set_gap(esp_lcd_panel_handle_t panel, int x, int y) { (void)panel; (void)x; (void)y; return ESP_OK; } + +esp_err_t esp_lcd_panel_draw_bitmap(esp_lcd_panel_handle_t panel, int x0, int y0, int x1, int y1, + const void *data) { + (void)panel; + if (x0 < 0) x0 = 0; + if (y0 < 0) y0 = 0; + if (x1 > hle::LCD_H_RES) x1 = hle::LCD_H_RES; + if (y1 > hle::LCD_V_RES) y1 = hle::LCD_V_RES; + hle::Display::instance().draw_bitmap(x0, y0, x1, y1, static_cast(data)); + + // Notify LVGL flush ready + auto *io = static_cast(panel); + if (io && io->on_color_trans_done) { + io->on_color_trans_done(io, nullptr, io->ctx); + } + return ESP_OK; +} + +esp_err_t esp_lcd_panel_io_register_event_callbacks(esp_lcd_panel_io_handle_t io, + const esp_lcd_panel_io_callbacks_t *cbs, void *ctx) { + auto *p = static_cast(io); + if (p && cbs) { + p->on_color_trans_done = cbs->on_color_trans_done; + p->ctx = ctx; + } + return ESP_OK; +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// VFS / Storage stubs (mount to host tmpdir) +// ═══════════════════════════════════════════════════════════════════════════════ + +#include +#include + +static std::string s_base_path = "/tmp/hle_storage"; + +void hle_set_storage_path(const char *path) { + if (path != nullptr && path[0] != '\0') { + s_base_path = path; + } +} + +const char *hle_get_storage_path(void) { + return s_base_path.c_str(); +} + +static std::string hle_translate_path(const char *path) { + if (!path || path[0] == '\0') return {}; + + const std::string src(path); + if (src.rfind("/sdcard", 0) == 0) { + return s_base_path + src; + } + + if (src.rfind("/assets", 0) == 0) { +#ifdef HLE_ASSETS_DIR + return std::string(HLE_ASSETS_DIR) + src.substr(sizeof("/assets") - 1); +#else + return s_base_path + src; +#endif + } + + return src; +} + +extern "C" FILE *__real_fopen(const char *path, const char *mode); +extern "C" int __real_stat(const char *path, struct stat *st); +extern "C" DIR *__real_opendir(const char *path); +extern "C" int __real_mkdir(const char *path, mode_t mode); +extern "C" int __real_access(const char *path, int mode); +extern "C" int __real_remove(const char *path); +extern "C" int __real_rename(const char *old_path, const char *new_path); +extern "C" int __real_unlink(const char *path); + +extern "C" FILE *__wrap_fopen(const char *path, const char *mode) { + const std::string translated = hle_translate_path(path); + return __real_fopen(translated.c_str(), mode); +} + +extern "C" int __wrap_stat(const char *path, struct stat *st) { + const std::string translated = hle_translate_path(path); + return __real_stat(translated.c_str(), st); +} + +extern "C" DIR *__wrap_opendir(const char *path) { + const std::string translated = hle_translate_path(path); + return __real_opendir(translated.c_str()); +} + +extern "C" int __wrap_mkdir(const char *path, mode_t mode) { + const std::string translated = hle_translate_path(path); + return __real_mkdir(translated.c_str(), mode); +} + +extern "C" int __wrap_access(const char *path, int mode) { + const std::string translated = hle_translate_path(path); + return __real_access(translated.c_str(), mode); +} + +extern "C" int __wrap_remove(const char *path) { + const std::string translated = hle_translate_path(path); + return __real_remove(translated.c_str()); +} + +extern "C" int __wrap_rename(const char *old_path, const char *new_path) { + const std::string translated_old = hle_translate_path(old_path); + const std::string translated_new = hle_translate_path(new_path); + return __real_rename(translated_old.c_str(), translated_new.c_str()); +} + +extern "C" int __wrap_unlink(const char *path) { + const std::string translated = hle_translate_path(path); + return __real_unlink(translated.c_str()); +} + +extern "C" { + +esp_err_t esp_vfs_fat_sdmmc_mount(const char *base_path, const sdmmc_host_t *host, const void *slot, + const void *mount_conf, sdmmc_card_t **out_card) { + (void)host; (void)slot; (void)mount_conf; + std::string dir = s_base_path + (base_path ? base_path : "/sdcard"); + mkdir(dir.c_str(), 0755); + fprintf(stderr, "I [VFS] Mounted HLE storage at %s\n", dir.c_str()); + if (out_card) *out_card = (sdmmc_card_t *)0x1; + return ESP_OK; +} + +esp_err_t esp_vfs_fat_sdmmc_unmount(void) { return ESP_OK; } +esp_err_t esp_vfs_fat_sdcard_unmount(const char *base_path, sdmmc_card_t *card) { + (void)base_path; (void)card; return ESP_OK; +} + +esp_err_t esp_vfs_fat_spiflash_mount(const char *base, const char *part_label, const void *mount, void *wl_handle) { + (void)base; (void)part_label; (void)mount; (void)wl_handle; + std::string dir = s_base_path + "/assets"; + mkdir(dir.c_str(), 0755); + return ESP_OK; +} + +esp_err_t esp_vfs_fat_spiflash_unmount(const char *base, void *wl_handle) { + (void)base; (void)wl_handle; return ESP_OK; +} + +} // extern "C" + +// LittleFS stubs +esp_err_t esp_vfs_littlefs_register(const esp_vfs_littlefs_conf_t *conf) { + if (conf && conf->base_path) { + std::string dir = s_base_path + conf->base_path; + mkdir(dir.c_str(), 0755); + fprintf(stderr, "I [LFS] Registered HLE LittleFS at %s\n", conf->base_path); + } + return ESP_OK; +} +esp_err_t esp_vfs_littlefs_unregister(const char *label) { (void)label; return ESP_OK; } + +// Partition stubs +esp_partition_t *esp_partition_find_first(int type, int subtype, const char *label) { + (void)type; (void)subtype; (void)label; + static esp_partition_t p = {0, 8*1024*1024, "storage"}; + return &p; +} +const esp_partition_t *esp_ota_get_running_partition(void) { return nullptr; } +void esp_partition_get_sha256(const esp_partition_t *p, uint8_t *sha) { (void)p; memset(sha, 0, 32); } diff --git a/tools/hle/src/hal/hle_kernel.cpp b/tools/hle/src/hal/hle_kernel.cpp new file mode 100644 index 00000000..5c01c6f7 --- /dev/null +++ b/tools/hle/src/hal/hle_kernel.cpp @@ -0,0 +1,197 @@ +#include "hle/hle_kernel.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hle/spi_bridge_channel.h" + +extern "C" { +#include "esp_err.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "spi_protocol.h" +} + +static const char *TAG = "HLE_SHIM"; + +// ═══════════════════════════════════════════════════════════════════════════════ +// Hardware init stubs (their .c files need deep ESP-IDF integration) +// ═══════════════════════════════════════════════════════════════════════════════ + +extern "C" { + +static bool s_vfs_mounted = false; +bool vfs_sdcard_is_mounted(void) { return s_vfs_mounted; } +esp_err_t vfs_register_sd_backend(void) { + const std::filesystem::path dir = + std::filesystem::path(hle_get_storage_path()) / "sdcard"; + std::error_code error; + std::filesystem::create_directories(dir, error); + if (error) { + ESP_LOGE(TAG, "Failed to create HLE storage path %s: %s", dir.c_str(), + error.message().c_str()); + s_vfs_mounted = false; + return ESP_FAIL; + } + + s_vfs_mounted = true; + ESP_LOGI(TAG, "HLE SD card mounted at %s", dir.c_str()); + return ESP_OK; +} +esp_err_t vfs_unregister_sd_backend(void) { + s_vfs_mounted = false; + return ESP_OK; +} + +} // extern "C" + +// ═══════════════════════════════════════════════════════════════════════════════ +// Recursive mutex — FreeRTOS recursive semaphore emulation via pthread +// ═══════════════════════════════════════════════════════════════════════════════ + +static std::mutex s_rmutex_set_mtx; +static std::unordered_set s_rmutex_set; + +static pthread_mutex_t *hle_rmutex_create(void) { + auto *m = new pthread_mutex_t; + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(m, &attr); + pthread_mutexattr_destroy(&attr); + { + std::lock_guard lock(s_rmutex_set_mtx); + s_rmutex_set.insert(m); + } + return m; +} +static bool hle_rmutex_lock(pthread_mutex_t *m) { return pthread_mutex_lock(m) == 0; } +static bool hle_rmutex_unlock(pthread_mutex_t *m) { return pthread_mutex_unlock(m) == 0; } + +extern "C" { + +SemaphoreHandle_t xSemaphoreCreateRecursiveMutex(void) { + return (SemaphoreHandle_t)hle_rmutex_create(); +} +BaseType_t xSemaphoreTakeRecursive(SemaphoreHandle_t m, TickType_t timeout) { + (void)timeout; + return hle_rmutex_lock((pthread_mutex_t *)m) ? pdTRUE : pdFALSE; +} +BaseType_t xSemaphoreGiveRecursive(SemaphoreHandle_t m) { + return hle_rmutex_unlock((pthread_mutex_t *)m) ? pdTRUE : pdFALSE; +} + +void uxTaskGetSystemState(void *tasks, uint32_t count, uint32_t *run_time) { + (void)tasks; (void)count; + if (run_time) *run_time = 0; +} +UBaseType_t uxTaskGetNumberOfTasks(void) { return 1; } + +} // extern "C" + +// Override vSemaphoreDelete to handle recursive mutexes as well +extern "C" void _hle_vSemaphoreDelete(void *sem); +void _hle_vSemaphoreDelete(void *sem) { + if (!sem) return; + { + std::lock_guard lock(s_rmutex_set_mtx); + auto it = s_rmutex_set.find((pthread_mutex_t *)sem); + if (it != s_rmutex_set.end()) { + pthread_mutex_destroy(*it); + delete *it; + s_rmutex_set.erase(it); + return; + } + } + vSemaphoreDelete(sem); +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// C5 Bridge Worker (simulates C5 co-processor SPI responses) +// ═══════════════════════════════════════════════════════════════════════════════ + +extern "C" { + +#include "ota_version.h" + +static std::thread s_bridge_worker; + +static void c5_bridge_worker(hle::SPIBridgeChannel *channel) { + ESP_LOGI(TAG, "C5 bridge worker started"); + while (!channel->is_closed()) { + uint16_t cmd_id; + uint8_t payload[256]; + uint8_t payload_len; + if (!channel->slave_wait_command(cmd_id, payload, payload_len)) { + break; + } + + switch (cmd_id) { + case SPI_ID_SYSTEM_PING: + channel->slave_send_response(cmd_id, SPI_STATUS_OK, nullptr, 0); + break; + case SPI_ID_SYSTEM_STATUS: { + uint8_t status[] = {1, 0, 1, 0}; + channel->slave_send_response(cmd_id, SPI_STATUS_OK, status, sizeof(status)); + break; + } + case SPI_ID_SYSTEM_VERSION: + channel->slave_send_response(cmd_id, SPI_STATUS_OK, + reinterpret_cast(FIRMWARE_VERSION), + static_cast(strlen(FIRMWARE_VERSION))); + break; + default: + channel->slave_send_response(cmd_id, SPI_STATUS_UNSUPPORTED, nullptr, 0); + break; + } + channel->slave_notify_irq(); + } + ESP_LOGI(TAG, "C5 bridge worker exiting"); +} + +} // extern "C" + +// ═══════════════════════════════════════════════════════════════════════════════ +// Public API: boots the REAL firmware kernel + UI +// ═══════════════════════════════════════════════════════════════════════════════ + +extern "C" { + +extern void kernel_init(void); + +void hle_kernel_init(void) { + auto *channel = hle_get_bridge_channel(); + if (channel != nullptr && !s_bridge_worker.joinable()) { + s_bridge_worker = std::thread(c5_bridge_worker, channel); + ESP_LOGI(TAG, "Bridge worker started"); + } else if (channel == nullptr) { + ESP_LOGI(TAG, "No bridge channel, worker not started"); + } else { + ESP_LOGW(TAG, "Bridge worker already running"); + } + + ESP_LOGI("MAIN", "Booting TentacleOS firmware..."); + kernel_init(); + ESP_LOGI("MAIN", "Firmware booted — running LVGL event loop"); +} + +void hle_kernel_shutdown(void) { + auto *channel = hle_get_bridge_channel(); + if (channel != nullptr) { + channel->close(); + } + if (s_bridge_worker.joinable()) { + s_bridge_worker.join(); + } + hle_set_bridge_channel(nullptr); +} + +} // extern "C" diff --git a/tools/hle/src/hal/hle_stub_dispatchers.cpp b/tools/hle/src/hal/hle_stub_dispatchers.cpp new file mode 100644 index 00000000..432a4308 --- /dev/null +++ b/tools/hle/src/hal/hle_stub_dispatchers.cpp @@ -0,0 +1,58 @@ +#include + +extern "C" { +#include "esp_err.h" +} + +// Types from spi_protocol.h (HLE shim has no C5 include path for the interactive target). +typedef uint8_t spi_id_t; +typedef uint8_t spi_status_t; +#define SPI_STATUS_OK 0x00 +#define SPI_STATUS_UNSUPPORTED 0x03 + +extern "C" { + +spi_status_t wifi_dispatcher_execute(spi_id_t id, const uint8_t *payload, uint8_t len, + uint8_t *out_resp_payload, uint8_t *out_resp_len) { + (void)id; (void)payload; (void)len; (void)out_resp_payload; + if (out_resp_len) *out_resp_len = 0; + return SPI_STATUS_UNSUPPORTED; +} +spi_status_t bt_dispatcher_execute(spi_id_t id, const uint8_t *payload, uint8_t len, + uint8_t *out_resp_payload, uint8_t *out_resp_len) { + (void)id; (void)payload; (void)len; (void)out_resp_payload; + if (out_resp_len) *out_resp_len = 0; + return SPI_STATUS_UNSUPPORTED; +} + +// Meshtastic GATT/TCP stubs — NimBLE/TCP unavailable in HLE. +// Signatures match firmware_c5/.../meshtastic_gatt.h and meshtastic_tcp.h exactly. +// Transport functions come from compiled meshtastic_transport.c / meshcore_transport.c. +esp_err_t meshtastic_gatt_init(uint32_t node_num) { (void)node_num; return ESP_OK; } +void meshtastic_gatt_stop(void) {} +bool meshtastic_gatt_is_running(void) { return false; } +bool meshtastic_gatt_is_connected(void) { return false; } +bool meshtastic_gatt_is_fromnum_subscribed(void) { return false; } +bool meshtastic_gatt_is_logradio_subscribed(void) { return false; } +void meshtastic_gatt_enqueue_fromradio(const uint8_t *frame, uint16_t len) { (void)frame; (void)len; } +void meshtastic_gatt_notify_log(const uint8_t *chunk, uint16_t len) { (void)chunk; (void)len; } + +esp_err_t meshtastic_tcp_init(uint32_t node_num) { (void)node_num; return ESP_ERR_NOT_SUPPORTED; } +void meshtastic_tcp_stop(void) {} +bool meshtastic_tcp_is_running(void) { return false; } +uint8_t meshtastic_tcp_get_client_count(void) { return 0; } +void meshtastic_tcp_send_fromradio(const uint8_t *frame, uint16_t len) { (void)frame; (void)len; } + +// MeshCore GATT stubs — NimBLE unavailable in HLE. +// Signatures match firmware_c5/.../meshcore_gatt.h exactly. +esp_err_t meshcore_gatt_init(const char *name_prefix, uint32_t pin) { + (void)name_prefix; (void)pin; + return ESP_OK; +} +void meshcore_gatt_stop(void) {} +bool meshcore_gatt_is_running(void) { return false; } +bool meshcore_gatt_is_connected(void) { return false; } +bool meshcore_gatt_is_subscribed(void) { return false; } +void meshcore_gatt_notify(const uint8_t *frame, uint16_t len) { (void)frame; (void)len; } + +} // extern "C" diff --git a/tools/hle/src/hal/hle_stub_system.cpp b/tools/hle/src/hal/hle_stub_system.cpp new file mode 100644 index 00000000..8b0a2274 --- /dev/null +++ b/tools/hle/src/hal/hle_stub_system.cpp @@ -0,0 +1,102 @@ +#include +#include +#include + +extern "C" { +#include "esp_err.h" +#include "esp_log.h" +} + +extern "C" { + +esp_err_t ota_post_boot_check(void) { return ESP_OK; } +esp_err_t console_service_init(void) { return ESP_OK; } + +// Companion USB/BLE transports need TinyUSB and C5 GATT. Keep the pairing UI +// available while reporting that no physical companion transport exists in HLE. +esp_err_t host_link_state_init(void) { return ESP_ERR_NOT_SUPPORTED; } +esp_err_t host_link_stream_init(void) { return ESP_ERR_NOT_SUPPORTED; } +esp_err_t host_link_init(void) { return ESP_ERR_NOT_SUPPORTED; } +esp_err_t host_link_cdc_init(void) { return ESP_ERR_NOT_SUPPORTED; } +esp_err_t host_link_log_init(void) { return ESP_ERR_NOT_SUPPORTED; } +esp_err_t host_link_c5log_init(void) { return ESP_ERR_NOT_SUPPORTED; } +esp_err_t host_link_ble_init(void) { return ESP_ERR_NOT_SUPPORTED; } +esp_err_t host_link_ble_start(void) { return ESP_ERR_NOT_SUPPORTED; } +esp_err_t host_link_ble_stop(void) { return ESP_ERR_NOT_SUPPORTED; } +bool host_link_ble_is_connected(void) { return false; } +bool host_link_ble_is_active(void) { return false; } +esp_err_t host_link_sec_get_psk_hex(char *out, size_t out_cap) { + if (out != nullptr && out_cap > 0) { + out[0] = '\0'; + } + return ESP_ERR_NOT_SUPPORTED; +} + +esp_err_t c5_flasher_init(void) { return ESP_ERR_NOT_SUPPORTED; } +void c5_flasher_enter_bootloader(void) {} +void c5_flasher_reset_normal(void) {} +esp_err_t c5_flasher_update(const uint8_t *bin_data, uint32_t bin_size) { + (void)bin_data; (void)bin_size; + return ESP_ERR_NOT_SUPPORTED; +} + +esp_err_t ys_rfid2_init(const void *cfg) { (void)cfg; return ESP_OK; } + +esp_err_t bad_usb_init(void) { return ESP_OK; } +esp_err_t bad_usb_deinit(void) { return ESP_OK; } +void bad_usb_wait_for_connection(void) {} +void ducky_set_layout(int layout) { (void)layout; } +void ducky_set_progress_callback(void (*cb)(int, int)) { (void)cb; } +void ducky_abort(void) {} +esp_err_t ducky_run_from_assets(const char *path) { (void)path; return ESP_OK; } + +esp_err_t ledc_timer_config(const void *cfg) { (void)cfg; return ESP_OK; } +esp_err_t ledc_channel_config(const void *cfg) { (void)cfg; return ESP_OK; } +esp_err_t ledc_set_duty(int mode, int chan, int duty) { (void)mode; (void)chan; (void)duty; return ESP_OK; } +esp_err_t ledc_update_duty(int mode, int chan) { (void)mode; (void)chan; return ESP_OK; } + +const char *esp_err_to_name(int err) { + switch (err) { + case ESP_OK: return "ESP_OK"; + case ESP_FAIL: return "ESP_FAIL"; + case ESP_ERR_NO_MEM: return "ESP_ERR_NO_MEM"; + case ESP_ERR_INVALID_ARG: return "ESP_ERR_INVALID_ARG"; + case ESP_ERR_INVALID_STATE: return "ESP_ERR_INVALID_STATE"; + case ESP_ERR_INVALID_SIZE: return "ESP_ERR_INVALID_SIZE"; + case ESP_ERR_NOT_FOUND: return "ESP_ERR_NOT_FOUND"; + case ESP_ERR_NOT_SUPPORTED: return "ESP_ERR_NOT_SUPPORTED"; + case ESP_ERR_TIMEOUT: return "ESP_ERR_TIMEOUT"; + case ESP_ERR_INVALID_RESPONSE: return "ESP_ERR_INVALID_RESPONSE"; + case ESP_ERR_INVALID_CRC: return "ESP_ERR_INVALID_CRC"; + case ESP_ERR_INVALID_VERSION: return "ESP_ERR_INVALID_VERSION"; + case ESP_ERR_INVALID_MAC: return "ESP_ERR_INVALID_MAC"; + case ESP_ERR_NOT_FINISHED: return "ESP_ERR_NOT_FINISHED"; + case ESP_ERR_NOT_ALLOWED: return "ESP_ERR_NOT_ALLOWED"; + case ESP_ERR_NVS_NOT_INITIALIZED: return "ESP_ERR_NVS_NOT_INITIALIZED"; + case ESP_ERR_NVS_NOT_FOUND: return "ESP_ERR_NVS_NOT_FOUND"; + case ESP_ERR_NVS_TYPE_MISMATCH: return "ESP_ERR_NVS_TYPE_MISMATCH"; + case ESP_ERR_NVS_READ_ONLY: return "ESP_ERR_NVS_READ_ONLY"; + case ESP_ERR_NVS_INVALID_NAME: return "ESP_ERR_NVS_INVALID_NAME"; + case ESP_ERR_NVS_INVALID_HANDLE: return "ESP_ERR_NVS_INVALID_HANDLE"; + case ESP_ERR_NVS_INVALID_LENGTH: return "ESP_ERR_NVS_INVALID_LENGTH"; + case ESP_ERR_NVS_NO_FREE_PAGES: return "ESP_ERR_NVS_NO_FREE_PAGES"; + case ESP_ERR_NVS_NEW_VERSION_FOUND: return "ESP_ERR_NVS_NEW_VERSION_FOUND"; + default: return "ESP_ERR_UNKNOWN"; + } +} +uint32_t esp_get_free_heap_size(void) { return 8 * 1024 * 1024; } +void spi_bus_free(int host) { (void)host; } +esp_err_t esp_littlefs_info(const char *label, size_t *total, size_t *used) { + if (total) *total = 4 * 1024 * 1024; + if (used) *used = 0; + return ESP_OK; +} + +void _esp_error_check_failed(int rc, const char *file, int line, const char *func, const char *expr) { + if (rc != 0) { + fprintf(stderr, "E [%s:%d] %s(): ESP_ERROR_CHECK failed: %s (err=0x%x)\n", file, line, func, expr, rc); + exit(1); + } +} + +} // extern "C" diff --git a/tools/hle/src/hal/hle_stub_ui_wifi.cpp b/tools/hle/src/hal/hle_stub_ui_wifi.cpp new file mode 100644 index 00000000..9ceb05df --- /dev/null +++ b/tools/hle/src/hal/hle_stub_ui_wifi.cpp @@ -0,0 +1,32 @@ +// WiFi UI screen stubs — backends excluded (Applications/wifi/ needs lwIP/esp_wifi). +// Keep in sync with: +// - firmware_p4/components/Applications/ui/ui_manager.c switch cases +// - tools/hle/CMakeLists.txt exclusions for ui/screens/wifi and connect_wifi + +extern "C" { + +void ui_connect_wifi_open(void) {} +void ui_wifi_ap_list_open(void) {} +void ui_wifi_attack_menu_open(void) {} +void ui_wifi_auth_flood_open(void) {} +void ui_wifi_beacon_spam_open(void) {} +void ui_wifi_beacon_spam_simple_open(void) {} +void ui_wifi_deauth_attack_open(void) {} +void ui_wifi_deauth_open(void) {} +void ui_wifi_evil_twin_open(void) {} +void ui_wifi_menu_open(void) {} +void ui_wifi_packets_menu_open(void) {} +void ui_wifi_probe_flood_open(void) {} +void ui_wifi_probe_open(void) {} +void ui_wifi_scan_ap_open(void) {} +void ui_wifi_scan_menu_open(void) {} +void ui_wifi_scan_monitor_open(void) {} +void ui_wifi_scan_open(void) {} +void ui_wifi_scan_probe_open(void) {} +void ui_wifi_scan_stations_open(void) {} +void ui_wifi_scan_target_open(void) {} +void ui_wifi_sniffer_attack_open(void) {} +void ui_wifi_sniffer_handshake_open(void) {} +void ui_wifi_sniffer_raw_open(void) {} + +} // extern "C" diff --git a/tools/hle/src/hal/lv_port_disp_hle.cpp b/tools/hle/src/hal/lv_port_disp_hle.cpp new file mode 100644 index 00000000..ae2d34a3 --- /dev/null +++ b/tools/hle/src/hal/lv_port_disp_hle.cpp @@ -0,0 +1,126 @@ +extern "C" { +#include "esp_lcd_panel_ops.h" +#include "esp_err.h" +#include "esp_log.h" +} + +#include "hle/hle_display.h" +#include +#include +#include +#include +#include +#include + +static const char *TAG = "LV_PORT_DISP"; + +#ifndef HLE_ASSETS_DIR +#define HLE_ASSETS_DIR "/tmp/hle_assets" +#endif + +#define LCD_H_RES 240 +#define LCD_V_RES 320 + +static lv_display_t *s_disp = nullptr; +static bool s_fs_registered = false; + +static std::string hle_fs_translate_path(const char *path) { + if (path == nullptr) { + return {}; + } + + std::string relative(path); + if (relative.rfind("assets/", 0) == 0) { + relative.erase(0, sizeof("assets/") - 1); + } + + return std::string(HLE_ASSETS_DIR) + "/" + relative; +} + +static void *fs_open_cb(lv_fs_drv_t *drv, const char *path, lv_fs_mode_t mode) { + (void)drv; + const char *fopen_mode = (mode == LV_FS_MODE_WR) ? "wb" : ((mode & LV_FS_MODE_WR) ? "w+b" : "rb"); + const std::string translated = hle_fs_translate_path(path); + return fopen(translated.c_str(), fopen_mode); +} + +static lv_fs_res_t fs_close_cb(lv_fs_drv_t *drv, void *file_p) { + (void)drv; + return fclose(static_cast(file_p)) == 0 ? LV_FS_RES_OK : LV_FS_RES_UNKNOWN; +} + +static lv_fs_res_t fs_read_cb(lv_fs_drv_t *drv, void *file_p, void *buf, uint32_t btr, uint32_t *br) { + (void)drv; + const size_t read = fread(buf, 1, btr, static_cast(file_p)); + if (br != nullptr) { + *br = static_cast(read); + } + return (read < btr && ferror(static_cast(file_p))) ? LV_FS_RES_UNKNOWN : LV_FS_RES_OK; +} + +static lv_fs_res_t fs_seek_cb(lv_fs_drv_t *drv, void *file_p, uint32_t pos, lv_fs_whence_t whence) { + (void)drv; + int origin = SEEK_SET; + if (whence == LV_FS_SEEK_CUR) origin = SEEK_CUR; + else if (whence == LV_FS_SEEK_END) origin = SEEK_END; + return fseek(static_cast(file_p), static_cast(pos), origin) == 0 ? LV_FS_RES_OK : LV_FS_RES_UNKNOWN; +} + +static lv_fs_res_t fs_tell_cb(lv_fs_drv_t *drv, void *file_p, uint32_t *pos_p) { + (void)drv; + const long pos = ftell(static_cast(file_p)); + if (pos < 0 || pos_p == nullptr) { + return LV_FS_RES_UNKNOWN; + } + *pos_p = static_cast(pos); + return LV_FS_RES_OK; +} + +static void hle_lv_fs_register(void) { + if (s_fs_registered) { + return; + } + + static lv_fs_drv_t s_fs_drv; + lv_fs_drv_init(&s_fs_drv); + s_fs_drv.letter = 'A'; + s_fs_drv.open_cb = fs_open_cb; + s_fs_drv.close_cb = fs_close_cb; + s_fs_drv.read_cb = fs_read_cb; + s_fs_drv.seek_cb = fs_seek_cb; + s_fs_drv.tell_cb = fs_tell_cb; + lv_fs_drv_register(&s_fs_drv); + s_fs_registered = true; +} + +static void disp_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map) { + const uint32_t stride = lv_draw_buf_width_to_stride(lv_area_get_width(area), lv_display_get_color_format(disp)); + hle::Display::instance().draw_bitmap_strided( + area->x1, area->y1, area->x2 + 1, area->y2 + 1, px_map, stride); + + lv_display_flush_ready(disp); +} + +extern "C" { + +void lv_port_disp_init(void); +void hle_lv_port_disp_init(void); + +void lv_port_disp_init(void) { + hle_lv_fs_register(); + s_disp = lv_display_create(LCD_H_RES, LCD_V_RES); + lv_display_set_color_format(s_disp, LV_COLOR_FORMAT_RGB565); + lv_display_set_flush_cb(s_disp, disp_flush_cb); + + const uint32_t stride = lv_draw_buf_width_to_stride(LCD_H_RES, LV_COLOR_FORMAT_RGB565); + size_t buf_size = stride * (LCD_V_RES / 2); + auto *buf1 = static_cast(malloc(buf_size)); + auto *buf2 = static_cast(malloc(buf_size)); + lv_display_set_buffers(s_disp, buf1, buf2, buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL); + + ESP_LOGI(TAG, "HLE display initialized (%dx%d)", LCD_H_RES, LCD_V_RES); +} + +void hle_lv_port_disp_init(void) { lv_port_disp_init(); } + +} // extern "C" diff --git a/tools/hle/src/hal/lv_port_indev_hle.cpp b/tools/hle/src/hal/lv_port_indev_hle.cpp new file mode 100644 index 00000000..a899e672 --- /dev/null +++ b/tools/hle/src/hal/lv_port_indev_hle.cpp @@ -0,0 +1,87 @@ +extern "C" { +#include "esp_err.h" +#include "esp_log.h" +#include "driver/gpio.h" +} + +#include + +extern "C" { +bool up_button_is_down(void); +bool down_button_is_down(void); +bool left_button_is_down(void); +bool right_button_is_down(void); +bool ok_button_is_down(void); +bool back_button_is_down(void); +} + +static const char *TAG = "LV_PORT_INDEV"; + +static bool s_is_keyboard_mode = false; + +static void keypad_read(lv_indev_t *indev, lv_indev_data_t *data); +static uint32_t keypad_get_key(void); + +extern "C" { + +lv_indev_t *indev_keypad = nullptr; +lv_group_t *main_group = nullptr; + +void lv_port_indev_init(void) { + indev_keypad = lv_indev_create(); + lv_indev_set_type(indev_keypad, LV_INDEV_TYPE_KEYPAD); + lv_indev_set_read_cb(indev_keypad, keypad_read); + + main_group = lv_group_create(); + lv_group_set_default(main_group); + lv_indev_set_group(indev_keypad, main_group); + + ESP_LOGI(TAG, "HLE input device initialized"); +} + +void lv_port_indev_set_keyboard_mode(bool is_enabled) { + s_is_keyboard_mode = is_enabled; +} + +void hle_lv_port_indev_init(void) { lv_port_indev_init(); } + +} // extern "C" + +static void keypad_read(lv_indev_t *indev, lv_indev_data_t *data) { + (void)indev; + static uint32_t s_last_key = 0; + + const uint32_t key = keypad_get_key(); + + if (key != 0) { + data->state = LV_INDEV_STATE_PRESSED; + s_last_key = key; + } else { + data->state = LV_INDEV_STATE_RELEASED; + } + + data->key = s_last_key; +} + +static uint32_t keypad_get_key(void) { + if (up_button_is_down()) { + return s_is_keyboard_mode ? LV_KEY_UP : LV_KEY_PREV; + } + if (down_button_is_down()) { + return s_is_keyboard_mode ? LV_KEY_DOWN : LV_KEY_NEXT; + } + if (ok_button_is_down()) { + return LV_KEY_ENTER; + } + if (back_button_is_down()) { + return LV_KEY_ESC; + } + if (left_button_is_down()) { + return LV_KEY_LEFT; + } + if (right_button_is_down()) { + return LV_KEY_RIGHT; + } + + return 0; +} diff --git a/tools/hle/src/hal/spi_bridge_channel.cpp b/tools/hle/src/hal/spi_bridge_channel.cpp new file mode 100644 index 00000000..56f34044 --- /dev/null +++ b/tools/hle/src/hal/spi_bridge_channel.cpp @@ -0,0 +1,204 @@ +#include "hle/spi_bridge_channel.h" + +extern "C" { +#include "spi_protocol.h" +} + +namespace hle { + +SPIBridgeChannel::SPIBridgeChannel() = default; + +SPIBridgeChannel::~SPIBridgeChannel() { + close(); +} + +void SPIBridgeChannel::close() { + { + std::scoped_lock lock(s_cmd_mutex, s_resp_mutex, s_irq_mutex, s_stream_mutex); + s_closed.store(true, std::memory_order_release); + s_has_command = false; + s_has_response = false; + s_irq_raised = false; + s_stream_queue.clear(); + } + s_cmd_cv.notify_all(); + s_resp_cv.notify_all(); + s_irq_cv.notify_all(); +} + +void SPIBridgeChannel::reset() { + std::scoped_lock lock(s_cmd_mutex, s_resp_mutex, s_irq_mutex, s_stream_mutex); + if (s_closed.load(std::memory_order_acquire)) { + return; + } + + s_command_frame = SPIFrame{}; + s_response_frame = SPIFrame{}; + s_has_command = false; + s_has_response = false; + s_irq_raised = false; + s_stream_queue.clear(); +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// Master (P4) side +// ═══════════════════════════════════════════════════════════════════════════════ + +void SPIBridgeChannel::master_send_command(uint16_t command_id, const uint8_t *payload, + uint8_t payload_len) { + std::lock_guard lock(s_cmd_mutex); + if (s_closed.load(std::memory_order_acquire) || (payload_len > 0 && payload == nullptr)) { + return; + } + + s_command_frame.build_header(SPI_TYPE_CMD, command_id, payload_len); + if (payload_len > 0) { + memcpy(s_command_frame.payload, payload, payload_len); + } + s_has_command = true; + s_cmd_cv.notify_one(); +} + +bool SPIBridgeChannel::master_receive_response(uint16_t &out_command_id, + uint8_t *out_payload, uint8_t &out_len, + uint32_t timeout_ms) { + std::unique_lock lock(s_resp_mutex); + if (!s_resp_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), [this] { + return s_has_response || s_closed.load(std::memory_order_acquire); + })) { + return false; + } + if (s_closed.load(std::memory_order_acquire) || !s_has_response) { + return false; + } + + out_command_id = s_response_frame.command_id(); + out_len = s_response_frame.length; + if (out_payload != nullptr && out_len > 0) { + memcpy(out_payload, s_response_frame.payload, out_len); + } + s_has_response = false; + return true; +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// Slave (C5) side +// ═══════════════════════════════════════════════════════════════════════════════ + +bool SPIBridgeChannel::slave_wait_command(uint16_t &out_command_id, uint8_t *out_payload, + uint8_t &out_len) { + std::unique_lock lock(s_cmd_mutex); + s_cmd_cv.wait(lock, [this] { + return s_has_command || s_closed.load(std::memory_order_acquire); + }); + if (s_closed.load(std::memory_order_acquire) || !s_has_command) { + return false; + } + + out_command_id = s_command_frame.command_id(); + out_len = s_command_frame.length; + if (out_payload != nullptr && out_len > 0) { + memcpy(out_payload, s_command_frame.payload, out_len); + } + s_has_command = false; + return true; +} + +void SPIBridgeChannel::slave_send_response(uint16_t command_id, uint8_t status, + const uint8_t *payload, uint8_t payload_len) { + if (payload_len > SPI_MAX_RESPONSE_DATA) { + payload_len = SPI_MAX_RESPONSE_DATA; + } + + std::lock_guard lock(s_resp_mutex); + if (s_closed.load(std::memory_order_acquire) || (payload_len > 0 && payload == nullptr)) { + return; + } + + s_response_frame.build_header(SPI_TYPE_RESP, command_id, payload_len + 1); + s_response_frame.payload[0] = status; + if (payload_len > 0) { + memcpy(s_response_frame.payload + 1, payload, payload_len); + } + s_has_response = true; + s_resp_cv.notify_one(); +} + +void SPIBridgeChannel::slave_send_stream(uint16_t stream_id, const uint8_t *payload, + uint8_t payload_len) { + if (stream_push(stream_id, payload, payload_len)) { + slave_notify_irq(); + } +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// Stream queue +// ═══════════════════════════════════════════════════════════════════════════════ + +bool SPIBridgeChannel::stream_push(uint16_t stream_id, const uint8_t *data, size_t len) { + std::lock_guard lock(s_stream_mutex); + if (s_closed.load(std::memory_order_acquire) || s_stream_queue.size() >= STREAM_QUEUE_LEN || + (len > 0 && data == nullptr)) { + return false; + } + if (len > SPI_MAX_PAYLOAD) { + len = SPI_MAX_PAYLOAD; + } + + StreamItem item{}; + item.stream_id = stream_id; + item.len = len; + if (len > 0) { + memcpy(item.data, data, len); + } + s_stream_queue.push_back(item); + return true; +} + +bool SPIBridgeChannel::stream_pop(uint16_t &out_stream_id, uint8_t *out_data, + size_t &out_len) { + std::lock_guard lock(s_stream_mutex); + if (s_closed.load(std::memory_order_acquire) || s_stream_queue.empty()) { + return false; + } + + auto &item = s_stream_queue.front(); + out_stream_id = item.stream_id; + out_len = item.len; + if (out_data != nullptr && item.len > 0) { + memcpy(out_data, item.data, item.len); + } + s_stream_queue.pop_front(); + return true; +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// IRQ simulation +// ═══════════════════════════════════════════════════════════════════════════════ + +void SPIBridgeChannel::slave_notify_irq() { + std::lock_guard lock(s_irq_mutex); + if (s_closed.load(std::memory_order_acquire)) { + return; + } + + s_irq_raised = true; + s_irq_cv.notify_one(); +} + +bool SPIBridgeChannel::master_wait_irq(uint32_t timeout_ms) { + std::unique_lock lock(s_irq_mutex); + if (!s_irq_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), [this] { + return s_irq_raised || s_closed.load(std::memory_order_acquire); + })) { + return false; + } + if (s_closed.load(std::memory_order_acquire)) { + return false; + } + + s_irq_raised = false; + return true; +} + +} // namespace hle diff --git a/tools/hle/src/main.cpp b/tools/hle/src/main.cpp new file mode 100644 index 00000000..366185a4 --- /dev/null +++ b/tools/hle/src/main.cpp @@ -0,0 +1,80 @@ +#include +#include +#include +#include + +extern "C" { +#include "esp_log.h" +} +#include "hle/sdl_renderer.h" +#include "hle/hle_display.h" +#include "hle/spi_bridge_channel.h" +#include "hle/hle_kernel.h" + +#include + +static const char *TAG = "MAIN"; + +static uint32_t lv_get_tick(void) { + static const auto s_start = std::chrono::steady_clock::now(); + const auto now = std::chrono::steady_clock::now(); + return (uint32_t)std::chrono::duration_cast(now - s_start).count(); +} + +int main(int argc, char **argv) { + (void)argc; (void)argv; + + const char *snapshot_path = std::getenv("HLE_SNAPSHOT_PATH"); + const char *snapshot_ms_env = std::getenv("HLE_SNAPSHOT_MS"); + const uint32_t snapshot_ms = + snapshot_ms_env ? static_cast(std::strtoul(snapshot_ms_env, nullptr, 10)) : 0; + bool snapshot_taken = false; + const char *storage_path = std::getenv("HLE_STORAGE_PATH"); + if (storage_path != nullptr && storage_path[0] != '\0') { + hle_set_storage_path(storage_path); + } + int exit_code = 0; + + hle::SDLRenderer renderer; + if (!renderer.init()) { + fprintf(stderr, "Failed to initialize SDL2\n"); + return 1; + } + + lv_init(); + lv_tick_set_cb(lv_get_tick); + + // Create bridge channel and hand it to kernel + shims + hle::SPIBridgeChannel channel; + hle_set_bridge_channel(&channel); + + // Boot firmware + hle_kernel_init(); + + ESP_LOGI(TAG, "=== TentacleOS HLE running ==="); + + bool quit = false; + while (!quit) { + uint8_t keys_held; + renderer.handle_events(keys_held, quit); + hle_set_button_mask(keys_held); + renderer.render(); + + if (!snapshot_taken && snapshot_path != nullptr && lv_get_tick() >= snapshot_ms) { + if (!hle::Display::instance().save_ppm(snapshot_path)) { + ESP_LOGE(TAG, "Failed to save snapshot to %s", snapshot_path); + exit_code = 1; + } + snapshot_taken = true; + quit = true; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(16)); // ~60 fps + } + + hle_kernel_shutdown(); + + renderer.shutdown(); + ESP_LOGI(TAG, "Shutdown complete"); + return exit_code; +} diff --git a/tools/hle/tests/test_cc1101.cpp b/tools/hle/tests/test_cc1101.cpp new file mode 100644 index 00000000..a5954fb2 --- /dev/null +++ b/tools/hle/tests/test_cc1101.cpp @@ -0,0 +1,79 @@ +#include +#include "hle/cc1101_emu.h" + +using namespace hle; + +TEST(CC1101Emulator, ResetState) { + CC1101Emulator cc; + EXPECT_EQ(cc.state(), CC_IDLE); + EXPECT_EQ(cc.chip_status(), 0x0F); + EXPECT_FALSE(cc.gdo0()); + EXPECT_FALSE(cc.gdo2()); +} + +TEST(CC1101Emulator, ReadWriteRegisters) { + CC1101Emulator cc; + cc.write_register(0x00, 0xAA); + EXPECT_EQ(cc.read_register(0x00), 0xAA); + + cc.write_register(0x2E, 0x55); + EXPECT_EQ(cc.read_register(0x2E), 0x55); +} + +TEST(CC1101Emulator, StrobeTransitions) { + CC1101Emulator cc; + EXPECT_EQ(cc.state(), CC_IDLE); + + cc.strobe(0x34); // STX → TX + EXPECT_EQ(cc.state(), CC_TX); + EXPECT_EQ(cc.chip_status(), 0x02); + + cc.strobe(0x30); // SIDLE → IDLE + EXPECT_EQ(cc.state(), CC_IDLE); + + cc.strobe(0x36); // SRX → RX + EXPECT_EQ(cc.state(), CC_RX); + EXPECT_EQ(cc.chip_status(), 0x01); +} + +TEST(CC1101Emulator, FrequencySetting) { + CC1101Emulator cc; + cc.set_frequency_mhz(433.92f); + float f = cc.frequency_mhz(); + EXPECT_NEAR(f, 433.92f, 0.1f); +} + +TEST(CC1101Emulator, FIFOOperations) { + CC1101Emulator cc; + + // Write to TX FIFO + cc.write_register(0x3F, 0x11); + cc.write_register(0x3F, 0x22); + cc.write_register(0x3F, 0x33); + + // Read from TX FIFO + EXPECT_EQ(cc.read_register(0x3F), 0x11); + EXPECT_EQ(cc.read_register(0x3F), 0x22); + EXPECT_EQ(cc.read_register(0x3F), 0x33); + + // RX FIFO injection + uint8_t rx_data[] = {0xAA, 0xBB}; + cc.inject_rx_data(rx_data, 2); + // Not directly readable via read_register without burst +} + +TEST(CC1101Emulator, PATABLE) { + CC1101Emulator cc; + cc.write_register(0x3E, 0xC0); + EXPECT_EQ(cc.read_register(0x3E), 0xC0); +} + +TEST(CC1101Emulator, GDOControl) { + CC1101Emulator cc; + cc.set_gdo0(true); + EXPECT_TRUE(cc.gdo0()); + cc.set_gdo2(true); + EXPECT_TRUE(cc.gdo2()); + cc.set_gdo0(false); + EXPECT_FALSE(cc.gdo0()); +} diff --git a/tools/hle/tests/test_dummy.cpp b/tools/hle/tests/test_dummy.cpp new file mode 100644 index 00000000..076c50b2 --- /dev/null +++ b/tools/hle/tests/test_dummy.cpp @@ -0,0 +1,141 @@ +#include + +#include +#include "esp_err.h" +#include "esp_log.h" +#include "nvs_flash.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/queue.h" +#include "hle/hle_display.h" + +TEST(HLEDummyTest, BuildSystemWorks) { + EXPECT_EQ(ESP_OK, 0); + EXPECT_NE(ESP_ERR_NO_MEM, ESP_OK); +} + +class HLENVS : public ::testing::Test { +protected: + void SetUp() override { + ASSERT_EQ(nvs_flash_erase(), ESP_OK); + ASSERT_EQ(nvs_flash_init(), ESP_OK); + } +}; + +TEST_F(HLENVS, ScalarRoundTrip) { + nvs_handle_t handle; + ASSERT_EQ(nvs_open("test", NVS_READWRITE, &handle), ESP_OK); + + ASSERT_EQ(nvs_set_i32(handle, "answer", 42), ESP_OK); + ASSERT_EQ(nvs_commit(handle), ESP_OK); + + int32_t value = 0; + ASSERT_EQ(nvs_get_i32(handle, "answer", &value), ESP_OK); + EXPECT_EQ(value, 42); + nvs_close(handle); +} + +TEST_F(HLENVS, SupportsTwoPassStringReads) { + nvs_handle_t handle; + ASSERT_EQ(nvs_open("test", NVS_READWRITE, &handle), ESP_OK); + ASSERT_EQ(nvs_set_str(handle, "greeting", "hello"), ESP_OK); + + size_t required_length = 0; + ASSERT_EQ(nvs_get_str(handle, "greeting", nullptr, &required_length), ESP_OK); + ASSERT_EQ(required_length, 6u); + + char too_small[4] = {'x', 'x', 'x', '\0'}; + size_t too_small_length = sizeof(too_small); + EXPECT_EQ(nvs_get_str(handle, "greeting", too_small, &too_small_length), + ESP_ERR_NVS_INVALID_LENGTH); + EXPECT_EQ(too_small_length, required_length); + EXPECT_STREQ(too_small, "xxx"); + + char value[6] = {}; + size_t value_length = sizeof(value); + ASSERT_EQ(nvs_get_str(handle, "greeting", value, &value_length), ESP_OK); + EXPECT_EQ(value_length, required_length); + EXPECT_STREQ(value, "hello"); + nvs_close(handle); +} + +TEST_F(HLENVS, EnforcesTypesModesAndHandleLifetime) { + nvs_handle_t missing_handle = 0; + EXPECT_EQ(nvs_open("missing", NVS_READONLY, &missing_handle), ESP_ERR_NVS_NOT_FOUND); + + nvs_handle_t writer; + ASSERT_EQ(nvs_open("test", NVS_READWRITE, &writer), ESP_OK); + ASSERT_EQ(nvs_set_u32(writer, "typed", 42), ESP_OK); + nvs_close(writer); + + nvs_handle_t reader; + ASSERT_EQ(nvs_open("test", NVS_READONLY, &reader), ESP_OK); + int32_t signed_value = 0; + EXPECT_EQ(nvs_get_i32(reader, "typed", &signed_value), ESP_ERR_NVS_TYPE_MISMATCH); + EXPECT_EQ(nvs_set_u32(reader, "typed", 7), ESP_ERR_NVS_READ_ONLY); + nvs_close(reader); + + uint32_t value = 0; + EXPECT_EQ(nvs_get_u32(reader, "typed", &value), ESP_ERR_NVS_INVALID_HANDLE); + EXPECT_EQ(nvs_commit(reader), ESP_ERR_NVS_INVALID_HANDLE); +} + +TEST(HLEDisplay, ClipsPixelsAtRowBoundaries) { + auto &display = hle::Display::instance(); + display.fill_screen(0); + + std::array pixels{}; + ASSERT_TRUE(display.copy_pixels_if_dirty(pixels.data(), + hle::LCD_H_RES * sizeof(uint16_t))); + + const uint16_t source[] = {0x1111, 0x2222, 0x3333}; + display.draw_bitmap(hle::LCD_H_RES - 1, 0, hle::LCD_H_RES + 2, 1, source); + ASSERT_TRUE(display.copy_pixels_if_dirty(pixels.data(), + hle::LCD_H_RES * sizeof(uint16_t))); + + EXPECT_EQ(pixels[hle::LCD_H_RES - 1], source[0]); + EXPECT_EQ(pixels[hle::LCD_H_RES], 0); + EXPECT_FALSE(display.copy_pixels_if_dirty(pixels.data(), + hle::LCD_H_RES * sizeof(uint16_t))); +} + +TEST(HLEDummyTest, FreeRTOSMutex) { + SemaphoreHandle_t mtx = xSemaphoreCreateMutex(); + ASSERT_NE(mtx, nullptr); + EXPECT_EQ(xSemaphoreTake(mtx, 100), pdTRUE); + EXPECT_EQ(xSemaphoreGive(mtx), pdTRUE); + vSemaphoreDelete(mtx); +} + +TEST(HLEDummyTest, FreeRTOSQueue) { + QueueHandle_t q = xQueueCreate(4, sizeof(int32_t)); + ASSERT_NE(q, nullptr); + + int32_t send_val = 99; + EXPECT_EQ(xQueueSend(q, &send_val, 0), pdTRUE); + + int32_t recv_val = 0; + EXPECT_EQ(xQueueReceive(q, &recv_val, 100), pdTRUE); + EXPECT_EQ(recv_val, 99); + + vQueueDelete(q); +} + +TEST(HLEDummyTest, FreeRTOSTask) { + static volatile bool ran = false; + + auto func = [](void *arg) { + (void)arg; + ran = true; + vTaskDelay(10); + vTaskDelete(nullptr); + }; + + TaskHandle_t handle; + BaseType_t ret = xTaskCreatePinnedToCore(func, "dummy", 4096, nullptr, 1, &handle, 0); + EXPECT_EQ(ret, pdPASS); + + // Give it time to run + vTaskDelay(100); + EXPECT_TRUE(ran); +} diff --git a/tools/hle/tests/test_e2e_bridge.cpp b/tools/hle/tests/test_e2e_bridge.cpp new file mode 100644 index 00000000..6d06d8c3 --- /dev/null +++ b/tools/hle/tests/test_e2e_bridge.cpp @@ -0,0 +1,136 @@ +#include +#include +#include +#include "hle/spi_bridge_channel.h" +#include "hle/cc1101_emu.h" + +using namespace hle; + +extern "C" { +#include "esp_err.h" +void hle_set_bridge_channel(hle::SPIBridgeChannel *ch); +} + +static constexpr uint16_t SYSTEM_PING_COMMAND = 0x0001; +static constexpr uint16_t SYSTEM_STATUS_COMMAND = 0x0002; +static constexpr uint16_t WIFI_SCAN_COMMAND = 0x0120; +static constexpr uint16_t WIFI_STREAM_COMMAND = 0x0125; + +TEST(E2EBridge, SystemPingViaBridge) { + SPIBridgeChannel ch; + hle_set_bridge_channel(&ch); + + // Emulate C5 side (normally runs in bridge_task) + std::thread c5_side([&ch]() { + uint16_t cmd_id; + uint8_t payload[256], payload_len; + ASSERT_TRUE(ch.slave_wait_command(cmd_id, payload, payload_len)); + + // Handle SYSTEM_PING + if (cmd_id == SYSTEM_PING_COMMAND) { + ch.slave_send_response(cmd_id, 0x00, nullptr, 0); + ch.slave_notify_irq(); + } + }); + + // P4 side: send ping command + ch.master_send_command(SYSTEM_PING_COMMAND, nullptr, 0); + ASSERT_TRUE(ch.master_wait_irq(1000)); + + uint16_t resp_id; + uint8_t resp_payload[256], resp_len; + ASSERT_TRUE(ch.master_receive_response(resp_id, resp_payload, resp_len, 100)); + EXPECT_EQ(resp_id, SYSTEM_PING_COMMAND); + EXPECT_GE(resp_len, 1u); + EXPECT_EQ(resp_payload[0], 0x00); // OK + + c5_side.join(); +} + +TEST(E2EBridge, SystemStatus) { + SPIBridgeChannel ch; + hle_set_bridge_channel(&ch); + + std::thread c5_side([&ch]() { + uint16_t cmd_id; + uint8_t payload[256], payload_len; + ASSERT_TRUE(ch.slave_wait_command(cmd_id, payload, payload_len)); + + // Emulate system status response with mock data + if (cmd_id == SYSTEM_STATUS_COMMAND) { + // STATUS response: [status_byte, wifi_active, bt_running, firmware_ver_major, minor, patch] + uint8_t status_data[] = {0x01, 0x00, 0x01, 0x00, 0x05}; // wifi inactive, bt running, v1.0.5 + ch.slave_send_response(cmd_id, 0x00, status_data, sizeof(status_data)); + ch.slave_notify_irq(); + } + }); + + ch.master_send_command(SYSTEM_STATUS_COMMAND, nullptr, 0); + ASSERT_TRUE(ch.master_wait_irq(1000)); + + uint16_t resp_id; + uint8_t resp_payload[256], resp_len; + ASSERT_TRUE(ch.master_receive_response(resp_id, resp_payload, resp_len, 100)); + EXPECT_EQ(resp_id, SYSTEM_STATUS_COMMAND); + EXPECT_EQ(resp_payload[0], 0x00); // OK + // status_data follows at offset 1 + EXPECT_EQ(resp_payload[1], 0x01); // wifi_active + EXPECT_EQ(resp_payload[2], 0x00); // bt_running + + c5_side.join(); +} + +TEST(E2EBridge, WiFiScanDataFetch) { + SPIBridgeChannel ch; + hle_set_bridge_channel(&ch); + + const int ap_count = 3; + + std::thread c5_side([&ch, ap_count]() { + uint16_t cmd_id; + uint8_t payload[256], payload_len; + ASSERT_TRUE(ch.slave_wait_command(cmd_id, payload, payload_len)); + + // Respond with count + if (cmd_id == WIFI_SCAN_COMMAND) { + uint8_t count[2] = {uint8_t(ap_count), 0}; + ch.slave_send_response(cmd_id, 0x00, count, 2); + ch.slave_notify_irq(); + } + }); + + ch.master_send_command(WIFI_SCAN_COMMAND, nullptr, 0); + ASSERT_TRUE(ch.master_wait_irq(1000)); + + uint16_t resp_id; + uint8_t resp_payload[256], resp_len; + ASSERT_TRUE(ch.master_receive_response(resp_id, resp_payload, resp_len, 200)); + EXPECT_EQ(resp_id, WIFI_SCAN_COMMAND); + EXPECT_EQ(resp_payload[1], (uint8_t)ap_count); + + c5_side.join(); +} + +TEST(E2EBridge, StreamBackpressure) { + SPIBridgeChannel ch; + hle_set_bridge_channel(&ch); + + uint8_t data[64] = {}; + for (int i = 0; i < 64; i++) data[i] = (uint8_t)i; + + // Fill stream queue to capacity (8) + for (int i = 0; i < 8; i++) { + ASSERT_TRUE(ch.stream_push(WIFI_STREAM_COMMAND, data, 64)); + } + // 9th should fail + EXPECT_FALSE(ch.stream_push(WIFI_STREAM_COMMAND, data, 64)); + + // Drain one + uint16_t out_id; + uint8_t out_data[256]; + size_t out_len; + ASSERT_TRUE(ch.stream_pop(out_id, out_data, out_len)); + + // Now should accept one more + EXPECT_TRUE(ch.stream_push(WIFI_STREAM_COMMAND, data, 64)); +} diff --git a/tools/hle/tests/test_spi_bridge_channel.cpp b/tools/hle/tests/test_spi_bridge_channel.cpp new file mode 100644 index 00000000..b6516713 --- /dev/null +++ b/tools/hle/tests/test_spi_bridge_channel.cpp @@ -0,0 +1,222 @@ +#include +#include +#include +#include "hle/spi_bridge_channel.h" + +using namespace hle; + +static constexpr uint16_t SYSTEM_PING_COMMAND = 0x0001; +static constexpr uint16_t TEST_COMMAND = 0x0210; +static constexpr uint16_t TEST_STREAM = 0x0525; + +TEST(SPIBridgeChannel, PingPong) { + SPIBridgeChannel ch; + + // Slave thread: waits for command, responds + std::thread slave([&ch]() { + uint16_t cmd_id; + uint8_t payload[256], len; + EXPECT_TRUE(ch.slave_wait_command(cmd_id, payload, len)); + EXPECT_EQ(cmd_id, SYSTEM_PING_COMMAND); + ch.slave_send_response(cmd_id, 0x00, nullptr, 0); // OK + }); + + // Master: sends command, waits for response + ch.master_send_command(SYSTEM_PING_COMMAND, nullptr, 0); + uint16_t resp_id; + uint8_t resp_payload[256], resp_len; + EXPECT_TRUE(ch.master_receive_response(resp_id, resp_payload, resp_len, 1000)); + EXPECT_EQ(resp_id, SYSTEM_PING_COMMAND); + EXPECT_EQ(resp_len, 1u); // 1 byte status + EXPECT_EQ(resp_payload[0], 0x00); // SPI_STATUS_OK + + slave.join(); +} + +TEST(SPIBridgeChannel, PayloadRoundTrip) { + SPIBridgeChannel ch; + + uint8_t test_data[64]; + for (int i = 0; i < 64; i++) test_data[i] = (uint8_t)i; + + std::thread slave([&ch, &test_data]() { + uint16_t cmd_id; + uint8_t payload[256], len; + EXPECT_TRUE(ch.slave_wait_command(cmd_id, payload, len)); + EXPECT_EQ(len, 64u); + EXPECT_EQ(memcmp(payload, test_data, 64), 0); + + // Echo back + ch.slave_send_response(cmd_id, 0x00, payload, len); + }); + + ch.master_send_command(TEST_COMMAND, test_data, 64); + uint16_t resp_id; + uint8_t resp_payload[256], resp_len; + EXPECT_TRUE(ch.master_receive_response(resp_id, resp_payload, resp_len, 1000)); + EXPECT_EQ(resp_id, TEST_COMMAND); + EXPECT_EQ(resp_len, 65u); // 1 status + 64 payload + EXPECT_EQ(resp_payload[0], 0x00); + EXPECT_EQ(memcmp(resp_payload + 1, test_data, 64), 0); + + slave.join(); +} + +TEST(SPIBridgeChannel, TimeoutWhenNoResponse) { + SPIBridgeChannel ch; + + ch.master_send_command(SYSTEM_PING_COMMAND, nullptr, 0); + uint16_t resp_id; + uint8_t resp_payload[256], resp_len; + EXPECT_FALSE(ch.master_receive_response(resp_id, resp_payload, resp_len, 100)); + + // Wait for the dangling command to be consumed by the channel + // (no slave to consume it, so we just reset) +} + +TEST(SPIBridgeChannel, StreamPushPop) { + SPIBridgeChannel ch; + + uint8_t data[] = {0xAA, 0xBB, 0xCC, 0xDD}; + EXPECT_TRUE(ch.stream_push(TEST_STREAM, data, 4)); + EXPECT_TRUE(ch.stream_push(TEST_STREAM, data, 4)); + EXPECT_TRUE(ch.stream_push(TEST_STREAM, data, 4)); + + uint8_t out_data[256]; + size_t out_len; + uint16_t out_id; + + EXPECT_TRUE(ch.stream_pop(out_id, out_data, out_len)); + EXPECT_EQ(out_id, TEST_STREAM); + EXPECT_EQ(out_len, 4u); + + EXPECT_TRUE(ch.stream_pop(out_id, out_data, out_len)); + EXPECT_TRUE(ch.stream_pop(out_id, out_data, out_len)); + EXPECT_FALSE(ch.stream_pop(out_id, out_data, out_len)); // empty +} + +TEST(SPIBridgeChannel, StreamOverflow) { + SPIBridgeChannel ch; + + uint8_t data[4] = {1, 2, 3, 4}; + for (int i = 0; i < 8; i++) { + EXPECT_TRUE(ch.stream_push(TEST_STREAM, data, 4)); + } + // 9th should fail + EXPECT_FALSE(ch.stream_push(TEST_STREAM, data, 4)); +} + +TEST(SPIBridgeChannel, IRQSimulation) { + SPIBridgeChannel ch; + + // Slave raises IRQ + std::thread slave([&ch]() { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + ch.slave_notify_irq(); + }); + + // Master waits for IRQ + EXPECT_TRUE(ch.master_wait_irq(1000)); + + slave.join(); + + // Second wait without new IRQ should timeout + EXPECT_FALSE(ch.master_wait_irq(100)); +} + +TEST(SPIBridgeChannel, CloseWakesAllWaitersWithoutSignalingIRQ) { + SPIBridgeChannel ch; + + std::promise command_started; + std::promise response_started; + std::promise irq_started; + auto command_ready = command_started.get_future(); + auto response_ready = response_started.get_future(); + auto irq_ready = irq_started.get_future(); + bool command_received = true; + bool response_received = true; + bool irq_received = true; + + std::thread command_waiter([&]() { + uint16_t cmd_id = 0; + uint8_t payload[SPI_MAX_PAYLOAD] = {}; + uint8_t len = 0; + command_started.set_value(); + command_received = ch.slave_wait_command(cmd_id, payload, len); + }); + std::thread response_waiter([&]() { + uint16_t response_id = 0; + uint8_t payload[SPI_MAX_PAYLOAD] = {}; + uint8_t len = 0; + response_started.set_value(); + response_received = + ch.master_receive_response(response_id, payload, len, 5000); + }); + std::thread irq_waiter([&]() { + irq_started.set_value(); + irq_received = ch.master_wait_irq(5000); + }); + + command_ready.wait(); + response_ready.wait(); + irq_ready.wait(); + ch.close(); + + command_waiter.join(); + response_waiter.join(); + irq_waiter.join(); + EXPECT_FALSE(command_received); + EXPECT_FALSE(response_received); + EXPECT_FALSE(irq_received); +} + +TEST(SPIBridgeChannel, ClosedChannelRejectsNewWork) { + SPIBridgeChannel ch; + ch.close(); + + const uint8_t data[] = {1, 2, 3}; + ch.master_send_command(TEST_COMMAND, data, sizeof(data)); + ch.slave_send_response(TEST_COMMAND, 0, data, sizeof(data)); + ch.slave_notify_irq(); + + uint16_t id = 0; + uint8_t payload[SPI_MAX_PAYLOAD] = {}; + uint8_t len = 0; + size_t stream_len = 0; + EXPECT_FALSE(ch.slave_wait_command(id, payload, len)); + EXPECT_FALSE(ch.master_receive_response(id, payload, len, 0)); + EXPECT_FALSE(ch.master_wait_irq(0)); + EXPECT_FALSE(ch.stream_push(TEST_STREAM, data, sizeof(data))); + EXPECT_FALSE(ch.stream_pop(id, payload, stream_len)); +} + +TEST(SPIBridgeChannel, ResponseLengthDoesNotOverflow) { + SPIBridgeChannel ch; + uint8_t data[UINT8_MAX]; + for (size_t index = 0; index < sizeof(data); ++index) { + data[index] = static_cast(index); + } + + ch.slave_send_response(TEST_COMMAND, 0x7F, data, sizeof(data)); + + uint16_t id = 0; + uint8_t payload[SPI_MAX_PAYLOAD] = {}; + uint8_t len = 0; + ASSERT_TRUE(ch.master_receive_response(id, payload, len, 0)); + EXPECT_EQ(id, TEST_COMMAND); + EXPECT_EQ(len, UINT8_MAX); + EXPECT_EQ(payload[0], 0x7F); + EXPECT_EQ(payload[UINT8_MAX - 1], data[UINT8_MAX - 2]); +} + +TEST(SPIBridgeChannel, FullStreamQueueDoesNotRaiseSpuriousIRQ) { + SPIBridgeChannel ch; + const uint8_t data[] = {1}; + for (size_t index = 0; index < STREAM_QUEUE_LEN; ++index) { + ASSERT_TRUE(ch.stream_push(TEST_STREAM, data, sizeof(data))); + } + + ch.slave_send_stream(TEST_STREAM, data, sizeof(data)); + + EXPECT_FALSE(ch.master_wait_irq(0)); +}