fat/usb-storage: flush the device cache on close so writes survive power-off

init writes /mnt/usb/DANOS.LOG at shutdown and then enters S5 — but the write sat in the USB flash controller's write cache and was lost when power was cut, because nothing issued SCSI SYNCHRONIZE CACHE. Invisible in QEMU (its backing file commits immediately); real on hardware. The boot-time flush survived only because the machine kept running afterward and the cache drained on its own.

- block protocol gains a flush op; usb-storage serves it with SYNCHRONIZE CACHE (10); runtime.block gains Device.flush()
- the FAT server tracks whether blocks were written and, on a file close, commits the device cache (durable-on-close — the right default for removable media, and it makes init's existing shutdown close() persist the log before S5, no init/VFS change needed)
- verified the SYNCHRONIZE CACHE actually reaches the driver on each dirty close; usb-storage/fat-mount/mutations/rename/mtime/log-flush all green
This commit is contained in:
Daniel Samson
2026-07-13 23:29:15 +01:00
parent 88644e57d6
commit f157a93c9c
5 changed files with 44 additions and 1 deletions
+14 -1
View File
@@ -40,11 +40,16 @@ const IpcBlock = struct {
const self: *IpcBlock = @ptrCast(@alignCast(context));
const destination: [*]u8 = @ptrFromInt(self.bounce.virtual);
@memcpy(destination[0..512], buffer[0..512]);
return self.device.write(lba, 1, self.bounce.physical);
if (!self.device.write(lba, 1, 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;
// Open handles the VFS holds against this backend: each maps a node id to a
@@ -192,6 +197,14 @@ fn onMessage(message: []const u8, out: []u8, sender: u32, capability: ?runtime.i
},
.close => {
if (openAt(request.node)) |o| o.used = false;
// Durable-on-close: if any block reached the device since the last
// flush, commit its cache to stable media now (best-effort). This is
// what makes init's shutdown log flush survive a real power-off, and is
// the right default for removable media the user may unplug.
if (device_dirty) {
_ = ipc_block.device.flush();
device_dirty = false;
}
return writeReply(out, .{ .status = 0 }, &.{});
},
.mkdir => {