//! User-space DMA memory: `dma_alloc` / `dma_free`. A driver that programs a
//! bus-mastering engine needs a descriptor ring the device can read — memory that is
//! physically contiguous, at a physical address the driver knows, uncacheable, and
//! pinned. `mmap` gives none of those; this does. Pair it with the barriers in
//! `/lib/mmio` (fill the ring, `wmb()`, ring the doorbell). See docs/driver-model.md.

const abi = @import("abi");
const sc = @import("system-call");

/// Allocation flags. `coherent` (uncacheable) is the portable default; the rest are
/// opt-in for specific hardware — see `abi`.
pub const coherent: usize = abi.dma_coherent;
pub const write_combining: usize = abi.dma_write_combining;
pub const below_4g: usize = abi.dma_below_4g;
/// Ask for a capability handle (in `Region.handle`) so the buffer can be delegated to
/// another driver and bound into a device's IOMMU domain (`driver.dmaBind`). A driver's
/// private rings don't need it; a buffer whose physical address crosses IPC does.
pub const shareable: usize = abi.dma_shareable;

/// A DMA allocation: the `virtual` address the CPU touches, the `physical` address to
/// program into the device's registers, and — when `shareable` was requested — a
/// capability `handle` naming the region for delegation (null otherwise).
pub const Region = struct {
    virtual: usize,
    physical: usize,
    handle: ?usize = null,
};

inline fn failed(r: usize) bool {
    return r > ~@as(usize, 0) - 4095;
}

/// Allocate `len` bytes of DMA-capable memory with `flags` (e.g. `coherent`, or
/// `coherent | shareable`). Returns virtual/physical (and a handle when `shareable`), or
/// null on failure. Three return values — virtual in rax, physical in rdx, handle in r8
/// — so it needs a hand-written stub.
pub fn alloc(len: usize, flags: usize) ?Region {
    var rax: usize = undefined;
    var rdx: usize = undefined; // out: physical address
    var r8: usize = undefined; // out: capability handle (abi.no_cap unless shareable)
    asm volatile ("syscall"
        : [rax] "={rax}" (rax),
          [rdx] "={rdx}" (rdx),
          [r8] "={r8}" (r8),
        : [n] "{rax}" (@intFromEnum(abi.SystemCall.dma_alloc)),
          [a0] "{rdi}" (len),
          [a1] "{rsi}" (flags),
        : .{ .rcx = true, .r11 = true, .memory = true });
    if (failed(rax)) return null;
    return .{ .virtual = rax, .physical = rdx, .handle = if (r8 == abi.no_cap) null else r8 };
}

/// Release a region from a prior `alloc` (`virtual` and the same `len`).
pub fn free(virtual: usize, len: usize) void {
    _ = sc.systemCall2(.dma_free, virtual, len);
}
