QSPI boot works
Some checks failed
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled

This commit is contained in:
Robin Mueller
2025-10-07 12:37:57 +02:00
parent 2c7f20695c
commit 15a91f59ab
4 changed files with 170 additions and 64 deletions

View File

@@ -9,6 +9,7 @@ use embedded_io::Write as _;
use log::{error, info}; use log::{error, info};
use zedboard_bsp::qspi_spansion::{self, QspiSpansionS25Fl256SLinearMode}; use zedboard_bsp::qspi_spansion::{self, QspiSpansionS25Fl256SLinearMode};
use zynq_boot_image::DestinationDevice; use zynq_boot_image::DestinationDevice;
use zynq7000_hal::priv_tim;
use zynq7000_hal::{ use zynq7000_hal::{
BootMode, BootMode,
clocks::{ clocks::{
@@ -91,17 +92,19 @@ pub fn main() -> ! {
let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200) let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
.unwrap() .unwrap()
.0; .0;
let mut uart = Uart::new_with_mio( let mut logger_uart = Uart::new_with_mio(
periphs.uart_1, periphs.uart_1,
Config::new_with_clk_config(uart_clk_config), Config::new_with_clk_config(uart_clk_config),
(mio_pins.mio48, mio_pins.mio49), (mio_pins.mio48, mio_pins.mio49),
) )
.unwrap(); .unwrap();
uart.write_all(b"-- Zedboard Rust FSBL --\n\r").unwrap(); logger_uart
.write_all(b"-- Zedboard Rust FSBL --\n\r")
.unwrap();
// Safety: We are not multi-threaded yet. // Safety: We are not multi-threaded yet.
unsafe { unsafe {
zynq7000_hal::log::uart_blocking::init_unsafe_single_core( zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
uart, logger_uart,
log::LevelFilter::Trace, log::LevelFilter::Trace,
false, false,
) )
@@ -135,6 +138,8 @@ pub fn main() -> ! {
l2_cache::init_with_defaults(&mut periphs.l2c); l2_cache::init_with_defaults(&mut periphs.l2c);
info!("L2 cache init done."); info!("L2 cache init done.");
let priv_tim = priv_tim::CpuPrivateTimer::take(clocks.arm_clocks()).unwrap();
if PERFORM_DDR_MEMTEST { if PERFORM_DDR_MEMTEST {
let ddr_base_addr = 0x100000; let ddr_base_addr = 0x100000;
info!("performing DDR memory test.."); info!("performing DDR memory test..");
@@ -168,14 +173,14 @@ pub fn main() -> ! {
let spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true); let spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true);
let spansion_lqspi = let spansion_lqspi =
spansion_qspi.into_linear_addressed(qspi_spansion::QSPI_DEV_COMBINATION_REV_F.into()); spansion_qspi.into_linear_addressed(qspi_spansion::QSPI_DEV_COMBINATION_REV_F.into());
qspi_boot(spansion_lqspi); qspi_boot(spansion_lqspi, priv_tim);
} }
loop { loop {
cortex_ar::asm::nop(); cortex_ar::asm::nop();
} }
} }
fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode) -> ! { fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode, _priv_tim: priv_tim::CpuPrivateTimer) -> ! {
let boot_bin_base_addr = ELF_BASE_ADDR + BOOT_BIN_STAGING_OFFSET; let boot_bin_base_addr = ELF_BASE_ADDR + BOOT_BIN_STAGING_OFFSET;
let mut boot_header_slice = unsafe { let mut boot_header_slice = unsafe {
core::slice::from_raw_parts_mut( core::slice::from_raw_parts_mut(
@@ -214,49 +219,116 @@ fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode) -> ! {
let boot_header = zynq_boot_image::BootHeader::new_unchecked(boot_header_slice); let boot_header = zynq_boot_image::BootHeader::new_unchecked(boot_header_slice);
let mut name_buf: [u8; 256] = [0; 256]; let mut name_buf: [u8; 256] = [0; 256];
for image_header in boot_header.image_header_iterator().unwrap() { let mut opt_jump_addr = None;
for (index, image_header) in boot_header.image_header_iterator().unwrap().enumerate() {
let name = image_header.image_name(&mut name_buf).unwrap(); let name = image_header.image_name(&mut name_buf).unwrap();
for partition in image_header if index == 0 {
if !name.contains("fsbl") {
log::warn!("first image name did not contain FSBL string");
}
// Skip the FSBL. It is probably currently running, and we do not want to re-flash it,
// which would also lead to a self-overwrite.
log::info!("skipping FSBL image");
continue;
}
let _read_guard = qspi.read_guard();
for (partition_index, partition) in image_header
.partition_header_iterator(boot_header_slice) .partition_header_iterator(boot_header_slice)
.unwrap() .unwrap()
.enumerate()
{ {
let section_attrs = partition.section_attributes(); let section_attrs = partition.section_attributes();
if let Ok(dest_dev) = section_attrs.destination_device() { if section_attrs.destination_device().is_err() {
match dest_dev { log::error!(
DestinationDevice::Pl => { "invalid destination device ID {}",
info!("Loading image '{name}' to PL (FPGA).."); section_attrs.destination_device().unwrap_err()
// Load the bitstream directly from linear mapped QSPI memory. );
let boot_bin_slice = unsafe { continue;
core::slice::from_raw_parts( }
(QSPI_START_ADDRESS let dest_dev = section_attrs.destination_device().unwrap();
+ partition match dest_dev {
.data_offset() DestinationDevice::Pl => {
.expect("invalid PL partition data offset")) info!("Loading image '{name}' to PL (FPGA)..");
as *const _, // Load the bitstream directly from linear mapped QSPI memory.
partition.total_partition_length().unwrap(), let boot_bin_slice = unsafe {
) core::slice::from_raw_parts(
}; (QSPI_START_ADDRESS
// The DMA will read from the linear mapped QSPI directly, so it + partition
// has to be configured for reads using the guard! .data_offset()
let _read_guard = qspi.read_guard(); .expect("invalid PL partition data offset"))
devcfg::configure_bitstream_non_secure(true, boot_bin_slice) as *const _,
.expect("unexpected unaligned address"); partition.total_partition_length().unwrap(),
log::info!("loaded bitstream successfully"); )
};
// The DMA will read from the linear mapped QSPI directly, so it
// has to be configured for reads using the guard!
devcfg::configure_bitstream_non_secure(true, boot_bin_slice)
.expect("unexpected unaligned address");
log::info!("loaded bitstream successfully");
}
DestinationDevice::Ps => {
// Load the bitstream directly from linear mapped QSPI memory.
let load_addr = partition.destination_load_address();
if load_addr < 0x10_0000 {
panic!("invalid load address which is not located in DDR memory");
} }
DestinationDevice::Ps => { log::info!(
// TODO: Load the binary into DDR. Jump at lowest load address after all "Loading partition {partition_index} for '{name}' to PS with load address {load_addr}.."
// partitions were parsed. );
}
_ => { let source_slice = unsafe {
error!("Unsupported destination device {dest_dev:?}"); core::slice::from_raw_parts(
continue; (QSPI_START_ADDRESS
+ partition
.data_offset()
.expect("invalid PS partition data offset"))
as *const _,
partition.total_partition_length().unwrap(),
)
};
let target_slice = unsafe {
core::slice::from_raw_parts_mut(
load_addr as *mut u8,
partition.total_partition_length().unwrap(),
)
};
// Copy from the linear mapped QSPI to DDR,
target_slice.copy_from_slice(source_slice);
match &mut opt_jump_addr {
Some(current) => *current = core::cmp::min(*current, load_addr),
None => opt_jump_addr = Some(load_addr),
} }
log::info!("load success");
}
_ => {
error!("Unsupported destination device {dest_dev:?}");
continue;
} }
} }
} }
} }
panic!("did not find application elf to boot inside boot binary!"); match opt_jump_addr {
Some(jump_addr) => {
log::info!("jumping to address {}", jump_addr);
zynq7000_hal::log::uart_blocking::flush();
// Some clean up and preparation for jumping to the user application.
zynq7000_hal::cache::clean_and_invalidate_data_cache();
cortex_ar::register::TlbIAll::write();
cortex_ar::register::BpIAll::write();
cortex_ar::asm::dsb();
cortex_ar::asm::isb();
let jump_func: extern "C" fn() -> ! = unsafe { core::mem::transmute(jump_addr) };
jump_func();
}
None => panic!("did not find application elf to boot inside boot binary!"),
}
} }
#[zynq7000_rt::exception(DataAbort)] #[zynq7000_rt::exception(DataAbort)]

View File

@@ -1,13 +1,23 @@
//! # Simple logging providers. //! # Simple logging providers.
use core::sync::atomic::{AtomicBool, AtomicU8};
static LOGGER_INIT_DONE: AtomicBool = AtomicBool::new(false);
const LOG_SEL_LOCKED: u8 = 1;
const LOG_SEL_UNSAFE_SINGLE_CORE: u8 = 2;
static LOG_SEL: AtomicU8 = AtomicU8::new(0);
/// Blocking UART loggers. /// Blocking UART loggers.
pub mod uart_blocking { pub mod uart_blocking {
use super::*;
use core::cell::{Cell, RefCell, UnsafeCell}; use core::cell::{Cell, RefCell, UnsafeCell};
use embedded_io::Write as _; use embedded_io::Write as _;
use cortex_ar::register::Cpsr; use cortex_ar::register::Cpsr;
use critical_section::Mutex; use critical_section::Mutex;
use log::{LevelFilter, set_logger, set_max_level}; use log::{LevelFilter, Log, set_logger, set_max_level};
use crate::uart::Uart; use crate::uart::Uart;
@@ -27,7 +37,10 @@ pub mod uart_blocking {
/// For async applications, it is strongly recommended to use the asynchronous ring buffer /// For async applications, it is strongly recommended to use the asynchronous ring buffer
/// logger instead. /// logger instead.
pub fn init_with_locks(uart: Uart, level: LevelFilter) { pub fn init_with_locks(uart: Uart, level: LevelFilter) {
// TODO: Impl debug for Uart if LOGGER_INIT_DONE.swap(true, core::sync::atomic::Ordering::Relaxed) {
return;
}
LOG_SEL.swap(LOG_SEL_LOCKED, core::sync::atomic::Ordering::Relaxed);
critical_section::with(|cs| { critical_section::with(|cs| {
let inner = UART_LOGGER_BLOCKING.0.borrow(cs); let inner = UART_LOGGER_BLOCKING.0.borrow(cs);
inner.replace(Some(uart)); inner.replace(Some(uart));
@@ -53,7 +66,16 @@ pub mod uart_blocking {
}) })
} }
fn flush(&self) {} fn flush(&self) {
critical_section::with(|cs| {
let mut opt_logger = self.0.borrow(cs).borrow_mut();
if opt_logger.is_none() {
return;
}
let logger = opt_logger.as_mut().unwrap();
logger.flush().unwrap();
});
}
} }
pub struct UartLoggerUnsafeSingleThread { pub struct UartLoggerUnsafeSingleThread {
@@ -70,23 +92,6 @@ pub mod uart_blocking {
uart: UnsafeCell::new(None), uart: UnsafeCell::new(None),
}; };
/// Initialize the logger with a blocking UART instance.
///
/// For async applications, it is strongly recommended to use the asynchronous ring buffer
/// logger instead.
///
/// # Safety
///
/// This is a blocking logger which performs a write WITHOUT a critical section. This logger is
/// NOT thread-safe. Users must ensure that this logger is not used inside a pre-emptive
/// multi-threading context and interrupt handlers.
pub unsafe fn create_unsafe_single_thread_logger(uart: Uart) -> UartLoggerUnsafeSingleThread {
UartLoggerUnsafeSingleThread {
skip_in_isr: Cell::new(false),
uart: UnsafeCell::new(Some(uart)),
}
}
/// Initialize the logger with a blocking UART instance which does not use locks. /// Initialize the logger with a blocking UART instance which does not use locks.
/// ///
/// # Safety /// # Safety
@@ -95,6 +100,13 @@ pub mod uart_blocking {
/// NOT thread-safe, which might lead to garbled output. Log output in ISRs can optionally be /// NOT thread-safe, which might lead to garbled output. Log output in ISRs can optionally be
/// surpressed. /// surpressed.
pub unsafe fn init_unsafe_single_core(uart: Uart, level: LevelFilter, skip_in_isr: bool) { pub unsafe fn init_unsafe_single_core(uart: Uart, level: LevelFilter, skip_in_isr: bool) {
if LOGGER_INIT_DONE.swap(true, core::sync::atomic::Ordering::Relaxed) {
return;
}
LOG_SEL.swap(
LOG_SEL_UNSAFE_SINGLE_CORE,
core::sync::atomic::Ordering::Relaxed,
);
let opt_uart = unsafe { &mut *UART_LOGGER_UNSAFE_SINGLE_THREAD.uart.get() }; let opt_uart = unsafe { &mut *UART_LOGGER_UNSAFE_SINGLE_THREAD.uart.get() };
opt_uart.replace(uart); opt_uart.replace(uart);
UART_LOGGER_UNSAFE_SINGLE_THREAD UART_LOGGER_UNSAFE_SINGLE_THREAD
@@ -134,7 +146,22 @@ pub mod uart_blocking {
.unwrap(); .unwrap();
} }
fn flush(&self) {} fn flush(&self) {
let uart_mut = unsafe { &mut *self.uart.get() }.as_mut();
if uart_mut.is_none() {
return;
}
uart_mut.unwrap().flush().unwrap();
}
}
// Flush the selected logger instance.
pub fn flush() {
match LOG_SEL.load(core::sync::atomic::Ordering::Relaxed) {
val if val == LOG_SEL_LOCKED => UART_LOGGER_BLOCKING.flush(),
val if val == LOG_SEL_UNSAFE_SINGLE_CORE => UART_LOGGER_UNSAFE_SINGLE_THREAD.flush(),
_ => (),
}
} }
} }
@@ -205,6 +232,9 @@ pub mod rb {
} }
pub fn init(level: LevelFilter) { pub fn init(level: LevelFilter) {
if super::LOGGER_INIT_DONE.swap(true, core::sync::atomic::Ordering::Relaxed) {
return;
}
critical_section::with(|cs| { critical_section::with(|cs| {
let rb = StaticRb::<u8, 4096>::default(); let rb = StaticRb::<u8, 4096>::default();
let rb_ref = LOGGER_RB.ring_buf.borrow(cs); let rb_ref = LOGGER_RB.ring_buf.borrow(cs);

View File

@@ -578,7 +578,7 @@ impl embedded_hal_nb::serial::Write for Uart {
} }
fn flush(&mut self) -> nb::Result<(), Self::Error> { fn flush(&mut self) -> nb::Result<(), Self::Error> {
self.tx.flush() embedded_hal_nb::serial::Write::flush(&mut self.tx)
} }
} }
@@ -604,7 +604,8 @@ impl embedded_io::Write for Uart {
} }
fn flush(&mut self) -> Result<(), Self::Error> { fn flush(&mut self) -> Result<(), Self::Error> {
self.tx.flush() self.tx.flush();
Ok(())
} }
} }

View File

@@ -78,6 +78,10 @@ impl Tx {
} }
} }
pub fn flush(&mut self) {
while !self.regs.read_sr().tx_empty() {}
}
#[inline] #[inline]
pub fn write_fifo_unchecked(&mut self, word: u8) { pub fn write_fifo_unchecked(&mut self, word: u8) {
self.regs.write_fifo(Fifo::new_with_raw_value(word as u32)); self.regs.write_fifo(Fifo::new_with_raw_value(word as u32));
@@ -161,11 +165,10 @@ impl embedded_hal_nb::serial::Write for Tx {
} }
fn flush(&mut self) -> nb::Result<(), Self::Error> { fn flush(&mut self) -> nb::Result<(), Self::Error> {
loop { if self.regs.read_sr().tx_empty() {
if self.regs.read_sr().tx_empty() { return Ok(());
return Ok(());
}
} }
Err(nb::Error::WouldBlock)
} }
} }
@@ -195,7 +198,7 @@ impl embedded_io::Write for Tx {
} }
fn flush(&mut self) -> Result<(), Self::Error> { fn flush(&mut self) -> Result<(), Self::Error> {
<Self as embedded_hal_nb::serial::Write<u8>>::flush(self).ok(); self.flush();
Ok(()) Ok(())
} }
} }