b22934fe0d
Browser SD files are persisted in OPFS (web/sdstore.js) and edited in the Files tab. At boot the browser builds a deterministic 64 MiB FAT32 image (web/fat32.js) with nested directories and VFAT long names, then mounts it as a raw block device, bypassing QEMU's broken populated-vvfat WASM coroutine path. Guest writes mutate only the ephemeral image. Exposes a hidden #frame-generation indicator that increments on every e-ink flush, so automation can wait on real display updates instead of fixed sleeps. Adds tests/ with a Makefile runner: 'make test' for JS + FAT32/mtools checks, 'make browser-test' for a headless-Firefox boot of the official CrossPoint 1.4.1 firmware that navigates to Test/example.txt. Bumps third_party/qemu to the SPI-SD/CMD13 fixes and updates README/ROADMAP/ISSUES for the new architecture.
116 lines
6.3 KiB
Markdown
116 lines
6.3 KiB
Markdown
# 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](https://github.com/crosspoint-reader/crosspoint-reader)
|
||
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:
|
||
|
||
```sh
|
||
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, buttons, logs, SD files, and debug metrics. Real
|
||
firmware boots in WASM and drives the X3 panel.
|
||
|
||
### 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`) | OPFS files → browser-built 64 MiB FAT32 image → raw MEMFS block device |
|
||
| 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` machine** — `variant=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. **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).
|