Skip to content

The device models

Chikuma cannot be checked against the original firmware unless both run on a machine that behaves like the iPod's hardware. That machine is qemu-ipod/, a QEMU port for the S5L8702, and every peripheral the firmware touches has a model in it. On the other side of the bus, Chikuma has a driver for each piece of hardware. This page is about both halves and the machine that joins them.

Why the machine is QEMU, and why the models are in C

An earlier oracle was a Python emulator built on Unicorn. It booted the firmware and it was measured against the device, but it was replaced, and the reason is the lesson worth carrying forward.

Unicorn shares QEMU's CPU core, so the processor and memory management were never the problem. The difference was interrupt timing. Unicorn delivers an interrupt only at the boundary of an execution slice; real hardware raises the line the instant a device asserts it, and QEMU does the same. That gap hides an entire class of bug - the kind that only appears when an interrupt handler runs immediately, in the middle of something else. Three separate bugs turned out to be exactly this shape: an audio interrupt acknowledging an event a polling loop was still waiting for, a bus-completion signal that had to arrive at the same instant on two different paths, and - the expensive one - an interrupt landing in the middle of a kernel service, which faulted on every QEMU boot and passed the old gate every time. A model that cannot produce the timing the hardware produces cannot be the oracle for behaviour that depends on it.

QEMU is also faster while staying exact: with instruction-counted timing and an inline counter it runs comfortably faster than native and far faster than the old accurate mode. The one real cost of the port was structural. A QEMU tracing plugin can observe a memory access but cannot supply the value a read returns, so the device models cannot live in a plugin - they have to be C devices compiled into the machine. That is why qemu-ipod/devices/ is C.

The models

The machine models the S5L8702's peripherals, each one a direct translation of the measured behaviour the earlier oracle had captured:

  • PMU - the Philips/NXP power-management chip, an I2C slave, including its real-time-clock block and the backlight and battery paths.
  • I2C - the on-chip controller, publishing completion two ways from one state machine (a status register for a poller, and an interrupt line for a driver that waits on the message) so that both arrive at the same instant.
  • LCD panel and display pipe - the panel understands both of the command sets the firmware uses (the MIPI display-command set the bootloader speaks, and the single-register set the diagnostics use); the pipe is a multi-layer compositor with per-window alpha and the panel tear-effect interrupt firing at 216 Hz.
  • Touchwheel - both the polled command/response path and the unsolicited interrupt packet that reports buttons, scroll position and touch.
  • ATA - the disk, backed by a real image file, with the programmed-I/O and DMA paths.
  • DWC2 USB - the Synopsys DesignWare USB 2.0 OTG controller, modelled as the device (gadget) half, not QEMU's stock host controller. See Accessories and iAP for what runs on top.
  • Interrupt controllers - the vectored interrupt controller and the external interrupt controller, the latter delivering each group on its own line.
  • Timer, DMA controller, AES, SHA-1, SPI NOR, the audio and video decoders, I2S, the dock UART, and the rest.

The drivers

On the Chikuma side, chikuma/drivers/hw/ holds the driver for each of these. They are being ported from C to Zig one file at a time, each keeping its name and signature so it can replace its predecessor in place (see chikuma/ZIG_MIGRATION.md). Some worth calling out:

  • I2C with a bus mutex. The mutex is declared OURS - the firmware's own lock for this bus was not recovered - and it is necessary: an unlocked bus let another task preempt a battery reading mid-transaction, which made the battery indicator read empty and then flash full. A deliberate divergence is marked in the source: this driver polls where the firmware is interrupt-driven, until the kernel's queue services are wired to the interrupt controller.
  • The RTC, read from the PMU's real-time-clock registers (seven of them, BCD-encoded), giving the clock screens the correct time.
  • Clockgate, which gates individual hardware blocks on and off. A cleared bit runs a block, which is the sort of inversion that has to be confirmed by behaviour rather than assumed.
  • Battery and charger. The battery driver takes the raw gauge reading (a small integer, not a voltage) all the way to the icon frame, with every constant recovered from the image except one inversion that is declared OURS. The charger driver reports external power and charge state.

The principle that keeps the models honest

A model must not invent behaviour the firmware never asks for. Where a bit's meaning has not been read out of the firmware, the model stores it and reads it back rather than acting on it, and says so at the site. The corollary is the one from the retired emulator that still governs the port: a field nothing in the image writes is the hardware's own, and the model must publish it - because if the firmware reads a value it never wrote, that value is an input the model owes it. Two audio crashes were exactly this, and are described on the media page.

The gate runs here

Chikuma's make verify runs on this machine. The gate drivers in qemu-ipod/tools/ and qemu-ipod/gates/ boot the machine, press buttons and turn the wheel, and assert against recovered constants. A fault - a data abort, or any complaint from a device model about how the guest programmed it - fails the gate and is printed, which is the whole reason the port replaced the oracle for behaviour. See running it in the emulator for how to drive the machine by hand, and reports/qemu_vs_unicorn.md for the device-by-device account of where the models still differ from the measured hardware.