//! x86-64 IOMMU backends, behind the architecture boundary: Intel VT-d
//! (iommu-intel.zig) and AMD-Vi (iommu-amd.zig). The architecture-neutral
//! core (system/kernel/iommu.zig) owns the domain table and the shared
//! page-table walker; it hands this file the firmware discovery facts and an
//! environment (frame allocation + the log sink, injected the same way
//! enablePaging receives its frame hooks), and gets back a hardware vtable.
//! A new architecture supplies its own unit (ARM: the SMMU) from its own
//! directory with no core change.

const intel = @import("iommu-intel.zig");
const amd = @import("iommu-amd.zig");

/// What the platform's firmware tables reported: where the unit's registers
/// live, and which programming model its table implies (an IVRS table
/// describes AMD-Vi; a DMAR table describes Intel VT-d).
pub const Discovery = struct {
    register_base: u64,
    amd: bool,
};

/// What the backends need from the generic kernel, injected at detect so this
/// module never imports kernel internals: physical-frame allocation for the
/// hardware structures, and the kernel log sink (fault reports, warnings, the
/// enable banner).
pub const Environment = struct {
    allocateFrame: *const fn () ?u64,
    allocateContiguous: *const fn (count: usize, max_physical: u64) ?u64,
    write: *const fn (bytes: []const u8) void,
};

/// The bit encodings and hardware operations a backend supplies to the shared
/// core. Entry helpers build the raw page-table entries for the backend's
/// format; the core walks the tree with them. The hardware ops act on a whole
/// domain (identified by its hardware domain id = core index + 1) or device
/// (by requester id / bdf).
pub const Backend = struct {
    /// Number of page-table levels (3 or 4) the backend selected from hardware caps.
    levels: u8,
    /// Largest leaf the walker may emit: 4 KiB always, 2 MiB when the backend allows.
    supports_huge_pages: bool,

    /// Raw entry bits for a leaf mapping `physical` (with the given size), and for a
    /// non-leaf entry pointing at `table_physical` at `level` (level counts down to 1
    /// at the leaf's parent). `isPresent` tests a read-back entry.
    makeLeaf: *const fn (physical: u64, huge: bool) u64,
    makeTable: *const fn (table_physical: u64, level: u8) u64,
    isPresent: *const fn (entry: u64) bool,
    /// Flush a cache line holding IOMMU structures the hardware reads non-coherently
    /// (VT-d with ECAP.C==0). A no-op where the unit snoops caches.
    flushStructure: *const fn (address: usize) void,

    /// Turn translation on (the core has already seeded any pre-claim domains)
    /// and write the unit's identity lines to the log — the core follows with
    /// the neutral posture lines.
    enable: *const fn () void,
    /// Point `bdf`'s translation structure at `domain` (hardware id) and invalidate the
    /// context/device caches so the change takes effect.
    attach: *const fn (bdf: u16, domain: u16, page_table_root: u64) void,
    /// Return `bdf`'s translation structure to not-present + invalidate — all its DMA
    /// faults afterward.
    detach: *const fn (bdf: u16) void,
    /// Invalidate cached translations for `domain` (after a map or unmap).
    invalidateDomain: *const fn (domain: u16) void,
    /// Pull pending faults out of the hardware, log them (rate-limited), return the
    /// count seen this call.
    faultDrain: *const fn () usize,
};

/// The injected kernel services, stored for the backends at detect time.
pub var environment: Environment = undefined;

/// Probe the discovered unit and return its vtable, or null when it is
/// unusable (the core stays fail-open and says so).
pub fn detect(discovery: Discovery, injected: Environment) ?Backend {
    environment = injected;
    return if (discovery.amd) amd.detect(discovery) else intel.detect(discovery);
}
