//! 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;
