Skip to content

FAT12

FAT12 is the primary read/write filesystem, stored on a 1.44 MB floppy disk. It is accessible as:

  • Absolute paths under /mnt/fat/
  • Bare filenames relative to the current working directory (cluster stored in SYSTEM_CONFIG)

Hardware: Floppy Controller (fs/fat12/block.rs)

Floppy implements BlockDevice using direct ISA DMA and FDC I/O port programming.

DMA Setup (Floppy::init)

Called before every read. Programs ISA DMA channel 2 to transfer one sector (512 bytes) from the floppy controller into the static DMA: [u8; 512] buffer:

port 0x0A ← 0x06   Mask channels 2+0
port 0x0C ← 0xFF   Reset flip-flop
port 0x04 ← addr_lo, addr_hi   DMA buffer address
port 0x05 ← 511 lo, 511 hi     Transfer count − 1
port 0x81 ← page               High byte of DMA address
port 0x0A ← 0x02               Unmask channel 2

The DMA buffer is placed in a .dma link section so its physical address is known at build time.

Read Sector (read_sector)

  1. Convert LBA → CHS: C = LBA / (18 × 2), H = (LBA % 36) / 18, S = (LBA % 18) + 1.
  2. Set DMA read mode (port 0x0B ← 0x56): single transfer, address increment, read, channel 2.
  3. Send FDC READ DATA command (0x46) with head/cylinder/head/sector/byte-size/18/GAP3/DTL.
  4. Wait for IRQ6 by polling port 0x3F4 MSR bit 7, then send Sense Interrupt (0x08) and drain 7 result bytes.
  5. copy_nonoverlapping(DMA, buffer, 512).

The floppy disk geometry assumed throughout: 80 cylinders, 2 heads, 18 sectors/track = 1440 sectors × 512 bytes = 1.44 MB.

Write Sector (write_sector)

  1. Seeks to the target cylinder/head using FDC SEEK command (0x0F).
  2. Reprograms DMA channel 2 for memory→device transfer (mode 0x58) with the data copied to DMA_BUFFER_ADDR = 0x1000.
  3. Sends FDC WRITE DATA command (0x45).
  4. Waits for IRQ6.

FDC Ports

Port Register
0x3F2 Digital Output Register (DOR) — motor, drive select
0x3F4 Main Status Register (MSR) — ready/busy flags
0x3F5 Data FIFO — command/result bytes

Filesystem Structure (fs/fat12/fs.rs)

Filesystem<D: BlockDevice> holds all computed layout values derived from the boot sector:

Field Description
device Reference to the underlying BlockDevice
boot_sector Parsed copy of the BPB (sector 0)
fat_start_lba LBA of FAT table (= reserved_sectors)
root_dir_start_lba LBA of root directory region
data_start_lba LBA of first data cluster
sectors_per_cluster Sectors per cluster from BPB

Boot Sector / BPB (fs/fat12/entry.rs)

BootSector is #[repr(C, packed)], read directly from sector 0:

Offset Field Description
0 jmp 3-byte jump instruction
3 oem OEM string (8 bytes)
11 bytes_per_sector Always 512
13 sectors_per_cluster Sectors per allocation unit
14 reserved_sectors Sectors before the FAT (usually 1)
16 fat_count Number of FAT copies (usually 2)
17 root_entry_count Max root directory entries (usually 224)
19 total_sectors_16 Total sectors on disk
22 fat_size_16 Sectors per FAT copy (usually 9)

Detection: Filesystem::new scans the boot sector for the 5-byte string "FAT12". If not found, returns Err.

Layout Arithmetic

fat_start_lba      = reserved_sectors
root_dir_sectors   = ceil(root_entry_count × 32 / 512)
root_dir_start_lba = fat_start_lba + (fat_count × fat_size_16)
data_start_lba     = root_dir_start_lba + root_dir_sectors
cluster_to_lba(n)  = data_start_lba + (n − 2) × sectors_per_cluster

Directory Entries (fs/fat12/entry.rs)

Each directory entry is 32 bytes (#[repr(C, packed)]):

Offset Size Field Description
0 8 name Filename, space-padded, uppercase
8 3 ext Extension, space-padded, uppercase
11 1 attr Attribute flags (see below)
12 1 reserved
13 1 create_time_tenths
14 2 create_time
16 2 create_date
18 2 last_access_date
20 2 high_cluster High 16 bits of cluster (unused in FAT12)
22 2 write_time
24 2 write_date
26 2 start_cluster First cluster of file data
28 4 file_size File size in bytes (0 for directories)

Attribute Byte Flags

Bit Value Meaning
0 0x01 Read-only
1 0x02 Hidden
2 0x04 System
3 0x08 Volume label
4 0x10 Directory
5 0x20 Archive

Special First-Byte Values

Value Meaning
0x00 End of directory (no more entries)
0xE5 Deleted entry (slot is reusable)
0xFF Unused (treated as invalid)

FAT Table Encoding (fs/fat12/table.rs, fs/fat12/fs.rs)

FAT12 encodes each cluster entry in 12 bits. Two cluster numbers share 3 bytes, packed as follows:

For an even cluster N at byte offset fat_offset = (N * 3) / 2:

value = byte[fat_offset] | ((byte[fat_offset+1] & 0x0F) << 8)

For an odd cluster N:

value = (byte[fat_offset] >> 4) | (byte[fat_offset+1] << 4)

Special Cluster Values

Range Meaning
0x000 Free cluster
0x001 Reserved
0x002–0xFEF Valid data cluster, value = next cluster in chain
0xFF0–0xFF6 Reserved
0xFF7 Bad sector
0xFF8–0xFFF End of chain (EOF)

FatTable reads all 9 FAT sectors (4608 bytes) into a single [u8; 4608] for batch inspection. Filesystem::read_fat12_entry reads individual sectors on demand, handling the cross-sector boundary case (when fat_offset == 511).


Operations

Read File (read_file)

Follows the cluster chain starting from start_cluster:

  1. Convert cluster → LBA.
  2. Read 512-byte sector into the caller's buffer at offset count × 512.
  3. Advance to read_fat12_entry(cluster).
  4. Stop when chain entry ≥ 0xFF8 or buffer is exhausted.

Write File (write_file)

  1. If a file with the same name exists: free_cluster_chain its old clusters, mark its directory entry 0xE5.
  2. allocate_cluster(): scan FAT entries for 0x000 (free), mark it 0xFFF, return its index.
  3. Write each 512-byte sector of data into allocated clusters; write_fat12_entry(cluster, next) to chain them; mark the last cluster 0xFFF.
  4. write_dir_entry(): scan the directory for a free slot (0x00 or 0xE5), write the 32-byte entry (name, attr 0x20, cluster, file_size).

Delete File (delete_file)

Marks the first byte of the directory entry as 0xE5. Does not free clusters (no garbage collection; on overwrite, write_file calls free_cluster_chain first).

Rename File (rename_file)

Overwrites the 11 name bytes in the directory entry. Does not change cluster chain or file data.

Create Subdirectory (create_subdirectory)

  1. allocate_cluster() for the new directory.
  2. Write a directory entry with attr = 0x10, file_size = 0.
  3. Zero the cluster's sector.
  4. Write . (self-pointer) and .. (parent-pointer) entries into the new cluster.
  5. Mark the new cluster as end-of-chain (0xFFF) in the FAT.

Directory Traversal (for_each_entry)

Iterates all 32-byte entries in a directory, calling a closure for each: - dir_cluster == 0: reads the fixed root directory region (root_dir_start_lba, root_entry_count entries). - dir_cluster > 0: follows the FAT cluster chain for subdirectories.

Path Lookup (find_entry, resolve_path_from)

find_entry(cluster, name83) calls for_each_entry and matches all 11 bytes (name + ext).

resolve_path_from(start_cluster, path) splits path on / and calls find_entry for each component, following directory cluster chains. Returns None if any component is missing. An empty path returns a synthetic directory entry for start_cluster itself.


Filename Format (fat83)

fat83(component) converts a file name component (e.g. b"file.txt") to the 11-byte FAT 8.3 format:

  • Split at the last .; left side = name (max 8), right side = extension (max 3).
  • Uppercase all bytes.
  • Space-pad to [u8; 11].

Example: b"SH.ELF"b"SH ELF".


Filesystem Check (fs/fat12/check.rs)

run_check()CheckReport { errors, orphan_clusters, cross_linked, invalid_entries }:

  1. FatTable::load() reads all 9 FAT sectors into a contiguous buffer.
  2. scan_directory(0, ...) recursively visits every directory and file from the root (max depth 64):
  3. Directories: recurse.
  4. Files: validate_chain marks each cluster as visited in a [bool; 4096] bitmap, detects cross-linked clusters (already-visited cluster reused by a different file).
  5. After scanning, counts orphan clusters: FAT entries that are non-zero (allocated) but were never visited by scan_directory.

Exposed via syscall 0x2B. Returns four u64 values written to a userland FsckReport_T struct.


Limits

Resource Value
Disk size 1.44 MB (80 cyl × 2 heads × 18 sectors × 512 B)
Max root directory entries 224 (from BPB; not expandable)
Sector size 512 bytes
Max cluster index 4084 (FAT12)
FAT copy sectors 9 sectors per copy, 2 copies
Max file size Limited by available clusters × 512 B
Max directory depth (fsck) 64