rsemu 0.0.1

A multiplatform emulator in pure Rust, built bottom-up on a generic framework.
Documentation
//! Access attributes, region-level access constraints, and the [`MemOps`]
//! trait every I/O region is driven through (`ROADMAP.md` §4.1).

use crate::core::error::BusError;
use crate::core::value::{Endian, Width};
use core::fmt;

/// The result of a guest access.
///
/// Three outcomes, never two: `Ok`, a bus fault ([`BusError::Unassigned`] or
/// [`BusError::BadAccess`]), and [`BusError::Retry`]. `Retry` is only legal
/// *before* any side effect or partial transfer; the dispatcher downgrades a
/// late one to [`BusError::BadAccess`] rather than re-running a half-completed
/// access.
pub type MemResult<T = ()> = core::result::Result<T, BusError>;

/// Identifies the bus master behind an access.
///
/// Opaque to the core: a PCI requester ID, an AXI master ID, or a CPU index —
/// whatever the machine assigns. It exists so an IOMMU or a per-master filter
/// has something to key on without the core knowing what a PCI device is
/// (`ROADMAP.md` §15, invariant 1).
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct RequesterId(pub u32);

impl RequesterId {
    /// The master that did not identify itself — the default for a CPU access
    /// in a machine with no IOMMU.
    pub const ANONYMOUS: RequesterId = RequesterId(0);
}

impl fmt::Display for RequesterId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "requester#{}", self.0)
    }
}

/// Everything about an access that is not its address, width, or direction.
///
/// Carried on every access because retrofitting it is a rewrite of every
/// device signature. Constructed from [`MemAttrs::DEFAULT`] (or
/// [`MemAttrs::DEBUG`]) plus `with_*` builders, so adding an attribute later
/// does not break callers.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub struct MemAttrs {
    /// Which bus master is asking.
    pub requester: RequesterId,
    /// The access is made in a secure world (ARM TrustZone, and anything
    /// shaped like it).
    pub secure: bool,
    /// The access is made from a privileged mode rather than user code.
    pub privileged: bool,
    /// Part of an exclusive/atomic sequence (`LDREX`/`STREX`, LR/SC).
    ///
    /// The core carries the flag; the monitor that implements the reservation
    /// lives with the CPU, not here.
    pub exclusive: bool,
    /// The access comes from a debugger, a monitor, or a snapshot, and **must
    /// have no side effects** — no FIFO pop, no status-bit clear, no pointer
    /// advance (`ROADMAP.md` §15, invariant 5).
    ///
    /// The core honours this by never charging an access to the unassigned
    /// log; every MMIO device is required to honour it in its own handler.
    pub debug: bool,
}

impl MemAttrs {
    /// An ordinary non-secure, unprivileged, non-debug access.
    pub const DEFAULT: MemAttrs = MemAttrs {
        requester: RequesterId::ANONYMOUS,
        secure: false,
        privileged: false,
        exclusive: false,
        debug: false,
    };

    /// A side-effect-free access, as issued by a debugger or a snapshot.
    ///
    /// Also privileged and secure: a monitor looks at the whole machine, and a
    /// debugger that could not read secure memory would be useless.
    pub const DEBUG: MemAttrs = MemAttrs {
        requester: RequesterId::ANONYMOUS,
        secure: true,
        privileged: true,
        exclusive: false,
        debug: true,
    };

    /// Same attributes, from `id`.
    #[must_use]
    pub const fn with_requester(mut self, id: RequesterId) -> Self {
        self.requester = id;
        self
    }

    /// Same attributes, with the secure flag set to `secure`.
    #[must_use]
    pub const fn with_secure(mut self, secure: bool) -> Self {
        self.secure = secure;
        self
    }

    /// Same attributes, with the privileged flag set to `privileged`.
    #[must_use]
    pub const fn with_privileged(mut self, privileged: bool) -> Self {
        self.privileged = privileged;
        self
    }

    /// Same attributes, with the exclusive flag set to `exclusive`.
    #[must_use]
    pub const fn with_exclusive(mut self, exclusive: bool) -> Self {
        self.exclusive = exclusive;
        self
    }

    /// Same attributes, with the debug flag set to `debug`.
    #[must_use]
    pub const fn with_debug(mut self, debug: bool) -> Self {
        self.debug = debug;
        self
    }
}

/// What a region will accept: widths, alignment, byte order, and the two
/// attribute filters cheap enough to check here.
///
/// This is a **fast reject**, not the whole guarantee (`ROADMAP.md` §4.1). A
/// register block with per-register rules still enforces its own inside
/// [`MemOps`]; what this catches is the byte write to a 32-bit-only aperture,
/// before a virtual call is made.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct AccessConstraints {
    /// Narrowest single access accepted.
    pub min: Width,
    /// Widest single access accepted.
    pub max: Width,
    /// Require natural alignment of the *region-relative* offset.
    ///
    /// Region-relative rather than absolute: a device with 32-bit registers
    /// cares where the access lands in its register file, which is not the
    /// same question as where the aperture happens to sit in the space.
    pub natural_alignment: bool,
    /// Byte order of this region. A big-endian device on a little-endian bus
    /// is normal, not exotic.
    pub endian: Endian,
    /// Whether a transfer that is not a single legal width (a DMA burst, a ROM
    /// load, a debugger dump) may be handed to the region in one call.
    ///
    /// True for memory, false by default for I/O: a device that wants bursts
    /// says so, rather than being handed a 4096-byte "register write".
    pub allow_bulk: bool,
    /// Reject non-secure accesses.
    pub secure_only: bool,
    /// Reject unprivileged accesses.
    pub privileged_only: bool,
}

impl AccessConstraints {
    /// Anything goes: any width, any alignment, little-endian, bursts allowed.
    pub const ANY: AccessConstraints = AccessConstraints {
        min: Width::U8,
        max: Width::U64,
        natural_alignment: false,
        endian: Endian::Little,
        allow_bulk: true,
        secure_only: false,
        privileged_only: false,
    };

    /// The default for an I/O region: any width, but no bulk bursts.
    pub const IO: AccessConstraints = AccessConstraints {
        allow_bulk: false,
        ..AccessConstraints::ANY
    };

    /// Exactly one width, naturally aligned, in `endian` byte order — the
    /// common shape of a hardware register block.
    #[must_use]
    pub const fn word(width: Width, endian: Endian) -> Self {
        AccessConstraints {
            min: width,
            max: width,
            natural_alignment: true,
            endian,
            allow_bulk: false,
            secure_only: false,
            privileged_only: false,
        }
    }

    /// Same constraints, in `endian` byte order.
    #[must_use]
    pub const fn with_endian(mut self, endian: Endian) -> Self {
        self.endian = endian;
        self
    }

    /// Same constraints, accepting widths from `min` to `max` inclusive.
    #[must_use]
    pub const fn with_widths(mut self, min: Width, max: Width) -> Self {
        self.min = min;
        self.max = max;
        self
    }

    /// Same constraints, requiring (or not) natural alignment.
    #[must_use]
    pub const fn with_natural_alignment(mut self, require: bool) -> Self {
        self.natural_alignment = require;
        self
    }

    /// Same constraints, allowing (or not) bulk transfers.
    #[must_use]
    pub const fn with_bulk(mut self, allow: bool) -> Self {
        self.allow_bulk = allow;
        self
    }

    /// Same constraints, rejecting non-secure accesses.
    #[must_use]
    pub const fn with_secure_only(mut self, secure_only: bool) -> Self {
        self.secure_only = secure_only;
        self
    }

    /// Same constraints, rejecting unprivileged accesses.
    #[must_use]
    pub const fn with_privileged_only(mut self, privileged_only: bool) -> Self {
        self.privileged_only = privileged_only;
        self
    }

    /// Check a single width-typed access at region-relative `offset`.
    ///
    /// Returns [`BusError::BadAccess`] rather than silently widening or
    /// splitting: a 32-bit-only register must *reject* a byte write.
    #[inline]
    pub fn check(&self, offset: u64, width: Width, attrs: MemAttrs) -> MemResult {
        self.check_attrs(attrs)?;
        if width < self.min || width > self.max {
            return Err(BusError::BadAccess);
        }
        if self.natural_alignment && !width.is_aligned(offset) {
            return Err(BusError::BadAccess);
        }
        Ok(())
    }

    /// Check a transfer of `len` bytes at region-relative `offset`.
    ///
    /// A transfer whose length happens to be a legal access width is checked
    /// as one; anything else needs [`AccessConstraints::allow_bulk`].
    #[inline]
    pub fn check_bulk(&self, offset: u64, len: u64, attrs: MemAttrs) -> MemResult {
        if let Some(width) = Width::from_bytes(len)
            && self.check(offset, width, attrs).is_ok()
        {
            return Ok(());
        }
        self.check_attrs(attrs)?;
        if self.allow_bulk {
            Ok(())
        } else {
            Err(BusError::BadAccess)
        }
    }

    #[inline]
    fn check_attrs(&self, attrs: MemAttrs) -> MemResult {
        if (self.secure_only && !attrs.secure) || (self.privileged_only && !attrs.privileged) {
            return Err(BusError::BadAccess);
        }
        Ok(())
    }
}

impl Default for AccessConstraints {
    fn default() -> Self {
        AccessConstraints::ANY
    }
}

/// The behaviour of an I/O region: a call, every time.
///
/// Offsets are region-relative and in bytes, and buffers are byte slices in
/// ascending address order — the region's own [`AccessConstraints::endian`]
/// decides how a guest word maps onto them, and the dispatcher has already
/// done that conversion.
///
/// Implementors are `Send + Sync` from the first commit (`ROADMAP.md` §0) and
/// `Debug` so that a machine's memory map can be printed without every
/// container needing a hand-written formatter.
///
/// **Every implementation must honour [`MemAttrs::debug`]**: a debug read may
/// not pop a FIFO, clear a status bit, or advance a pointer
/// (`ROADMAP.md` §15, invariant 5).
pub trait MemOps: fmt::Debug + Send + Sync {
    /// Read `dst.len()` bytes from region-relative `offset`.
    fn read(&self, offset: u64, dst: &mut [u8], attrs: MemAttrs) -> MemResult;

    /// Write `src` to region-relative `offset`.
    fn write(&self, offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult;

    /// What this region accepts. Defaults to [`AccessConstraints::IO`].
    ///
    /// Used as the region's constraints when one is built with
    /// [`Region::io`](super::Region::io), so a device declares its rules once.
    fn constraints(&self) -> AccessConstraints {
        AccessConstraints::IO
    }
}