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());
+ }
+}