//! 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 device_manager_protocol = @import("device-manager-protocol"); const driver = @import("driver"); const ipc = @import("ipc"); const block = @import("block"); const memory = @import("memory"); const logging = @import("logging"); 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 one channel to the device manager, opened on first need and kept — the /// poll retries on it, never spending a handle-table slot per attempt. var manager_handle: ?ipc.Handle = null; /// The prefixes this volume installs: /volumes/usb from the volume root, plus /// the two hierarchy subtrees the boot volume carries (rewrite == prefix), so /// hierarchy paths (the logger's /system/logs) stay decoupled from which volume /// backs them. const fat_mounts = [_]harness.MountSpec{ .{ .prefix = "/volumes/usb" }, .{ .prefix = "/system/configuration", .rewrite = "/system/configuration" }, .{ .prefix = "/system/logs", .rewrite = "/system/logs" }, }; /// Find the volume's provider through the device manager (establishment by /// lineage, communication.md "Establishment: two planes" — `block` is not a /// registry name; one storage process serves each stick): enumerate the /// manager's tree, take the FIRST usb mass-storage child by enumeration order /// (deterministic within a boot; single-volume by construction, and choosing /// the BOOT volume by content when two sticks are present is the volume-manager /// track), and consumer-hello for the channel of the driver bound to it. /// Null until the chain is up — the harness's poll retries. fn acquireVolume() ?block.Device { const manager = manager_handle orelse opened: { const handle = channel.openEndpoint("device-manager") orelse return null; manager_handle = handle; break :opened handle; }; // The envelope's reserved `enumerate` verb, PAGED: one reply carries only // a handful of entries and a real tree (a dozen ACPI nodes before the // first USB child) is bigger, so `Header.target` is the start cursor and // a short page is the end. Identity is the bus's native triple, for USB // (base << 16) | (class << 8) | protocol — mass storage is base 0x08, // subclass 0x06 (SCSI transparent), the same key devices.csv matches on. const Entry = device_manager_protocol.ChildEntry; var start: u64 = 0; while (true) { const enumerate = envelope.Header{ .operation = envelope.operation_enumerate, .target = start }; var reply: [device_manager_protocol.message_maximum]u8 = undefined; const length = ipc.call(manager, std.mem.asBytes(&enumerate), &reply) catch return null; const status = envelope.statusOf(reply[0..length]) orelse return null; if (status.status != 0) return null; const carried = @min(@as(usize, status.len), length -| envelope.prefix_size); const tail = reply[envelope.prefix_size..][0..carried]; const count = tail.len / @sizeOf(Entry); if (count == 0) return null; // the tree is exhausted; no volume yet var index: usize = 0; while (index < count) : (index += 1) { const entry = std.mem.bytesToValue(Entry, tail[index * @sizeOf(Entry) ..][0..@sizeOf(Entry)]); if (entry.device_id == device_manager_protocol.no_device) continue; if ((entry.identity >> 16) & 0xff != 0x08 or (entry.identity >> 8) & 0xff != 0x06) continue; const exchanged = driver.helloOn(manager, .consumer, entry.device_id, null, true) orelse return null; const provider = exchanged.channel orelse continue; // its driver not up yet — next tick return .{ .endpoint = provider }; } start += count; } } /// 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 }); return .{ .engine = &filesystem, .mounts = &fat_mounts, .flush = flushIfDirty }; } pub fn main() void { _ = logging.write("/system/services/fat: starting, waiting for a block device\n"); Harness.run(.{ .bringUp = fatBringUp }); }