11 KiB
Docker/QEMU Emulation Scaffold
Scope
Workspace-local implementation:
emulation/
Purpose:
- Build a Docker-packaged QEMU environment for booting a generated copy of the extracted AArch64 rootfs.
- Expose the guest display through QEMU VNC and noVNC in a browser.
- Provide a browser dashboard for logs and early virtual switch events.
- Keep target rootfs evidence files unmodified.
Static-analysis boundary: this page documents the emulator scaffold and evidence-based design. Creating and booting emulation/work/rootfs.ext4 executes a generated guest copy, not the extracted evidence files directly.
Evidence Basis
The main game and SPIKE menu use a direct DRM/KMS display path rather than a desktop windowing path.
Confirmed string/import evidence:
/games/pokemon_pro/gameimportslibEGL.so.1,libGLESv2.so.2,libdrm.so.2, andlibgbm.so.1; seeanalysis/static-triage/game.objdump-p.txt./games/pokemon_pro/gamestrings include/sys/class/drm,/dev/dri/card1,/dev/dri/card0,drmModeSetCrtc fail,drmModePageFlip,gbm_bo_create,eglGetPlatformDisplayEXT, andegl resolution: %d x %d; seeanalysis/static-triage/game.strings./games/pokemon_pro/spike3/spike_menu/gamehas matching GBM/EGL/DRM strings at offsets including0x382cd0(/sys/class/drm),0x383030(/dev/dri/card1),0x383058(/dev/dri/card0), and0x383730(drmModeSetCrtc fail); seeanalysis/static-triage/spike_menu_game.strings./etc/init.d/S12lvds_panellaunches/games/spike3/bin/boot_displayearly, and/etc/init.d/game_monitorlater stopsboot_displaybefore launching/games/game.
Implementation consequence: the first emulator path uses QEMU virtio-gpu and browser-visible VNC/noVNC, not X11 forwarding.
Implemented Files
Primary files:
| Path | Purpose |
|---|---|
emulation/compose.yaml |
Docker Compose entry point and port mapping. |
emulation/docker/Dockerfile |
Debian-based image with QEMU, noVNC, ffmpeg, qemu-user-static, and AArch64 cross tools. |
emulation/docker/fetch-debian-arm64-kernel.sh |
Installs a Debian ARM64 kernel and initramfs into /opt/spike3-kernel/; the initramfs seeds DRM/virtio_gpu modules. |
emulation/scripts/prepare-rootfs-image.sh |
Copies target-root entries into emulation/work/rootfs-stage, injects helpers, and builds emulation/work/rootfs.ext4. |
emulation/scripts/run-qemu.sh |
Boots QEMU virt with virtio-blk, virtio-gpu, VNC, serial logging, user networking, and a virtio-serial control socket. |
emulation/guest/emu-init |
Guest init used by QEMU with init=/usr/local/spike-emu/bin/emu-init. |
emulation/dashboard/server.py |
Browser dashboard on port 8090 for links, logs, and virtual switch events. |
emulation/stubs/spike3emu_stub.c |
Optional AArch64 LD_PRELOAD stub for inert fake device opens/ioctls on GPIO, I2C, serial, input, and /dev/mem paths. |
Generated runtime files:
| Path | Purpose |
|---|---|
emulation/work/rootfs.ext4 |
Generated guest root disk; not checked in as evidence. |
emulation/work/logs/ |
Host/container logs for QEMU, dashboard, noVNC, and conagent backend. |
emulation/work/control.sock |
QEMU virtio-serial socket used by the dashboard event bridge. |
Usage
Start the default boot-display target:
docker compose -f emulation/compose.yaml up --build
Open:
- Dashboard:
http://localhost:8090/ - Display:
http://localhost:6080/vnc.html?host=localhost&port=6080&autoconnect=true - Conagent backend emulator:
http://localhost:8088/
Try later targets after the virtual display is visible:
SPIKE3_AUTOSTART=spike-menu docker compose -f emulation/compose.yaml up
SPIKE3_AUTOSTART=game docker compose -f emulation/compose.yaml up
SPIKE3_AUTOSTART=monitors docker compose -f emulation/compose.yaml up
The guest init accepts these autostart targets:
boot-displayspike-menugamemonitorsshell
Current Emulator Behavior
The generated guest image:
- Mounts
/proc,/sys,/dev,/dev/pts,/run, and/tmp. - Creates writable
/connectivity,/connectivity/dump,/data, and/tmp/cache/mesa. - Preserves
/games/game -> /games/pokemon_pro/game. - Injects
/usr/local/spike-emu/bin/launch-target.shto startboot_display, SPIKE menu, game, or monitors. - Injects an optional
LD_PRELOADhardware stub at/usr/local/spike-emu/lib/libspike3emu_stub.sowhen the Docker image hasaarch64-linux-gnu-gcc. - Injects overlay Mesa DRI fallback drivers under
/usr/local/spike-emu/mesa-driwhen the Docker image has ARM64virtio_gpu,kms_swrast,swrast, andzinkdrivers. - Injects matching ARM64 Debian Mesa/GLVND EGL libraries under
/usr/local/spike-emu/mesa-libplus/usr/share/glvnd/egl_vendor.d/50_mesa.json, so the fallback DRI drivers are loaded by the same Mesa userspace stack instead of the target rootfslibEGL. - Injects ARM64 Mesa Vulkan/lavapipe support when present:
mesa-vulkan-drivers:arm64,libvulkan.so*,libvulkan_lvp.so, and/usr/share/vulkan/icd.d/lvp_icd.aarch64.json. - Starts a guest control agent that logs virtio-serial dashboard events to
/run/spike-emu/events.logand/connectivity/dump/log/spike-emu/control-agent.log.
The dashboard:
- Links to noVNC and the existing conagent backend emulator.
- Reads host logs from
emulation/work/logs/. - Sends virtual switch/button events as JSON to
emulation/work/control.sockwhen QEMU is running. Verified APIs arePOST /api/eventandPOST /api/switch/<name>.
Verification Status
Commands run from the workspace root:
docker compose -f emulation/compose.yaml build spike3-emu
docker compose -f emulation/compose.yaml run --rm --entrypoint /workspace/emulation/scripts/prepare-rootfs-image.sh spike3-emu
SPIKE3_SKIP_PREPARE=1 SPIKE3_AUTOSTART=boot-display docker compose -f emulation/compose.yaml up --force-recreate
Confirmed results:
- Docker image contains
/opt/spike3-kernel/vmlinuzand/opt/spike3-kernel/initrd.img; the initramfs includesdrm.ko,drm_kms_helper.ko,virtio_dma_buf.ko, andvirtio-gpu.ko. - QEMU boots the generated
emulation/work/rootfs.ext4and reachesSPIKE3 emulator init complete; autostart=boot-displayinemulation/work/logs/qemu-serial.log. - QEMU serial log confirms
virtio-gpu-pci detected,Initialized virtio_gpu, andfb0: virtio_gpudrmfb frame buffer device. - Guest hardware log confirms
/dev/dri/card0,/dev/dri/renderD128,/sys/class/drm/card0,/sys/class/drm/card0-Virtual-1, and loadedvirtio_gpu/drmmodules. - noVNC responds at
http://localhost:6080/vnc.html; the dashboard responds athttp://localhost:8090/api/status. POST /api/switch/startreturnssent_to_guest: trueand appends a switch event toemulation/work/dashboard-events.jsonl.- The generated
/dev/dri/card1 -> card0symlink fixes the first/dev/dri/card1open failure forboot_displayand SPIKE menu. - QEMU
virtio-gpu-pci,max_outputs=2exposes two DRM connectors. The second connector is disconnected by QEMU VNC, so the LD_PRELOAD shim now snapshots the first connected connector and presents connector id39as connected with mode1360x768. This removes SPIKE menu's repeatedfailed to get DRM connector for display_number 1failure. - Adding
libegl1:arm64,libegl-mesa0:arm64,libgles2:arm64, and related ARM64 Mesa/GLVND packages to the Docker image removes the prior Mesa loader fataldid not find extension DRI_Mesa version 1. - Adding
mesa-vulkan-drivers:arm64to the Docker image makes the ARM64 lavapipe ICD andlibvulkan_lvp.soavailable to the generated rootfs overlay. emulation/scripts/capture-vnc.pycaptures QEMU VNC frames to PPM; captures from the currentboot-displayruns were still all black (1360x768, RGB extrema all zero).
Current blocker:
/games/spike3/bin/boot_displayis reached and the old connector/EGL-loader fatals are cleared, but its EGL display creation still fails before it can allocate framebuffer BOs.- The current shim trace confirms
LD_PRELOADis loaded andgbm_create_device(fd=3)succeeds on thekms_swrast/softpipedefault path, returning a non-null GBM device pointer. The subsequenteglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, gbm_device, NULL)returnsNULLwitheglGetError() == 0x300c(EGL_BAD_PARAMETER). - Shim fallbacks also return
NULLwithEGL_BAD_PARAMETERforEGL_PLATFORM_SURFACELESS_MESA, coreeglGetPlatformDisplay, andeglGetDisplay(NULL)even withEGL_PLATFORM=surfaceless. - Forcing
MESA_LOADER_DRIVER_OVERRIDE=zinkandGALLIUM_DRIVER=zinkwithVK_ICD_FILENAMES=/usr/share/vulkan/icd.d/lvp_icd.aarch64.jsonmakesgbm_create_devicereturnNULL, so that is not the default path to leave enabled. - After EGL display creation fails,
boot_displaylater reports shader compile errors and then loops onfailed to lock front buffer: gbm_buffer[0] is null (frame=0). This appears to be an uninitialized app framebuffer array after the failed window/context setup, not a directgbm_surface_lock_front_bufferfailure. - A nonblank SPIKE frame is not yet confirmed. The next display pass should either make the GBM EGL platform work with QEMU's non-virgl
virtio_gpu, or bypass the app's EGL/GBM path with a targeted display interposer that supplies a fake EGL context plus framebuffer BOs or streams rendered/uploaded image data directly to the browser.
Limitations and Next Steps
Confirmed current limitations:
- The dashboard event path is only a guest-visible event log. It does not yet inject events into the game’s SPIKE nodebus model.
- The optional
LD_PRELOADstub is intentionally broad and inert. It can move early hardware probing forward, but any deterministic game failure still needs specific static/dynamic analysis before adding a targeted shim. - QEMU
virtio-gpuis not Raspberry Pi VC4/V3D. It is the right first display target because the binary asks for standard DRM/GBM/EGL surfaces, but shader/extension mismatches may still require a display interposer or patch. - Docker Desktop on macOS runs this under QEMU TCG without KVM acceleration, so startup may be slow.
Next implementation targets:
- Fix the remaining
boot_displayEGL display creation failure (EGL_BAD_PARAMETERfromeglGetPlatformDisplayEXTon the GBM device) so the app can allocate framebuffer BOs and produce a nonblank noVNC frame. - Return to
spike-menu; its connector blocker is cleared, but it still depends on the same GBM/EGL buffer path and also hits expected netbridge/OpenOCD hardware failures. - Run
gameand capture the first deterministic hardware/display failure from serial logs and/connectivity/dump/log/spike-emu/. - Map dashboard switch events into a first SPIKE nodebus emulator, reusing recovered node command evidence from
main-game-behavior.mdandtopper-serial-key-emulator.md. - Add targeted stubs only for confirmed failing APIs or device paths.