Add erase/write/read verify test. fixed erase/write transit user error seperated SPI and FMC segment range calculation.
diff --git a/target/ast10x0/peripherals/smc/controller.rs b/target/ast10x0/peripherals/smc/controller.rs index 1ae7248..f588ba0 100644 --- a/target/ast10x0/peripherals/smc/controller.rs +++ b/target/ast10x0/peripherals/smc/controller.rs
@@ -7,8 +7,9 @@ use core::marker::PhantomData; use crate::smc::helpers::{ - encode_segment, flash_capacity_bytes, get_mid_point_of_longest_one, spi_calibration_enable, - spi_freq_div, total_capacity_bytes, validate_dma_read, validate_mapped_range, + encode_fmc_segment, encode_spi_segment, flash_capacity_bytes, get_mid_point_of_longest_one, + spi_calibration_enable, spi_freq_div, total_capacity_bytes, validate_dma_read, + validate_mapped_range, SPI_CTRL_FREQ_MASK, SPI_DMA_CALC_CKSUM, SPI_DMA_CALIB_MODE, SPI_DMA_ENABLE, SPI_DMA_RAM_MAP_BASE, }; @@ -179,6 +180,13 @@ _mode: PhantomData, }) } + fn encode_segment(&self, start: usize, end: usize) -> Result<u32, SmcError> { + match self.config.controller_id { + SmcController::Fmc => encode_fmc_segment(start, end), + SmcController::Spi1 | SmcController::Spi2 => encode_spi_segment(start, end), + } + } + fn setup_segments(&self) -> Result<(), SmcError> { // Decode-range sizing is topology-aware. // @@ -198,12 +206,12 @@ total_capacity_bytes(self.config.cs0, self.config.cs1)?; if cs0_size > 0 { - let seg = encode_segment(0, cs0_size)?; + let seg = self.encode_segment(0, cs0_size)?; self.regs.write_cs0_segment(seg); } if cs1_size > 0 { - let seg = encode_segment(cs0_size, cs0_size + cs1_size)?; + let seg = self.encode_segment(cs0_size, cs0_size + cs1_size)?; self.regs.write_cs1_segment(seg); } @@ -506,7 +514,7 @@ let cs_idx = cs as usize; // Derive user-mode base from the stored normal-read value: preserve // frequency bits and replace mode type with ASPEED_SPI_USER. - let user_base = (self.normal_read_ctrl[cs_idx] & SPI_CTRL_FREQ_MASK) | ASPEED_SPI_USER; + let user_base = (self.normal_read_ctrl[cs_idx] & !0x7) | ASPEED_SPI_USER; let window = self.flash_window_base[cs_idx] as *mut u32; // Assert CS: inactive first, then active (matches aspeed-rust activate_user).
diff --git a/target/ast10x0/peripherals/smc/device/flash.rs b/target/ast10x0/peripherals/smc/device/flash.rs index 70992a5..062409e 100644 --- a/target/ast10x0/peripherals/smc/device/flash.rs +++ b/target/ast10x0/peripherals/smc/device/flash.rs
@@ -82,6 +82,10 @@ pub const READ_ID: u8 = 0x9F; } +const STATUS_WIP: u8 = 0x01; +const WRITE_COMPLETE_MAX_POLLS: u32 = 1_000_000; +const STATUS_POLL_SPIN_DELAY: u32 = 64; + /// Addressing policy for SPI NOR command transactions. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum FlashAddressingPolicy { @@ -166,6 +170,12 @@ Ok(true) } +fn poll_delay() { + for _ in 0..STATUS_POLL_SPIN_DELAY { + core::hint::spin_loop(); + } +} + enum FlashBackend<'a> { Fmc(&'a FmcReady), Spi(&'a SpiReady), @@ -472,13 +482,19 @@ Ok(id) } + fn write_enable(&mut self) -> Result<(), SmcError> { + let profile = self.command_profile(); + self.issue_command(&[profile.write_enable], &[]) + } + fn wait_write_complete(&self, max_polls: u32) -> Result<(), SmcError> { let mut polls = 0u32; while polls < max_polls { let sr = self.read_status_impl()?; - if (sr & 0x01) == 0 { + if (sr & STATUS_WIP) == 0 { return Ok(()); } + poll_delay(); polls += 1; } Err(SmcError::Timeout) @@ -510,10 +526,10 @@ let profile = self.command_profile(); let width = self.addr_width(); - self.issue_command(&[profile.write_enable], &[])?; + self.write_enable()?; let (cmd, len) = encode_addr_cmd(profile.erase_sector_4k, offset, width); self.issue_command(&cmd[..len], &[])?; - self.wait_write_complete(10_000) + self.wait_write_complete(WRITE_COMPLETE_MAX_POLLS) } fn program_page(&mut self, offset: u32, data: &[u8]) -> Result<usize, SmcError> { @@ -521,10 +537,10 @@ let profile = self.command_profile(); let width = self.addr_width(); - self.issue_command(&[profile.write_enable], &[])?; + self.write_enable()?; let (cmd, len) = encode_addr_cmd(profile.page_program, offset, width); self.issue_command(&cmd[..len], data)?; - self.wait_write_complete(10_000)?; + self.wait_write_complete(WRITE_COMPLETE_MAX_POLLS)?; Ok(data.len()) }
diff --git a/target/ast10x0/peripherals/smc/helpers.rs b/target/ast10x0/peripherals/smc/helpers.rs index 78a889a..27c9d0d 100644 --- a/target/ast10x0/peripherals/smc/helpers.rs +++ b/target/ast10x0/peripherals/smc/helpers.rs
@@ -164,18 +164,32 @@ }) } -/// Encode a memory segment into hardware register format. +/// Encode an FMC memory segment into hardware register format. /// -/// Hardware uses 512 KB units for addressing. -pub(crate) fn encode_segment(start: usize, end: usize) -> Result<u32, SmcError> { - let start_512k = (start >> 19) as u32; - let end_512k = ((end >> 19) - 1) as u32; - - if start_512k > 0xFFFF || end_512k > 0xFFFF { +/// FMC decode fields use 512 KiB alignment. `end` is exclusive. +pub(crate) fn encode_fmc_segment(start: usize, end: usize) -> Result<u32, SmcError> { + if end == 0 || end <= start { return Err(SmcError::InvalidCapacity); } - Ok((end_512k << 16) | start_512k) + let start = start as u32; + let inclusive_end = (end - 1) as u32; + Ok(((((start >> 19) << 19) >> 16) & 0x0ff8) + | (((inclusive_end >> 19) << 19) & 0x0ff8_0000)) +} + +/// Encode an SPI1/SPI2 memory segment into hardware register format. +/// +/// SPI decode fields use 1 MiB alignment. `end` is exclusive. +pub(crate) fn encode_spi_segment(start: usize, end: usize) -> Result<u32, SmcError> { + if end == 0 || end <= start { + return Err(SmcError::InvalidCapacity); + } + + let start = start as u32; + let inclusive_end = (end - 1) as u32; + Ok(((((start >> 20) << 20) >> 16) & 0xffff) + | (((inclusive_end >> 20) << 20) & 0xffff_0000)) } /// Calculate AST-compatible SPI clock divider field for CS control registers. @@ -267,18 +281,18 @@ #[test] fn test_encode_segment() { - let seg = encode_segment(0, 16 * 1024 * 1024).unwrap(); - let start_512k = seg & 0xFFFF; - let end_512k = (seg >> 16) & 0xFFFF; + let seg = encode_fmc_segment(0, 16 * 1024 * 1024).unwrap(); + let start_512k = ((seg & 0x0ff8) << 16) >> 19; + let end_512k = (((seg & 0x0ff8_0000) | 0x0007_ffff) >> 19); assert_eq!(start_512k, 0); assert_eq!(end_512k, 31); } #[test] fn test_encode_segment_8mb_cs0() { - let seg = encode_segment(0, 8 * 1024 * 1024).unwrap(); - let start_512k = seg & 0xFFFF; - let end_512k = (seg >> 16) & 0xFFFF; + let seg = encode_fmc_segment(0, 8 * 1024 * 1024).unwrap(); + let start_512k = ((seg & 0x0ff8) << 16) >> 19; + let end_512k = (((seg & 0x0ff8_0000) | 0x0007_ffff) >> 19); assert_eq!(start_512k, 0); assert_eq!(end_512k, 15); } @@ -287,9 +301,9 @@ fn test_encode_segment_64mb_cs1_after_8mb_cs0() { let start = 8 * 1024 * 1024; let end = start + 64 * 1024 * 1024; - let seg = encode_segment(start, end).unwrap(); - let start_512k = seg & 0xFFFF; - let end_512k = (seg >> 16) & 0xFFFF; + let seg = encode_fmc_segment(start, end).unwrap(); + let start_512k = ((seg & 0x0ff8) << 16) >> 19; + let end_512k = (((seg & 0x0ff8_0000) | 0x0007_ffff) >> 19); assert_eq!(start_512k, 16); assert_eq!(end_512k, 143); } @@ -328,7 +342,7 @@ #[test] fn test_segment_overflow() { - let result = encode_segment(0, 512 * 1024 * 1024); + let result = encode_fmc_segment(0, 512 * 1024 * 1024); assert!(result.is_err()); }
diff --git a/target/ast10x0/tests/smc/BUILD.bazel b/target/ast10x0/tests/smc/BUILD.bazel index 0e61d54..9d9937a 100644 --- a/target/ast10x0/tests/smc/BUILD.bazel +++ b/target/ast10x0/tests/smc/BUILD.bazel
@@ -6,6 +6,7 @@ visibility = [ "//target/ast10x0/tests/smc/dma_irq:__pkg__", "//target/ast10x0/tests/smc/read:__pkg__", + "//target/ast10x0/tests/smc/write:__pkg__", ], ) @@ -28,3 +29,13 @@ name = "smc_irq_evb_test", actual = "//target/ast10x0/tests/smc/dma_irq:smc_irq_evb_test", ) + +alias( + name = "smc_write_test", + actual = "//target/ast10x0/tests/smc/write:smc_write_test", +) + +alias( + name = "smc_write_evb_test", + actual = "//target/ast10x0/tests/smc/write:smc_write_evb_test", +)
diff --git a/target/ast10x0/tests/smc/write/BUILD.bazel b/target/ast10x0/tests/smc/write/BUILD.bazel new file mode 100644 index 0000000..9a2bb1a --- /dev/null +++ b/target/ast10x0/tests/smc/write/BUILD.bazel
@@ -0,0 +1,79 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@pigweed//pw_kernel/tooling:system_image.bzl", "system_image", "system_image_test") +load("@pigweed//pw_kernel/tooling:target_codegen.bzl", "target_codegen") +load("@pigweed//pw_kernel/tooling:target_linker_script.bzl", "target_linker_script") +load("@pigweed//pw_kernel/tooling/panic_detector:rust_binary_no_panics_test.bzl", "rust_binary_no_panics_test") +load("@rules_rust//rust:defs.bzl", "rust_binary") +load("//target/ast10x0:defs.bzl", "TARGET_COMPATIBLE_WITH") + +filegroup( + name = "system_config", + srcs = ["system.json5"], +) + +target_codegen( + name = "codegen", + arch = "@pigweed//pw_kernel/arch/arm_cortex_m:arch_arm_cortex_m", + system_config = ":system_config", + target_compatible_with = TARGET_COMPATIBLE_WITH, +) + +target_linker_script( + name = "linker_script", + system_config = ":system_config", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + template = "//target/ast10x0:linker_script_template", +) + +rust_binary( + name = "target", + srcs = [ + "target.rs", + "//target/ast10x0/tests/smc:target_debug.rs", + ], + edition = "2024", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + deps = [ + ":codegen", + ":linker_script", + "//target/ast10x0:entry", + "//target/ast10x0/peripherals", + "@pigweed//pw_kernel/arch/arm_cortex_m:arch_arm_cortex_m", + "@pigweed//pw_kernel/kernel", + "@pigweed//pw_kernel/subsys/console:console_backend", + "@pigweed//pw_kernel/target:target_common", + "@pigweed//pw_log/rust:pw_log", + ], +) + +system_image( + name = "smc_write_test", + kernel = ":target", + platform = "//target/ast10x0", + system_config = ":system_config", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + userspace = False, + visibility = ["//visibility:public"], +) + +system_image_test( + name = "smc_write_evb_test", + image = ":smc_write_test", + tags = ["hardware"], + target_compatible_with = select({ + "//target/ast10x0:qemu_enabled": ["@platforms//:incompatible"], + "//conditions:default": [], + }), + visibility = ["//visibility:public"], +) + +rust_binary_no_panics_test( + name = "no_panics_test", + binary = ":smc_write_test", + tags = ["kernel"], +)
diff --git a/target/ast10x0/tests/smc/write/system.json5 b/target/ast10x0/tests/smc/write/system.json5 new file mode 100644 index 0000000..6c3a1a6 --- /dev/null +++ b/target/ast10x0/tests/smc/write/system.json5
@@ -0,0 +1,17 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +// AST10x0 kernel-only SMC FMC CS1 erase/write/read verify test configuration. +{ + arch: { + type: "armv7m", + vector_table_start_address: 0x00000000, + vector_table_size_bytes: 1280, + }, + kernel: { + flash_start_address: 0x00000500, + flash_size_bytes: 262144, + ram_start_address: 0x00040500, + ram_size_bytes: 393216, + }, +}
diff --git a/target/ast10x0/tests/smc/write/target.rs b/target/ast10x0/tests/smc/write/target.rs new file mode 100644 index 0000000..ef5eb1d --- /dev/null +++ b/target/ast10x0/tests/smc/write/target.rs
@@ -0,0 +1,145 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! AST10x0 SMC FMC CS1 erase/write/read verify test target. + +#![no_std] +#![no_main] + +#[allow(unused_imports)] +use ast10x0_peripherals::scu::pinctrl::PINCTRL_FMC_QUAD; +use ast10x0_peripherals::scu::ScuRegisters; +use ast10x0_peripherals::smc::{ + ChipSelect, FlashConfig, FmcUninit, SmcConfig, SmcController, SmcError, SmcTopology, + SpiNorFlash, SpiNorFlashDevice, +}; +use console_backend::console_backend_write_all; +use target_common::{declare_target, TargetInterface}; +use {console_backend as _, entry as _}; + +#[path = "../target_debug.rs"] +mod target_debug; +use target_debug::{dump_smc_read, dump_smc_register}; + +const CS0_CONFIG: FlashConfig = FlashConfig { + capacity_mb: 8, + page_size: 256, + sector_size: 4096, + block_size: 65536, + spi_clock_mhz: 50, +}; + +const CS1_CONFIG: FlashConfig = FlashConfig { + capacity_mb: 64, + page_size: 256, + sector_size: 4096, + block_size: 65536, + spi_clock_mhz: 50, +}; + +const TEST_OFFSET: u32 = 0x10_0000; +const TEST_LEN: usize = 256; + +pub struct Target {} + +fn fill_test_pattern(out: &mut [u8; TEST_LEN]) { + let mut i = 0usize; + while i < out.len() { + out[i] = (i as u8).wrapping_mul(17).wrapping_add(0x5a); + i += 1; + } +} + +fn expect_erased(buf: &[u8]) -> Result<(), SmcError> { + for &byte in buf { + if byte != 0xff { + return Err(SmcError::HardwareError); + } + } + Ok(()) +} + +fn run_smc_fmc_cs1_write_test() -> Result<(), SmcError> { + let scu = unsafe { ScuRegisters::new_global_unlocked() }; + scu.apply_pinctrl_group(PINCTRL_FMC_QUAD); + + let config = SmcConfig { + controller_id: SmcController::Fmc, + cs0: Some(CS0_CONFIG), + cs1: Some(CS1_CONFIG), + dma_enabled: true, + enable_interrupts: false, + topology: SmcTopology::BootSpi { master_idx: 0 }, + }; + + pw_log::info!("=== AST10x0 SMC FMC CS1 write test ==="); + let fmc = unsafe { FmcUninit::new(config)? }; + let mut fmc = fmc.init()?; + fmc.spi_nor_read_init(ChipSelect::Cs1)?; + + if !fmc.is_ready() { + return Err(SmcError::HardwareError); + } + + let mut flash = SpiNorFlash::from_fmc_cs(&mut fmc, CS1_CONFIG, ChipSelect::Cs1)?; + let jedec = flash.jedec_id()?; + pw_log::info!( + "CS1 JEDEC ID: {:02x} {:02x} {:02x}", + jedec[0] as u32, + jedec[1] as u32, + jedec[2] as u32 + ); + + pw_log::info!("=== erase CS1 sector ==="); + flash.erase_sector(TEST_OFFSET)?; + + let mut read_buf = [0u8; TEST_LEN]; + let n = flash.read(TEST_OFFSET, &mut read_buf)?; + if n != TEST_LEN { + return Err(SmcError::HardwareError); + } + expect_erased(&read_buf)?; + dump_smc_read(&read_buf, TEST_LEN as u32); + + pw_log::info!("=== program CS1 page ==="); + let mut pattern = [0u8; TEST_LEN]; + fill_test_pattern(&mut pattern); + let written = flash.program_page(TEST_OFFSET, &pattern)?; + if written != TEST_LEN { + return Err(SmcError::HardwareError); + } + + pw_log::info!("=== read CS1 page ==="); + read_buf.fill(0); + let n = flash.read(TEST_OFFSET, &mut read_buf)?; + if n != TEST_LEN || read_buf != pattern { + return Err(SmcError::HardwareError); + } + dump_smc_read(&read_buf, TEST_LEN as u32); + + pw_log::info!("=== verify CS1 page ==="); + if !flash.verify(TEST_OFFSET, &pattern)? { + return Err(SmcError::HardwareError); + } + + dump_smc_register(0x7E62_0000, 16); + Ok(()) +} + +impl TargetInterface for Target { + const NAME: &'static str = "AST10x0 SMC FMC CS1 write Test"; + + fn main() -> ! { + let sentinel = if run_smc_fmc_cs1_write_test().is_ok() { + b"TEST_RESULT:PASS\n" + } else { + b"TEST_RESULT:FAIL\n" + }; + let _ = console_backend_write_all(sentinel); + + #[expect(clippy::empty_loop)] + loop {} + } +} + +declare_target!(Target);