Add FVP system peripheral driver

The system peripheral on the FVP manages several system-level features,
including:
* System and processor identification
* LED and switch control
* Handling of volatile and non-volatile flags
* 100Hz and 24MHz counters
* System operations such as shutdown and reboot
* Various other miscellaneous functions

Signed-off-by: Imre Kis <imre.kis@arm.com>
Change-Id: I6f18ec55d9baf6b217d95ee01b96cd31ecfb0bbd
diff --git a/src/lib.rs b/src/lib.rs
index 6c9551d..b02f407 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -7,6 +7,8 @@
 
 #![no_std]
 
+pub mod system;
+
 // Re-export peripheral drivers and common safe-mmio types
 pub use arm_gic;
 pub use arm_pl011_uart;
@@ -20,6 +22,7 @@
 use core::{fmt::Debug, ops::RangeInclusive};
 use power_controller::FvpPowerControllerRegisters;
 use spin::mutex::Mutex;
+use system::FvpSystemRegisters;
 
 static PERIPHERALS_TAKEN: Mutex<bool> = Mutex::new(false);
 
@@ -105,6 +108,7 @@
 /// FVP peripherals
 #[derive(Debug)]
 pub struct Peripherals {
+    pub system: PhysicalInstance<FvpSystemRegisters>,
     pub uart0: PhysicalInstance<PL011Registers>,
     pub uart1: PhysicalInstance<PL011Registers>,
     pub uart2: PhysicalInstance<PL011Registers>,
@@ -134,6 +138,7 @@
         *PERIPHERALS_TAKEN.lock() = true;
 
         Peripherals {
+            system: PhysicalInstance::new(*MemoryMap::VE_SYSTEM.start()),
             uart0: PhysicalInstance::new(*MemoryMap::UART0.start()),
             uart1: PhysicalInstance::new(*MemoryMap::UART1.start()),
             uart2: PhysicalInstance::new(*MemoryMap::UART2.start()),
diff --git a/src/system.rs b/src/system.rs
new file mode 100644
index 0000000..e86b930
--- /dev/null
+++ b/src/system.rs
@@ -0,0 +1,601 @@
+// SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates <open-source-office@arm.com>
+// SPDX-License-Identifier: MIT OR Apache-2.0
+
+//! FVP System Peripheral driver implementation.
+
+use bitflags::bitflags;
+use safe_mmio::{
+    field, field_shared,
+    fields::{ReadPure, ReadPureWrite, WriteOnly},
+    UniqueMmioPointer,
+};
+use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
+
+/// Status of data transfer to motherboard controller.
+#[repr(transparent)]
+#[derive(Copy, Clone, Debug, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)]
+struct SysCfgStat(u32);
+
+bitflags! {
+    impl SysCfgStat: u32 {
+        const ERROR = 1 << 1;
+        const COMPLETE = 1 << 0;
+    }
+}
+
+/// FVP System Peripheral registers based on 'Table 5-17 VE_SysRegs registers'
+#[derive(Clone, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)]
+#[repr(C, align(4))]
+pub struct FvpSystemRegisters {
+    /// 0x00 System identity
+    sys_id: ReadPure<u32>,
+    /// 0x04 Bits\[7:0\] map to switch S6.
+    sys_sw: ReadPureWrite<u32>,
+    /// 0x08 Bits\[7:0\] map to user LEDs.
+    sys_led: ReadPureWrite<u32>,
+    /// 0x0c - 0x20
+    reserved_0c: [u32; 6],
+    /// 0x24 100Hz counter.
+    sys_100hz: ReadPure<u32>,
+    /// 0x28 - 0x2c
+    reserved_28: [u32; 2],
+    /// 0x30 General-purpose flags.
+    sys_flags: ReadPureWrite<u32>,
+    /// 0x34 Clear bits in general-purpose flags.
+    sys_flagsclr: WriteOnly<u32>,
+    /// 0x38 General-purpose non-volatile flags.
+    sys_nvflags: ReadPureWrite<u32>,
+    /// 0x3c Clear bits in general-purpose non-volatile flags.
+    sys_nvflagsclr: WriteOnly<u32>,
+    /// 0x40 - 0x44
+    reserved_40: [u32; 2],
+    /// 0x48 MCI.
+    sys_mci: ReadPure<u32>,
+    /// 0x4c Flash control.
+    sys_flash: ReadPureWrite<u32>,
+    /// 0x50 - 0x54
+    reserved_50: [u32; 2],
+    /// 0x58 Boot-select switch.
+    sys_cfgsw: ReadPureWrite<u32>,
+    /// 0x5c 24MHz counter.
+    sys_24mhz: ReadPure<u32>,
+    /// 0x60 Miscellaneous control flags.
+    sys_misc: ReadPureWrite<u32>,
+    /// 0x64 Read/write DMA peripheral map.
+    sys_dma: ReadPureWrite<u32>,
+    /// 0x68 - 0x80
+    reserved_68: [u32; 7],
+    /// 0x84 Processor ID.
+    sys_procid0: ReadPureWrite<u32>,
+    /// 0x88 Processor ID.
+    sys_procid1: ReadPureWrite<u32>,
+    /// 0x8c Processor ID.
+    sys_procid2: ReadPureWrite<u32>,
+    /// 0x90 Processor ID.
+    sys_procid3: ReadPureWrite<u32>,
+    /// 0x94 - 0x9c
+    reserved_8c: [u32; 3],
+    /// 0xa0 Data to read/write from & to motherboard controller.
+    sys_cfgdata: ReadPureWrite<u32>,
+    /// 0xa4 Control data transfer to motherboard controller.
+    sys_cfgctrl: ReadPureWrite<u32>,
+    /// 0xa8 Status of data transfer to motherboard controller.
+    sys_cfgstat: ReadPure<SysCfgStat>,
+    /// 0xac - 0xffc
+    reserved_ac: [u32; 981],
+}
+
+/// FVP System Peripheral error type.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Error {
+    InvalidBoardRevision,
+    InvalidHbi,
+    InvalidVariant,
+    InvalidPlatformType,
+}
+
+/// Board revision.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum BoardRevision {
+    RevA,
+    RevB,
+    RevC,
+}
+
+impl TryFrom<u32> for BoardRevision {
+    type Error = Error;
+
+    fn try_from(value: u32) -> Result<Self, Self::Error> {
+        Ok(match value {
+            0x0 => Self::RevA,
+            0x1 => Self::RevB,
+            0x2 => Self::RevC,
+            _ => return Err(Error::InvalidBoardRevision),
+        })
+    }
+}
+
+/// HBI platform description.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Hbi {
+    V8FoundationPlatform,
+    V8BasePlatform,
+}
+
+impl TryFrom<u32> for Hbi {
+    type Error = Error;
+
+    fn try_from(value: u32) -> Result<Self, Self::Error> {
+        Ok(match value {
+            0x10 => Self::V8FoundationPlatform,
+            0x20 => Self::V8BasePlatform,
+            _ => return Err(Error::InvalidHbi),
+        })
+    }
+}
+
+/// Platform variant.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Variant {
+    VariantA = 0x0,
+    VariantB = 0x1,
+    VariantC = 0x2,
+}
+
+impl TryFrom<u32> for Variant {
+    type Error = Error;
+
+    fn try_from(value: u32) -> Result<Self, Self::Error> {
+        Ok(match value {
+            0x0 => Self::VariantA,
+            0x1 => Self::VariantB,
+            0x2 => Self::VariantC,
+            _ => return Err(Error::InvalidVariant),
+        })
+    }
+}
+
+/// Platform type.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum PlatformType {
+    Board,
+    Model,
+    Emulator,
+    Simulator,
+    Fpga,
+}
+
+impl TryFrom<u32> for PlatformType {
+    type Error = Error;
+
+    fn try_from(value: u32) -> Result<Self, Self::Error> {
+        Ok(match value {
+            0x0 => Self::Board,
+            0x1 => Self::Model,
+            0x2 => Self::Emulator,
+            0x3 => Self::Simulator,
+            0x4 => Self::Fpga,
+            _ => return Err(Error::InvalidPlatformType),
+        })
+    }
+}
+
+/// System identification of the FVP platform.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct SystemId {
+    pub revision: BoardRevision,
+    pub hbi: Hbi,
+    pub variant: Variant,
+    pub platform_type: PlatformType,
+    pub fpga_build: u8,
+}
+
+impl TryFrom<u32> for SystemId {
+    type Error = Error;
+
+    fn try_from(value: u32) -> Result<Self, Self::Error> {
+        Ok(Self {
+            revision: ((value >> 28) & 0x0f).try_into()?,
+            hbi: ((value >> 16) & 0x3ff).try_into()?,
+            variant: ((value >> 12) & 0x0f).try_into()?,
+            platform_type: ((value >> 8) & 0x0f).try_into()?,
+            fpga_build: value as u8,
+        })
+    }
+}
+
+/// System configuration functions
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+#[repr(u32)]
+pub enum SystemConfigFunction {
+    /// Shutdown system.
+    Shutdown = 0x08,
+    /// Reboot system.
+    Reboot = 0x09,
+}
+
+/// FVP System Peripheral Driver
+pub struct FvpSystemPeripheral<'a> {
+    regs: UniqueMmioPointer<'a, FvpSystemRegisters>,
+}
+
+impl<'a> FvpSystemPeripheral<'a> {
+    const SYSCFG_START: u32 = 1 << 31;
+    const SYSCFG_WRITE: u32 = 1 << 30;
+    const SYSCFG_FUNC_SHIFT: usize = 20;
+
+    /// Create new driver instance.
+    pub fn new(regs: UniqueMmioPointer<'a, FvpSystemRegisters>) -> Self {
+        Self { regs }
+    }
+
+    /// Reads system ID.
+    pub fn system_id(&self) -> Result<SystemId, Error> {
+        field_shared!(self.regs, sys_id).read().try_into()
+    }
+
+    /// Reads switch state.
+    pub fn switches(&self) -> u8 {
+        field_shared!(self.regs, sys_sw).read() as u8
+    }
+
+    /// Sets LED state.
+    pub fn set_leds(&mut self, leds: u8) {
+        field!(self.regs, sys_led).write(leds as u32);
+    }
+
+    /// Reads LED state.
+    pub fn leds(&self) -> u8 {
+        field_shared!(self.regs, sys_led).read() as u8
+    }
+
+    /// Reads 100Hz counter value.
+    pub fn counter_100hz(&self) -> u32 {
+        field_shared!(self.regs, sys_100hz).read()
+    }
+
+    /// Sets flag bits.
+    pub fn set_flags(&mut self, flags: u32) {
+        field!(self.regs, sys_flags).write(flags);
+    }
+
+    /// Clears flag bits.
+    pub fn clear_flags(&mut self, flags: u32) {
+        field!(self.regs, sys_flagsclr).write(flags);
+    }
+
+    /// Reads flag bits.
+    pub fn flags(&self) -> u32 {
+        field_shared!(self.regs, sys_flags).read()
+    }
+
+    /// Sets non-volatile flag bits.
+    pub fn set_non_volatile_flags(&mut self, flags: u32) {
+        field!(self.regs, sys_nvflags).write(flags);
+    }
+
+    /// Clears non-volatile flag bits.
+    pub fn clear_non_volatile_flags(&mut self, flags: u32) {
+        field!(self.regs, sys_nvflagsclr).write(flags);
+    }
+
+    /// Reads non-volatile flag bits.
+    pub fn non_volatile_flags(&self) -> u32 {
+        field_shared!(self.regs, sys_nvflags).read()
+    }
+
+    /// Checks that the MMC card is present.
+    pub fn mmc_card_present(&self) -> bool {
+        (field_shared!(self.regs, sys_mci).read() & 0x0000_0001) != 0
+    }
+
+    /// Sets flash control register value.
+    pub fn set_flash_control(&mut self, value: u32) {
+        field!(self.regs, sys_flash).write(value);
+    }
+
+    /// Reads flash control register value.
+    pub fn flash_control(&self) -> u32 {
+        field_shared!(self.regs, sys_flash).read()
+    }
+
+    /// Read boot select switch.
+    pub fn boot_select_switch(&self) -> u32 {
+        field_shared!(self.regs, sys_cfgsw).read()
+    }
+
+    /// Read 24MHz counter.
+    pub fn counter_24mhz(&self) -> u32 {
+        field_shared!(self.regs, sys_24mhz).read()
+    }
+
+    /// Miscellaneous control flags.
+    pub fn misc(&self) -> u32 {
+        field_shared!(self.regs, sys_misc).read()
+    }
+
+    /// Reads DMA peripheral map.
+    pub fn dma(&self) -> u32 {
+        field_shared!(self.regs, sys_dma).read()
+    }
+
+    /// Reads processor ID.
+    pub fn processor_id(&self) -> [u32; 4] {
+        [
+            field_shared!(self.regs, sys_procid0).read(),
+            field_shared!(self.regs, sys_procid1).read(),
+            field_shared!(self.regs, sys_procid2).read(),
+            field_shared!(self.regs, sys_procid3).read(),
+        ]
+    }
+
+    /// Writes system configuration and its optional data.
+    pub fn write_system_configuration(&mut self, function: SystemConfigFunction) {
+        field!(self.regs, sys_cfgctrl).write(
+            Self::SYSCFG_START
+                | Self::SYSCFG_WRITE
+                | ((function as u32) << Self::SYSCFG_FUNC_SHIFT),
+        );
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use zerocopy::transmute_mut;
+
+    #[repr(align(4096))]
+    pub struct FakeSystemRegisters {
+        regs: [u32; 1024],
+    }
+
+    impl FakeSystemRegisters {
+        pub fn new() -> Self {
+            Self { regs: [0u32; 1024] }
+        }
+
+        pub fn clear(&mut self) {
+            self.regs.fill(0);
+        }
+
+        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<FvpSystemRegisters> {
+            UniqueMmioPointer::from(transmute_mut!(&mut self.regs))
+        }
+
+        pub fn system_for_test(&mut self) -> FvpSystemPeripheral {
+            FvpSystemPeripheral::new(self.get())
+        }
+    }
+
+    #[test]
+    fn regs_size() {
+        assert_eq!(core::mem::size_of::<FvpSystemRegisters>(), 0x1000);
+    }
+
+    #[test]
+    fn board_revision() {
+        assert_eq!(Ok(BoardRevision::RevA), BoardRevision::try_from(0x0));
+        assert_eq!(Ok(BoardRevision::RevB), BoardRevision::try_from(0x1));
+        assert_eq!(Ok(BoardRevision::RevC), BoardRevision::try_from(0x2));
+        assert_eq!(
+            Err(Error::InvalidBoardRevision),
+            BoardRevision::try_from(0x3)
+        );
+    }
+
+    #[test]
+    fn hbi() {
+        assert_eq!(Ok(Hbi::V8FoundationPlatform), Hbi::try_from(0x10));
+        assert_eq!(Ok(Hbi::V8BasePlatform), Hbi::try_from(0x20));
+        assert_eq!(Err(Error::InvalidHbi), Hbi::try_from(0xff));
+    }
+
+    #[test]
+    fn variant() {
+        assert_eq!(Ok(Variant::VariantA), Variant::try_from(0x0));
+        assert_eq!(Ok(Variant::VariantB), Variant::try_from(0x1));
+        assert_eq!(Ok(Variant::VariantC), Variant::try_from(0x2));
+        assert_eq!(Err(Error::InvalidVariant), Variant::try_from(0x3));
+    }
+
+    #[test]
+    fn platform_type() {
+        assert_eq!(Ok(PlatformType::Board), PlatformType::try_from(0x0));
+        assert_eq!(Ok(PlatformType::Model), PlatformType::try_from(0x1));
+        assert_eq!(Ok(PlatformType::Emulator), PlatformType::try_from(0x2));
+        assert_eq!(Ok(PlatformType::Simulator), PlatformType::try_from(0x3));
+        assert_eq!(Ok(PlatformType::Fpga), PlatformType::try_from(0x4));
+        assert_eq!(Err(Error::InvalidPlatformType), PlatformType::try_from(0x5));
+    }
+
+    #[test]
+    fn system_id() {
+        assert_eq!(
+            Ok(SystemId {
+                revision: BoardRevision::RevC,
+                hbi: Hbi::V8BasePlatform,
+                variant: Variant::VariantC,
+                platform_type: PlatformType::Fpga,
+                fpga_build: 0x5a
+            }),
+            SystemId::try_from(0x2020_245a)
+        );
+
+        assert_eq!(
+            Err(Error::InvalidBoardRevision),
+            SystemId::try_from(0xf020_245a)
+        );
+        assert_eq!(Err(Error::InvalidHbi), SystemId::try_from(0x20f0_245a));
+        assert_eq!(Err(Error::InvalidVariant), SystemId::try_from(0x2020_f45a));
+        assert_eq!(
+            Err(Error::InvalidPlatformType),
+            SystemId::try_from(0x2020_2f5a)
+        );
+    }
+
+    #[test]
+    fn system_peripheral() {
+        let mut regs = FakeSystemRegisters::new();
+
+        {
+            // System ID
+            regs.reg_write(0x00, 0x2020_245a);
+            let sys = regs.system_for_test();
+            assert_eq!(
+                Ok(SystemId {
+                    revision: BoardRevision::RevC,
+                    hbi: Hbi::V8BasePlatform,
+                    variant: Variant::VariantC,
+                    platform_type: PlatformType::Fpga,
+                    fpga_build: 0x5a
+                }),
+                sys.system_id()
+            );
+        }
+
+        regs.clear();
+
+        {
+            // Switches
+            regs.reg_write(0x04, 0xabcd_ef5a);
+            let sys = regs.system_for_test();
+            assert_eq!(0x5a, sys.switches());
+        }
+
+        regs.clear();
+
+        {
+            // LEDs
+            regs.reg_write(0x08, 0xabcd_ef5a);
+            let mut sys = regs.system_for_test();
+            assert_eq!(0x5a, sys.leds());
+            sys.set_leds(0xa5);
+        }
+
+        assert_eq!(0xa5, regs.reg_read(0x08));
+
+        regs.clear();
+
+        {
+            // 100Hz counter
+            regs.reg_write(0x24, 100);
+            let sys = regs.system_for_test();
+            assert_eq!(100, sys.counter_100hz());
+        }
+
+        regs.clear();
+
+        {
+            // Flags
+            let mut sys = regs.system_for_test();
+            // The set and read registers are the same, the clear register is a separate one.
+            sys.set_flags(0x0123_4567);
+            assert_eq!(0x0123_4567, sys.flags());
+            sys.clear_flags(0x89ab_cdef);
+        }
+
+        assert_eq!(0x0123_4567, regs.reg_read(0x30));
+        assert_eq!(0x89ab_cdef, regs.reg_read(0x34));
+        regs.clear();
+
+        {
+            // Non-volatile flags
+            let mut sys = regs.system_for_test();
+            // The set and read registers are the same, the clear register is a separate one.
+            sys.set_non_volatile_flags(0x0123_4567);
+            assert_eq!(0x0123_4567, sys.non_volatile_flags());
+            sys.clear_non_volatile_flags(0x89ab_cdef);
+        }
+
+        assert_eq!(0x0123_4567, regs.reg_read(0x38));
+        assert_eq!(0x89ab_cdef, regs.reg_read(0x3c));
+        regs.clear();
+
+        {
+            // MMC card present
+            regs.reg_write(0x48, 0x0000_0001);
+            let sys = regs.system_for_test();
+            assert!(sys.mmc_card_present());
+        }
+
+        regs.clear();
+
+        {
+            // Flash control
+            let mut sys = regs.system_for_test();
+            sys.set_flash_control(0x89ab_cdef);
+            assert_eq!(0x89ab_cdef, sys.flash_control());
+        }
+
+        assert_eq!(0x89ab_cdef, regs.reg_read(0x4c));
+        regs.clear();
+
+        {
+            // Boot select switch
+            regs.reg_write(0x58, 0x89ab_cdef);
+            let sys = regs.system_for_test();
+            assert_eq!(0x89ab_cdef, sys.boot_select_switch());
+        }
+
+        regs.clear();
+
+        {
+            // 24MHz counter
+            regs.reg_write(0x5c, 0x89ab_cdef);
+            let sys = regs.system_for_test();
+            assert_eq!(0x89ab_cdef, sys.counter_24mhz());
+        }
+
+        regs.clear();
+
+        {
+            // Miscellaneous control flags.
+            regs.reg_write(0x60, 0x89ab_cdef);
+            let sys = regs.system_for_test();
+            assert_eq!(0x89ab_cdef, sys.misc());
+        }
+
+        regs.clear();
+
+        {
+            // DMA peripheral map
+            regs.reg_write(0x64, 0x89ab_cdef);
+            let sys = regs.system_for_test();
+            assert_eq!(0x89ab_cdef, sys.dma());
+        }
+
+        regs.clear();
+
+        {
+            // Processor ID
+            regs.reg_write(0x84, 0x0123_4567);
+            regs.reg_write(0x88, 0x89ab_cdef);
+            regs.reg_write(0x8c, 0x4567_89ab);
+            regs.reg_write(0x90, 0xcdef_0123);
+            let sys = regs.system_for_test();
+
+            assert_eq!(
+                [0x0123_4567, 0x89ab_cdef, 0x4567_89ab, 0xcdef_0123],
+                sys.processor_id()
+            );
+        }
+
+        regs.clear();
+
+        {
+            // System configuration
+            let mut sys = regs.system_for_test();
+            sys.write_system_configuration(SystemConfigFunction::Shutdown);
+        }
+
+        assert_eq!(0xc080_0000, regs.reg_read(0xa4));
+    }
+}