diff --git a/.cargo/def-config.toml b/.cargo/config.toml.template similarity index 100% rename from .cargo/def-config.toml rename to .cargo/config.toml.template diff --git a/README.md b/README.md index 7a1e81e..373294b 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ The folder contains a README with all the steps required to load this project fr Use the following command to have a starting `config.toml` file ```sh -cp .cargo/def-config.toml .cargo/config.toml +cp .cargo/config.toml.template .cargo/config.toml ``` You then can adapt the `config.toml` to your needs. For example, you can configure runners diff --git a/zedboard-bsp/src/qspi_spansion.rs b/zedboard-bsp/src/qspi_spansion.rs index d86062d..31258d6 100644 --- a/zedboard-bsp/src/qspi_spansion.rs +++ b/zedboard-bsp/src/qspi_spansion.rs @@ -6,6 +6,13 @@ use zynq7000_hal::qspi::{ QspiLinearReadGuard, }; +pub const QSPI_DEV_COMBINATION_REV_F: zynq7000_hal::qspi::QspiDeviceCombination = + zynq7000_hal::qspi::QspiDeviceCombination { + vendor: zynq7000_hal::qspi::QspiVendor::WinbondAndSpansion, + operating_mode: zynq7000_hal::qspi::OperatingMode::FastReadQuadOutput, + two_devices: false, + }; + #[derive(Debug, Clone, Copy)] pub enum RegisterId { /// WRR @@ -593,6 +600,8 @@ impl QspiSpansionS25Fl256SIoMode { pub struct QspiSpansionS25Fl256SLinearMode(QspiLinearAddressing); impl QspiSpansionS25Fl256SLinearMode { + pub const BASE_ADDR: usize = QspiLinearAddressing::BASE_ADDRESS; + pub fn into_io_mode(self, dual_flash: bool) -> QspiSpansionS25Fl256SIoMode { let qspi = self.0.into_io_mode(dual_flash); QspiSpansionS25Fl256SIoMode::new(qspi, false) diff --git a/zedboard-fsbl/Cargo.toml b/zedboard-fsbl/Cargo.toml index 204267a..00a44e2 100644 --- a/zedboard-fsbl/Cargo.toml +++ b/zedboard-fsbl/Cargo.toml @@ -13,6 +13,8 @@ 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" } +zedboard-bsp = { path = "../zedboard-bsp" } +zynq-boot-image = { path = "../zynq-boot-image" } embedded-io = "0.6" embedded-hal = "1" fugit = "0.3" diff --git a/zedboard-fsbl/src/main.rs b/zedboard-fsbl/src/main.rs index 9f0512a..1c601f7 100644 --- a/zedboard-fsbl/src/main.rs +++ b/zedboard-fsbl/src/main.rs @@ -7,6 +7,8 @@ use core::panic::PanicInfo; 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::{ @@ -15,8 +17,10 @@ use zynq7000_hal::{ }, ddr::{DdrClockSetupConfig, configure_ddr_for_ddr3, memtest}, gic::GicConfigurator, - gpio::GpioPins, + gpio::{GpioPins, mio}, l2_cache, + prelude::*, + qspi::{self, QSPI_START_ADDRESS}, time::Hertz, uart::{ClockConfigRaw, Uart, UartConfig}, }; @@ -42,6 +46,19 @@ const DDR_CLK: Hertz = Hertz::from_raw(2 * DDR_FREQUENCY.raw()); const PERFORM_DDR_MEMTEST: bool = false; +#[derive(Debug, PartialEq, Eq)] +pub enum BootMethod { + Qspi, + SdCard, +} + +pub const BOOT_METHOD: BootMethod = BootMethod::Qspi; + +pub const ELF_BASE_ADDR: usize = 0x100000; + +/// 8 MB reserved for application ELF. +pub const BOOT_BIN_STAGING_OFFSET: usize = 8 * 1024 * 1024; + /// Entry point (not called like a normal main function) #[unsafe(no_mangle)] pub extern "C" fn boot_core(cpu_id: u32) -> ! { @@ -130,6 +147,79 @@ pub fn main() -> ! { } info!("DDR memory test success."); } + + if BOOT_METHOD == BootMethod::Qspi { + let qspi_clock_config = qspi::ClockConfig::calculate_with_loopback( + zynq7000::slcr::clocks::SrcSelIo::IoPll, + &clocks, + 100.MHz(), + ) + .expect("QSPI clock calculation failed"); + let qspi = qspi::Qspi::new_single_qspi_with_feedback( + dp.qspi, + qspi_clock_config, + embedded_hal::spi::MODE_0, + qspi::IoType::LvCmos33, + mio_pins.mio1, + (mio_pins.mio2, mio_pins.mio3, mio_pins.mio4, mio_pins.mio5), + mio_pins.mio6, + mio_pins.mio8, + ); + + let qspi_io_mode = qspi.into_io_mode(false); + let spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true); + let spansion_lqspi = + spansion_qspi.into_linear_addressed(qspi_spansion::QSPI_DEV_COMBINATION_REV_F.into()); + qspi_boot(spansion_lqspi); + } + loop { + cortex_ar::asm::nop(); + } +} + +fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode) -> ! { + let boot_bin_base_addr = ELF_BASE_ADDR + BOOT_BIN_STAGING_OFFSET; + let mut boot_header_slice = unsafe { + core::slice::from_raw_parts_mut( + boot_bin_base_addr as *mut u8, + zynq_boot_image::FIXED_BOOT_HEADER_SIZE, + ) + }; + let read_guard = qspi.read_guard(); + // Currently, only boot.bin at address 0x0 of the QSPI is supported. + unsafe { + core::ptr::copy_nonoverlapping( + QspiSpansionS25Fl256SLinearMode::BASE_ADDR as *mut u8, + boot_header_slice.as_mut_ptr(), + zynq_boot_image::FIXED_BOOT_HEADER_SIZE, + ); + } + drop(read_guard); + + let boot_header = zynq_boot_image::BootHeader::new(boot_header_slice).unwrap(); + let fsbl_offset = boot_header.source_offset(); + boot_header_slice = + unsafe { core::slice::from_raw_parts_mut(boot_bin_base_addr as *mut u8, fsbl_offset) }; + + // Read the rest of the boot header metadata. + let read_guard = qspi.read_guard(); + unsafe { + core::ptr::copy_nonoverlapping( + (QspiSpansionS25Fl256SLinearMode::BASE_ADDR + zynq_boot_image::FIXED_BOOT_HEADER_SIZE) + as *mut u8, + boot_header_slice[zynq_boot_image::FIXED_BOOT_HEADER_SIZE..].as_mut_ptr(), + fsbl_offset - zynq_boot_image::FIXED_BOOT_HEADER_SIZE, + ); + } + drop(read_guard); + + let boot_header = zynq_boot_image::BootHeader::new_unchecked(boot_header_slice); + + 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(); + } + loop { cortex_ar::asm::nop(); } diff --git a/zynq-boot-image/Cargo.toml b/zynq-boot-image/Cargo.toml index 03a0c28..922b69b 100644 --- a/zynq-boot-image/Cargo.toml +++ b/zynq-boot-image/Cargo.toml @@ -5,3 +5,5 @@ edition = "2024" [dependencies] thiserror = { version = "2", default-features = false } +arbitrary-int = "2" +bitbybit = "1.4" diff --git a/zynq-boot-image/src/lib.rs b/zynq-boot-image/src/lib.rs index c908577..a88d2cc 100644 --- a/zynq-boot-image/src/lib.rs +++ b/zynq-boot-image/src/lib.rs @@ -8,6 +8,16 @@ pub const IMAGE_ID_U32: u32 = 0x584C4E58; /// This is the fixed size of the boot header. pub const FIXED_BOOT_HEADER_SIZE: usize = 0xA0; +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +pub enum InvalidBootHeader { + #[error("image ID invalid")] + ImageIdInvalid, + #[error("checksum is invalid")] + ChecksumInvalid, + #[error("provided data slice too small")] + DataTooSmall, +} + pub struct BootHeader<'a> { //base_addr: usize, // Snapshot of the boot header and following data (at least fixed header size) @@ -27,15 +37,18 @@ impl<'a> BootHeader<'a> { /// The passed buffer must have a minimal size of [Self::FIXED_SIZED_PART]. /// This constructor calls [Self::check_image_id_validity] and [Self::verify_header_checksum] /// to verify whether the boot header structure is actually valid. - pub fn new(data: &'a [u8]) -> Option { + pub fn new(data: &'a [u8]) -> Result { if data.len() < Self::FIXED_SIZED_PART { - return None; + return Err(InvalidBootHeader::DataTooSmall); } let header = BootHeader { data }; - if !header.check_image_id_validity() || !header.verify_header_checksum() { - return None; + if !header.check_image_id_validity() { + return Err(InvalidBootHeader::ImageIdInvalid); } - Some(header) + if !header.verify_header_checksum() { + return Err(InvalidBootHeader::ChecksumInvalid); + } + Ok(header) } #[inline] @@ -55,21 +68,13 @@ impl<'a> BootHeader<'a> { self.image_id() == IMAGE_ID_U32 } + /// Offset to the FSBL image in bytes. This information can be used to only extract the boot + /// binary metadata (everything except actual partition data). #[inline] pub fn source_offset(&self) -> usize { self.read_u32_le(0x30) as usize } - // The boot image metadata is all the data up to the first partition of the FSBL. - // - // This information can be used to copy all relevant information for image and partition - // parsing into RAM. This includes the image header table, the image headers and the - // partition headers. - //#[inline] - //pub fn boot_image_metadata_len(&self) -> usize { - //self.source_offset() - self.base_addr - //} - #[inline] pub fn header_checksum(&self) -> u32 { self.read_u32_le(0x48) @@ -329,6 +334,44 @@ pub struct PartitionHeader<'a> { header_data: &'a [u8], } +#[bitbybit::bitenum(u2, exhaustive = false)] +#[derive(Debug)] +#[non_exhaustive] +pub enum PartitionOwner { + Fsbl = 0, + Uboot = 1, +} + +#[bitbybit::bitenum(u3, exhaustive = false)] +#[derive(Debug)] +#[non_exhaustive] +pub enum ChecksumType { + None = 0, + Md5 = 1, +} + +#[bitbybit::bitenum(u4, exhaustive = false)] +#[derive(Debug)] +#[non_exhaustive] +pub enum DestinationDevice { + None = 0, + Ps = 1, + Pl = 2, + Int = 3, +} + +#[bitbybit::bitfield(u32, debug)] +pub struct SectionAttributes { + #[bits(16..=17, rw)] + partition_owner: Option, + #[bit(15, rw)] + rsa_signature_present: bool, + #[bits(12..=14, rw)] + checksum_type: Option, + #[bits(4..=7, rw)] + destination_device: Option, +} + impl<'a> PartitionHeader<'a> { pub const SIZE: usize = 0x40; @@ -372,6 +415,16 @@ impl<'a> PartitionHeader<'a> { self.read_u32_le(0x10) } + #[inline] + pub fn section_attributes(&self) -> SectionAttributes { + SectionAttributes::new_with_raw_value(self.section_attributes_raw()) + } + + #[inline] + pub fn section_attributes_raw(&self) -> u32 { + self.read_u32_le(0x18) + } + #[inline] pub fn section_count(&self) -> usize { self.read_u32_le(0x1C) as usize diff --git a/zynq-boot-image/tester/Cargo.lock b/zynq-boot-image/tester/Cargo.lock index a6fe1da..e8d5fdc 100644 --- a/zynq-boot-image/tester/Cargo.lock +++ b/zynq-boot-image/tester/Cargo.lock @@ -52,6 +52,30 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "arbitrary-int" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "825297538d77367557b912770ca3083f778a196054b3ee63b22673c4a3cae0a5" + +[[package]] +name = "arbitrary-int" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c858caffa49edfc4ecc45a4bec37abd3e88041a2903816f10f990b7b41abc281" + +[[package]] +name = "bitbybit" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec187a89ab07e209270175faf9e07ceb2755d984954e58a2296e325ddece2762" +dependencies = [ + "arbitrary-int 1.3.0", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "clap" version = "4.5.46" @@ -275,5 +299,7 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" name = "zynq-boot-image" version = "0.1.0" dependencies = [ + "arbitrary-int 2.0.0", + "bitbybit", "thiserror", ] diff --git a/zynq-boot-image/tester/src/main.rs b/zynq-boot-image/tester/src/main.rs index 29aa0ba..bff0c55 100644 --- a/zynq-boot-image/tester/src/main.rs +++ b/zynq-boot-image/tester/src/main.rs @@ -51,6 +51,7 @@ fn main() { .image_header_iterator() .expect("failed extracting boot header iterator"); for (idx, image_header) in image_header_iter.enumerate() { + println!("--------------------------------------"); println!( "Image header {} with partition count {}", idx, @@ -64,6 +65,9 @@ fn main() { let partition_iter = image_header .partition_header_iterator(header_buf.as_slice()) .unwrap(); + if image_header.partition_count() > 0 { + println!("--------------------------------------"); + } for partition in partition_iter { println!( "partition with size {} and load address {:#08x}, section count {}", @@ -71,6 +75,8 @@ fn main() { partition.destination_load_address(), partition.section_count() ); + println!("section attributes: {:?}", partition.section_attributes()); } + println!("--------------------------------------\n\r"); } } diff --git a/zynq7000-hal/src/qspi/mod.rs b/zynq7000-hal/src/qspi/mod.rs index 1e196ef..cff4353 100644 --- a/zynq7000-hal/src/qspi/mod.rs +++ b/zynq7000-hal/src/qspi/mod.rs @@ -623,6 +623,9 @@ pub struct QspiLinearAddressing { } impl QspiLinearAddressing { + /// Memory-mapped QSPI base address. + pub const BASE_ADDRESS: usize = QSPI_START_ADDRESS; + pub fn into_io_mode(mut self, dual_flash: bool) -> QspiIoMode { self.ll.enable_io_mode(dual_flash); QspiIoMode { ll: self.ll } @@ -636,6 +639,9 @@ impl QspiLinearAddressing { pub struct QspiLinearReadGuard<'a>(&'a mut QspiLinearAddressing); impl QspiLinearReadGuard<'_> { + /// Memory-mapped QSPI base address. + pub const BASE_ADDRESS: usize = QSPI_START_ADDRESS; + pub fn new(qspi: &mut QspiLinearAddressing) -> QspiLinearReadGuard<'_> { qspi.ll .0