Files
xteink-web-emulator/ROADMAP.md
T
evan 9eceacea17 fix(wasm): boot real firmware past allocator and signature crashes
Drop EMULATE_FUNCTION_POINTER_CASTS (it broke the wasm32 TCG ffi_call_js
path with BigInt errors) and fix the underlying function-pointer signature
mismatches instead: object_class_cmp GCompareFunc, esp_sha hash-callback
wrappers, and an xts_aes NULL guard in the ESP32-C3 cache. Backport the
upstream Emscripten anonymous-RAM allocator fix. Firmware now boots and
initializes the X3 panel; a post-init boot stall remains.
2026-07-20 08:03:59 -04:00

6.5 KiB
Raw Blame History

xteink-web-emulator — architecture & roadmap

Goal: an offline, static-asset web page where you upload an xteink X3/X4 firmware .bin and it runs client-side — real ESP32-C3 machine code, with the e-ink screen on a canvas, on-screen buttons, and a virtual SD card. No server, no proprietary engine.

Why QEMU (and why not the alternatives)

Approach Runs any .bin Open source Offline in browser
Wokwi (closed engine, licensed)
Recompile firmware → WASM (crosspoint-simulator + Emscripten) source only
QEMU (espressif fork) → WASM

Only QEMU satisfies all three. espressif/qemu models the ESP32-C3 SoC and executes real firmware; ktock/qemu-wasm shows QEMU compiling to WASM and running in-browser from static files. The endgame merges those two.

Status

The native foundation and WASM compile phase are done. Unmodified canonical firmware auto-detects xteink,variant=x3 through its real I²C fingerprint and boots the X3 home screen with SD support. variant=x4 omits those chips, so the same firmware selects its SSD1677 driver and sends its command stream to a blank X4 panel stub. Reproduce:

make qemu                 # clone espressif/qemu @ pinned commit, patch, build
make firmware sdimage     # 16 MB flash.bin + FAT32 sd.img from a pio build
make run                  # X3 (default)
make run VARIANT=x4       # X4 detection + blank panel stub

QMP screenshots confirmed the X3 home screen and 528×792 orientation; captured X4 SPI traffic confirmed the firmware selected its SSD1677 driver. make qemu-wasm produces the Emscripten module, WASM binary, and pthread worker under dist/wasm/; Node smoke checks confirm xteink is registered and its browser framebuffer/input bridge initializes. make web serves a dependency-free UI for firmware, canvas, and buttons. Real firmware now boots past the allocator and function-pointer crashes and initializes the X3 panel, but still stalls before painting a full home screen.

Device selection (X3 vs X4)

X3 and X4 share one ESP32-C3 binary and pick a profile at runtime via an I²C fingerprint (BQ27220 gauge + DS3231 RTC + QMI8658 IMU on SDA20/SCL0), with an NVS override (cphw/dev_ovr: 1=X4, 2=X3). The emulator picks X3 vs X4 at launch with one machine property: -machine xteink,variant=x3|x4. X3 attaches minimal BQ27220/DS3231/QMI8658 targets; X4 leaves the I²C bus empty. This drives both firmware detection and panel wiring without modifying the firmware.

The interfaces (native now → browser later)

Each peripheral is modelled behind a QEMU-native seam that the WASM front-end reuses unchanged — no bespoke abstraction layer:

Peripheral Native seam Browser binding
e-ink panel X3 UC8253 (528×792) or blank X4 SSD1677 stub (480×800) → QEMU graphical console <canvas> from the same display surface
SD card ssi-sd + sd-card-spi backed by QEMU block layer (-drive if=sd) browser-supplied blockdev (File/OPFS)
Buttons + battery ADC QOM props adci[1]/adci[2]; GPIO QOM input-level[3] for Power DOM buttons → qom-set equivalent
Firmware image flash via -drive if=mtd uploaded .bin written to emulated flash

Phase 1 device models (in qemu/patches/0001-xteink-machine.patch)

  • esp32c3.adc — SAR ADC: oneshot completes immediately; per-channel value is settable live via QOM (adci[*]). Feeds battery and the two resistor-ladder button groups. Replaced the original always-done register shim.
  • esp32_gpio — real OUT/ENABLE/IN registers with 32 named input + output lines and live input-level[*] QOM controls. Outputs read high while a pin is input-configured (matches the board's pull-ups).
  • ssi.esp32c3.spi2 — the general-purpose SPI2 controller (CPU-buffer transactions on an SSI bus). DMA path deliberately unimplemented until needed.
  • xteink-x3-eink — UC8253 panel: decodes DTM1/DTM2 planes (52 272 B each), drives BUSY, renders to a QEMU console. Protocol from chip/eink-x3.chip.c.
  • ssi-sd fix — track card idle state so CMD58 reports ready after ACMD41 (the 9.2 fork hardcoded idle, which SdFat rejects).
  • esp32.i2c + fingerprint targets — C3 opcode support and minimal BQ27220/DS3231/QMI8658 register responses for stock X3 detection.
  • xteink machinevariant=x3|x4 controls fingerprint presence and panel type; shared panel/SD wiring stays in one machine.
  • X4 panel stub — blank 480×800 console with X4 idle-low BUSY; accepts the SSD1677 command stream without implementing it.

Remaining phases

  1. Browser SD storage — add an uploaded or OPFS-backed SD block device. The basic static frontend already covers firmware, canvas, buttons, and X3/X4.
  2. Full X4 panel — implement SSD1677 RAM/window/update commands when visible X4 rendering is needed; the selection, dimensions, wiring, and stub exist.

Repo layout

Path Role
flake.nix espressif-qemu release pkg (nix run .#run-upstream) + full QEMU build devShell
Makefile qemu (source build), firmware, sdimage, run, chip
scripts/build-qemu.sh clone pinned espressif/qemu, apply patch, build riscv32-softmmu
qemu/patches/ xteink devices plus the qemu-wasm host port, applied onto pinned Espressif QEMU
qemu/wasm/Dockerfile Minimal pinned Emscripten dependency environment
scripts/build-qemu-wasm.sh X3/X4-only WASM build → dist/wasm/
chip/eink-x3.chip.c legacy Wokwi model — the reverse-engineered X3 e-ink protocol reference
web/ Static frontend: firmware upload, canvas, variant, and buttons

Debugging notes

  • Headless capture: run with -qmp unix:… and use scripts/qmp-screendump.py; the console is 528×792 (physical portrait), rotate not needed.
  • Inject a button: scripts/qmp-qom.py <sock> set /machine/adc "adci[1]" 2760 (ladder-1 midpoints: Back 3512 / Confirm 2694 / Left 1493 / Right ~5; ladder-2 Up 2242 / Down ~5). Value > 3900 = no button.
  • Symbolize a panic PC: llvm-addr2line -f -C -e firmware.elf 0x4210d286.
  • Flash must be 16 MB — the partition table spans 16 MB even though the app fits under 4 MB. Espressif QEMU only accepts 2/4/8/16 MB mtd images.
  • Trace SD/panel: flip DEBUG_SSI_SD in hw/sd/ssi-sd.c or add an info_report in xteink_x3_eink.c (both off by default in the patch).