Audio and video¶
There is no Apple codec in this image¶
The single most useful finding in the media work is a negative one. sm1, at 0x38500000, is a
bus-mastering hardware decoder: packets go in over a descriptor chain in DRAM, PCM comes out into
fixed SRAM buffers. The firmware's job is to feed it and to service its interrupts.
So there is nothing to re-derive on the decode side, and a great deal to get exactly right on the plumbing side. The register map, the descriptor format, the interrupt and semaphore wake, and the output contract are written down as a specification with the function that establishes each one.
Fields nobody writes belong to the hardware¶
Two crashes taught the same lesson, and it generalises past audio:
- the sm1 output ring size, at
TABLE + 2. The firmware wraps its index withidx = (idx + 1) % size, so a modelled zero meant no wrap at all and the buffer address walked out of SRAM; - the position pair at
+0xa44/+0xa48. The firmware scales it into a pointer, so a cumulative counter left memory after 391 seconds of playback.
Neither field is written by anything in the image. The test is exactly that: watch a whole boot for writes to the address. If nobody writes it and the firmware reads it, it is an input the model owes the hardware.
Feeding it without freezing the UI¶
The audio feeder blocks on the DMA controller's terminal count instead of spinning. That one change is why the window manager gets scheduled during playback, and it is visible rather than theoretical: Now Playing paints its title, album, "1 of 11", progress bar and cover while a track is playing.
Video¶
Video decodes through the same family of hardware, and closing it needed a model correction rather than a firmware discovery: the decoder starts on the run bit's 0 → 1 edge, as the hardware does, not on every write to the control register. With that fixed, both test films decode with zero errors from the reference decoder -- the simple IPPP one and the two-layer GOP.
The video path also turned out not to be what the format documentation would suggest: the stream carries stripped slice bodies with a per-slice descriptor, not NALs, and the slice NALs are reconstructed. That was measured, and it is the sort of thing that is invisible until the film either plays or does not.
Protected content¶
The device can play the DRM-protected content that was synced to it, using its own keybag -- the encrypted store the device already carries, holding its GUID, its account and its user keys -- opened with the device's own AES engine. Chikuma drives that engine directly: the block is real hardware with a clock gate, and it is validated at boot against a published NIST SP 800-38A CBC-AES test vector, which is a stronger check than "the movie played".
This is a device reading its own library. It is not a tool for stripping protection from content, and this documentation is not going to become one.
A warning about arithmetic¶
Three times in one day, a formula that fitted the data was reported as a mechanism, and each was refuted by measurement:
- "one packet in three is dropped" fitted 391 × 0.663 = 259 to two significant figures, and died when a cross-correlation put the best match at the identity lag -- the stream was thinned, not skipped;
- a thumbnail stride computed as
size / (height * 2)agreed with every album cover, and broke the first time a poster was not square; - "bit 31 is a tag" agreed with reality for exactly as long as the address happened to be RetailOS's.
A number that fits is not a mechanism. A formula validated on a degenerate input set predicts nothing outside it -- check it against a case where the inputs actually differ before believing it.
Also true of code reading¶
Three claims in one session came from decompiled C and were all wrong: that a codec setup function had several early exits (it has exactly one return), that a packet reader zeroed its out-parameters (the store never executes), and that a give-up came from one of three conditions (all three were false; the branch was a fourth, elsewhere).
When the decompiler and the disassembly disagree, the disassembly wins -- and for anything
load-bearing, check it first. The decompiler also hides register reuse: one function reloads r1
with a table midway, so a field the C reads as header + 4 is really TABLE + 4. Modelling the
wrong one cost hours.