MMC and Partitions
The first storage step is deliberately small and serious: bring up the MMC card path, read sector zero, prove that the medium has an MBR signature, and turn the partition map into something a human can inspect over UART.
A disk is not a filesystem yet. It is a block device with borders. Today I only ask the first professional question: where are the partitions, and can I trust the map?
Goal
Start the basic runtime again: UART console, timer, scheduler, and a heartbeat LED that keeps blinking
while the console or disk program waits. Then add a program named disk. It initializes the
Raspberry Pi MMC/eMMC storage path, reads LBA 0, checks the MBR signature, parses four partition entries,
and prints logical disks through UART commands. The first visible win can be as small as reading the first
bytes of sector zero and finding 0x55AA at the end. Everything is read-only.
The heartbeat is a task, not a side effect of the scheduler tick. The scheduler timer now ticks fast enough
for a responsive console, so the visible ACT LED blink belongs in heartbeat_task(), where it can
toggle at a human pace while disk work continues in another task.
Not a VFS
This stage is not Linux architecture. There is no VFS, inode layer, mount table, cache hierarchy, or
generic block layer. The disk program owns exactly the work it needs today: inspect one
medium, read sector zero, understand the partition table, and remember the result for the console. Later
chapters will split this into a small universal disk core plus filesystem modules. This chapter keeps the
surface small so the hardware and partition contract are visible.
What a Partition Table Is
A storage device exposes numbered sectors. A partition table is the first map that gives those sectors names and limits. It says that one range is a FAT32 boot partition, another range is a Linux root filesystem, and perhaps another range is reserved or empty. Filesystems do not own the whole card; they own a bounded slice of it. The disk driver must enforce that boundary before a filesystem module reads anything inside it.
In this first lesson the map is MBR: one 512-byte sector, a 0x55AA signature, and four
16-byte entries. Each entry carries a boot flag, a type byte, a first LBA, and a sector count. The old CHS
fields are present for compatibility, but the driver uses LBA values because modern cards are block
devices, not cylinder/head/sector machines.
The Commands
help
tasks
led on
led off
led toggle
disk
disks
partitions
disk partitions
debug
partitions and disk partitions ask the disk task to read LBA 0 and print the
discovered logical disks. The led command is a direct ACT LED diagnostic, separate from the
scheduled heartbeat task. Low-level register commands, raw block reads, cached-sector dumps, and task
kill/restart controls still exist, but they live behind debug so the normal help stays readable.
FAT32 directory walking,
FAT32 file reads, and ext4 inspection are later lessons; this one ends when the driver can read the card
and the partition map is trustworthy.
MBR, Carefully
The parser does not cast arbitrary bytes to a packed structure and hope the CPU likes it. It reads
little-endian fields by hand from the 512-byte sector buffer. It accepts only the 0x55 0xAA
signature at offsets 510 and 511, then walks four 16-byte entries beginning at offset 446.
type=0x00 empty
type=0x0B FAT32 CHS
type=0x0C FAT32 LBA
type=0x0E FAT16 LBA
type=0x83 Linux
type=0xEE protective MBR / GPT
A protective MBR is reported, but GPT parsing is intentionally left for a later stage.
Read-Only Contract
There is no write command. There is no write_lba. There is no format path, partition editor,
FAT mutation, or boot-sector patcher. The disk hardware module exposes only
disk_hw_init(), disk_hw_read_lba(), and disk_hw_get_info().
File-backed Disk
Before the bare-metal driver touches real media, the example creates a logical disk backed by a regular
host file. Linux tools build the disk: truncate allocates the raw file, sfdisk
writes a DOS/MBR partition table, and mkfs.fat formats the partition as FAT32 at LBA 2048.
make test-mbr
make test-disk-image
make test-mbr still tests the parser against in-memory sectors. make test-disk-image
creates the disposable disk image and prints its Linux-visible partition table. The image is not a custom
C mock disk; it is a small raw disk prepared by the same class of tools you would use on Linux.
QEMU then attaches that file as the Raspberry Pi machine's primary SD card:
-drive file=tests/disk.img,format=raw,if=sd,index=0
The bare-metal side still treats the medium as read-only. Formatting belongs to Linux; this stage only learns how to identify and read what Linux prepared.
Two Disk Backends
The QEMU disk is attached correctly when the boot log appears with -drive file=tests/disk.img,format=raw,if=sd,index=0.
If the kernel then prints disk: no card-present bit from eMMC2 host, the failure is not the
image file or the QEMU command line. It means the current stage is still using the Raspberry Pi eMMC2
backend while QEMU's emulated SD path needs its own backend.
The stable boundary is the small hardware interface: disk_hw_init(), disk_hw_read_lba(),
and disk_hw_get_info(). The disk program, MBR parser, shell commands, and later FAT32 code stay
above that line. Below it, QEMU and Raspberry Pi are allowed to differ. QEMU will get a backend that talks
to the emulated controller; Raspberry Pi hardware will keep the BCM2711/eMMC2 path.
QEMU Tests
make clean
make test-mbr
make test-disk-image
make qemu
make test-disk-image must show one Linux-visible MBR partition: bootable, starting at LBA 2048,
type 0x0C / W95 FAT32 (LBA). make qemu must boot the kernel and show the
UART banner. Until the QEMU disk backend is implemented, hardware storage acceptance is performed on the
Raspberry Pi rather than inside QEMU.
Alquist boot
UART ready
Timer ready
Scheduler ready
Disk program started
disk: probing Raspberry Pi 4 eMMC2 host (read-only)
disk: no card-present bit from eMMC2 host
console ready
After the QEMU disk backend lands, the same test can grow: disk scan should read LBA 0 from
tests/disk.img, accept the 0x55AA signature, and list the FAT32 partition created by Linux.
Hardware Tests
Hardware testing uses a real Raspberry Pi 4 booting boot/kernel8.img from the SD boot partition
with UART attached. The test must confirm the system environment first: boot banner, UART console, timer,
scheduler, heartbeat LED, and a live prompt. Storage commands are then exercised from UART.
help
tasks
led toggle
disk
partitions
debug
The hardware acceptance test is concrete: read LBA 0 from the real card, parse the MBR, and list the
FAT32 boot partition and Linux root partition prepared on that card by Linux. The read-only rule is part
of the test: there is no write command, no format path, and no code path that mutates the real SD card.
If a register-level check is needed, run debug and use readblk or
disk dump0 deliberately.
Minimum Proof
The smallest useful proof is not a filesystem. It is this sequence: initialize the MMC host, read LBA 0,
use the debug tools to dump the cached sector, and see the MBR signature bytes 55 AA at offsets 510 and 511. Parsing
the four entries turns that raw proof into named ranges, but the driver milestone is already visible when
those signature bytes come back from the real card.
Example Package
The source package lives in the Program Disk example directory. It
contains the UART console, scheduler tasks, read-only disk program, MBR parser, Raspberry Pi disk module,
host-side parser tests, and a prebuilt kernel8.img:
download program-disk-example.zip.
Try It
make
make test-mbr
make test-disk-image
make qemu
On hardware, boot the produced boot/kernel8.img with the included boot config. The expected
boot log begins with Alquist boot, UART ready, Timer ready,
Scheduler ready, and Disk program ready. Then partitions should ask
the disk task to read LBA 0 from the real card and print the FAT32 and Linux partition ranges.
Next
The next formal lesson is Partition tables: disks with or without maps, MBR/GPT theory, partition boundaries, and unused ranges. Two hardware examples also document the register-first path: MBR by registers and FAT32 dir by registers.