From a8eb124e47e633cd81e0863253d5f6bdd7545260 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Wed, 19 Nov 2025 09:11:54 -0800 Subject: OSTimer updates (#24) * Initialize OSTIMER0 during HAL initialization Provide the user with a working time driver. Signed-off-by: Felipe Balbi * Handle time_driver interrupt internally Signed-off-by: Felipe Balbi * Gate `time-driver` impl behind a `time` flag Also prevents creation of an `Ostimer` instance if the `time` feature is active. * Remove some dead code --------- Signed-off-by: Felipe Balbi Co-authored-by: James Munns --- Cargo.lock | 1 + Cargo.toml | 18 +- examples/Cargo.lock | 1 + examples/Cargo.toml | 4 +- examples/src/bin/blinky.rs | 13 -- examples/src/bin/ostimer_alarm.rs | 122 ---------- examples/src/bin/ostimer_async.rs | 64 ------ examples/src/bin/ostimer_counter.rs | 139 ------------ examples/src/bin/ostimer_race_test.rs | 405 ---------------------------------- src/clocks/mod.rs | 6 +- src/config.rs | 2 + src/lib.rs | 11 +- src/ostimer.rs | 88 ++++++-- 13 files changed, 98 insertions(+), 776 deletions(-) delete mode 100644 examples/src/bin/ostimer_alarm.rs delete mode 100644 examples/src/bin/ostimer_async.rs delete mode 100644 examples/src/bin/ostimer_counter.rs delete mode 100644 examples/src/bin/ostimer_race_test.rs diff --git a/Cargo.lock b/Cargo.lock index 0fb5bfb12..46322c4de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -328,6 +328,7 @@ source = "git+https://github.com/OpenDevicePartnership/mcxa-pac?rev=3ab4c868f75a dependencies = [ "cortex-m", "cortex-m-rt", + "critical-section", "vcell", ] diff --git a/Cargo.toml b/Cargo.toml index 60f836ac3..0ed6995c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,14 +9,12 @@ categories = ["embedded", "hardware-support", "no-std"] [dependencies] cortex-m = { version = "0.7", features = ["critical-section-single-core"] } -cortex-m-rt = { version = "0.7", features = ["device"] } +cortex-m-rt = { version = "0.7" } critical-section = "1.2.0" defmt = { version = "1.0", optional = true } embassy-embedded-hal = "0.5.0" embassy-hal-internal = { version = "0.3.0", features = ["cortex-m", "prio-bits-3"] } embassy-sync = "0.7.2" -embassy-time = "0.5.0" -embassy-time-driver = "0.2.1" embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } embedded-hal-async = { version = "1.0" } @@ -24,10 +22,14 @@ embedded-hal-nb = { version = "1.0" } embedded-io = "0.6" embedded-io-async = { version = "0.6.1" } heapless = "0.8" -mcxa-pac = { git = "https://github.com/OpenDevicePartnership/mcxa-pac", features = ["rt"], rev = "3ab4c868f75a9240bb8fdce24982d34f2273aabf", version = "0.1.0" } +mcxa-pac = { git = "https://github.com/OpenDevicePartnership/mcxa-pac", features = ["rt", "critical-section"], rev = "3ab4c868f75a9240bb8fdce24982d34f2273aabf", version = "0.1.0" } nb = "1.1.0" paste = "1.0.15" +# `time` dependencies +embassy-time = { version = "0.5.0", optional = true } +embassy-time-driver = { version = "0.2.1", optional = true } + [features] default = [] @@ -35,6 +37,12 @@ default = [] # Use with one logger feature: defmt-rtt (preferred) or defmt-uart (fallback) defmt = ["dep:defmt"] -rt = [] +rt = ["cortex-m-rt/device"] unstable-pac = [] + +# Embassy time +time = [ + "dep:embassy-time", + "dep:embassy-time-driver", +] diff --git a/examples/Cargo.lock b/examples/Cargo.lock index b2ac9a051..14f472cbf 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -446,6 +446,7 @@ source = "git+https://github.com/OpenDevicePartnership/mcxa-pac?rev=3ab4c868f75a dependencies = [ "cortex-m", "cortex-m-rt", + "critical-section", "vcell", ] diff --git a/examples/Cargo.toml b/examples/Cargo.toml index d03d3d95c..d1c6a2071 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -6,13 +6,13 @@ license = "MIT OR Apache-2.0" [dependencies] cortex-m = { version = "0.7", features = ["critical-section-single-core"] } -cortex-m-rt = { version = "0.7", features = ["device"] } +cortex-m-rt = { version = "0.7" } critical-section = "1.2.0" defmt = "1.0" defmt-rtt = "1.0" embassy-embedded-hal = "0.5.0" embassy-executor = { version = "0.9.0", features = ["arch-cortex-m", "executor-interrupt", "executor-thread"], default-features = false } -embassy-mcxa = { path = "../", features = ["defmt", "rt", "unstable-pac"] } +embassy-mcxa = { path = "../", features = ["defmt", "rt", "unstable-pac", "time"] } embassy-sync = "0.7.2" embassy-time = "0.5.0" embassy-time-driver = "0.2.1" diff --git a/examples/src/bin/blinky.rs b/examples/src/bin/blinky.rs index 28d83a12e..ab1e59bdb 100644 --- a/examples/src/bin/blinky.rs +++ b/examples/src/bin/blinky.rs @@ -2,29 +2,16 @@ #![no_main] use embassy_executor::Spawner; -use embassy_mcxa::bind_interrupts; use embassy_time::Timer; use hal::gpio::{Level, Output}; use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; -// Bind only OS_EVENT for timer interrupts -bind_interrupts!(struct Irqs { - OS_EVENT => hal::ostimer::time_driver::OsEventHandler; -}); - -#[used] -#[no_mangle] -static KEEP_OS_EVENT: unsafe extern "C" fn() = OS_EVENT; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = hal::init(hal::config::Config::default()); defmt::info!("Blink example"); - // Initialize embassy-time global driver backed by OSTIMER0 - hal::ostimer::time_driver::init(hal::config::Config::default().time_interrupt_priority, 1_000_000); - let mut red = Output::new(p.P3_18, Level::High); let mut green = Output::new(p.P3_19, Level::High); let mut blue = Output::new(p.P3_21, Level::High); diff --git a/examples/src/bin/ostimer_alarm.rs b/examples/src/bin/ostimer_alarm.rs deleted file mode 100644 index 6d38741b7..000000000 --- a/examples/src/bin/ostimer_alarm.rs +++ /dev/null @@ -1,122 +0,0 @@ -#![no_std] -#![no_main] - -use core::sync::atomic::{AtomicBool, Ordering}; - -use embassy_executor::Spawner; -use embassy_mcxa::bind_interrupts; -use embassy_mcxa::clocks::periph_helpers::OstimerClockSel; -use embassy_mcxa::clocks::PoweredClock; -use embassy_mcxa::lpuart::{Config, Lpuart}; -use embassy_mcxa_examples::init_uart2_pins; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -// Bind only OS_EVENT, and retain the symbol explicitly so it can't be GC'ed. -bind_interrupts!(struct Irqs { - OS_EVENT => hal::ostimer::time_driver::OsEventHandler; -}); - -#[used] -#[no_mangle] -static KEEP_OS_EVENT: unsafe extern "C" fn() = OS_EVENT; - -// Global flag for alarm callback -static ALARM_FLAG: AtomicBool = AtomicBool::new(false); - -// Alarm callback function -fn alarm_callback() { - ALARM_FLAG.store(true, Ordering::Release); -} - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let p = hal::init(hal::config::Config::default()); - - // Create UART configuration - let config = Config { - baudrate_bps: 115_200, - enable_tx: true, - enable_rx: true, - ..Default::default() - }; - - // Create UART instance using LPUART2 with P2_2 as TX and P2_3 as RX - unsafe { - init_uart2_pins(hal::pac()); - } - let mut uart = Lpuart::new_blocking( - p.LPUART2, // Peripheral - p.P2_2, // TX pin - p.P2_3, // RX pin - config, - ) - .unwrap(); - - uart.write_str_blocking("OSTIMER Alarm Example\n"); - - // Initialize embassy-time global driver backed by OSTIMER0 - hal::ostimer::time_driver::init(hal::config::Config::default().time_interrupt_priority, 1_000_000); - - // Create OSTIMER instance - let config = hal::ostimer::Config { - init_match_max: true, - power: PoweredClock::NormalEnabledDeepSleepDisabled, - source: OstimerClockSel::Clk1M, - }; - let ostimer = hal::ostimer::Ostimer::::new(p.OSTIMER0, config); - - // Create alarm with callback - let alarm = hal::ostimer::Alarm::new() - .with_callback(alarm_callback) - .with_flag(&ALARM_FLAG); - - uart.write_str_blocking("Scheduling alarm for 2 seconds...\n"); - - // Schedule alarm to expire in 2 seconds (2,000,000 microseconds) - let scheduled = ostimer.schedule_alarm_delay(&alarm, 2_000_000); - if scheduled { - uart.write_str_blocking("Alarm scheduled successfully\n"); - } else { - uart.write_str_blocking("Failed to schedule alarm (would exceed timer capacity)\n"); - return; - } - - // Wait for alarm to expire - loop { - // Check if alarm has expired - if ALARM_FLAG.load(Ordering::Acquire) { - uart.write_str_blocking("Alarm expired! Callback executed.\n"); - break; - } - - // Busy wait - don't use Timer::after_millis as it interferes with alarm MATCH - for _ in 0..100000 { - cortex_m::asm::nop(); - } - } - - // Demonstrate canceling an alarm - uart.write_str_blocking("Scheduling another alarm for 3 seconds...\n"); - ALARM_FLAG.store(false, Ordering::Release); // Reset flag - - let scheduled = ostimer.schedule_alarm_delay(&alarm, 3_000_000); - if scheduled { - uart.write_str_blocking("Alarm scheduled. Waiting 1 second then canceling...\n"); - - // Wait 1 second - embassy_time::Timer::after_millis(1000).await; - - // Cancel the alarm - ostimer.cancel_alarm(&alarm); - uart.write_str_blocking("Alarm canceled\n"); - - // Check immediately if alarm flag is set - if !ALARM_FLAG.load(Ordering::Acquire) { - uart.write_str_blocking("Alarm was successfully canceled\n"); - } else { - uart.write_str_blocking("Alarm fired despite cancellation\n"); - } - } - - uart.write_str_blocking("Example complete\n"); -} diff --git a/examples/src/bin/ostimer_async.rs b/examples/src/bin/ostimer_async.rs deleted file mode 100644 index f043184e7..000000000 --- a/examples/src/bin/ostimer_async.rs +++ /dev/null @@ -1,64 +0,0 @@ -#![no_std] -#![no_main] - -use embassy_executor::Spawner; -use embassy_mcxa::bind_interrupts; -use embassy_mcxa_examples::init_uart2_pins; -use embassy_time::{Duration, Timer}; -use hal::lpuart::{Config, Lpuart}; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -// Bind only OS_EVENT, and retain the symbol explicitly so it can’t be GC’ed. -bind_interrupts!(struct Irqs { - OS_EVENT => hal::ostimer::time_driver::OsEventHandler; -}); - -#[used] -#[no_mangle] -static KEEP_OS_EVENT: unsafe extern "C" fn() = OS_EVENT; - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let p = hal::init(hal::config::Config::default()); - - // Create UART configuration - let config = Config { - baudrate_bps: 115_200, - enable_tx: true, - enable_rx: true, - ..Default::default() - }; - - // Create UART instance using LPUART2 with P2_2 as TX and P2_3 as RX - unsafe { - init_uart2_pins(hal::pac()); - } - let mut uart = Lpuart::new_blocking( - p.LPUART2, // Peripheral - p.P2_2, // TX pin - p.P2_3, // RX pin - config, - ) - .unwrap(); - uart.blocking_write(b"boot\n").unwrap(); - - // Avoid mass NVIC writes here; DefaultHandler now safely returns. - - // Initialize embassy-time global driver backed by OSTIMER0 (re-enables OS_EVENT with priority) - // The bind_interrupts! macro handles handler binding automatically - - // Initialize OSTIMER with default 1MHz frequency - // Adjust this value to match your actual OSTIMER clock frequency - hal::ostimer::time_driver::init(hal::config::Config::default().time_interrupt_priority, 1_000_000); - - // Removed force-pend; rely on real hardware match to trigger OS_EVENT. - - // Log using defmt if enabled - defmt::info!("OSTIMER async example starting..."); - - loop { - defmt::info!("tick"); - uart.write_str_blocking("tick\n"); - Timer::after(Duration::from_millis(1000)).await; - } -} diff --git a/examples/src/bin/ostimer_counter.rs b/examples/src/bin/ostimer_counter.rs deleted file mode 100644 index f36915ff2..000000000 --- a/examples/src/bin/ostimer_counter.rs +++ /dev/null @@ -1,139 +0,0 @@ -//! # OSTIMER Counter Reading and Reset Example -//! -//! This example demonstrates the new timer counter reading and reset functionality -//! of the OSTIMER driver. - -#![no_std] -#![no_main] - -use embassy_executor::Spawner; -use embassy_mcxa::clocks::periph_helpers::OstimerClockSel; -use embassy_mcxa::clocks::PoweredClock; -use embassy_mcxa::lpuart::{Blocking, Config, Lpuart}; -use embassy_time::{Duration, Timer}; -use hal::bind_interrupts; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -bind_interrupts!(struct Irqs { - OS_EVENT => hal::ostimer::time_driver::OsEventHandler; -}); - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let p = hal::init(Default::default()); - - // Create UART configuration - let config = Config { - baudrate_bps: 115_200, - enable_tx: true, - enable_rx: true, - ..Default::default() - }; - - // Create UART instance using LPUART2 with P2_2 as TX and P2_3 as RX - unsafe { - embassy_mcxa_examples::init_uart2_pins(hal::pac()); - } - let mut uart = Lpuart::new_blocking( - p.LPUART2, // Peripheral - p.P2_2, // TX pin - p.P2_3, // RX pin - config, - ) - .unwrap(); - - uart.write_str_blocking("OSTIMER Counter Reading and Reset Example\n"); - - // Initialize the OSTIMER time driver - hal::ostimer::time_driver::init( - hal::interrupt::Priority::from(3), - 1_000_000, // 1MHz clock - ); - - // Create OSTIMER instance - let ostimer = hal::ostimer::Ostimer::::new( - p.OSTIMER0, - hal::ostimer::Config { - init_match_max: true, - power: PoweredClock::NormalEnabledDeepSleepDisabled, - source: OstimerClockSel::Clk1M, - }, - ); - - // Read initial counter value - let initial_counter = ostimer.now(); - uart.write_str_blocking("Initial counter value: "); - write_u64(&mut uart, initial_counter); - uart.write_str_blocking("\n"); - - // Wait a bit to let counter increment - Timer::after(Duration::from_millis(100)).await; - - // Read counter again - let counter_after_wait = ostimer.now(); - uart.write_str_blocking("Counter after 100ms wait: "); - write_u64(&mut uart, counter_after_wait); - uart.write_str_blocking("\n"); - uart.write_str_blocking("Difference: "); - write_u64(&mut uart, counter_after_wait - initial_counter); - uart.write_str_blocking(" ticks\n"); - - // Reset the timer - uart.write_str_blocking("Resetting timer...\n"); - ostimer.reset(hal::pac()); - - // Read counter after reset - let counter_after_reset = ostimer.now(); - uart.write_str_blocking("Counter after reset: "); - write_u64(&mut uart, counter_after_reset); - uart.write_str_blocking("\n"); - - // Wait again to verify timer is working - Timer::after(Duration::from_millis(50)).await; - - let final_counter = ostimer.now(); - uart.write_str_blocking("Counter after another 50ms: "); - write_u64(&mut uart, final_counter); - uart.write_str_blocking("\n"); - uart.write_str_blocking("Difference after reset: "); - write_u64(&mut uart, final_counter - counter_after_reset); - uart.write_str_blocking(" ticks\n"); - - uart.write_str_blocking("Example complete\n"); -} - -// Helper function to write a u64 value as decimal string -fn write_u64(uart: &mut Lpuart<'_, Blocking>, value: u64) { - if value == 0 { - uart.write_str_blocking("0"); - return; - } - - let mut buffer = [0u8; 20]; // Enough for max u64 - let mut i = 0; - let mut v = value; - - while v > 0 { - buffer[i] = b'0' + (v % 10) as u8; - v /= 10; - i += 1; - } - - // Write digits in reverse order - while i > 0 { - i -= 1; - match buffer[i] { - b'0' => uart.write_str_blocking("0"), - b'1' => uart.write_str_blocking("1"), - b'2' => uart.write_str_blocking("2"), - b'3' => uart.write_str_blocking("3"), - b'4' => uart.write_str_blocking("4"), - b'5' => uart.write_str_blocking("5"), - b'6' => uart.write_str_blocking("6"), - b'7' => uart.write_str_blocking("7"), - b'8' => uart.write_str_blocking("8"), - b'9' => uart.write_str_blocking("9"), - _ => uart.write_str_blocking("?"), - } - } -} diff --git a/examples/src/bin/ostimer_race_test.rs b/examples/src/bin/ostimer_race_test.rs deleted file mode 100644 index 0106b92a7..000000000 --- a/examples/src/bin/ostimer_race_test.rs +++ /dev/null @@ -1,405 +0,0 @@ -//! # OSTIMER Race Condition Test -//! -//! This example tests for race conditions in the OSTIMER driver by: -//! - Scheduling alarms sequentially (hardware limitation: only one at a time) -//! - Reading the counter during interrupt-heavy periods -//! - Testing concurrent timer operations -//! - Stress testing interrupt handling - -#![no_std] -#![no_main] - -use core::sync::atomic::{AtomicU32, Ordering}; - -use embassy_executor::Spawner; -use embassy_mcxa::clocks::periph_helpers::OstimerClockSel; -use embassy_mcxa::clocks::PoweredClock; -use embassy_mcxa::lpuart::{Blocking, Config, Lpuart}; -use embassy_time::{Duration, Timer}; -use hal::bind_interrupts; -use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; - -bind_interrupts!(struct Irqs { - OS_EVENT => hal::ostimer::time_driver::OsEventHandler; -}); - -#[used] -#[no_mangle] -static KEEP_OS_EVENT: unsafe extern "C" fn() = OS_EVENT; - -// Global counters for race condition detection -static ALARM_CALLBACK_COUNT: AtomicU32 = AtomicU32::new(0); -static INTERRUPT_COUNT: AtomicU32 = AtomicU32::new(0); -static RACE_DETECTED: AtomicU32 = AtomicU32::new(0); - -// Alarm callback function -fn alarm_callback() { - let _count = ALARM_CALLBACK_COUNT.fetch_add(1, Ordering::SeqCst); - INTERRUPT_COUNT.fetch_add(1, Ordering::SeqCst); - - // Simulate some work in the callback to increase chance of races - for _ in 0..10 { - cortex_m::asm::nop(); - } -} - -fn report_default_handler(uart: &mut Lpuart<'_, Blocking>) { - let snapshot = hal::interrupt::default_handler_snapshot(); - if snapshot.count == 0 { - return; - } - - uart.write_str_blocking("WARNING: DefaultHandler executed "); - write_u32(uart, snapshot.count); - uart.write_str_blocking(" time(s). Vector="); - write_u32(uart, snapshot.vector as u32); - uart.write_str_blocking(" CFSR=0x"); - write_hex32(uart, snapshot.cfsr); - uart.write_str_blocking(" HFSR=0x"); - write_hex32(uart, snapshot.hfsr); - uart.write_str_blocking(" PC=0x"); - write_hex32(uart, snapshot.stacked_pc); - uart.write_str_blocking(" LR=0x"); - write_hex32(uart, snapshot.stacked_lr); - uart.write_str_blocking(" SP=0x"); - write_hex32(uart, snapshot.stacked_sp); - uart.write_str_blocking("\n"); - - hal::interrupt::clear_default_handler_snapshot(); -} - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let p = hal::init(Default::default()); - - // Create UART configuration - let config = Config { - baudrate_bps: 115_200, - enable_tx: true, - enable_rx: true, - ..Default::default() - }; - - // Create UART instance using LPUART2 with P2_2 as TX and P2_3 as RX - unsafe { - embassy_mcxa_examples::init_uart2_pins(hal::pac()); - } - let mut uart = Lpuart::new_blocking( - p.LPUART2, // Peripheral - p.P2_2, // TX pin - p.P2_3, // RX pin - config, - ) - .unwrap(); - - uart.write_str_blocking("OSTIMER Race Condition Test Starting...\n"); - - // The bind_interrupts! macro handles handler binding automatically - - // Initialize the OSTIMER time driver FIRST - hal::ostimer::time_driver::init( - hal::interrupt::Priority::from(3), - 1_000_000, // 1MHz clock - ); - - uart.write_str_blocking("Time driver initialized\n"); - - // Create OSTIMER instance - let ostimer = hal::ostimer::Ostimer::::new( - p.OSTIMER0, - hal::ostimer::Config { - init_match_max: true, - power: PoweredClock::NormalEnabledDeepSleepDisabled, - source: OstimerClockSel::Clk1M, - }, - ); - - uart.write_str_blocking("OSTIMER instance created\n"); - - // Test 1: Sequential alarm scheduling (OSTIMER only supports one alarm at a time) - uart.write_str_blocking("Test 1: Sequential alarm scheduling...\n"); - test_rapid_alarms(&ostimer, &mut uart).await; - report_default_handler(&mut uart); - - // Test 2: Counter reading during interrupts - uart.write_str_blocking("Test 2: Counter reading during interrupts...\n"); - test_counter_reading_during_interrupts(&ostimer, &mut uart).await; - report_default_handler(&mut uart); - - // Test 3: Concurrent timer operations - uart.write_str_blocking("Test 3: Concurrent timer operations...\n"); - test_concurrent_operations(&ostimer, &mut uart).await; - report_default_handler(&mut uart); - - // Test 4: Timer reset during operation - uart.write_str_blocking("Test 4: Timer reset during operation...\n"); - test_reset_during_operation(&ostimer, &mut uart, hal::pac()).await; - report_default_handler(&mut uart); - - // Report results - uart.write_str_blocking("Race condition test complete\n"); - uart.write_str_blocking("Callback count: "); - write_u32(&mut uart, ALARM_CALLBACK_COUNT.load(Ordering::SeqCst)); - uart.write_str_blocking("\nInterrupt count: "); - write_u32(&mut uart, INTERRUPT_COUNT.load(Ordering::SeqCst)); - uart.write_str_blocking("\nRaces detected: "); - write_u32(&mut uart, RACE_DETECTED.load(Ordering::SeqCst)); - uart.write_str_blocking("\n"); -} - -// Test rapid alarm scheduling to stress interrupt handling -async fn test_rapid_alarms( - ostimer: &hal::ostimer::Ostimer<'_, hal::ostimer::Ostimer0>, - uart: &mut Lpuart<'_, Blocking>, -) { - let initial_count = ALARM_CALLBACK_COUNT.load(Ordering::SeqCst); - - // Schedule 10 alarms sequentially (OSTIMER only supports one alarm at a time) - for _i in 0..10 { - let alarm = hal::ostimer::Alarm::new().with_callback(alarm_callback); - let delay_us = 1000; // 1ms delay for each alarm - if ostimer.schedule_alarm_delay(&alarm, delay_us) { - // Wait for this alarm to complete before scheduling the next - Timer::after(Duration::from_micros(delay_us + 100)).await; - report_default_handler(uart); - } else { - RACE_DETECTED.fetch_add(1, Ordering::SeqCst); - uart.write_str_blocking("ERROR: Failed to program OSTIMER alarm (match not ready)\n"); - } - } - - // All alarms should have completed by now - let final_count = ALARM_CALLBACK_COUNT.load(Ordering::SeqCst); - let expected_count = initial_count + 10; - - if final_count != expected_count { - RACE_DETECTED.fetch_add(1, Ordering::SeqCst); - uart.write_str_blocking("ERROR: Expected "); - write_u32(uart, expected_count); - uart.write_str_blocking(" callbacks, got "); - write_u32(uart, final_count); - uart.write_str_blocking("\n"); - } else { - uart.write_str_blocking("PASS: All rapid alarms executed\n"); - } -} - -// Test reading counter while interrupts are firing -async fn test_counter_reading_during_interrupts( - ostimer: &hal::ostimer::Ostimer<'_, hal::ostimer::Ostimer0>, - uart: &mut Lpuart<'_, Blocking>, -) { - let initial_interrupt_count = INTERRUPT_COUNT.load(Ordering::SeqCst); - - // Schedule an alarm that will fire soon - let alarm = hal::ostimer::Alarm::new().with_callback(alarm_callback); - if !ostimer.schedule_alarm_delay(&alarm, 500) { - RACE_DETECTED.fetch_add(1, Ordering::SeqCst); - uart.write_str_blocking("ERROR: Failed to program OSTIMER alarm before counter stress\n"); - } - - // While alarm is pending, read the counter many times rapidly - // This tests if counter reading is atomic and doesn't get corrupted by interrupts - let mut last_counter = ostimer.now(); - let mut consistent_reads = 0; - let mut total_reads = 0; - - for _ in 0..1000 { - let current_counter = ostimer.now(); - total_reads += 1; - - // Check if counter is monotonically increasing (basic sanity check) - if current_counter >= last_counter { - consistent_reads += 1; - } - last_counter = current_counter; - - // Small delay between reads - for _ in 0..10 { - cortex_m::asm::nop(); - } - - report_default_handler(uart); - } - - // Wait for alarm to complete - Timer::after(Duration::from_millis(1)).await; - - let final_interrupt_count = INTERRUPT_COUNT.load(Ordering::SeqCst); - - if consistent_reads == total_reads { - uart.write_str_blocking("PASS: Counter reading consistent during interrupts\n"); - } else { - RACE_DETECTED.fetch_add(1, Ordering::SeqCst); - uart.write_str_blocking("ERROR: Counter reading inconsistent: "); - write_u32(uart, consistent_reads); - uart.write_str_blocking("/"); - write_u32(uart, total_reads); - uart.write_str_blocking(" consistent\n"); - } - - if final_interrupt_count > initial_interrupt_count { - uart.write_str_blocking("PASS: Interrupt fired during counter reading test\n"); - } else { - uart.write_str_blocking("WARNING: No interrupt fired during counter reading test\n"); - } -} - -// Test concurrent timer operations (embassy-time + alarms) -async fn test_concurrent_operations( - ostimer: &hal::ostimer::Ostimer<'_, hal::ostimer::Ostimer0>, - uart: &mut Lpuart<'_, Blocking>, -) { - let initial_interrupt_count = INTERRUPT_COUNT.load(Ordering::SeqCst); - - // Start an embassy-time timer - let timer_future = Timer::after(Duration::from_millis(2)); - - // Schedule an alarm that should fire before the timer - let alarm = hal::ostimer::Alarm::new().with_callback(alarm_callback); - if !ostimer.schedule_alarm_delay(&alarm, 1000) { - RACE_DETECTED.fetch_add(1, Ordering::SeqCst); - uart.write_str_blocking("ERROR: Failed to program OSTIMER alarm before concurrent operations\n"); - } - - // Wait for both to complete - timer_future.await; - - let final_interrupt_count = INTERRUPT_COUNT.load(Ordering::SeqCst); - - if final_interrupt_count > initial_interrupt_count { - uart.write_str_blocking("PASS: Concurrent operations completed\n"); - } else { - uart.write_str_blocking("WARNING: No interrupts during concurrent operations\n"); - } -} - -// Test timer reset during active operations -async fn test_reset_during_operation( - ostimer: &hal::ostimer::Ostimer<'_, hal::ostimer::Ostimer0>, - uart: &mut Lpuart<'_, Blocking>, - peripherals: &hal::pac::Peripherals, -) { - let initial_counter = ostimer.now(); - - // Schedule an alarm - let alarm = hal::ostimer::Alarm::new().with_callback(alarm_callback); - if !ostimer.schedule_alarm_delay(&alarm, 2000) { - RACE_DETECTED.fetch_add(1, Ordering::SeqCst); - uart.write_str_blocking("ERROR: Failed to program OSTIMER alarm before reset test\n"); - } - - // Wait a bit then reset the timer - Timer::after(Duration::from_millis(1)).await; - ostimer.reset(peripherals); - - // Check counter after reset - let counter_after_reset = ostimer.now(); - - // Wait to see if the alarm still fires (it shouldn't after reset) - Timer::after(Duration::from_millis(2)).await; - - let final_counter = ostimer.now(); - - if counter_after_reset < initial_counter { - uart.write_str_blocking("PASS: Timer reset successful\n"); - } else { - RACE_DETECTED.fetch_add(1, Ordering::SeqCst); - uart.write_str_blocking("ERROR: Timer reset may have failed\n"); - } - - uart.write_str_blocking("Counter progression after reset: "); - write_u64(uart, initial_counter); - uart.write_str_blocking(" -> "); - write_u64(uart, counter_after_reset); - uart.write_str_blocking(" -> "); - write_u64(uart, final_counter); - uart.write_str_blocking("\n"); -} - -// Helper function to write a u32 value as decimal string -fn write_u32(uart: &mut Lpuart<'_, Blocking>, value: u32) { - if value == 0 { - uart.write_str_blocking("0"); - return; - } - - let mut buffer = [0u8; 10]; // Enough for max u32 - let mut i = 0; - let mut v = value; - - while v > 0 { - buffer[i] = b'0' + (v % 10) as u8; - v /= 10; - i += 1; - } - - // Write digits in reverse order - while i > 0 { - i -= 1; - match buffer[i] { - b'0' => uart.write_str_blocking("0"), - b'1' => uart.write_str_blocking("1"), - b'2' => uart.write_str_blocking("2"), - b'3' => uart.write_str_blocking("3"), - b'4' => uart.write_str_blocking("4"), - b'5' => uart.write_str_blocking("5"), - b'6' => uart.write_str_blocking("6"), - b'7' => uart.write_str_blocking("7"), - b'8' => uart.write_str_blocking("8"), - b'9' => uart.write_str_blocking("9"), - _ => uart.write_str_blocking("?"), - } - } -} - -fn write_hex32(uart: &mut Lpuart<'_, Blocking>, value: u32) { - let mut buf = [b'0'; 8]; - let mut tmp = value; - for i in (0..8).rev() { - let digit = (tmp & 0xF) as u8; - buf[i] = match digit { - 0..=9 => b'0' + digit, - 10..=15 => b'A' + (digit - 10), - _ => b'?', - }; - tmp >>= 4; - } - uart.blocking_write(&buf).unwrap(); -} - -// Helper function to write a u64 value as decimal string -fn write_u64(uart: &mut Lpuart<'_, Blocking>, value: u64) { - if value == 0 { - uart.blocking_write(b"0").unwrap(); - return; - } - - let mut buffer = [0u8; 20]; // Enough for max u64 - let mut i = 0; - let mut v = value; - - while v > 0 { - buffer[i] = b'0' + (v % 10) as u8; - v /= 10; - i += 1; - } - - // Write digits in reverse order - while i > 0 { - i -= 1; - match buffer[i] { - b'0' => uart.blocking_write(b"0").unwrap(), - b'1' => uart.blocking_write(b"1").unwrap(), - b'2' => uart.blocking_write(b"2").unwrap(), - b'3' => uart.blocking_write(b"3").unwrap(), - b'4' => uart.blocking_write(b"4").unwrap(), - b'5' => uart.blocking_write(b"5").unwrap(), - b'6' => uart.blocking_write(b"6").unwrap(), - b'7' => uart.blocking_write(b"7").unwrap(), - b'8' => uart.blocking_write(b"8").unwrap(), - b'9' => uart.blocking_write(b"9").unwrap(), - _ => uart.blocking_write(b"?").unwrap(), - } - } -} diff --git a/src/clocks/mod.rs b/src/clocks/mod.rs index 558fb0278..a12e125c6 100644 --- a/src/clocks/mod.rs +++ b/src/clocks/mod.rs @@ -867,7 +867,9 @@ macro_rules! impl_cc_gate { /// This module contains implementations of MRCC APIs, specifically of the [`Gate`] trait, /// for various low level peripherals. pub(crate) mod gate { - use super::periph_helpers::{AdcConfig, LpuartConfig, NoConfig, OsTimerConfig}; + #[cfg(not(feature = "time"))] + use super::periph_helpers::OsTimerConfig; + use super::periph_helpers::{AdcConfig, LpuartConfig, NoConfig}; use super::*; // These peripherals have no additional upstream clocks or configuration required @@ -888,7 +890,9 @@ pub(crate) mod gate { // These peripherals DO have meaningful configuration, and could fail if the system // clocks do not match their needs. + #[cfg(not(feature = "time"))] impl_cc_gate!(OSTIMER0, mrcc_glb_cc1, mrcc_glb_rst1, ostimer0, OsTimerConfig); + impl_cc_gate!(LPUART2, mrcc_glb_cc0, mrcc_glb_rst0, lpuart2, LpuartConfig); impl_cc_gate!(ADC1, mrcc_glb_cc1, mrcc_glb_rst1, adc1, AdcConfig); } diff --git a/src/config.rs b/src/config.rs index 0939c11f1..9c0d47ecb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,6 +5,7 @@ use crate::interrupt::Priority; #[non_exhaustive] pub struct Config { + #[cfg(feature = "time")] pub time_interrupt_priority: Priority, pub rtc_interrupt_priority: Priority, pub adc_interrupt_priority: Priority, @@ -14,6 +15,7 @@ pub struct Config { impl Default for Config { fn default() -> Self { Self { + #[cfg(feature = "time")] time_interrupt_priority: Priority::from(0), rtc_interrupt_priority: Priority::from(0), adc_interrupt_priority: Priority::from(0), diff --git a/src/lib.rs b/src/lib.rs index c885ecc50..e93ff61a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,9 @@ pub mod lpuart; pub mod ostimer; pub mod rtc; +#[cfg(feature = "rt")] +pub use crate::pac::NVIC_PRIO_BITS; + #[rustfmt::skip] embassy_hal_internal::peripherals!( ADC0, @@ -83,6 +86,8 @@ embassy_hal_internal::peripherals!( MBC0, MRCC0, OPAMP0, + + #[cfg(not(feature = "time"))] OSTIMER0, P0_0, @@ -335,7 +340,6 @@ pub use interrupt::InterruptExt; pub use mcxa_pac as pac; #[cfg(not(feature = "unstable-pac"))] pub(crate) use mcxa_pac as pac; -pub use ostimer::Ostimer0 as Ostimer0Token; pub use rtc::Rtc0 as Rtc0Token; /// Initialize HAL with configuration (mirrors embassy-imxrt style). Minimal: just take peripherals. @@ -343,6 +347,7 @@ pub use rtc::Rtc0 as Rtc0Token; pub fn init(cfg: crate::config::Config) -> Peripherals { let peripherals = Peripherals::take(); // Apply user-configured priority early; enabling is left to examples/apps + #[cfg(feature = "time")] crate::interrupt::OS_EVENT.set_priority(cfg.time_interrupt_priority); // Apply user-configured priority early; enabling is left to examples/apps crate::interrupt::RTC.set_priority(cfg.rtc_interrupt_priority); @@ -352,6 +357,10 @@ pub fn init(cfg: crate::config::Config) -> Peripherals { // Configure clocks crate::clocks::init(cfg.clock_cfg).unwrap(); + // Initialize embassy-time global driver backed by OSTIMER0 + #[cfg(feature = "time")] + crate::ostimer::time_driver::init(crate::config::Config::default().time_interrupt_priority, 1_000_000); + // Enable GPIO clocks unsafe { _ = crate::clocks::enable_and_reset::(&crate::clocks::periph_helpers::NoConfig); diff --git a/src/ostimer.rs b/src/ostimer.rs index cd5451b53..c51812e3d 100644 --- a/src/ostimer.rs +++ b/src/ostimer.rs @@ -35,7 +35,6 @@ use crate::clocks::periph_helpers::{OsTimerConfig, OstimerClockSel}; use crate::clocks::{assert_reset, enable_and_reset, is_reset_released, release_reset, Gate, PoweredClock}; use crate::interrupt::InterruptExt; use crate::pac; -use crate::peripherals::OSTIMER0; // PAC defines the shared RegisterBlock under `ostimer0`. type Regs = pac::ostimer0::RegisterBlock; @@ -283,15 +282,15 @@ impl<'d, I: Instance> Ostimer<'d, I> { .write(|w| w.ostimer_intrflag().clear_bit_by_one().ostimer_intena().clear_bit()); unsafe { - assert_reset::(); + assert_reset::(); for _ in 0..RESET_STABILIZE_SPINS { cortex_m::asm::nop(); } - release_reset::(); + release_reset::(); - while !is_reset_released::() { + while !is_reset_released::() { cortex_m::asm::nop(); } } @@ -490,9 +489,7 @@ pub trait Instance: Gate + PeripheralType { fn ptr() -> *const Regs; } -// Token for OSTIMER0 provided by embassy-hal-internal peripherals macro. -pub type Ostimer0 = crate::peripherals::OSTIMER0; - +#[cfg(not(feature = "time"))] impl Instance for crate::peripherals::OSTIMER0 { #[inline(always)] fn ptr() -> *const Regs { @@ -500,14 +497,6 @@ impl Instance for crate::peripherals::OSTIMER0 { } } -// Also implement Instance for the Peri wrapper type -// impl Instance for embassy_hal_internal::Peri<'_, crate::peripherals::OSTIMER0> { -// #[inline(always)] -// fn ptr() -> *const Regs { -// pac::Ostimer0::ptr() -// } -// } - #[inline(always)] fn bin_to_gray(x: u64) -> u64 { x ^ (x >> 1) @@ -523,6 +512,7 @@ fn gray_to_bin(gray: u64) -> u64 { bin } +#[cfg(feature = "time")] pub mod time_driver { use core::sync::atomic::Ordering; use core::task::Waker; @@ -537,7 +527,55 @@ pub mod time_driver { use crate::clocks::periph_helpers::{OsTimerConfig, OstimerClockSel}; use crate::clocks::{enable_and_reset, PoweredClock}; use crate::pac; - use crate::peripherals::OSTIMER0; + + #[allow(non_camel_case_types)] + pub(crate) struct _OSTIMER0_TIME_DRIVER { + _x: (), + } + + // #[cfg(feature = "time")] + // impl_cc_gate!(_OSTIMER0_TIME_DRIVER, mrcc_glb_cc1, mrcc_glb_rst1, ostimer0, OsTimerConfig); + + impl crate::clocks::Gate for _OSTIMER0_TIME_DRIVER { + type MrccPeriphConfig = crate::clocks::periph_helpers::OsTimerConfig; + + #[inline] + unsafe fn enable_clock() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.mrcc_glb_cc1().modify(|_, w| w.ostimer0().enabled()); + } + + #[inline] + unsafe fn disable_clock() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.mrcc_glb_cc1().modify(|_r, w| w.ostimer0().disabled()); + } + + #[inline] + fn is_clock_enabled() -> bool { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.mrcc_glb_cc1().read().ostimer0().is_enabled() + } + + #[inline] + unsafe fn release_reset() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.mrcc_glb_rst1().modify(|_, w| w.ostimer0().enabled()); + } + + #[inline] + unsafe fn assert_reset() { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.mrcc_glb_rst1().modify(|_, w| w.ostimer0().disabled()); + } + + #[inline] + fn is_reset_released() -> bool { + let mrcc = unsafe { pac::Mrcc0::steal() }; + mrcc.mrcc_glb_rst1().read().ostimer0().is_enabled() + } + } + pub struct Driver; static TIMER_WAKER: AtomicWaker = AtomicWaker::new(); @@ -625,7 +663,7 @@ pub mod time_driver { /// The embassy_time_driver macro handles driver registration automatically. pub fn init(priority: crate::interrupt::Priority, frequency_hz: u64) { let _clock_freq = unsafe { - enable_and_reset::(&OsTimerConfig { + enable_and_reset::<_OSTIMER0_TIME_DRIVER>(&OsTimerConfig { power: PoweredClock::AlwaysEnabled, source: OstimerClockSel::Clk1M, }) @@ -694,12 +732,14 @@ pub mod time_driver { } }); } +} - /// Type-level handler to be used with bind_interrupts! for OS_EVENT. - pub struct OsEventHandler; - impl crate::interrupt::typelevel::Handler for OsEventHandler { - unsafe fn on_interrupt() { - on_interrupt(); - } - } +#[cfg(feature = "time")] +use crate::pac::interrupt; + +#[cfg(feature = "time")] +#[allow(non_snake_case)] +#[interrupt] +fn OS_EVENT() { + time_driver::on_interrupt() } -- cgit