Initial implementation of the Arm TZ-ASC driver

Adds the `Tzc` struct, which uses `safe_mmio` to manipulate the
registers of the Arm TZ-ASC component. The structure directly exposes
general purposes registers, and other wrappers for failure and region
registers.

Signed-off-by: Ludovic Mermod <ludovic.mermod@arm.com>
Change-Id: Ife2757e62634c634da0d4efabe3171a29245f386
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..551dea9
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,850 @@
+// SPDX-FileCopyrightText: Copyright The arm-tzc Contributors.
+// SPDX-License-Identifier: MIT OR Apache-2.0
+#![no_std]
+#![doc = include_str!("../README.md")]
+#![deny(clippy::undocumented_unsafe_blocks)]
+#![deny(unsafe_op_in_unsafe_fn)]
+
+mod fail;
+mod region;
+mod utils;
+
+use bitflags::bitflags;
+use safe_mmio::{
+    SharedMmioPointer, UniqueMmioPointer, field, field_shared,
+    fields::{ReadOnly, ReadPure, ReadPureWrite, WriteOnly},
+};
+use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
+
+pub use crate::fail::{FailAccessDirection, FailControlRegister, FailIDRegister, TzcFail};
+pub use crate::region::{RegionAttributes, RegionIDAccess, TzcRegion, TzcRegionMut};
+use crate::{
+    fail::FailRegisters,
+    region::RegionRegisters,
+    utils::{extract_bits, get_bit, set_bit},
+};
+
+macro_rules! for_in_slice {
+    ($var:ident in $slice:expr, $($body:stmt)*) => {
+        let mut i = 0;
+        while i < $slice.len() {
+            let $var = &$slice[i];
+            $($body)*
+            i += 1;
+        }
+    };
+}
+pub(crate) use for_in_slice;
+
+#[derive(Debug, Clone, Copy, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)]
+/// Provides information about the configuration of the TZC-400.
+#[repr(transparent)]
+pub struct BuildConfigRegister(u32);
+
+impl BuildConfigRegister {
+    // Bit values for number of filters.
+    const NF_1: u32 = 0b00;
+    const NF_2: u32 = 0b01;
+    const NF_4: u32 = 0b11;
+
+    /// Returns the number of filter units in the design implementation.
+    pub const fn number_of_filters(&self) -> usize {
+        match extract_bits(self.0, 25, 24) {
+            Self::NF_1 => 1,
+            Self::NF_2 => 2,
+            Self::NF_4 => 4,
+            _ => panic!("Use of reserved value for number of filters"),
+        }
+    }
+
+    // Bit values for address width.
+    const AW_32: u32 = 0b011111;
+    const AW_36: u32 = 0b100011;
+    const AW_40: u32 = 0b100111;
+    const AW_48: u32 = 0b101111;
+    const AW_64: u32 = 0b111111;
+
+    /// Returns the width of the ACE-Lite address bus.
+    pub const fn address_width(&self) -> usize {
+        match extract_bits(self.0, 13, 8) {
+            Self::AW_32 => 32,
+            Self::AW_36 => 36,
+            Self::AW_40 => 40,
+            Self::AW_48 => 48,
+            Self::AW_64 => 64,
+            _ => panic!("Use of reserved value for address width"),
+        }
+    }
+
+    // Bit values for number of regions.
+    const NR_9: u32 = 0b01000;
+
+    /// Returns the number of regions that the TZC-400 provides.
+    pub fn number_of_regions(&self) -> usize {
+        match extract_bits(self.0, 4, 0) {
+            Self::NR_9 => 9,
+            _ => panic!("Use of reserved value for number of regions"),
+        }
+    }
+}
+
+#[derive(Debug, Clone, Copy, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)]
+/// Controls the interrupt and bus response signaling behavior of the TZC-400 when region permission
+/// failures occur.
+#[repr(transparent)]
+pub struct ActionRegister(u32);
+
+impl ActionRegister {
+    /// Sets TZCINT LOW and issues an OKAY response.
+    pub const TZCINTLOW_OKAY: ActionRegister = ActionRegister(0b00);
+    /// Sets TZCINT LOW and issues a DECERR response.
+    pub const TZCINTLOW_DECERR: ActionRegister = ActionRegister(0b01);
+    /// Sets TZCINT HIGH and issues an OKAY response.
+    pub const TZCINTHIGH_OKAY: ActionRegister = ActionRegister(0b10);
+    /// Sets TZCINT HIGH and issues a DECERR response.
+    pub const TZCINTHIGH_DECERR: ActionRegister = ActionRegister(0b11);
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+#[repr(u32)]
+/// See [`GateKeeper::status`].
+pub enum GateKeeperStatus {
+    /// Access to the associated filter is not permitted.
+    Closed = 0,
+    /// Access to the associated filter is permitted.
+    Opened = 1,
+}
+
+#[derive(Debug)]
+/// Provides control and status for the gate keeper in each filter unit implemented.
+///
+/// ## Usage constraints Closing the gate can cause accesses on the command channels to stall and can inadvertently cause a bus deadlock. Use this with caution.
+pub struct GateKeeper<'reg> {
+    reg: SharedMmioPointer<'reg, ReadPureWrite<u32>>,
+    index: usize,
+}
+
+impl<'reg> GateKeeper<'reg> {
+    /// The current state of the gate keeper in the filter unit at `index`. `index` must be the
+    /// index of a valid filter in the TZ-ASC configuration.
+    pub fn status(&self) -> GateKeeperStatus {
+        if get_bit(self.reg.read(), self.index + 16) {
+            GateKeeperStatus::Opened
+        } else {
+            GateKeeperStatus::Closed
+        }
+    }
+}
+
+#[derive(Debug)]
+/// Provides control and status for the gate keeper in each filter unit implemented.
+///
+/// ## Usage constraints
+///
+/// Closing the gate can cause accesses on the command channels to stall and can inadvertently cause
+/// a bus deadlock. Use this with caution.
+pub struct GateKeeperMut<'reg> {
+    reg: UniqueMmioPointer<'reg, ReadPureWrite<u32>>,
+    index: usize,
+}
+
+impl<'reg> GateKeeperMut<'reg> {
+    /// See [GateKeeper::status].
+    pub fn status(&self) -> GateKeeperStatus {
+        GateKeeper {
+            reg: *self.reg,
+            index: self.index,
+        }
+        .status()
+    }
+
+    /// Request the gate at `index` to be set to `status`. The request will take effect once all
+    /// outstanding accesses through the filter unit are complete.
+    pub fn request(&mut self, status: GateKeeperStatus) {
+        let mut value = self.reg.read();
+
+        set_bit(
+            &mut value,
+            self.index,
+            matches!(status, GateKeeperStatus::Opened),
+        );
+        self.reg.write(value);
+
+        while self.status() != status {}
+    }
+}
+
+#[derive(Debug, Clone, Copy, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)]
+/// Controls the read access speculation and write access speculation.
+#[repr(transparent)]
+pub struct SpeculationControlRegister(u32);
+
+bitflags! {
+    impl SpeculationControlRegister: u32 {
+        /// Disables read access speculation.
+        ///
+        /// Note: This bit is ignored and assumed to be zero at a filter unit if the corresponding
+        /// QVNENABLE signal is HIGH.
+        const READ_SPEC_DISABLE = 1 << 0;
+
+        /// Disables write access speculation.
+        ///
+        /// Note: This bit is ignored and assumed to be zero at a filter unit if the corresponding
+        /// QVNENABLE signal is HIGH.
+        const WRITE_SPEC_DISABLE = 1 << 1;
+    }
+}
+
+#[derive(Debug, Clone, Copy, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)]
+/// Contains the status of the interrupt signal, *TZCINT*, that reports access security violations
+/// or region overlap errors.
+#[repr(transparent)]
+pub struct InterruptStatusRegister(u32);
+
+impl InterruptStatusRegister {
+    /// When a bit is set to 1 it indicates a violation of the overlap region configuration rules
+    /// for the associated filter unit. This occurs when an access matches with two enabled regions
+    /// at the same time unless the overlap is only with Region 0.
+    ///
+    /// This bit is set even if the [`ActionRegister`] is set to not drive the interrupt.
+    ///
+    /// When this bit is true, the interrupt status bit is also set to true.
+    ///
+    /// Clear the interrupt status of the associated bit in the [`InterruptClearRegister`] to also
+    /// clear this field.
+    pub const fn overlap(&self, index: usize) -> bool {
+        assert!(index < 4);
+        get_bit(self.0, index + 16)
+    }
+
+    /// When a bit is set to `true`, it indicates the occurrence of two or more region permission or
+    /// region overlapping failures at the associated filter unit after the interrupt was cleared by
+    /// the associated bit in the [`InterruptClearRegister`].
+    ///
+    /// This bit is set even if the [`ActionRegister`] is set to not drive the interrupt.
+    ///
+    /// Clear the interrupt status of the associated bit in the [`InterruptClearRegister`] to also
+    /// clear this field.
+    pub const fn overrun(&self, index: usize) -> bool {
+        assert!(index < 4);
+        get_bit(self.0, index + 8)
+    }
+
+    /// This field indicates the status of the interrupt from the corresponding filter unit as
+    /// follows:
+    /// - `false`: Interrupt is not asserted.
+    /// - `true`: Interrupt is asserted and waiting to be cleared.
+    ///
+    /// This bit is set even if the [`ActionRegister`] is set to not drive the interrupt output
+    /// TZCINT HIGH.
+    ///
+    /// Therefore, the status acts as an indicator that a region permission check failure or an
+    /// overlap error has occurred at a particular filter unit.
+    pub const fn status(&self, index: usize) -> bool {
+        assert!(index < 4);
+        get_bit(self.0, index)
+    }
+}
+
+#[derive(
+    Debug, Clone, Copy, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq, Default,
+)]
+/// Clears the interrupt.
+#[repr(transparent)]
+pub struct InterruptClearRegister(u32);
+
+impl InterruptClearRegister {
+    /// Creates an instance of [`InterruptClearRegister`] requesting to clear all the interrupts
+    /// present in `interrupts`.
+    pub const fn new(interrupts: &[usize]) -> Self {
+        let mut s = Self(0);
+
+        for_in_slice!(int in interrupts, s.0 |= 1 << *int);
+
+        s
+    }
+}
+/// View over the TZC memory.
+#[derive(Debug, Clone, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)]
+#[repr(C, align(4))]
+pub struct TzcRegisters {
+    /// 0x000: Build configuration register.
+    build_config: ReadPure<BuildConfigRegister>,
+    /// 0x004: Action register.
+    action: ReadPureWrite<ActionRegister>,
+    /// 0x008: Gate keeper register.
+    gate_keeper: ReadPureWrite<u32>,
+    /// 0x00C: Speculation register.
+    speculation_ctrl: ReadPureWrite<SpeculationControlRegister>,
+    /// 0x010: Interrupt status register.
+    int_status: ReadOnly<InterruptStatusRegister>,
+    /// 0x014: Interrupt clear register.
+    int_clear: WriteOnly<InterruptClearRegister>,
+    /// 0x018-0x020: reserved.
+    reserved_18: [u32; 2],
+
+    /// 0x020-0x060: The fail status registers provide information about an access that fails
+    /// because of insufficient permissions or an overlap condition. A separate set of fail status
+    /// registers is provided for each filter unit.
+    fail_registers: [FailRegisters; 4],
+    /// 0x060-0x100: reserved.
+    reserved_60: [u32; 40],
+
+    /// 0x100-0x218: Registers for region 0 to 8 (included).
+    ///
+    /// The region control registers control the address space that each region controls and the
+    /// security attributes to use for that region.
+    region_registers: [RegionRegisters; 9],
+
+    /// 0x220-0xf00: reserved.
+    ///
+    /// Although the specification states that this reserved range should start at 0x218, the 8
+    /// first reserved bytes are actually at the end of the `RegionRegister` entry for Region 9.
+    reserved_220: [u32; 823],
+    padding_f00: [u32; 53],
+
+    /// 0xFD0: Peripheral ID 4 register.
+    pid4: ReadPure<u32>,
+    /// 0xFD4: Peripheral ID 5 register.
+    pid5: ReadPure<u32>,
+    /// 0xFD8: Peripheral ID 6 register.
+    pid6: ReadPure<u32>,
+    /// 0xFDC: Peripheral ID 7 register.
+    pid7: ReadPure<u32>,
+    /// 0xFE0: Peripheral ID 0 register.
+    pid0: ReadPure<u32>,
+    /// 0xFE4: Peripheral ID 1 register.
+    pid1: ReadPure<u32>,
+    /// 0xFE8: Peripheral ID 2 register.
+    pid2: ReadPure<u32>,
+    /// 0xFEC: Peripheral ID 3 register.
+    pid3: ReadPure<u32>,
+
+    /// 0xFF0: Component ID 0 register.
+    cid0: ReadPure<u32>,
+    /// 0xFF4: Component ID 1 register.
+    cid1: ReadPure<u32>,
+    /// 0xFF8: Component ID 2 register.
+    cid2: ReadPure<u32>,
+    /// 0xFFC: Component ID 3 register.
+    cid3: ReadPure<u32>,
+}
+
+/// Interface to manipulate the [TZC-400 TrustZone Address Space Controller](https://developer.arm.com/documentation/ddi0504/latest).
+pub struct Tzc<'a> {
+    regs: UniqueMmioPointer<'a, TzcRegisters>,
+}
+
+impl<'a> Tzc<'a> {
+    /// Creates new TZC instance from a [`UniqueMmioPointer`] that points to the memory region of
+    /// the Controller.
+    pub const fn new(regs: UniqueMmioPointer<'a, TzcRegisters>) -> Self {
+        Self { regs }
+    }
+
+    /// Returns the [`BuildConfigRegister`] for this TZC-400 instance.
+    pub fn build_configuration(&self) -> BuildConfigRegister {
+        field_shared!(self.regs, build_config).read()
+    }
+
+    /// Returns the current [`ActionRegister`] configuration.
+    pub fn action(&self) -> ActionRegister {
+        field_shared!(self.regs, action).read()
+    }
+    /// Sets the [`ActionRegister`] configuration.
+    pub fn set_action(&mut self, value: ActionRegister) {
+        field!((self.regs), action).write(value);
+    }
+
+    /// Returns a [`GateKeeper`] wrapper to read from the Gate Keeper Register.
+    pub fn gate_keeper<'reg>(&'reg self, index: usize) -> Option<GateKeeper<'reg>> {
+        if index < self.build_configuration().number_of_filters() {
+            Some(GateKeeper {
+                reg: field_shared!(self.regs, gate_keeper),
+                index,
+            })
+        } else {
+            None
+        }
+    }
+
+    /// Returns a [`GateKeeperMut`] wrapper to read from/write to the Gate Keeper Register.
+    pub fn gate_keeper_mut<'reg>(&'reg mut self, index: usize) -> Option<GateKeeperMut<'reg>> {
+        if index < self.build_configuration().number_of_filters() {
+            Some(GateKeeperMut {
+                reg: field!(self.regs, gate_keeper),
+                index,
+            })
+        } else {
+            None
+        }
+    }
+
+    /// Returns the current [`SpeculationControlRegister`] configuration.
+    pub fn speculation_control(&self) -> SpeculationControlRegister {
+        field_shared!(self.regs, speculation_ctrl).read()
+    }
+    /// Sets the [`SpeculationControlRegister`] configuration.
+    pub fn set_speculation_control(&mut self, value: SpeculationControlRegister) {
+        field!(self.regs, speculation_ctrl).write(value);
+    }
+
+    /// Returns the current [`InterruptStatusRegister`].
+    pub fn interrupt_status(&mut self) -> InterruptStatusRegister {
+        field!(self.regs, int_status).read()
+    }
+
+    /// Sets the [`InterruptClearRegister`].
+    pub fn clear_interrupts(&mut self, value: InterruptClearRegister) {
+        field!(self.regs, int_clear).write(value);
+    }
+
+    /// Returns a [`TzcFail`] structure to read from the failure register of the filter at `index`,
+    /// or `None` if `index` is out of bounds.
+    pub fn filter_failure<'regs>(&'regs self, idx: usize) -> Option<TzcFail<'regs>> {
+        if idx < self.build_configuration().number_of_filters() {
+            let fails = field_shared!(self.regs, fail_registers);
+            fails.get(idx).map(|regs| TzcFail { regs })
+        } else {
+            None
+        }
+    }
+
+    /// Returns a [`TzcRegion`] structure to read from the region registers at `index`, or `None` if
+    /// `index` is out of bounds.
+    pub fn region<'regs>(&'regs self, idx: usize) -> Option<TzcRegion<'regs>> {
+        let config = self.build_configuration();
+
+        if idx < config.number_of_regions() {
+            let regions = field_shared!(self.regs, region_registers);
+            regions.get(idx).map(|regs| TzcRegion {
+                regs,
+                address_width_mask: ((1 << config.address_width()) - 1),
+            })
+        } else {
+            None
+        }
+    }
+
+    /// Returns a [`TzcRegionMut`] structure to read and write from the region registers at `index`,
+    /// or `None` if `index` is out of bounds.
+    pub fn region_mut<'regs>(&'regs mut self, idx: usize) -> Option<TzcRegionMut<'regs>> {
+        let config = self.build_configuration();
+
+        if idx < config.number_of_regions() {
+            let regions = field!(self.regs, region_registers);
+            regions.take(idx).map(|regs| TzcRegionMut {
+                regs,
+                addresses_writable: idx != 0,
+                address_width_mask: ((1 << config.address_width()) - 1),
+            })
+        } else {
+            None
+        }
+    }
+
+    /// Returns the peripheral ID, which is `0x4002BB460`.
+    pub fn peripheral_id(&self) -> u64 {
+        let values = [
+            field_shared!(self.regs, pid0).read(),
+            field_shared!(self.regs, pid1).read(),
+            field_shared!(self.regs, pid2).read(),
+            field_shared!(self.regs, pid3).read(),
+            field_shared!(self.regs, pid4).read(),
+            field_shared!(self.regs, pid5).read(),
+            field_shared!(self.regs, pid6).read(),
+            field_shared!(self.regs, pid7).read(),
+        ];
+
+        values
+            .into_iter()
+            .map(|v| v as u64)
+            .enumerate()
+            .fold(0, |acc, (idx, value)| (value << (8 * idx as u64)) | acc)
+    }
+
+    /// Holds a 32 bit component ID value. Used for automatic BIOS configuration. The result is
+    /// `0xB105F00D`.
+    pub fn component_id(&self) -> u64 {
+        let values = [
+            field_shared!(self.regs, cid0).read(),
+            field_shared!(self.regs, cid1).read(),
+            field_shared!(self.regs, cid2).read(),
+            field_shared!(self.regs, cid3).read(),
+        ];
+
+        values
+            .into_iter()
+            .map(|v| v as u64)
+            .enumerate()
+            .fold(0, |acc, (idx, value)| (value << (8 * idx as u64)) | acc)
+    }
+}
+
+#[cfg(test)]
+pub(crate) mod tests {
+    use safe_mmio::UniqueMmioPointer;
+    use zerocopy::transmute_mut;
+
+    use crate::*;
+
+    const DEFAULT_BUILD_CONFIG: u32 = 0b0000_0001_0000_0000_0010_1111_0000_1000;
+
+    pub(crate) struct FakeTZCRegisters {
+        regs: [u32; 1024],
+    }
+
+    impl FakeTZCRegisters {
+        pub fn new() -> Self {
+            let mut s = Self { regs: [0u32; 1024] };
+            s.reg_write(0x000, DEFAULT_BUILD_CONFIG);
+            s
+        }
+
+        pub fn reg_write(&mut self, offset: usize, value: u32) {
+            self.regs[offset / 4] = value;
+        }
+
+        pub fn reg_read(&self, offset: usize) -> u32 {
+            self.regs[offset / 4]
+        }
+
+        fn get(&mut self) -> UniqueMmioPointer<'_, TzcRegisters> {
+            UniqueMmioPointer::from(transmute_mut!(&mut self.regs))
+        }
+
+        pub fn tzc_for_test(&mut self) -> Tzc<'_> {
+            Tzc::new(self.get())
+        }
+    }
+
+    #[test]
+    fn size() {
+        assert_eq!(size_of::<TzcRegisters>(), 4096);
+    }
+
+    #[test]
+    fn build_config() {
+        let mut regs = FakeTZCRegisters::new();
+
+        let config = regs.tzc_for_test().build_configuration();
+        assert_eq!(config.number_of_filters(), 2);
+        assert_eq!(config.address_width(), 48);
+        assert_eq!(config.number_of_regions(), 9);
+
+        regs.reg_write(0x000, 0b0000_0000_0000_0000_0011_1111_0000_1000);
+        let config = regs.tzc_for_test().build_configuration();
+        assert_eq!(config.number_of_filters(), 1);
+        assert_eq!(config.address_width(), 64);
+        assert_eq!(config.number_of_regions(), 9);
+
+        regs.reg_write(0x000, 0b0000_0011_0000_0000_0010_0111_0000_1000);
+        let config = regs.tzc_for_test().build_configuration();
+        assert_eq!(config.number_of_filters(), 4);
+        assert_eq!(config.address_width(), 40);
+        assert_eq!(config.number_of_regions(), 9);
+
+        regs.reg_write(0x000, 0b0000_0011_0000_0000_0010_0011_0000_1000);
+        let config = regs.tzc_for_test().build_configuration();
+        assert_eq!(config.number_of_filters(), 4);
+        assert_eq!(config.address_width(), 36);
+        assert_eq!(config.number_of_regions(), 9);
+
+        regs.reg_write(0x000, 0b0000_0011_0000_0000_0001_1111_0000_1000);
+        let config = regs.tzc_for_test().build_configuration();
+        assert_eq!(config.number_of_filters(), 4);
+        assert_eq!(config.address_width(), 32);
+        assert_eq!(config.number_of_regions(), 9);
+    }
+
+    #[test]
+    #[should_panic]
+    fn build_config_reserved_filters() {
+        let mut regs = FakeTZCRegisters::new();
+        regs.reg_write(0x000, 0b0000_0010_0000_0000_0010_1111_0000_1000);
+        let config = regs.tzc_for_test().build_configuration();
+
+        config.number_of_filters();
+    }
+
+    #[test]
+    #[should_panic]
+    fn build_config_reserved_address_width() {
+        fn run(address_width: u32, regs: &mut FakeTZCRegisters) {
+            regs.reg_write(
+                0x000,
+                0b0000_0001_0000_0000_0000_0000_0000_1000 | address_width << 8,
+            );
+            regs.tzc_for_test().build_configuration().address_width();
+        }
+
+        let mut regs = FakeTZCRegisters::new();
+
+        run(0b11_0011, &mut regs);
+        run(0b11_1101, &mut regs);
+        run(0b00_0000, &mut regs);
+    }
+
+    #[test]
+    #[should_panic]
+    fn build_config_reserved_regions() {
+        fn run(region_count: u32, regs: &mut FakeTZCRegisters) {
+            regs.reg_write(
+                0x000,
+                0b0000_0001_0000_0000_0011_1111_0000_0000 | region_count,
+            );
+            regs.tzc_for_test()
+                .build_configuration()
+                .number_of_regions();
+        }
+
+        let mut regs = FakeTZCRegisters::new();
+
+        run(0b1_1111, &mut regs);
+        run(0b0_0000, &mut regs);
+        run(0b0_1100, &mut regs);
+    }
+
+    #[test]
+    fn get_action() {
+        let mut regs = FakeTZCRegisters::new();
+
+        regs.reg_write(0x004, 0b0000_0000_0000_0000_0000_0000_0000_0000);
+        assert_eq!(regs.tzc_for_test().action(), ActionRegister::TZCINTLOW_OKAY);
+
+        regs.reg_write(0x004, 0b0000_0000_0000_0000_0000_0000_0000_0001);
+        assert_eq!(
+            regs.tzc_for_test().action(),
+            ActionRegister::TZCINTLOW_DECERR
+        );
+
+        regs.reg_write(0x004, 0b0000_0000_0000_0000_0000_0000_0000_0010);
+        assert_eq!(
+            regs.tzc_for_test().action(),
+            ActionRegister::TZCINTHIGH_OKAY
+        );
+
+        regs.reg_write(0x004, 0b0000_0000_0000_0000_0000_0000_0000_0011);
+        assert_eq!(
+            regs.tzc_for_test().action(),
+            ActionRegister::TZCINTHIGH_DECERR
+        );
+    }
+
+    #[test]
+    fn set_action() {
+        let mut regs = FakeTZCRegisters::new();
+        let mut tzc = regs.tzc_for_test();
+
+        tzc.set_action(ActionRegister::TZCINTLOW_OKAY);
+        assert_eq!(tzc.action(), ActionRegister::TZCINTLOW_OKAY);
+
+        tzc.set_action(ActionRegister::TZCINTLOW_DECERR);
+        assert_eq!(tzc.action(), ActionRegister::TZCINTLOW_DECERR);
+
+        tzc.set_action(ActionRegister::TZCINTHIGH_OKAY);
+        assert_eq!(tzc.action(), ActionRegister::TZCINTHIGH_OKAY);
+
+        tzc.set_action(ActionRegister::TZCINTHIGH_DECERR);
+        assert_eq!(tzc.action(), ActionRegister::TZCINTHIGH_DECERR);
+    }
+
+    #[test]
+    fn gate_keeper_oob() {
+        let mut regs = FakeTZCRegisters::new();
+        regs.reg_write(0x008, 0b0000_0000_0000_0010_0000_0000_0000_0000);
+        let mut tzc = regs.tzc_for_test();
+
+        assert!(tzc.gate_keeper(2).is_none());
+        assert!(tzc.gate_keeper(3).is_none());
+        assert!(tzc.gate_keeper(4).is_none());
+        assert!(tzc.gate_keeper(18).is_none());
+
+        assert!(tzc.gate_keeper_mut(2).is_none());
+        assert!(tzc.gate_keeper_mut(3).is_none());
+        assert!(tzc.gate_keeper_mut(4).is_none());
+        assert!(tzc.gate_keeper_mut(18).is_none());
+    }
+
+    #[test]
+    fn gate_keeper_status() {
+        let mut regs = FakeTZCRegisters::new();
+        regs.reg_write(0x008, 0b0000_0000_0000_0010_0000_0000_0000_0000);
+        let tzc = regs.tzc_for_test();
+
+        assert_eq!(
+            tzc.gate_keeper(0).unwrap().status(),
+            GateKeeperStatus::Closed
+        );
+        assert_eq!(
+            tzc.gate_keeper(1).unwrap().status(),
+            GateKeeperStatus::Opened
+        );
+    }
+
+    #[test]
+    fn gate_keeper_request() {
+        let mut regs = FakeTZCRegisters::new();
+        regs.reg_write(0x008, 0b0000_0000_0000_0010_0000_0000_0000_0001);
+        let mut tzc = regs.tzc_for_test();
+
+        tzc.gate_keeper_mut(0)
+            .unwrap()
+            .request(GateKeeperStatus::Closed);
+        tzc.gate_keeper_mut(1)
+            .unwrap()
+            .request(GateKeeperStatus::Opened);
+
+        assert_eq!(
+            regs.reg_read(0x008),
+            0b0000_0000_0000_0010_0000_0000_0000_0010,
+        )
+    }
+
+    #[test]
+    fn get_speculation_control() {
+        fn run(flags: u32, regs: &mut FakeTZCRegisters, value: SpeculationControlRegister) {
+            regs.reg_write(0x00C, flags);
+            let spec = regs.tzc_for_test().speculation_control();
+            assert_eq!(spec, value);
+        }
+
+        let mut regs = FakeTZCRegisters::new();
+
+        run(0b00, &mut regs, SpeculationControlRegister::empty());
+        run(
+            0b01,
+            &mut regs,
+            SpeculationControlRegister::READ_SPEC_DISABLE,
+        );
+        run(
+            0b10,
+            &mut regs,
+            SpeculationControlRegister::WRITE_SPEC_DISABLE,
+        );
+        run(
+            0b11,
+            &mut regs,
+            SpeculationControlRegister::READ_SPEC_DISABLE
+                | SpeculationControlRegister::WRITE_SPEC_DISABLE,
+        );
+    }
+
+    #[test]
+    fn set_speculation_control() {
+        fn run(flags: u32, regs: &mut FakeTZCRegisters, value: SpeculationControlRegister) {
+            regs.tzc_for_test().set_speculation_control(value);
+            assert_eq!(regs.reg_read(0x00C), flags);
+        }
+
+        let mut regs = FakeTZCRegisters::new();
+
+        run(0b00, &mut regs, SpeculationControlRegister::empty());
+        run(
+            0b01,
+            &mut regs,
+            SpeculationControlRegister::READ_SPEC_DISABLE,
+        );
+        run(
+            0b10,
+            &mut regs,
+            SpeculationControlRegister::WRITE_SPEC_DISABLE,
+        );
+        run(
+            0b11,
+            &mut regs,
+            SpeculationControlRegister::READ_SPEC_DISABLE
+                | SpeculationControlRegister::WRITE_SPEC_DISABLE,
+        );
+    }
+
+    #[test]
+    pub fn interrupt_status() {
+        let mut regs = FakeTZCRegisters::new();
+        regs.reg_write(0x010, 0b0000_0000_0000_0010_0000_0001_0000_0010);
+        let mut tzc = regs.tzc_for_test();
+
+        let status = tzc.interrupt_status();
+
+        assert!(!status.overlap(0));
+        assert!(status.overlap(1));
+
+        assert!(status.overrun(0));
+        assert!(!status.overrun(1));
+
+        assert!(!status.status(0));
+        assert!(status.status(1));
+    }
+
+    #[test]
+    pub fn interrupt_clear() {
+        let mut regs = FakeTZCRegisters::new();
+        let mut tzc = regs.tzc_for_test();
+
+        let clear = InterruptClearRegister::new(&[0, 3]);
+        tzc.clear_interrupts(clear);
+
+        assert_eq!(
+            regs.reg_read(0x014),
+            0b0000_0000_0000_0000_0000_0000_0000_1001
+        );
+    }
+
+    #[test]
+    fn peripheral_id() {
+        let mut regs = FakeTZCRegisters::new();
+        regs.reg_write(0xFD0, 0x04);
+        regs.reg_write(0xFD4, 0x00);
+        regs.reg_write(0xFD8, 0x00);
+        regs.reg_write(0xFDC, 0x00);
+        regs.reg_write(0xFE0, 0x60);
+        regs.reg_write(0xFE4, 0xB4);
+        regs.reg_write(0xFE8, 0x2B);
+        regs.reg_write(0xFEC, 0x00);
+
+        let tzc = regs.tzc_for_test();
+        assert_eq!(tzc.peripheral_id(), 0x0000_0004_002B_B460);
+    }
+
+    #[test]
+    fn component_id() {
+        let mut regs = FakeTZCRegisters::new();
+        regs.reg_write(0xFF0, 0x0D);
+        regs.reg_write(0xFF4, 0xF0);
+        regs.reg_write(0xFF8, 0x05);
+        regs.reg_write(0xFFC, 0xB1);
+
+        let tzc = regs.tzc_for_test();
+        assert_eq!(tzc.component_id(), 0xB105F00D);
+    }
+
+    #[test]
+    fn fail_oob() {
+        let mut regs = FakeTZCRegisters::new();
+        let tzc = regs.tzc_for_test();
+
+        assert!(tzc.filter_failure(4).is_none());
+        assert!(tzc.filter_failure(8).is_none());
+        assert!(tzc.filter_failure(123124).is_none());
+    }
+
+    #[test]
+    fn region_oob() {
+        let mut regs = FakeTZCRegisters::new();
+        let tzc = regs.tzc_for_test();
+
+        assert!(tzc.region(9).is_none());
+        assert!(tzc.region(32).is_none());
+        assert!(tzc.region(123124).is_none());
+    }
+
+    #[test]
+    fn region_mut_oob() {
+        let mut regs = FakeTZCRegisters::new();
+        let mut tzc = regs.tzc_for_test();
+
+        assert!(tzc.region_mut(9).is_none());
+        assert!(tzc.region_mut(32).is_none());
+        assert!(tzc.region_mut(123124).is_none());
+    }
+}