Disk Tutorial · 01

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.

[o-o] |=| /_\

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.