M6: FAT read/write filesystem server, mounted into the VFS
Add a FAT12/16/32 filesystem the VFS mounts at /mnt/usb, reading and writing a USB stick through the block device. Verified end to end under QEMU: the fat server mounts the volume, the VFS routes /mnt/usb to it, and a client lists the root and reads a file (the ELF magic of /mnt/usb/system/kernel). - engine.zig: the FAT engine over a BlockDevice interface — mount (a bare FAT or, as QEMU's VVFAT and most real sticks present it, an MBR-partitioned disk), FAT chain walk (12/16/32), cluster allocation, directory traversal with long-name read, and file read / write / create. Host-tested against a RAM-backed FAT16 image (create, cluster-spanning write, mid-file overwrite, read-back, list). - on-disk.zig: the align(1) boot-sector / directory / long-name / FSInfo structs and the cluster-count FAT-type detection. - fat.zig: the server — wraps the .block device (a DMA bounce buffer) in a BlockDevice, mounts the FAT, serves the vfs-protocol as a backend, and mounts itself into the VFS at /mnt/usb. Spawned by init as a boot service. - runtime.block: the block-device client (geometry / read / write by physical address, so whole sectors never cross IPC). - Raise the kernel service-name registry (maximum_services) 8 -> 16: it is indexed directly by ServiceId, and fat = 8 was being rejected, so the fat server exited before registering. - VFS: an absolute path with no matching mount is now not-found rather than silently created in the flat ramfs — so /mnt/usb fails cleanly until mounted. Tests: fat-mount (the full stack: block -> FAT -> VFS mount -> list + file read) passes; host units cover the engine and on-disk structs; the vfs, shutdown, and USB regression suite stays green (10/10).
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
//! Block-device client: the helper a filesystem uses to read and write a block
|
||||
//! device (a USB stick, via usb-storage) without hand-rolling the block-protocol
|
||||
//! IPC. Layered over `ipc` and the shared `block-protocol` wire format, like
|
||||
//! `runtime.usb` over the transfer protocol.
|
||||
//!
|
||||
//! Transfers name a caller-owned DMA buffer by physical address (from
|
||||
//! `runtime.dma.alloc`), so whole sectors move without crossing the IPC size
|
||||
//! limit — the same handoff usb-storage uses toward the controller.
|
||||
|
||||
const std = @import("std");
|
||||
const ipc = @import("ipc.zig");
|
||||
const system = @import("system.zig");
|
||||
const protocol = @import("block-protocol");
|
||||
|
||||
pub const Geometry = struct { block_size: u32, block_count: u64 };
|
||||
|
||||
pub const Device = struct {
|
||||
endpoint: ipc.Handle,
|
||||
|
||||
/// The device's block size and total block count.
|
||||
pub fn geometry(self: Device) ?Geometry {
|
||||
var request = protocol.Request{ .operation = @intFromEnum(protocol.Operation.geometry), .lba = 0, .count = 0, .physical = 0 };
|
||||
var reply: [protocol.reply_size]u8 = undefined;
|
||||
const n = ipc.call(self.endpoint, std.mem.asBytes(&request), &reply) catch return null;
|
||||
if (n < protocol.reply_size) return null;
|
||||
const result = std.mem.bytesToValue(protocol.Reply, reply[0..protocol.reply_size]);
|
||||
if (result.status != 0) return null;
|
||||
return .{ .block_size = result.block_size, .block_count = result.block_count };
|
||||
}
|
||||
|
||||
/// Read `count` blocks starting at `lba` into the DMA buffer at `physical`.
|
||||
pub fn read(self: Device, lba: u64, count: u32, physical: u64) bool {
|
||||
return self.transfer(.read, lba, count, physical);
|
||||
}
|
||||
|
||||
/// Write `count` blocks starting at `lba` from the DMA buffer at `physical`.
|
||||
pub fn write(self: Device, lba: u64, count: u32, physical: u64) bool {
|
||||
return self.transfer(.write, lba, count, physical);
|
||||
}
|
||||
|
||||
fn transfer(self: Device, operation: protocol.Operation, lba: u64, count: u32, physical: u64) bool {
|
||||
var request = protocol.Request{ .operation = @intFromEnum(operation), .lba = lba, .count = count, .physical = physical };
|
||||
var reply: [protocol.reply_size]u8 = undefined;
|
||||
const n = ipc.call(self.endpoint, std.mem.asBytes(&request), &reply) catch return false;
|
||||
if (n < protocol.reply_size) return false;
|
||||
return std.mem.bytesToValue(protocol.Reply, reply[0..protocol.reply_size]).status == 0;
|
||||
}
|
||||
};
|
||||
|
||||
/// Look up the block device, retrying generously while the USB storage chain
|
||||
/// (controller reset, enumeration, mass-storage bring-up) comes up.
|
||||
pub fn open() ?Device {
|
||||
// Patient: the whole USB storage chain (firmware discovery, xHCI reset and
|
||||
// enumeration, mass-storage bring-up) must complete first, which can take
|
||||
// tens of seconds under emulation.
|
||||
var attempts: usize = 0;
|
||||
while (attempts < 1200) : (attempts += 1) {
|
||||
if (ipc.lookup(.block)) |handle| return .{ .endpoint = handle };
|
||||
system.sleep(50);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user