Files
evan b22934fe0d Replace vvfat SD with browser-built raw FAT32 from OPFS
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.
2026-07-21 07:43:07 -04:00

116 lines
6.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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).