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