//! The block-device wire protocol — what a filesystem (the FAT server) says to a //! block driver (usb-storage) over `/protocol/block`. Defined through the //! envelope, so every packet begins with the folded `Header`. //! //! **`Header.target` is always 0 here**: a block driver instance serves exactly //! one device over its own endpoint, so there is no object within the peer to //! address. A driver that later fronts several volumes gives them target ids and //! `enumerate` lists them; nothing else about the protocol changes. //! //! Data path: read and write move whole blocks to or from a **caller-owned DMA //! buffer**, named by its physical address — the same physical-address handoff //! usb-storage already uses toward the controller, one layer up. So a 512-byte //! sector never has to cross the packet floor; only the small request / reply //! parts do. Under an enforcing IOMMU the buffer's physical addresses are only //! reachable by the device once the filesystem has `attach`ed the buffer's //! capability (the block server forwards it to the controller); see //! docs/driver-model.md. const envelope = @import("envelope"); /// The answer to `geometry()`. pub const Geometry = extern struct { block_size: u32, // bytes per block (512) _padding: u32 = 0, block_count: u64, // total blocks }; /// `read(lba, count, physical)` / `write(...)`: move `count` blocks between the /// device and the caller's DMA buffer at `physical`. pub const Transfer = extern struct { lba: u64, count: u32, _padding: u32 = 0, physical: u64, // caller's DMA buffer physical address }; /// How many blocks a transfer actually moved. pub const Transferred = extern struct { count: u32 }; pub const Protocol = envelope.Define(.{ .name = "block", .version = 1, .operations = &.{ .{ .name = "geometry", .reply = Geometry }, .{ .name = "read", .request = Transfer, .reply = Transferred }, .{ .name = "write", .request = Transfer, .reply = Transferred }, // flush(): commit any device write cache to stable media (no data // transfer). A filesystem calls this to make prior writes durable — // before power-off, so a shutdown-time write isn't lost in the USB flash // controller's cache. .{ .name = "flush" }, // attach(): the caller's DMA-region capability rides the call's cap // slot; the block server forwards it to the controller so the buffer's // physical addresses (named in later read/write) are reachable by the // device under an enforcing IOMMU. Call once per buffer before using it. .{ .name = "attach" }, }, }); pub const Operation = Protocol.Operation; pub const message_maximum: usize = Protocol.message_maximum;