Skip to content

Storage

The disk is asynchronous, by design

The two ATA tasks -- ATAWorkLoopTask (id 0x20, priority 0x33) and ATAWorkLoopIRQTask (id 0x21, priority 9) -- own bulk disk work, and they block rather than spin.

So the filesystem never calls the disk. It posts to a queue and waits. This is not a design Chikuma chose; it is the one in the image, and it has a practical consequence for anyone tracing: a stack walk at the ATA command register write can never name the caller, because the caller is in a different task by construction. Trace the command write, not the data fetch -- DMA completes long after the driver returned, so at the data fetch lr reads zero and the stack holds nothing useful.

The layers

    HFS / HFS+ reader        FAT16 reader
    ------------------------------------------
    partition layer (APM, MBR)
    ------------------------------------------
    ATA driver (LBA28 and LBA48)

HFS and HFS+ are one driver, because they are one driver in the firmware.

Sector size, which has bitten this project

On the iPod's disk, MBR partition LBAs are in 4096-byte units, not 512. A test image built on the 512-byte assumption does not fail loudly; it produces offsets that are wrong by a factor of eight and land on plausible-looking data.

Struct offsets are tested, not remembered

Two mistakes made here, both from trusting memory over the specification:

  • HFS+ fork data at 0x70/0xB8 instead of 0x70/0xC0/0x110;
  • the B-tree header's nodeSize read at +10, which is firstLeafNode, instead of +18.

Neither produced an error. Both produced numbers that looked fine and made every later read land nowhere.

The fix is the rule the project now uses generally: assert that numbers are physically possible. A zero-byte file spanning four million blocks fails a test instantly and costs nothing to check.

Tests run against a real volume

Host tests read retailos_re/build/ipod_apm.img -- an actual APM disk with a FAT16 firmware partition and an HFS+ data partition -- and the real resource archive, never a fixture written to match the parser. That is what caught both offset bugs above, in seconds each.

The music database

The library is Apple's iTunesDB, not a filesystem walk, and its readers are recovered: the track parser (mhltmhitmhod), the playlist parser (mhlpmhypmhodmhip), the MHSD dispatcher, and the artwork database with its .ithmb thumbnail atlases. Genius is a separate SQLite database, which is why SQLite is in scope.

A real bug in Apple's firmware

The firmware builds its track lookup index by walking the parsed track list in file order and never sorting it, then binary-searches that index. A binary search over an unsorted array silently fails for some entries, and the playlist parser skips any entry whose lookup fails -- so those tracks vanish from every browse screen while still being counted on the main screen. A neighbouring function doing the same job qsorts its index first; this one does not.

In practice iTunes always wrote track lists sorted by id ascending (9,671 tracks, zero inversions), so the bug rarely fires on a device synced the normal way. Any replacement firmware should sort the index or use a hash map, and the whole class of failure disappears.

This is the kind of finding that argues for the project: you cannot fix what you cannot read.

Known gap

The emulator's ATA model has no write path -- firmware writes are dropped. That is the first emulator gap worth closing, because it blocks a subsystem rather than a screen.