From 38e2109e525936f42ce1b50e830a4d1e3ffc7ea9 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Fri, 5 Sep 2025 19:12:47 +0200 Subject: [PATCH] add bitstream prog function --- zedboard-fsbl/src/main.rs | 40 +++-- zynq-boot-image/src/lib.rs | 6 +- zynq7000-hal/src/devcfg.rs | 63 ++++++++ zynq7000-hal/src/lib.rs | 1 + zynq7000/src/devcfg.rs | 304 +++++++++++++++++++++++++++++++++++++ zynq7000/src/lib.rs | 5 + zynq7000/src/xadc.rs | 29 ++++ 7 files changed, 435 insertions(+), 13 deletions(-) create mode 100644 zynq7000-hal/src/devcfg.rs create mode 100644 zynq7000/src/devcfg.rs create mode 100644 zynq7000/src/xadc.rs diff --git a/zedboard-fsbl/src/main.rs b/zedboard-fsbl/src/main.rs index 1c601f7..3078c7d 100644 --- a/zedboard-fsbl/src/main.rs +++ b/zedboard-fsbl/src/main.rs @@ -8,23 +8,21 @@ use cortex_ar::asm::nop; use embedded_io::Write as _; use log::{error, info}; use zedboard_bsp::qspi_spansion::{self, QspiSpansionS25Fl256SLinearMode}; -use zynq7000::{PsPeripherals, qspi::MmioQspi}; use zynq7000_hal::{ - BootMode, clocks::{ + pll::{configure_arm_pll, configure_io_pll, PllConfig}, Clocks, - pll::{PllConfig, configure_arm_pll, configure_io_pll}, }, - ddr::{DdrClockSetupConfig, configure_ddr_for_ddr3, memtest}, - gic::GicConfigurator, - gpio::{GpioPins, mio}, - l2_cache, + ddr::{configure_ddr_for_ddr3, memtest, DdrClockSetupConfig}, + gic, gpio, l2_cache, prelude::*, - qspi::{self, QSPI_START_ADDRESS}, + qspi, time::Hertz, uart::{ClockConfigRaw, Uart, UartConfig}, + BootMode, }; use zynq7000_rt as _; +use zynq_boot_image::DestinationDevice; use crate::ddr_cfg::{DDRC_CONFIG_ZEDBOARD_FULL_BUILDERS, DDRIOB_CONFIG_SET_ZEDBOARD}; @@ -87,7 +85,7 @@ pub fn main() -> ! { // Clock was already initialized by PS7 Init TCL script or FSBL, we just read it. let clocks = Clocks::new_from_regs(PS_CLK).unwrap(); - let gpio_pins = GpioPins::new(dp.gpio); + let gpio_pins = gpio::GpioPins::new(dp.gpio); let mio_pins = gpio_pins.mio; // Set up the UART, we are logging with it. let uart_clk_config = ClockConfigRaw::new_autocalc_with_error(clocks.io_clocks(), 115200) @@ -110,7 +108,7 @@ pub fn main() -> ! { }; // Set up the global interrupt controller. - let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd); + let mut gic = gic::GicConfigurator::new_with_init(dp.gicc, dp.gicd); gic.enable_all_interrupts(); gic.set_all_spi_interrupt_targets_cpu0(); gic.enable(); @@ -218,6 +216,28 @@ fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode) -> ! { let mut name_buf: [u8; 256] = [0; 256]; for image_header in boot_header.image_header_iterator().unwrap() { let name = image_header.image_name(&mut name_buf).unwrap(); + for partition in image_header + .partition_header_iterator(boot_header_slice) + .unwrap() + { + let section_attrs = partition.section_attributes(); + if let Ok(dest_dev) = section_attrs.destination_device() { + match dest_dev { + DestinationDevice::Pl => { + info!("Loading image '{name}' to PL (FPGA).."); + // TODO: Load the bitstream. + } + DestinationDevice::Ps => { + // TODO: Load the binary into DDR. Jump at lowest load address after all + // partitions were parsed. + } + _ => { + error!("Unsupported destination device {dest_dev:?}"); + continue; + } + } + } + } } loop { diff --git a/zynq-boot-image/src/lib.rs b/zynq-boot-image/src/lib.rs index a88d2cc..1f27c97 100644 --- a/zynq-boot-image/src/lib.rs +++ b/zynq-boot-image/src/lib.rs @@ -335,7 +335,7 @@ pub struct PartitionHeader<'a> { } #[bitbybit::bitenum(u2, exhaustive = false)] -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] #[non_exhaustive] pub enum PartitionOwner { Fsbl = 0, @@ -343,7 +343,7 @@ pub enum PartitionOwner { } #[bitbybit::bitenum(u3, exhaustive = false)] -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] #[non_exhaustive] pub enum ChecksumType { None = 0, @@ -351,7 +351,7 @@ pub enum ChecksumType { } #[bitbybit::bitenum(u4, exhaustive = false)] -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] #[non_exhaustive] pub enum DestinationDevice { None = 0, diff --git a/zynq7000-hal/src/devcfg.rs b/zynq7000-hal/src/devcfg.rs new file mode 100644 index 0000000..ad36cd7 --- /dev/null +++ b/zynq7000-hal/src/devcfg.rs @@ -0,0 +1,63 @@ +#[derive(Debug, thiserror::Error)] +#[error("unaligned address: {0}")] +pub struct UnalignedAddrError(usize); + +/// Configures the bitstream using the PCAP interface in non-secure mode. +/// +/// Blocking function which only returns when the bitstream configuration is complete. +pub fn configure_bitstream_non_secure( + init_pl: bool, + bitstream: &[u8], +) -> Result<(), UnalignedAddrError> { + if !(bitstream.as_ptr() as usize).is_multiple_of(64) { + return Err(UnalignedAddrError(bitstream.as_ptr() as usize)); + } + if bitstream.is_empty() { + return Ok(()); + } + let devcfg = unsafe { zynq7000::devcfg::DevCfg::new_mmio_fixed() }; + devcfg.modify_control(|mut val| { + val.set_config_access_select(zynq7000::devcfg::PlConfigAccess::ConfigAccessPort); + val.set_access_port_select(zynq7000::devcfg::ConfigAccessPortSelect::Pcap); + val + }); + devcfg.write_interrupt_status(zynq7000::devcfg::Interrupt::new_with_raw_value(0xFFFF_FFFF)); + if init_pl { + devcfg.modify_control(|mut val| { + val.set_prog_b_bit(true); + val + }); + devcfg.modify_control(|mut val| { + val.set_prog_b_bit(false); + val + }); + while devcfg.read_status().pcfg_init() {} + devcfg.modify_control(|mut val| { + val.set_prog_b_bit(true); + val + }); + devcfg.write_interrupt_status( + zynq7000::devcfg::Interrupt::ZERO.with_pl_programming_done(true), + ); + } + while !devcfg.read_status().pcfg_init() {} + if !init_pl { + while devcfg.read_status().dma_command_queue_full() {} + } + devcfg.modify_misc_control(|mut val| { + val.set_loopback(false); + val + }); + devcfg.modify_control(|mut val| { + val.set_pcap_rate_enable(false); + val + }); + devcfg.write_dma_source_addr(bitstream.as_ptr() as u32); + devcfg.write_dma_dest_addr(0xFFFF_FFFF); + devcfg.write_dma_source_len(bitstream.len()); + devcfg.write_dma_dest_len(bitstream.len()); + + while !devcfg.read_interrupt_status().dma_done() {} + // TODO: Check for errors. + while !devcfg.read_interrupt_status().pl_programming_done() {} +} diff --git a/zynq7000-hal/src/lib.rs b/zynq7000-hal/src/lib.rs index c37d13e..a5447c6 100644 --- a/zynq7000-hal/src/lib.rs +++ b/zynq7000-hal/src/lib.rs @@ -30,6 +30,7 @@ pub mod l2_cache; pub mod log; pub mod prelude; pub mod priv_tim; +pub mod devcfg; pub mod qspi; pub mod slcr; pub mod spi; diff --git a/zynq7000/src/devcfg.rs b/zynq7000/src/devcfg.rs new file mode 100644 index 0000000..7299a6e --- /dev/null +++ b/zynq7000/src/devcfg.rs @@ -0,0 +1,304 @@ +use arbitrary_int::{u4, u5, u7}; + +pub const DEVCFG_BASE_ADDR: usize = 0xF8007000; + +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +pub enum PlConfigAccess { + /// Used for JTAG access + TapController = 0, + /// Used for PCAP or ICAP access. + ConfigAccessPort = 1, +} + +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +pub enum ConfigAccessPortSelect { + /// Internal Configuration Access Port (ICAP), using PL or PS-based software. + Icap = 0, + /// Processor Configuration Access Port (PCAP), using PS-based software. + Pcap = 1, +} + +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +pub enum TimerSelect { + _64kTimer = 0, + _4kTimer = 1, +} + +#[bitbybit::bitenum(u3, exhaustive = false)] +#[derive(Debug, PartialEq, Eq)] +pub enum AesEnable { + Disable = 0b000, + Enable = 0b111, +} + +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +pub enum PsBootMode { + NonSecure = 0, + Secure = 1, +} + +#[bitbybit::bitenum(u3, exhaustive = false)] +#[derive(Debug, PartialEq, Eq)] +pub enum ArmDapEnable { + Enabled = 0b111, +} + +#[bitbybit::bitfield(u32, debug)] +pub struct Control { + #[bit(31, rw)] + force_reset: bool, + /// Program singal used to reset the PL. It acts at the PROG_B signal in the PL. + #[bit(30, rw)] + prog_b_bit: bool, + /// Called PCFG_POR_CNT_4K by Xilinx. + #[bit(29, rw)] + timer_select: TimerSelect, + /// Called XDCFG_CTRL_PCAP_PR_MASK by Xilinx. + #[bit(27, rw)] + access_port_select: ConfigAccessPortSelect, + #[bit(26, rw)] + config_access_select: PlConfigAccess, + #[bit(25, rw)] + pcap_rate_enable: bool, + #[bit(24, rw)] + multiboot_enable: bool, + #[bit(23, rw)] + jtag_chain_disable: bool, + #[bit(12, rw)] + pcfg_aes_fuse: bool, + #[bits(9..=11, rw)] + pcfg_aes_enable: Option, + #[bit(8, rw)] + seu_enable: bool, + /// Read-only because this is set and locked by BootROM. + #[bit(7, r)] + ps_boot_mode: PsBootMode, + /// SPNIDEN + #[bit(6, rw)] + secure_non_invasive_debug_enable: bool, + /// SPIDEN + #[bit(5, rw)] + secure_invasive_debug_enable: bool, + /// NIDEN + #[bit(4, rw)] + non_invasive_debug_enable: bool, + /// DBGEN + #[bit(3, rw)] + invasive_debug_enable: bool, + #[bits(0..=2, rw)] + dap_enable: Option, +} + +/// The bits in this register and read/write, set-only, which means that only a PS_POR_B reset +/// can clear the bits. +#[bitbybit::bitfield(u32, debug)] +pub struct Lock { + #[bit(4, rw)] + aes_fuse: bool, + #[bit(3, rw)] + aes: bool, + #[bit(2, rw)] + seu: bool, + /// Locks the SEC_EN bit. BootROM will set this bit. + #[bit(1, rw)] + sec: bool, + /// Locks SPNIDEN, SPIDEN, NIDEN, DBGEN and DAP_EN + #[bit(0, rw)] + debug: bool, +} + +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +pub enum EdgeConfig { + Falling = 0, + Rising = 1, +} + +/// Related to the full level for reads, and the empty level for writes. +#[bitbybit::bitenum(u2, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +pub enum FifoThresholdConfig { + OneFourth = 0b00, + HalfEmpty = 0b01, + ThreeFourth = 0b10, + EmptyOrFull = 0b11, +} + +#[bitbybit::bitfield(u32, debug)] +pub struct Config { + #[bits(10..=11, rw)] + read_fifo_threshhold: FifoThresholdConfig, + #[bits(8..=9, rw)] + write_fifo_threshold: FifoThresholdConfig, + #[bit(7, rw)] + read_data_active_clock_edge: EdgeConfig, + #[bit(6, rw)] + write_data_active_clock_edge: EdgeConfig, + #[bit(5, rw)] + disable_src_increment: bool, + #[bit(4, rw)] + disable_dst_incremenet: bool, +} + +#[bitbybit::bitfield(u32, debug)] +pub struct Interrupt { + /// Tri-state PL IO during HIZ. + #[bit(31, rw)] + gts_usr_b: bool, + #[bit(30, rw)] + first_config_done: bool, + #[bit(29, rw)] + global_powerdown: bool, + /// Tri-state PL IO during configuration. + #[bit(28, rw)] + gts_cfg_b: bool, + /// PSS_CFG_RESET_B_INT + #[bit(27, rw)] + pl_config_reset: bool, + #[bit(23, rw)] + axi_write_timeout: bool, + #[bit(22, rw)] + axi_write_response_error: bool, + #[bit(21, rw)] + axi_read_timeout: bool, + #[bit(20, rw)] + axi_read_response_error: bool, + #[bit(18, rw)] + rx_overflow: bool, + #[bit(17, rw)] + tx_fifo_below_threshold: bool, + #[bit(16, rw)] + rx_fifo_above_threshold: bool, + #[bit(15, rw)] + dma_illegal_command: bool, + #[bit(14, rw)] + dma_queue_overflow: bool, + #[bit(13, rw)] + dma_done: bool, + #[bit(12, rw)] + dma_pcap_done: bool, + #[bit(11, rw)] + inconsistent_pcap_to_dma_transfer_len: bool, + #[bit(6, rw)] + hamc_error: bool, + #[bit(5, rw)] + seu_error: bool, + #[bit(4, rw)] + pl_power_loss_por_b_low: bool, + #[bit(3, rw)] + pl_config_controller_under_reset: bool, + #[bit(2, rw)] + pl_programming_done: bool, + #[bit(1, rw)] + positive_edge_pl_init: bool, + #[bit(0, rw)] + negative_edge_pl_init: bool, +} + +#[bitbybit::bitfield(u32, debug)] +pub struct MiscControl { + #[bits(28..=31, r)] + ps_version: u4, + + #[bit(8, r)] + por_b_signal: bool, + + #[bit(4, rw)] + loopback: bool, +} + +#[bitbybit::bitenum(u2, exhaustive = true)] +#[derive(Debug, PartialEq, Eq)] +pub enum UnacknowledgedDmaTransfers { + None = 0b00, + One =0b01, + Two = 0b10, + ThreeOrMore = 0b11, +} + +#[bitbybit::bitfield(u32, debug)] +pub struct Status { + #[bit(31, rw)] + dma_command_queue_full: bool, + #[bit(30, rw)] + dma_command_queue_empty: bool, + #[bits(28..=29, rw)] + unacknowledged_dma_transfers: UnacknowledgedDmaTransfers, + #[bits(20..=24, rw)] + rx_fifo_level: u5, + #[bits(12..=18, rw)] + tx_fifo_level: u7, + #[bit(11, rw)] + gts_usr_b: bool, + #[bit(10, rw)] + first_config_done: bool, + #[bit(9, rw)] + global_powerdown: bool, + #[bit(8, rw)] + gts_cfg_b: bool, + #[bit(7, rw)] + secure_lockdown: bool, + #[bit(6, rw)] + illegal_apb_access: bool, + /// Active low reset bit. + #[bit(5, rw)] + pl_reset_n: bool, + #[bit(4, rw)] + pcfg_init: bool, + #[bit(3, rw)] + efuse_bbram_aes_key_disabled: bool, + #[bit(2, rw)] + efuse_sec_enable: bool, + #[bit(1, rw)] + efuse_jtag_disabled: bool + +} + +#[derive(derive_mmio::Mmio)] +#[repr(C)] +pub struct DevCfg { + control: Control, + lock: Lock, + config: Config, + /// Interrupt is cleared by writing to this register. + interrupt_status: Interrupt, + /// Bits can be set to one to mask the interrupts. + interrupt_mask: Interrupt, + status: Status, + dma_source_addr: u32, + dma_dest_addr: u32, + dma_source_len: u32, + dma_dest_len: u32, + _reserved0: u32, + multiboot_addr: u32, + _reserved1: u32, + unlock_control: u32, + _reserved2: [u32; 0x12], + misc_control: MiscControl, + + _reserved3: [u32; 0x1F], + + // Included here but not exposed to avoid providing multiple references to the same peripheral. + // Exposed in [crate::xadc]. + _xadc: crate::xadc::XAdc, +} + +static_assertions::const_assert_eq!(core::mem::size_of::(), 0x11C); + +impl DevCfg { + /// Create a new DevCfg MMIO instance for for device configuration peripheral at address + /// [DEVCFG_BASE_ADDR]. + /// + /// # 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. + pub unsafe fn new_mmio_fixed() -> MmioDevCfg<'static> { + unsafe { Self::new_mmio_at(DEVCFG_BASE_ADDR) } + } +} diff --git a/zynq7000/src/lib.rs b/zynq7000/src/lib.rs index ee52340..0db7ab8 100644 --- a/zynq7000/src/lib.rs +++ b/zynq7000/src/lib.rs @@ -27,6 +27,8 @@ pub mod l2_cache; pub mod mpcore; pub mod priv_tim; pub mod qspi; +pub mod devcfg; +pub mod xadc; pub mod slcr; pub mod spi; pub mod ttc; @@ -58,6 +60,8 @@ pub struct PsPeripherals { pub eth_0: eth::MmioEthernet<'static>, pub eth_1: eth::MmioEthernet<'static>, pub qspi: qspi::MmioQspi<'static>, + pub devcfg: devcfg::MmioDevCfg<'static>, + pub xadc: xadc::MmioXAdc<'static>, } impl PsPeripherals { @@ -96,6 +100,7 @@ impl PsPeripherals { eth_0: eth::Ethernet::new_mmio_fixed_0(), eth_1: eth::Ethernet::new_mmio_fixed_1(), qspi: qspi::Qspi::new_mmio_fixed(), + devcfg: devcfg::DevCfg::new_mmio_fixed(), } } } diff --git a/zynq7000/src/xadc.rs b/zynq7000/src/xadc.rs new file mode 100644 index 0000000..e80bf22 --- /dev/null +++ b/zynq7000/src/xadc.rs @@ -0,0 +1,29 @@ +pub const XADC_BASE_ADDR: usize = 0xF8007100; + +#[derive(derive_mmio::Mmio)] +#[repr(C)] +pub struct XAdc { + config: u32, + interrupt_status: u32, + interrupt_mask: u32, + misc_status: u32, + command_fifo: u32, + data_fifo: u32, + misc_control: u32, +} + +impl XAdc { + /// Create a new XADC MMIO instance for for device configuration peripheral at address + /// [XADC_BASE_ADDR]. + /// + /// # 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. + pub unsafe fn new_mmio_fixed() -> MmioXAdc<'static> { + unsafe { XAdc::new_mmio_at(XADC_BASE_ADDR) } + } +} + +static_assertions::const_assert_eq!(core::mem::size_of::(), 0x1C);