//! virtio 1.0 PCI transport — the vendor capabilities in PCI config space that point at the
//! device's structures (common config, notify, ISR) in a BAR, the common-config register
//! block, and the split-virtqueue layout. `extern` structs matching the spec. Host-tested
//! for size. See docs/display-v2.md.

const std = @import("std");

/// PCI vendor-specific capability id (0x09) — virtio 1.0 structures are advertised as these.
pub const pci_cap_vendor: u8 = 0x09;

/// virtio_pci_cap `cfg_type`: which structure a vendor capability points at.
pub const cfg_common: u8 = 1;
pub const cfg_notify: u8 = 2;
pub const cfg_isr: u8 = 3;
pub const cfg_device: u8 = 4;
pub const cfg_pci: u8 = 5;

/// virtio_pci_cap — a vendor capability naming a structure at (bar, offset, length) within
/// a PCI BAR. Read straight out of config space.
pub const PciCap = extern struct {
    cap_vndr: u8, // 0x09
    cap_next: u8, // next capability's offset in config space (0 = end)
    cap_len: u8,
    cfg_type: u8, // cfg_common / cfg_notify / ...
    bar: u8, // which BAR the structure lives in
    padding: [3]u8,
    offset: u32, // offset within the BAR
    length: u32, // length of the structure
};

/// virtio_pci_notify_cap: a notify capability carries a multiplier after the base cap; the
/// per-queue notify address is `notify_base + queue_notify_off * notify_off_multiplier`.
pub const NotifyCap = extern struct {
    cap: PciCap,
    notify_off_multiplier: u32,
};

/// virtio_pci_common_cfg — the common configuration register block (little-endian MMIO).
pub const CommonCfg = extern struct {
    device_feature_select: u32,
    device_feature: u32,
    driver_feature_select: u32,
    driver_feature: u32,
    msix_config: u16,
    num_queues: u16,
    device_status: u8,
    config_generation: u8,
    queue_select: u16,
    queue_size: u16,
    queue_msix_vector: u16,
    queue_enable: u16,
    queue_notify_off: u16,
    queue_desc: u64,
    queue_driver: u64,
    queue_device: u64,
};

/// device_status bits (written to `CommonCfg.device_status` during bring-up).
pub const status_acknowledge: u8 = 1;
pub const status_driver: u8 = 2;
pub const status_driver_ok: u8 = 4;
pub const status_features_ok: u8 = 8;

/// VIRTIO_F_VERSION_1 — feature bit 32 (in the second 32-bit feature word). Required for a
/// modern device; we negotiate exactly this bit and nothing else.
pub const feature_version_1_word: u32 = 1; // device_feature_select value for bits 32..63
pub const feature_version_1_bit: u32 = 1 << 0; // bit 32 within that word

// --- split virtqueue -------------------------------------------------------

pub const Desc = extern struct {
    addr: u64, // guest-physical
    len: u32,
    flags: u16,
    next: u16,
};
pub const desc_flag_next: u16 = 1; // buffer continues in `next`
pub const desc_flag_write: u16 = 2; // device-writable (else driver-writable/device-readable)

/// The available ring's fixed header; a `[queue_size]u16` ring and a trailing `used_event`
/// u16 follow it in memory (laid out by the driver).
pub const AvailHdr = extern struct {
    flags: u16,
    idx: u16,
};

/// One entry of the used ring.
pub const UsedElem = extern struct {
    id: u32,
    len: u32,
};

/// The used ring's fixed header; a `[queue_size]UsedElem` ring and a trailing `avail_event`
/// u16 follow it.
pub const UsedHdr = extern struct {
    flags: u16,
    idx: u16,
};

test "virtio-pci struct sizes match the spec" {
    try std.testing.expectEqual(@as(usize, 16), @sizeOf(PciCap));
    try std.testing.expectEqual(@as(usize, 56), @sizeOf(CommonCfg));
    try std.testing.expectEqual(@as(usize, 16), @sizeOf(Desc));
    try std.testing.expectEqual(@as(usize, 8), @sizeOf(UsedElem));
}
