//! The virtio-gpu control protocol — the command/response structs the driver exchanges with
//! the device over its control virtqueue (virtio spec, "GPU Device"). `extern` structs, so
//! the layout matches the little-endian wire format exactly. Host-tested for size. See
//! docs/display-v2.md.

const std = @import("std");

/// Control command / response types (virtio_gpu_ctrl_type). Commands are 0x01xx, responses
/// 0x11xx (ok) / 0x12xx (error).
pub const CmdType = enum(u32) {
    get_display_info = 0x0100,
    resource_create_2d = 0x0101,
    resource_unref = 0x0102,
    set_scanout = 0x0103,
    resource_flush = 0x0104,
    transfer_to_host_2d = 0x0105,
    resource_attach_backing = 0x0106,
    resource_detach_backing = 0x0107,
    get_edid = 0x010a,

    resp_ok_nodata = 0x1100,
    resp_ok_display_info = 0x1101,
    resp_ok_edid = 0x1104,
    resp_err_unspec = 0x1200,
    _,
};

/// Set in a command's `flags` to request a fence; the device echoes `fence_id` in the
/// response and does not report completion until the command's effects are visible.
pub const flag_fence: u32 = 1 << 0;

/// VIRTIO_GPU_F_EDID — device feature bit 1 (the low feature word): the device answers the
/// `get_edid` command. Negotiate it only when the device offers it.
pub const feature_edid: u32 = 1 << 1;

/// virtio_gpu_ctrl_hdr — the header on every command and response.
pub const CtrlHdr = extern struct {
    type: u32,
    flags: u32 = 0,
    fence_id: u64 = 0,
    ctx_id: u32 = 0,
    ring_idx: u8 = 0,
    padding: [3]u8 = .{ 0, 0, 0 },
};

pub const Rect = extern struct {
    x: u32,
    y: u32,
    width: u32,
    height: u32,
};

/// 2D pixel formats. QEMU's virtio-gpu host default is B8G8R8X8 (matches our bgrx).
pub const format_b8g8r8x8_unorm: u32 = 2;
pub const format_r8g8b8x8_unorm: u32 = 134;

pub const ResourceCreate2d = extern struct {
    hdr: CtrlHdr,
    resource_id: u32,
    format: u32,
    width: u32,
    height: u32,
};

/// One scatter-gather entry of a resource's guest backing (a physical span).
pub const MemEntry = extern struct {
    addr: u64,
    length: u32,
    padding: u32 = 0,
};

/// Header for RESOURCE_ATTACH_BACKING; `nr_entries` `MemEntry` follow it inline.
pub const ResourceAttachBacking = extern struct {
    hdr: CtrlHdr,
    resource_id: u32,
    nr_entries: u32,
};

pub const SetScanout = extern struct {
    hdr: CtrlHdr,
    rect: Rect,
    scanout_id: u32,
    resource_id: u32,
};

pub const ResourceFlush = extern struct {
    hdr: CtrlHdr,
    rect: Rect,
    resource_id: u32,
    padding: u32 = 0,
};

/// Copy the guest backing into the host resource for `rect` (2D resources must transfer
/// before a flush shows the update).
pub const TransferToHost2d = extern struct {
    hdr: CtrlHdr,
    rect: Rect,
    offset: u64,
    resource_id: u32,
    padding: u32 = 0,
};

pub const max_scanouts = 16;

pub const DisplayOne = extern struct {
    rect: Rect,
    enabled: u32,
    flags: u32,
};

pub const RespDisplayInfo = extern struct {
    hdr: CtrlHdr,
    pmodes: [max_scanouts]DisplayOne,
};

pub const GetEdid = extern struct {
    hdr: CtrlHdr,
    scanout: u32,
    padding: u32 = 0,
};

pub const RespEdid = extern struct {
    hdr: CtrlHdr,
    size: u32,
    padding: u32 = 0,
    edid: [1024]u8,
};

test "virtio-gpu struct sizes match the wire layout" {
    try std.testing.expectEqual(@as(usize, 24), @sizeOf(CtrlHdr));
    try std.testing.expectEqual(@as(usize, 16), @sizeOf(Rect));
    try std.testing.expectEqual(@as(usize, 40), @sizeOf(ResourceCreate2d));
    try std.testing.expectEqual(@as(usize, 16), @sizeOf(MemEntry));
    try std.testing.expectEqual(@as(usize, 32), @sizeOf(ResourceAttachBacking));
    try std.testing.expectEqual(@as(usize, 48), @sizeOf(SetScanout));
    try std.testing.expectEqual(@as(usize, 48), @sizeOf(ResourceFlush));
    try std.testing.expectEqual(@as(usize, 56), @sizeOf(TransferToHost2d));
    try std.testing.expectEqual(@as(usize, 24 + 4 + 4 + 1024), @sizeOf(RespEdid));
}
