//! system/services/fat — the FAT filesystem service. This is FAT's FAT-specific //! half: it finds its block device, sets up the DMA bounce buffer, mounts the //! FAT engine on it, and hands the mounted volume to the shared filesystem //! harness (library/kernel/file-system-harness), which owns everything else — //! the vfs-protocol serving, the open-node table, mount registration, the exit //! sweep, durable-on-close. The engine (engine.zig) is the pure, host-testable //! format code; on-disk.zig its byte layout. A second filesystem reuses the //! harness and supplies its own engine //! (docs/file-system-development/storage-architecture.md). //! //! The block data path never crosses IPC: a DMA bounce buffer is handed to the //! block driver by physical address, and the engine copies sectors in and out. const std = @import("std"); const channel = @import("channel"); const volume_manager_protocol = @import("volume-manager-protocol"); const ipc = @import("ipc"); const process = @import("process"); const block = @import("block"); const memory = @import("memory"); const logging = @import("logging"); const time = @import("time"); const engine = @import("engine.zig"); const envelope = @import("envelope"); const harness = @import("file-system-harness"); /// The serving harness, specialized for the FAT engine. One volume per process. const Harness = harness.Server(engine.FileSystem); // The engine's BlockDevice, backed by the `.block` driver plus a DMA bounce // buffer the driver reads/writes by physical address. const IpcBlock = struct { device: block.Device, bounce: memory.DmaRegion, // engine.max_transfer_sectors * 512 bytes fn readBlocks(context: *anyopaque, lba: u64, count: u32, buffer: []u8) bool { const self: *IpcBlock = @ptrCast(@alignCast(context)); if (count == 0 or count > engine.max_transfer_sectors) return false; const len = count * 512; if (!self.device.read(lba, count, self.bounce.physical)) return false; const source: [*]const u8 = @ptrFromInt(self.bounce.virtual); @memcpy(buffer[0..len], source[0..len]); return true; } fn writeBlocks(context: *anyopaque, lba: u64, count: u32, buffer: []const u8) bool { const self: *IpcBlock = @ptrCast(@alignCast(context)); if (count == 0 or count > engine.max_transfer_sectors) return false; const len = count * 512; const destination: [*]u8 = @ptrFromInt(self.bounce.virtual); @memcpy(destination[0..len], buffer[0..len]); if (!self.device.write(lba, count, self.bounce.physical)) return false; device_dirty = true; // a block reached the device; a close will flush it return true; } }; var ipc_block: IpcBlock = undefined; // Set whenever a block is written, cleared when the device cache is flushed on a // file close — so writes are committed to stable media before a power-off. var device_dirty: bool = false; var filesystem: engine.FileSystem = undefined; /// The volume this FAT process serves, its id given as argv[1] by the volume /// manager that spawned it. The startup hello names it so the manager returns /// the right volume's channel. var my_volume_id: u64 = 0; /// The volume's own mount path, handed in as argv[2] by the volume manager: the /// volume's content id-path (e.g. /volumes/fat-12345678). Defaults to /// /volumes/usb only for a bare launch with no argument; the manager always /// passes it. The slice points into the entry block, valid for the process life. var volume_mount_prefix: []const u8 = "/volumes/usb"; /// The mounts this volume installs: its own root, plus — only if it is the boot /// volume (it resolves /system/configuration) — the two FHS rewrites, so the /// logger's /system/logs stays decoupled from which volume backs it. Boot-volume /// detection is by content, so it works no matter which volume carries /system. /// bound: mounts one volume installs (its root + the two boot rewrites) /// decided-by: ours /// protects: the mount_specs array /// at-limit: truncate - unreachable today (fixed at 3); more configured mounts /// would need this raised, a deliberate change /// observed-by: a mount silently missing from the harness's mount log const maximum_mounts_per_volume = 4; var mount_specs: [maximum_mounts_per_volume]harness.MountSpec = undefined; /// Get this volume's block channel from the volume manager (establishment by /// lineage, communication.md "Establishment: two planes" — `block` is not a /// registry name). The manager spawned this process, confined it to its /// partition, and answers the hello with the channel; the channel is /// range-confined to this process's badge, so reads and writes are /// volume-relative and cannot reach the neighbouring partition. Null until the /// manager has the volume ready — this retries. fn acquireVolume() ?block.Device { var attempts: u32 = 0; const vm = while (attempts < 500) : (attempts += 1) { if (channel.openEndpoint("volume-manager")) |handle| break handle; time.sleepMillis(20); } else return null; attempts = 0; while (attempts < 500) : (attempts += 1) { var packet: [volume_manager_protocol.message_maximum]u8 = undefined; const framed = volume_manager_protocol.Protocol.encodeRequest(.hello, my_volume_id, .{}, &.{}, &packet) orelse return null; var reply: [volume_manager_protocol.message_maximum]u8 = undefined; const answered = ipc.callCap(vm, framed, &reply, null) catch return null; const status = envelope.statusOf(reply[0..answered.len]) orelse return null; if (status.status != 0) { if (answered.cap) |stray| _ = ipc.close(stray); _ = logging.write("/system/services/fat: volume manager refused the hello\n"); return null; } if (answered.cap) |bus| return .{ .endpoint = bus }; // Acked with no channel: the volume is not ready yet — retry. time.sleepMillis(20); } return null; } /// Durable-on-close: commit the device write cache if any block reached it since /// the last flush. The harness calls this on every close; the dirty check keeps /// it cheap. `device_dirty` lives here because `IpcBlock.writeBlocks` sets it. fn flushIfDirty() void { if (device_dirty) { _ = ipc_block.device.flush(); device_dirty = false; } } /// FAT bring-up: find the block device, set up DMA, mount the engine, and hand /// the volume to the harness — or null to retry on the harness's timer. fn fatBringUp(endpoint: ipc.Handle) ?Harness.Volume { _ = endpoint; const device = acquireVolume() orelse return null; const geometry = device.geometry() orelse { _ = logging.write("/system/services/fat: block geometry unavailable\n"); return null; }; // Shareable so the buffer's capability can be attached down the chain (block // server -> controller), making its physical addresses reachable by the // device under an enforcing IOMMU. No-op binding otherwise. const bounce = memory.dmaAlloc(engine.max_transfer_sectors * 512, memory.dma_coherent | memory.dma_shareable) orelse return null; if (bounce.handle) |handle| { // Attach, detach, and attach again: the round trip exercises BOTH verbs // of the DMA-window lifecycle through the whole chain (fat -> storage -> // bus -> kernel) on every boot, so a broken detach fails every fat case // rather than lying dormant until the first buffer replacement. if (!device.attach(handle)) { _ = logging.write("/system/services/fat: could not attach the DMA bounce buffer\n"); return null; } if (!device.detach(handle)) { _ = logging.write("/system/services/fat: could not detach the DMA bounce buffer\n"); return null; } if (!device.attach(handle)) { _ = logging.write("/system/services/fat: could not re-attach the DMA bounce buffer\n"); return null; } _ = ipc.close(handle); // the binding holds its own reference now } ipc_block = .{ .device = device, .bounce = bounce }; const block_device = engine.BlockDevice{ .context = &ipc_block, .block_size = geometry.block_size, .block_count = geometry.block_count, .readBlocksFn = IpcBlock.readBlocks, .writeBlocksFn = IpcBlock.writeBlocks, }; filesystem = engine.FileSystem.mount(block_device) orelse { _ = logging.write("/system/services/fat: not a FAT filesystem\n"); return null; }; std.log.info("mounted FAT ({s}, {d} clusters, partition lba {d})", .{ @tagName(filesystem.geometry.fat_type), filesystem.geometry.cluster_count, filesystem.base_lba }); // The volume mounts at its id-path (argv[2]), plus the two FHS rewrites so // hierarchy paths (the logger's /system/logs) stay decoupled from which // volume backs them. This single-volume increment's one volume IS the boot // volume, so it installs both unconditionally; S3 (multi-volume) makes the // rewrites content-conditional — installed only by whichever volume carries // the system, decided by content, not order. mount_specs[0] = .{ .prefix = volume_mount_prefix }; mount_specs[1] = .{ .prefix = "/system/configuration", .rewrite = "/system/configuration" }; mount_specs[2] = .{ .prefix = "/system/logs", .rewrite = "/system/logs" }; return .{ .engine = &filesystem, .mounts = mount_specs[0..3], .flush = flushIfDirty }; } pub fn main(init: process.Init) void { // The volume manager spawns this process with its volume id as argv[1] and // the volume's mount path (its id-path) as argv[2]. if (init.arguments.get(1)) |id| { my_volume_id = std.fmt.parseInt(u64, id, 10) catch 0; } if (init.arguments.get(2)) |prefix| { volume_mount_prefix = prefix; } _ = logging.write("/system/services/fat: starting, waiting for a block device\n"); Harness.run(.{ .bringUp = fatBringUp }); }