diff --git a/zynq-examples/src/main.rs b/zynq-examples/src/main.rs index 4d7f567..4fa8a17 100644 --- a/zynq-examples/src/main.rs +++ b/zynq-examples/src/main.rs @@ -19,7 +19,7 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! { #[unsafe(export_name = "main")] pub fn main() -> ! { - let mut gpio = unsafe { zynq7000::gpio::Gpio::new_mmio() }; + let mut gpio = unsafe { zynq7000::pac::gpio::Gpio::new_mmio() }; gpio.modify_dirm_0(|v| v | ZEDBOARD_LED_MASK); gpio.modify_out_en_0(|v| v | ZEDBOARD_LED_MASK); loop { diff --git a/zynq7000/Cargo.toml b/zynq7000/Cargo.toml index 8c3f858..5041c6a 100644 --- a/zynq7000/Cargo.toml +++ b/zynq7000/Cargo.toml @@ -15,6 +15,7 @@ static_assertions = "1.1" derive-mmio = { path = "../../derive-mmio", default-features = false } bitbybit = "1.3" arbitrary-int = "1.3" +thiserror = { version = "2", default-features = false } # cortex-r # defmt = { version = "0.3", optional = true } # critical-section = { version = "1", optional = true } @@ -22,6 +23,7 @@ arbitrary-int = "1.3" [features] # Adds Debug implementation debug = [] +std = ["thiserror/std"] [package.metadata.docs.rs] all-features = true diff --git a/zynq7000/src/hal/gic.rs b/zynq7000/src/hal/gic.rs new file mode 100644 index 0000000..a1928fc --- /dev/null +++ b/zynq7000/src/hal/gic.rs @@ -0,0 +1,76 @@ +use crate::pac::gic::{MmioGicc, MmioGicd}; + +pub struct Gic { + pub gicc: MmioGicc<'static>, + pub gicd: MmioGicd<'static>, +} + +pub enum SpiSensitivity { + Level = 0b01, + Edge = 0b11, +} + +pub const ICFR_2_FIXED_VALUE: u32 = 0b01010101010111010101010001011111; +/// This configures PL[2:0] to high-level sensitivity. +pub const ICFR_3_FIXED_VALUE: u32 = 0b01010111010101011101010101010101; +/// This configures PL[7:3] to high-level sensitivity. +pub const ICFR_4_FIXED_VALUE: u32 = 0b01110101010101010101010101010101; +/// This configures PL[15:8] to high-level sensitivity. +pub const ICFR_5_FIXED_VALUE: u32 = 0b00000011010101010101010101010101; + +#[derive(Debug, thiserror::Error)] +#[error("Invalid PL interrupt ID {0}")] +pub struct InvalidPlInterruptId(pub u32); + +impl Gic { + pub fn new(gicc: MmioGicc<'static>, gicd: MmioGicd<'static>) -> Self { + Gic { gicc, gicd } + } + + /// Sets up the GIC by configuring the required sensitivites for the shared peripheral + /// interrupts. + /// + /// With a few exeception, the GIC expects software to set up the sensitivities + /// to fixed values. The only exceptions are the interupts coming from the programmable + /// logic. These are configured to high level sensitivity by this function. + /// If you need a different sensitivity, you need to update the bits using the + /// [Self::set_pl_interrupt_sensitivity] function. + pub fn initialize(&mut self) { + self.gicd.write_icfr_2_spi(ICFR_2_FIXED_VALUE); + self.gicd.write_icfr_3_spi(ICFR_3_FIXED_VALUE); + self.gicd.write_icfr_4_spi(ICFR_4_FIXED_VALUE); + self.gicd.write_icfr_5_spi(ICFR_5_FIXED_VALUE); + } + + pub fn set_pl_interrupt_sensitivity( + &mut self, + pl_int_id: u32, + sensitivity: SpiSensitivity, + ) -> Result<(), InvalidPlInterruptId> { + if pl_int_id >= 16 { + return Err(InvalidPlInterruptId(pl_int_id)); + } + match pl_int_id { + 0..=2 => { + let pos = 26 + (pl_int_id * 2); + let mask = 0b11 << pos; + self.gicd + .modify_icfr_3_spi(|v| (v & !mask) | ((sensitivity as u32) << pos)); + } + 3..=7 => { + let pos = pl_int_id * 2; + let mask = 0b11 << pos; + self.gicd + .modify_icfr_4_spi(|v| (v & !mask) | ((sensitivity as u32) << pos)); + } + 8..=15 => { + let pos = 8 + (pl_int_id * 2); + let mask = 0b11 << pos; + self.gicd + .modify_icfr_5_spi(|v| (v & !mask) | ((sensitivity as u32) << pos)); + } + _ => unreachable!(), + } + Ok(()) + } +} diff --git a/zynq7000/src/hal/mod.rs b/zynq7000/src/hal/mod.rs new file mode 100644 index 0000000..3dee22b --- /dev/null +++ b/zynq7000/src/hal/mod.rs @@ -0,0 +1 @@ +pub mod gic; diff --git a/zynq7000/src/lib.rs b/zynq7000/src/lib.rs index 85282f6..67e89a1 100644 --- a/zynq7000/src/lib.rs +++ b/zynq7000/src/lib.rs @@ -3,7 +3,5 @@ pub const MPCORE_BASE_ADDR: usize = 0xF8F0_0000; -pub mod gpio; -pub mod uart; -pub mod gtc; -pub mod slcr; +pub mod pac; +pub mod hal; diff --git a/zynq7000/src/pac/gic.rs b/zynq7000/src/pac/gic.rs new file mode 100644 index 0000000..42761e2 --- /dev/null +++ b/zynq7000/src/pac/gic.rs @@ -0,0 +1,199 @@ +pub use crate::pac::mpcore::{GICC_BASE_ADDR, GICD_BASE_ADDR}; +use arbitrary_int::{u10, u3, u5}; +use static_assertions::const_assert_eq; + +/// Distributor Control Register +#[bitbybit::bitfield(u32)] +pub struct Dcr { + #[bit(1, rw)] + enable_non_secure: bool, + #[bit(0, rw)] + enable_secure: bool, +} + +/// Read only bit. This register only returns fixed constants. +#[bitbybit::bitfield(u32)] +pub struct TypeRegister { + #[bits(11..=15, r)] + lspi: u5, + #[bit(10, r)] + security_extension: bool, + #[bits(5..=7, r)] + cpu_number: u3, + #[bits(0..=4, r)] + it_lines_number: u5, +} + +impl TypeRegister { + pub const SECURITY_EXTNS_BIT: bool = true; + /// 31 LSPIs. + pub const NUM_LSPI: usize = 0x1f; + /// Encoding: 0b001 means that the Cortex-A9 MPCore has 2 processors. + pub const CPU_NUMBER_BITS: u8 = 0b001; + /// The distributor provides 96 interrupts. + pub const IT_LINES_NUMBER: u8 = 0x2; + + pub const NUM_OF_CPUS: usize = 2; + pub const NUM_OF_INTERRUPTS: usize = 96; +} + +pub type Typer = TypeRegister; + +/// GIC Distributor registers. +#[derive(derive_mmio::Mmio)] +#[mmio(no_ctors)] +#[repr(C, align(8))] +pub struct Gicd { + /// Distributor Control Register + pub dcr: Dcr, + /// Interrupt Controller Type Register + #[mmio(RO)] + pub ictr: Typer, + /// Distributor Implementer Identification Register + #[mmio(RO)] + pub iidr: u32, + _reserved_0: [u32; 0x1D], + /// Interrupt security registers + pub isr: [u32; 3], + _reserved_1: [u32; 0x1D], + /// Interrupt Set-Enable Registers + pub iser: [u32; 0x3], + _reserved_3: [u32; 0x1D], + /// Interrupt Clear-Enable Registers + pub icer: [u32; 0x3], + _reserved_4: [u32; 0x1D], + /// Interrupt Set-Pending Registers + pub ispr: [u32; 0x3], + _reserved_5: [u32; 0x1D], + /// Interrupt Clear-Pending Registers + pub icpr: [u32; 0x3], + _reserved_6: [u32; 0x1D], + /// Active Bit Registers + pub abr: [u32; 0x3], + _reserved_10: [u32; 0x3D], + /// Interrupt Priority Registers + pub ipr: [u32; 0x18], + _reserved_11: [u32; 0xE8], + /// Interrupt Processor Targes Registers + pub iptr: [u32; 0x18], + // Those are split in the ARM documentation for some reason.. + _reserved_12: [u32; 0xE8], + /// Interrupt Configuration Registers + /// Interupt sensitivity register for software generated interrupts (SGI) + #[mmio(RO)] + pub icfr_0_sgi: u32, + /// Interupt sensitivity register for private peripheral interrupts (PPI) + #[mmio(RO)] + pub icfr_1_ppi: u32, + pub icfr_2_spi: u32, + pub icfr_3_spi: u32, + pub icfr_4_spi: u32, + pub icfr_5_spi: u32, + _reserved_13: [u32; 0x3A], + pub ppi_status: u32, + pub spi_status_0: u32, + pub spi_status_1: u32, + _reserved_14: [u32; 0x7D], + /// Software Generated Interrupt Register. + pub sgir: u32, + _reserved_15: [u32; 0x33], + pub pidr_4: u32, + pub pidr_5: u32, + pub pidr_6: u32, + pub pidr_7: u32, + pub pidr_0: u32, + pub pidr_1: u32, + pub pidr_2: u32, + pub pidr_3: u32, + pub cidr: [u32; 4], +} + +const_assert_eq!(core::mem::size_of::(), 0x1000); + +impl Gicd { + /// Create a new XGPIOPS GPIO MMIO instance. + /// + /// # Safety + /// + /// This API can be used to potentially create a driver to the same peripheral structure + /// from multiple threads. The user must ensure that concurrent accesses are safe and do not + /// interfere with each other. + #[inline] + pub unsafe fn new_mmio() -> MmioGicd<'static> { + unsafe { Self::_new_mmio(GICD_BASE_ADDR as *mut _) } + } +} + +/// CPU interface control register. +#[bitbybit::bitfield(u32)] +pub struct Icr { + #[bit(4, rw)] + sbpr: bool, + #[bit(3, rw)] + fiq_en: bool, + #[bit(2, rw)] + ack_ctrl: bool, + #[bit(1, rw)] + enable_non_secure: bool, + #[bit(0, rw)] + enable_secure: bool, +} + +/// Priority Mask Register +#[bitbybit::bitfield(u32)] +pub struct PriorityRegister { + #[bits(0..=7, rw)] + priority: u8, +} + +/// Interrupt acknowledge register. +#[bitbybit::bitfield(u32)] +pub struct InterruptSignalRegister { + #[bits(10..=12, rw)] + cpu_id: u3, + #[bits(0..=9, rw)] + ack_int_id: u10, +} + +/// GIC CPU interface registers. +#[derive(derive_mmio::Mmio)] +#[mmio(no_ctors)] +#[repr(C, align(8))] +pub struct Gicc { + /// CPU Interface Control Register. + pub icr: Icr, + /// Interrupt Priority Mask Register. + pub pmr: PriorityRegister, + /// Binary Point Register. + pub bpr: u32, + /// Interrupt Acknowledge Register. + pub iar: InterruptSignalRegister, + /// End of Interrupt Register. + pub eoir: InterruptSignalRegister, + /// Running Priority Register. + pub rpr: PriorityRegister, + /// Highest Pending Interrupt Register. + pub hpir: InterruptSignalRegister, + /// Aliased Binary Point Register + pub abpr: u32, + _reserved_0: [u32; 0x37], + /// CPU Interface Identification Register. + #[mmio(RO)] + pub iidr: u32, +} + +const_assert_eq!(core::mem::size_of::(), 0x100); + +impl Gicc { + /// Create a new XGPIOPS GPIO MMIO instance. + /// + /// # Safety + /// + /// This API can be used to potentially create a driver to the same peripheral structure + /// from multiple threads. The user must ensure that concurrent accesses are safe and do not + /// interfere with each other. + #[inline] + pub unsafe fn new_mmio() -> MmioGicc<'static> { + unsafe { Self::_new_mmio(GICC_BASE_ADDR as *mut _) } + } +} diff --git a/zynq7000/src/gpio.rs b/zynq7000/src/pac/gpio.rs similarity index 100% rename from zynq7000/src/gpio.rs rename to zynq7000/src/pac/gpio.rs diff --git a/zynq7000/src/gtc.rs b/zynq7000/src/pac/gtc.rs similarity index 69% rename from zynq7000/src/gtc.rs rename to zynq7000/src/pac/gtc.rs index d5235a2..efd5cd5 100644 --- a/zynq7000/src/gtc.rs +++ b/zynq7000/src/pac/gtc.rs @@ -1,11 +1,12 @@ //! Global timer counter module. -pub const GTC_BASE_ADDR: usize = super::MPCORE_BASE_ADDR + 0x0000_0200; +pub const GTC_BASE_ADDR: usize = crate::pac::mpcore::MPCORE_BASE_ADDR + 0x0000_0200; +/// Global timer counter. #[derive(derive_mmio::Mmio)] #[mmio(no_ctors)] #[repr(C)] -pub struct GlobalTimerCounter { +pub struct Gtc { /// Count register 0, lower 32 bits count_lower: u32, /// Count register 1, upper 32 bits @@ -19,16 +20,12 @@ pub struct GlobalTimerCounter { /// Comparator 1, upper 32 bits comparator_upper: u32, /// Auto-increment register - auto_increment: u32 + auto_increment: u32, } -pub type Gtc = GlobalTimerCounter; - static_assertions::const_assert_eq!(core::mem::size_of::(), 0x1C); -pub type MmioGtc = MmioGlobalTimerCounter<'static>; - -impl GlobalTimerCounter { +impl Gtc { /// Create a new GTC MMIO instance. /// /// # Safety @@ -36,7 +33,8 @@ impl GlobalTimerCounter { /// This API can be used to potentially create a driver to the same peripheral structure /// from multiple threads. The user must ensure that concurrent accesses are safe and do not /// interfere with each other. - pub const unsafe fn new_mmio() -> MmioGtc { - MmioGtc { ptr: GTC_BASE_ADDR as *mut Gtc, phantom: core::marker::PhantomData } + #[inline] + pub const unsafe fn new_mmio() -> MmioGtc<'static> { + unsafe { Gtc::_new_mmio(GTC_BASE_ADDR as *mut Gtc) } } } diff --git a/zynq7000/src/pac/mod.rs b/zynq7000/src/pac/mod.rs new file mode 100644 index 0000000..27a4ff0 --- /dev/null +++ b/zynq7000/src/pac/mod.rs @@ -0,0 +1,6 @@ +pub mod gpio; +pub mod uart; +pub mod gtc; +pub mod slcr; +pub mod mpcore; +pub mod gic; diff --git a/zynq7000/src/pac/mpcore.rs b/zynq7000/src/pac/mpcore.rs new file mode 100644 index 0000000..b79f0bf --- /dev/null +++ b/zynq7000/src/pac/mpcore.rs @@ -0,0 +1,82 @@ +use static_assertions::const_assert_eq; + +use crate::pac::{ + gic::{Gicc, Gicd, MmioGicc, MmioGicd}, + gtc::{Gtc, MmioGtc}, +}; + +pub const MPCORE_BASE_ADDR: usize = 0xF8F0_0000; +pub const SCU_BASE_ADDR: usize = MPCORE_BASE_ADDR; +pub const GICC_BASE_ADDR: usize = MPCORE_BASE_ADDR + 0x100; +pub const GICD_BASE_ADDR: usize = MPCORE_BASE_ADDR + 0x1000; + +#[derive(derive_mmio::Mmio)] +#[mmio(no_ctors)] +#[repr(C)] +pub struct Scu { + ctrl: u32, + config: u32, + cpu_power_status: u32, + invalidate_all_regs_in_secure_state: u32, + _reserved_0: [u32; 0xC], + filtering_start_addr: u32, + filtering_end_addr: u32, + _reserved_1: [u32; 0x2], + access_ctrl: u32, + non_secure_access_ctrl: u32, +} + +impl Scu { + /// Create a new Snoop Control Unit interface. + /// + /// # Safety + /// + /// This API can be used to potentially create a driver to the same peripheral structure + /// from multiple threads. The user must ensure that concurrent accesses are safe and do not + /// interfere with each other. + #[inline] + pub const unsafe fn new_mmio() -> MmioScu<'static> { + unsafe { Self::_new_mmio(MPCORE_BASE_ADDR as *mut _) } + } +} + +const_assert_eq!(core::mem::size_of::(), 0x58); + +#[derive(derive_mmio::Mmio)] +#[mmio(no_ctors)] +#[repr(C)] +pub struct Mpcore { + #[mmio(inner)] + scu: Scu, + + _reserved_0: [u32; 0x2A], + + #[mmio(inner)] + gicc: Gicc, + + #[mmio(inner)] + gt: Gtc, + + _reserved_1: [u32; 0xF9], + + private_timer_load: u32, + private_timer_counter: u32, + private_timer_ctrl: u32, + private_interrupt_status: u32, + + _reserved_2: [u32; 0x4], + + watchdog_load: u32, + watchdog_counter: u32, + watchdog_ctrl: u32, + watchdog_interrupt_status: u32, + watchdog_reset_status: u32, + watchdog_disable: u32, + + _reserved_3: [u32; 0x272], + + #[mmio(inner)] + gicd: Gicd, +} + +const_assert_eq!(core::mem::size_of::(), 0x2000); diff --git a/zynq7000/src/slcr.rs b/zynq7000/src/pac/slcr.rs similarity index 100% rename from zynq7000/src/slcr.rs rename to zynq7000/src/pac/slcr.rs diff --git a/zynq7000/src/uart.rs b/zynq7000/src/pac/uart.rs similarity index 100% rename from zynq7000/src/uart.rs rename to zynq7000/src/pac/uart.rs