From 077a44017d92b5862ae653854d980f71f3eb94d6 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sat, 27 Sep 2025 15:53:20 +0200 Subject: [PATCH] finished QSPI flasher --- examples/embassy/src/main.rs | 2 +- examples/zedboard/src/bin/qspi.rs | 2 +- examples/zedboard/src/main.rs | 2 +- zedboard-bsp/src/qspi_spansion.rs | 67 +++++++++++++++--------- zedboard-fsbl/src/main.rs | 18 +++++-- zedboard-qspi-flasher/Cargo.toml | 1 + zedboard-qspi-flasher/src/main.rs | 86 +++++++++++++++++++++++++++++-- zynq7000-hal/src/lib.rs | 2 +- 8 files changed, 145 insertions(+), 35 deletions(-) diff --git a/examples/embassy/src/main.rs b/examples/embassy/src/main.rs index afbdfe4..1047373 100644 --- a/examples/embassy/src/main.rs +++ b/examples/embassy/src/main.rs @@ -8,7 +8,7 @@ use embassy_time::{Duration, Ticker}; use embedded_hal::digital::StatefulOutputPin; use embedded_io::Write; use log::{error, info}; -use zynq7000_hal::{clocks, gic, gpio, gtc, time::Hertz, uart, BootMode, InteruptConfig}; +use zynq7000_hal::{BootMode, InteruptConfig, clocks, gic, gpio, gtc, time::Hertz, uart}; use zynq7000_rt as _; diff --git a/examples/zedboard/src/bin/qspi.rs b/examples/zedboard/src/bin/qspi.rs index 7c1e30f..89fd2e8 100644 --- a/examples/zedboard/src/bin/qspi.rs +++ b/examples/zedboard/src/bin/qspi.rs @@ -10,7 +10,7 @@ use embedded_io::Write; use log::{error, info}; use zedboard::PS_CLOCK_FREQUENCY; use zedboard_bsp::qspi_spansion; -use zynq7000_hal::{clocks, gic, gpio, gtc, prelude::*, qspi, uart, BootMode}; +use zynq7000_hal::{BootMode, clocks, gic, gpio, gtc, prelude::*, qspi, uart}; use zynq7000_rt as _; diff --git a/examples/zedboard/src/main.rs b/examples/zedboard/src/main.rs index b08c435..59c4962 100644 --- a/examples/zedboard/src/main.rs +++ b/examples/zedboard/src/main.rs @@ -9,7 +9,7 @@ use embedded_hal::digital::StatefulOutputPin; use embedded_io::Write; use log::{error, info}; use zedboard::PS_CLOCK_FREQUENCY; -use zynq7000_hal::{clocks, gic, gpio, gtc, uart, BootMode}; +use zynq7000_hal::{BootMode, clocks, gic, gpio, gtc, uart}; use zynq7000_rt as _; diff --git a/zedboard-bsp/src/qspi_spansion.rs b/zedboard-bsp/src/qspi_spansion.rs index 31258d6..9eafd3b 100644 --- a/zedboard-bsp/src/qspi_spansion.rs +++ b/zedboard-bsp/src/qspi_spansion.rs @@ -64,6 +64,8 @@ pub enum SectorArchictecture { Hybrid = 0x01, } +pub const PAGE_SIZE: usize = 256; + #[derive(Debug, Clone, Copy)] pub struct BaseDeviceId { manufacturer_id: u8, @@ -221,6 +223,8 @@ pub enum ProgramPageError { ProgrammingErrorBitSet, #[error("address error: {0}")] Addr(#[from] AddrError), + #[error("data is larger than page size {PAGE_SIZE}")] + DataLargerThanPage, } pub struct QspiSpansionS25Fl256SIoMode(RefCell); @@ -428,14 +432,17 @@ impl QspiSpansionS25Fl256SIoMode { /// This function also takes care of enabling writes before programming the page. /// This function will block until the operation has completed. /// - /// TODO: Allow smaller write size - pub fn program_page(&mut self, addr: u32, data: &[u8; 256]) -> Result<(), ProgramPageError> { + /// The data length max not exceed the page size [PAGE_SIZE]. + pub fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ProgramPageError> { if addr + data.len() as u32 > u24::MAX.as_u32() { return Err(AddrError::OutOfRange.into()); } if !addr.is_multiple_of(0x100) { return Err(AddrError::Alignment.into()); } + if data.len() > PAGE_SIZE { + return Err(ProgramPageError::DataLargerThanPage); + } self.write_enable(); let qspi = self.0.get_mut(); let mut transfer = qspi.transfer_guard(); @@ -448,8 +455,9 @@ impl QspiSpansionS25Fl256SIoMode { transfer.write_word_txd_00(u32::from_ne_bytes(raw_word)); let mut read_index: u32 = 0; let mut current_byte_index = 0; + let fifo_writes = data.len().div_ceil(4); // Fill the FIFO until it is full. - for _ in 0..FIFO_DEPTH - 1 { + for _ in 0..core::cmp::min(fifo_writes, FIFO_DEPTH - 1) { transfer.write_word_txd_00(u32::from_ne_bytes( data[current_byte_index..current_byte_index + 4] .try_into() @@ -470,25 +478,38 @@ impl QspiSpansionS25Fl256SIoMode { } }; - // Immediately fill the FIFO again with the remaining 8 bytes. - wait_for_tx_slot(&mut transfer); + while current_byte_index < data.len() { + // Immediately fill the FIFO again with the remaining 8 bytes. + wait_for_tx_slot(&mut transfer); - transfer.write_word_txd_00(u32::from_ne_bytes( - data[current_byte_index..current_byte_index + 4] - .try_into() - .unwrap(), - )); - current_byte_index += 4; + let word = match core::cmp::min(4, data.len() - current_byte_index) { + 1 => { + let mut bytes = [0; 4]; + bytes[0] = data[current_byte_index]; + u32::from_ne_bytes(bytes) + } + 2 => { + let mut bytes = [0; 4]; + bytes[0..2].copy_from_slice(&data[current_byte_index..current_byte_index + 2]); + u32::from_ne_bytes(bytes) + } + 3 => { + let mut bytes = [0; 4]; + bytes[0..3].copy_from_slice(&data[current_byte_index..current_byte_index + 3]); + u32::from_ne_bytes(bytes) + } + 4 => u32::from_ne_bytes( + data[current_byte_index..current_byte_index + 4] + .try_into() + .unwrap(), + ), + _ => unreachable!(), + }; + transfer.write_word_txd_00(word); + current_byte_index += 4; + } - wait_for_tx_slot(&mut transfer); - - transfer.write_word_txd_00(u32::from_ne_bytes( - data[current_byte_index..current_byte_index + 4] - .try_into() - .unwrap(), - )); - - while read_index < 256 { + while read_index < data.len() as u32 { if transfer.read_status().rx_above_threshold() { transfer.read_rx_data(); read_index = read_index.wrapping_add(4); @@ -528,11 +549,7 @@ impl QspiSpansionS25Fl256SIoMode { if dummy_byte { bytes_to_write += 1; } - let fifo_writes = if bytes_to_write.is_multiple_of(4) { - bytes_to_write / 4 - } else { - (bytes_to_write / 4) + 1 - }; + let fifo_writes = bytes_to_write.div_ceil(4); // Fill the FIFO until it is full or all 0 bytes have been written. for _ in 0..core::cmp::min(fifo_writes, FIFO_DEPTH - 1) { transfer.write_word_txd_00(0); diff --git a/zedboard-fsbl/src/main.rs b/zedboard-fsbl/src/main.rs index a7e1d68..ce9fdad 100644 --- a/zedboard-fsbl/src/main.rs +++ b/zedboard-fsbl/src/main.rs @@ -16,9 +16,9 @@ use zynq7000_hal::{ pll::{PllConfig, configure_arm_pll, configure_io_pll}, }, ddr::{DdrClockSetupConfig, configure_ddr_for_ddr3, memtest}, - gic, gpio, l2_cache, + devcfg, gic, gpio, l2_cache, prelude::*, - qspi, + qspi::{self, QSPI_START_ADDRESS}, time::Hertz, uart::{ClockConfig, Config, Uart}, }; @@ -225,7 +225,19 @@ fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode) -> ! { match dest_dev { DestinationDevice::Pl => { info!("Loading image '{name}' to PL (FPGA).."); - // TODO: Load the bitstream. + // Load the bitstream directly from linear mapped QSPI memory. + let boot_bin_slice = unsafe { + core::slice::from_raw_parts( + (QSPI_START_ADDRESS + + partition + .data_offset() + .expect("invalid PL partition data offset")) + as *const _, + partition.total_partition_length().unwrap(), + ) + }; + devcfg::configure_bitstream_non_secure(true, boot_bin_slice) + .expect("unexpected unaligned address"); } DestinationDevice::Ps => { // TODO: Load the binary into DDR. Jump at lowest load address after all diff --git a/zedboard-qspi-flasher/Cargo.toml b/zedboard-qspi-flasher/Cargo.toml index 77d7669..03ad697 100644 --- a/zedboard-qspi-flasher/Cargo.toml +++ b/zedboard-qspi-flasher/Cargo.toml @@ -8,6 +8,7 @@ cortex-ar = { version = "0.2", git = "https://github.com/rust-embedded/cortex-ar zynq7000-rt = { path = "../zynq7000-rt" } zynq7000 = { path = "../zynq7000" } zynq7000-hal = { path = "../zynq7000-hal" } +zynq-boot-image = { path = "../zynq-boot-image" } zedboard-bsp = { path = "../zedboard-bsp" } embedded-io = "0.6" embedded-hal = "1" diff --git a/zedboard-qspi-flasher/src/main.rs b/zedboard-qspi-flasher/src/main.rs index e43c3f7..a805531 100644 --- a/zedboard-qspi-flasher/src/main.rs +++ b/zedboard-qspi-flasher/src/main.rs @@ -7,10 +7,11 @@ use core::panic::PanicInfo; use cortex_ar::asm::nop; use embedded_hal::{delay::DelayNs as _, digital::StatefulOutputPin as _}; use embedded_io::Write as _; -use log::info; +use log::{error, info}; use zedboard_bsp::qspi_spansion; +use zynq_boot_image::BootHeader; use zynq7000_hal::{ - clocks, gpio, prelude::*, priv_tim, qspi, time::Hertz, uart, BootMode, LevelShifterConfig, + BootMode, LevelShifterConfig, clocks, gpio, prelude::*, priv_tim, qspi, time::Hertz, uart, }; use zynq7000_rt as _; @@ -19,6 +20,15 @@ use zynq7000_rt as _; // Not required for the PAC mode, is required for clean delays in HAL mode. const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_333); +// TODO: Make this configurable somehow? +const BOOT_BIN_BASE_ADDR: usize = 0x1000_0000; +const BOOT_BIN_SIZE_ADDR: usize = 0x900_000; + +// Maximum of 16 MB is allowed for now. +const MAX_BOOT_BIN_SIZE: usize = 16 * 1024 * 1024; + +const VERIFY_PROGRAMMING: bool = true; + #[allow(dead_code)] const QSPI_DEV_COMBINATION: qspi::QspiDeviceCombination = qspi::QspiDeviceCombination { vendor: qspi::QspiVendor::WinbondAndSpansion, @@ -96,7 +106,77 @@ pub fn main() -> ! { let qspi_io_mode = qspi.into_io_mode(false); - let _spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true); + let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true); + + let mut boot_bin_slice = unsafe { + core::slice::from_raw_parts(BOOT_BIN_BASE_ADDR as *const _, BootHeader::FIXED_SIZED_PART) + }; + // This perform some basic validity checks. + let _boot_header = BootHeader::new(&boot_bin_slice[0..BootHeader::FIXED_SIZED_PART]) + .expect("failed to parse boot header"); + let boot_bin_size = + unsafe { core::ptr::read_volatile(BOOT_BIN_SIZE_ADDR as *const u32) as usize }; + if boot_bin_size == 0 || boot_bin_size > MAX_BOOT_BIN_SIZE { + panic!( + "boot binary size read at address {:#x} is invalid: found {}, must be in range [0, {}]", + BOOT_BIN_SIZE_ADDR, boot_bin_size, MAX_BOOT_BIN_SIZE + ); + } + boot_bin_slice = + unsafe { core::slice::from_raw_parts(BOOT_BIN_BASE_ADDR as *const _, boot_bin_size) }; + info!( + "flashing boot binary with {} bytes to QSPI address 0x0", + boot_bin_size + ); + + let mut current_addr = 0; + let mut read_buf = [0u8; 256]; + while current_addr < boot_bin_size { + if current_addr % 0x10000 == 0 { + info!("Erasing sector at address {:#x}", current_addr); + match spansion_qspi.erase_sector(current_addr as u32) { + Ok(()) => {} + Err(e) => { + error!( + "failed to erase sector at address {:#x}: {:?}", + current_addr, e + ); + panic!("QSPI erase failed"); + } + } + } + let write_size = core::cmp::min(256, boot_bin_size - current_addr); + let write_slice = &boot_bin_slice[current_addr..current_addr + write_size]; + info!("Programming address {:#x}", current_addr); + match spansion_qspi.program_page(current_addr as u32, write_slice) { + Ok(()) => {} + Err(e) => { + error!( + "failed to write data to QSPI at address {:#x}: {:?}", + current_addr, e + ); + panic!("QSPI write failed"); + } + } + if VERIFY_PROGRAMMING { + spansion_qspi.read_page_fast_read( + current_addr as u32, + &mut read_buf[0..write_size], + true, + ); + if &read_buf[0..write_size] != write_slice { + error!( + "data verification failed at address {:#x}: wrote {:x?}, read {:x?}", + current_addr, + &write_slice[0..core::cmp::min(16, write_size)], + &read_buf[0..core::cmp::min(16, write_size)] + ); + panic!("QSPI data verification failed"); + } + } + current_addr += write_size; + } + info!("flashing done"); let mut mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low); loop { diff --git a/zynq7000-hal/src/lib.rs b/zynq7000-hal/src/lib.rs index c04ea6e..56390bf 100644 --- a/zynq7000-hal/src/lib.rs +++ b/zynq7000-hal/src/lib.rs @@ -14,8 +14,8 @@ extern crate alloc; use slcr::Slcr; use zynq7000::{ - slcr::{BootModeRegister, BootPllConfig, LevelShifterRegister}, SpiClockPhase, SpiClockPolarity, + slcr::{BootModeRegister, BootPllConfig, LevelShifterRegister}, }; pub mod cache;