diff --git a/Cargo.lock b/Cargo.lock index c647e7cbdb..2c7e450905 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -926,6 +926,7 @@ dependencies = [ "ecdsa-sw", "kernel", "nrf52840", + "nrf52840dk-test-base", "nrf52_components", "segger", "tock-tbf", @@ -943,6 +944,7 @@ dependencies = [ "cortexm4", "kernel", "nrf52840", + "nrf52840dk-test-base", "nrf52_components", "segger", "tock_build_scripts", @@ -959,11 +961,27 @@ dependencies = [ "cortexm4", "kernel", "nrf52840", + "nrf52840dk-test-base", "nrf52_components", "segger", "tock_build_scripts", ] +[[package]] +name = "nrf52840dk-test-base" +version = "0.2.3-dev" +dependencies = [ + "capsules-core", + "capsules-extra", + "capsules-system", + "components", + "cortexm4", + "kernel", + "nrf52840", + "nrf52_components", + "segger", +] + [[package]] name = "nrf52840dk-test-dynamic-app-load" version = "0.2.3-dev" @@ -991,6 +1009,7 @@ dependencies = [ "cortexm4", "kernel", "nrf52840", + "nrf52840dk-test-base", "nrf52_components", "segger", "tock_build_scripts", @@ -1093,6 +1112,20 @@ dependencies = [ "tock_build_scripts", ] +[[package]] +name = "nucleo_u545re_q" +version = "0.2.3-dev" +dependencies = [ + "capsules-core", + "capsules-extra", + "capsules-system", + "components", + "cortexm33", + "kernel", + "stm32u545", + "tock_build_scripts", +] + [[package]] name = "opaque-debug" version = "0.3.1" @@ -1582,6 +1615,23 @@ dependencies = [ "kernel", ] +[[package]] +name = "stm32u545" +version = "0.2.3-dev" +dependencies = [ + "cortexm33", + "kernel", + "stm32u5xx", +] + +[[package]] +name = "stm32u5xx" +version = "0.2.3-dev" +dependencies = [ + "cortexm33", + "kernel", +] + [[package]] name = "stm32wle5jc" version = "0.2.3-dev" diff --git a/Cargo.toml b/Cargo.toml index 9ab95d1603..c6201564e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ members = [ "boards/sma_q3", "boards/nucleo_f429zi", "boards/nucleo_f446re", + "boards/nucleo_u545re_q", "boards/particle_boron", "boards/pico_explorer_base", "boards/raspberry_pi_pico", @@ -61,6 +62,7 @@ members = [ "boards/configurations/nrf52840dk/nrf52840dk-test-appid-ecdsap256", "boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256", "boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf", + "boards/configurations/nrf52840dk/nrf52840dk-test-base", "boards/configurations/nrf52840dk/nrf52840dk-test-invs", "boards/configurations/nrf52840dk/nrf52840dk-test-kernel", "boards/configurations/nrf52840dk/nrf52840dk-test-dynamic-app-load", @@ -109,6 +111,8 @@ members = [ "chips/stm32f429zi", "chips/stm32f446re", "chips/stm32f412g", + "chips/stm32u545", + "chips/stm32u5xx", "chips/stm32f4xx", "chips/veer_el2", "chips/stm32wle5xx", diff --git a/arch/cortex-m33/src/lib.rs b/arch/cortex-m33/src/lib.rs index 95aacb7243..4ae7f039e5 100644 --- a/arch/cortex-m33/src/lib.rs +++ b/arch/cortex-m33/src/lib.rs @@ -25,6 +25,7 @@ pub mod mpu { } } +pub use cortexm::dma_fence; pub use cortexm::initialize_ram_jump_to_main; pub use cortexm::interrupt_mask; pub use cortexm::nvic; diff --git a/arch/riscv/src/csr/mod.rs b/arch/riscv/src/csr/mod.rs index ca8b3018f0..27e6e614f0 100644 --- a/arch/riscv/src/csr/mod.rs +++ b/arch/riscv/src/csr/mod.rs @@ -5,18 +5,21 @@ //! Tock Register interface for using CSR registers. use riscv_csr::csr::{ - ReadWriteRiscvCsr, MCAUSE, MCYCLE, MCYCLEH, MEPC, MIE, MINSTRET, MINSTRETH, MIP, MSCRATCH, - MSECCFG, MSECCFGH, MSTATUS, MTVAL, MTVEC, PMPADDR0, PMPADDR1, PMPADDR10, PMPADDR11, PMPADDR12, - PMPADDR13, PMPADDR14, PMPADDR15, PMPADDR16, PMPADDR17, PMPADDR18, PMPADDR19, PMPADDR2, - PMPADDR20, PMPADDR21, PMPADDR22, PMPADDR23, PMPADDR24, PMPADDR25, PMPADDR26, PMPADDR27, - PMPADDR28, PMPADDR29, PMPADDR3, PMPADDR30, PMPADDR31, PMPADDR32, PMPADDR33, PMPADDR34, - PMPADDR35, PMPADDR36, PMPADDR37, PMPADDR38, PMPADDR39, PMPADDR4, PMPADDR40, PMPADDR41, - PMPADDR42, PMPADDR43, PMPADDR44, PMPADDR45, PMPADDR46, PMPADDR47, PMPADDR48, PMPADDR49, - PMPADDR5, PMPADDR50, PMPADDR51, PMPADDR52, PMPADDR53, PMPADDR54, PMPADDR55, PMPADDR56, - PMPADDR57, PMPADDR58, PMPADDR59, PMPADDR6, PMPADDR60, PMPADDR61, PMPADDR62, PMPADDR63, - PMPADDR7, PMPADDR8, PMPADDR9, PMPCFG0, PMPCFG1, PMPCFG10, PMPCFG11, PMPCFG12, PMPCFG13, - PMPCFG14, PMPCFG15, PMPCFG2, PMPCFG3, PMPCFG4, PMPCFG5, PMPCFG6, PMPCFG7, PMPCFG8, PMPCFG9, - STVEC, UTVEC, + ReadWriteRiscvCsr, MCAUSE, MCYCLE, MEPC, MIE, MINSTRET, MIP, MSCRATCH, MSECCFG, MSTATUS, MTVAL, + MTVEC, PMPADDR0, PMPADDR1, PMPADDR10, PMPADDR11, PMPADDR12, PMPADDR13, PMPADDR14, PMPADDR15, + PMPADDR16, PMPADDR17, PMPADDR18, PMPADDR19, PMPADDR2, PMPADDR20, PMPADDR21, PMPADDR22, + PMPADDR23, PMPADDR24, PMPADDR25, PMPADDR26, PMPADDR27, PMPADDR28, PMPADDR29, PMPADDR3, + PMPADDR30, PMPADDR31, PMPADDR32, PMPADDR33, PMPADDR34, PMPADDR35, PMPADDR36, PMPADDR37, + PMPADDR38, PMPADDR39, PMPADDR4, PMPADDR40, PMPADDR41, PMPADDR42, PMPADDR43, PMPADDR44, + PMPADDR45, PMPADDR46, PMPADDR47, PMPADDR48, PMPADDR49, PMPADDR5, PMPADDR50, PMPADDR51, + PMPADDR52, PMPADDR53, PMPADDR54, PMPADDR55, PMPADDR56, PMPADDR57, PMPADDR58, PMPADDR59, + PMPADDR6, PMPADDR60, PMPADDR61, PMPADDR62, PMPADDR63, PMPADDR7, PMPADDR8, PMPADDR9, PMPCFG0, + PMPCFG10, PMPCFG12, PMPCFG14, PMPCFG2, PMPCFG4, PMPCFG6, PMPCFG8, STVEC, UTVEC, +}; +#[cfg(not(target_arch = "riscv64"))] +use riscv_csr::csr::{ + MCYCLEH, MINSTRETH, MSECCFGH, PMPCFG1, PMPCFG11, PMPCFG13, PMPCFG15, PMPCFG3, PMPCFG5, PMPCFG7, + PMPCFG9, }; use tock_registers::fields::FieldValue; use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; @@ -51,7 +54,10 @@ pub struct CSR { pub mcycleh: ReadWriteRiscvCsr, pub mcycle: ReadWriteRiscvCsr, - #[cfg(not(target_arch = "riscv64"))] + // `pmpcfg0` exists on both RV32 and RV64. On RV64 each `pmpcfgN` is XLEN-wide + // and holds 8 entries, so only the even-indexed registers (pmpcfg0, 2, 4, …, + // 14) are valid; the odd-indexed ones below are RV32-only. `pmpcfg0` is the + // first of those even-indexed registers and must always be present. pub pmpcfg0: ReadWriteRiscvCsr, #[cfg(not(target_arch = "riscv64"))] pub pmpcfg1: ReadWriteRiscvCsr, @@ -313,7 +319,9 @@ impl CSR { // reads the cycle counter #[cfg(target_arch = "riscv64")] pub fn read_cycle_counter(&self) -> u64 { - CSR.mcycle.read(mcycle::mcycle::mcycle) + // On RV64 `mcycle` is a single 64-bit CSR; `read` returns `usize`, which + // is `u64` here but still needs an explicit cast to the return type. + CSR.mcycle.read(mcycle::mcycle::mcycle) as u64 } pub fn pmpconfig_get(&self, index: usize) -> usize { diff --git a/arch/riscv/src/lib.rs b/arch/riscv/src/lib.rs index 1c90b7ec46..9ed4bf4545 100644 --- a/arch/riscv/src/lib.rs +++ b/arch/riscv/src/lib.rs @@ -13,17 +13,23 @@ use kernel::utilities::registers::interfaces::{Readable, Writeable}; pub mod csr; pub mod dma_fence; pub mod pmp; +pub mod pseudo_instructions; pub mod support; pub mod syscall; pub mod thread_id; +/// `XLEN` is the width of an integer register in bits (either 32 or 64). +pub const XLEN: usize = 1 << XLEN_LOG2; + +/// `XLEN_LOG2` is the log base 2 of XLEN. +#[cfg(target_arch = "riscv32")] +pub const XLEN_LOG2: usize = 5; +#[cfg(target_arch = "riscv64")] +pub const XLEN_LOG2: usize = 6; // Default to 32 bit if no architecture is specified of if this is being // compiled for docs or testing on a different architecture. -pub const XLEN: usize = if cfg!(target_arch = "riscv64") { - 64 -} else { - 32 -}; +#[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64")))] +pub const XLEN_LOG2: usize = 5; extern "C" { // Where the end of the stack region is (and hence where the stack should @@ -183,7 +189,11 @@ pub unsafe fn configure_trap_handler() { } // Mock implementation for tests on Travis-CI. -#[cfg(not(any(doc, all(target_arch = "riscv32", target_os = "none"))))] +#[cfg(not(any( + doc, + all(target_arch = "riscv32", target_os = "none"), + all(target_arch = "riscv64", target_os = "none") +)))] pub extern "C" fn _start_trap() -> ! { unimplemented!() } @@ -208,10 +218,18 @@ pub extern "C" fn _start_trap() -> ! { /// assume that the hart is currently executing kernel code. /// /// If it contains any other value, we interpret it to be a memory address -/// pointing to a particular data structure: +/// pointing to a particular data structure (`custom_trap_handler`): +/// +/// ```text +/// custom_trap_handler: [usize; 2]; +/// +/// mscratch = &custom_trap_handler; +/// ``` +/// +/// or in memory: /// /// ```text -/// mscratch 0 1 2 3 +/// mscratch /// \->|--------------------------------------------------------------| /// | scratch word, overwritten with s1 register contents | /// |--------------------------------------------------------------| @@ -228,9 +246,9 @@ pub extern "C" fn _start_trap() -> ! { /// 2. execute the default kernel trap handler if s0 now contains `0` (meaning /// that the mscratch CSR contained `0` before entering this trap handler), /// -/// 3. otherwise, save s1 to `0*4(s0)`, and finally +/// 3. otherwise, save s1 to `0*(XLEN/8)(s0)`, and finally /// -/// 4. load the address at `1*4(s0)` into s1, and jump to it. +/// 4. load the address at `1*(XLEN/8)(s0)` into s1, and jump to it. /// /// No registers other than s0, s1 and the mscratch CSR are to be clobbered /// before continuing execution at the address loaded into the mscratch CSR @@ -282,7 +300,11 @@ pub extern "C" fn _start_trap() -> ! { /// invoked, it may, for instance, choose to ignore a certain trap, access /// global state (subject to synchronization), etc. It must still abide to /// the contract as stated above. -#[cfg(any(doc, all(target_arch = "riscv32", target_os = "none")))] +#[cfg(any( + doc, + all(target_arch = "riscv32", target_os = "none"), + all(target_arch = "riscv64", target_os = "none") +))] #[link_section = ".riscv.trap"] // We need the `_start_trap` function to be 256 byte aligned. The linker script // includes a check for whether a symbol named `_start_trap` exists. If it does, @@ -294,6 +316,7 @@ pub extern "C" fn _start_trap() -> ! { pub extern "C" fn _start_trap() -> ! { use core::arch::naked_asm; naked_asm!( + xlen_macros!(), " // This is the global trap handler. By default, Tock expects this // trap handler to be registered at all times, and that all traps @@ -302,19 +325,21 @@ pub extern "C" fn _start_trap() -> ! { // // For documentation of its behavior, and how process // implementations can hook their own trap handler code, see the - // comment on the `extern C _start_trap` symbol above. + // comment on the `_start_trap` function. - // Atomically swap s0 and mscratch: + // Atomically swap s0 and mscratch. This puts `&custom_trap_handler` + // in s0. csrrw s0, mscratch, s0 // s0 = mscratch; mscratch = s0 // If mscratch contained 0, invoke the kernel trap handler. - beq s0, x0, 100f // if s0==x0: goto 100 + beq s0, x0, 100f // if s0==x0: goto 100 - // Else, save the current value of s1 to `0*4(s0)`, load `1*4(s0)` - // into s1 and jump to it (invoking a custom trap handler). - sw s1, 0*4(s0) // *s0 = s1 - lw s1, 1*4(s0) // s1 = *(s0+4) - jr s1 // goto s1 + // Else, save the current value of s1 to `custom_trap_handler[0]`, + // load `custom_trap_handler[1]` into s1 and jump to it (invoking + // a custom trap handler). + sx s1, 0*({XLEN}/8)(s0) // custom_trap_handler[0] = s1 + lx s1, 1*({XLEN}/8)(s0) // s1 = custom_trap_handler[1] + jr s1 // goto s1 100: // _start_kernel_trap @@ -328,50 +353,50 @@ pub extern "C" fn _start_trap() -> ! { // Load the address of the bottom of the stack (`_sstack`) into our // newly freed-up s0 register. - la s0, {sstack} // s0 = _sstack + la s0, {sstack} // s0 = _sstack // Compare the kernel stack pointer to the bottom of the stack. If // the stack pointer is above the bottom of the stack, then continue // handling the fault as normal. - bgtu sp, s0, 200f // branch if sp > s0 + bgtu sp, s0, 200f // branch if sp > s0 // If we get here, then we did encounter a stack overflow. We are // going to panic at this point, but for that to work we need a // valid stack to run the panic code. We do this by just starting // over with the kernel stack and placing the stack pointer at the // top of the original stack. - la sp, {estack} // sp = _estack + la sp, {estack} // sp = _estack 200: // _start_kernel_trap_continue // Restore s0. We reset mscratch to 0 (kernel trap handler mode) - csrrw s0, mscratch, zero // s0 = mscratch; mscratch = 0 + csrrw s0, mscratch, zero // s0 = mscratch; mscratch = 0 // Make room for the caller saved registers we need to restore after running // any trap handler code. - addi sp, sp, -20*4 + addi sp, sp, -20*({XLEN}/8) // riscv32: sp = sp - (20*4), riscv64: sp = sp - (20*8) // Save all of the caller saved registers. - sw ra, 0*4(sp) - sw t0, 1*4(sp) - sw t1, 2*4(sp) - sw t2, 3*4(sp) - sw t3, 4*4(sp) - sw t4, 5*4(sp) - sw t5, 6*4(sp) - sw t6, 7*4(sp) - sw a0, 8*4(sp) - sw a1, 9*4(sp) - sw a2, 10*4(sp) - sw a3, 11*4(sp) - sw a4, 12*4(sp) - sw a5, 13*4(sp) - sw a6, 14*4(sp) - sw a7, 15*4(sp) + sx ra, 0*({XLEN}/8)(sp) // riscv32: *(stackptr + (0*4)) = ra, riscv64: *(stackptr + (0*8)) = ra + sx t0, 1*({XLEN}/8)(sp) // riscv32: *(stackptr + (1*4)) = t0, riscv64: *(stackptr + (1*8)) = t0 + sx t1, 2*({XLEN}/8)(sp) // riscv32: *(stackptr + (2*4)) = t1, riscv64: *(stackptr + (2*8)) = t1 + sx t2, 3*({XLEN}/8)(sp) // riscv32: *(stackptr + (3*4)) = t2, riscv64: *(stackptr + (3*8)) = t2 + sx t3, 4*({XLEN}/8)(sp) // riscv32: *(stackptr + (4*4)) = t3, riscv64: *(stackptr + (4*8)) = t3 + sx t4, 5*({XLEN}/8)(sp) // riscv32: *(stackptr + (5*4)) = t4, riscv64: *(stackptr + (5*8)) = t4 + sx t5, 6*({XLEN}/8)(sp) // riscv32: *(stackptr + (6*4)) = t5, riscv64: *(stackptr + (6*8)) = t5 + sx t6, 7*({XLEN}/8)(sp) // riscv32: *(stackptr + (7*4)) = t6, riscv64: *(stackptr + (7*8)) = t6 + sx a0, 8*({XLEN}/8)(sp) // riscv32: *(stackptr + (8*4)) = a0, riscv64: *(stackptr + (8*8)) = a0 + sx a1, 9*({XLEN}/8)(sp) // riscv32: *(stackptr + (9*4)) = a1, riscv64: *(stackptr + (9*8)) = a1 + sx a2, 10*({XLEN}/8)(sp) // riscv32: *(stackptr + (10*4)) = a2, riscv64: *(stackptr + (10*8)) = a2 + sx a3, 11*({XLEN}/8)(sp) // riscv32: *(stackptr + (11*4)) = a3, riscv64: *(stackptr + (11*8)) = a3 + sx a4, 12*({XLEN}/8)(sp) // riscv32: *(stackptr + (12*4)) = a4, riscv64: *(stackptr + (12*8)) = a4 + sx a5, 13*({XLEN}/8)(sp) // riscv32: *(stackptr + (13*4)) = a5, riscv64: *(stackptr + (13*8)) = a5 + sx a6, 14*({XLEN}/8)(sp) // riscv32: *(stackptr + (14*4)) = a6, riscv64: *(stackptr + (14*8)) = a6 + sx a7, 15*({XLEN}/8)(sp) // riscv32: *(stackptr + (15*4)) = a7, riscv64: *(stackptr + (15*8)) = a7 // Save one callee-saved register (s0), which we place the address of // the hart-specific 'are we in a trap handler' flag in: - sw s0, 16*4(sp) + sx s0, 16*({XLEN}/8)(sp) // riscv32: *(stackptr + (16*4)) = s0, riscv64: *(stackptr + (16*8)) = s0 // Determine the address of the hart-specific 'are we in a trap handler' // flag as an offset to the _trap_handler_active symbol. The chip crate @@ -379,12 +404,12 @@ pub extern "C" fn _start_trap() -> ! { // enough to fit `max(mhartid) * MXLEN` bytes. la s0, _trap_handler_active // s0 = addr(_trap_handler_active) csrr t0, mhartid // t0 = hartid - slli t0, t0, 2 // t0 = t0 * 4 + slli t0, t0, ({XLEN_LOG2}-3) // t0 = t0 * sizeof(usize) add s0, s0, t0 // s0 = addr(_trap_handler_active[hartid]) // Indicate that we are in a trap handler on this hart: - li t0, 1 - sw t0, 0(s0) + li t0, 1 // t0 = 1 + sw t0, 0(s0) // _trap_handler_active[hartid] = 1 // Jump to board-specific trap handler code. Likely this was an // interrupt and we want to disable a particular interrupt, but each @@ -393,32 +418,32 @@ pub extern "C" fn _start_trap() -> ! { // Indicate that we are no longer going to be in a trap handler on this // hart: - sw x0, 0(s0) + sw x0, 0(s0) // _trap_handler_active[hartid] = 0 // Restore the caller saved registers from the stack. - lw ra, 0*4(sp) - lw t0, 1*4(sp) - lw t1, 2*4(sp) - lw t2, 3*4(sp) - lw t3, 4*4(sp) - lw t4, 5*4(sp) - lw t5, 6*4(sp) - lw t6, 7*4(sp) - lw a0, 8*4(sp) - lw a1, 9*4(sp) - lw a2, 10*4(sp) - lw a3, 11*4(sp) - lw a4, 12*4(sp) - lw a5, 13*4(sp) - lw a6, 14*4(sp) - lw a7, 15*4(sp) + lx ra, 0*({XLEN}/8)(sp) // riscv32: ra = *(stackptr + (0*4)), riscv64: ra = *(stackptr + (0*8)) + lx t0, 1*({XLEN}/8)(sp) // riscv32: t0 = *(stackptr + (1*4)), riscv64: t0 = *(stackptr + (1*8)) + lx t1, 2*({XLEN}/8)(sp) // riscv32: t1 = *(stackptr + (2*4)), riscv64: t1 = *(stackptr + (2*8)) + lx t2, 3*({XLEN}/8)(sp) // riscv32: t2 = *(stackptr + (3*4)), riscv64: t2 = *(stackptr + (3*8)) + lx t3, 4*({XLEN}/8)(sp) // riscv32: t3 = *(stackptr + (4*4)), riscv64: t3 = *(stackptr + (4*8)) + lx t4, 5*({XLEN}/8)(sp) // riscv32: t4 = *(stackptr + (5*4)), riscv64: t4 = *(stackptr + (5*8)) + lx t5, 6*({XLEN}/8)(sp) // riscv32: t5 = *(stackptr + (6*4)), riscv64: t5 = *(stackptr + (6*8)) + lx t6, 7*({XLEN}/8)(sp) // riscv32: t6 = *(stackptr + (7*4)), riscv64: t6 = *(stackptr + (7*8)) + lx a0, 8*({XLEN}/8)(sp) // riscv32: a0 = *(stackptr + (8*4)), riscv64: a0 = *(stackptr + (8*8)) + lx a1, 9*({XLEN}/8)(sp) // riscv32: a1 = *(stackptr + (9*4)), riscv64: a1 = *(stackptr + (9*8)) + lx a2, 10*({XLEN}/8)(sp) // riscv32: a2 = *(stackptr + (10*4)), riscv64: a2 = *(stackptr + (10*8)) + lx a3, 11*({XLEN}/8)(sp) // riscv32: a3 = *(stackptr + (11*4)), riscv64: a3 = *(stackptr + (11*8)) + lx a4, 12*({XLEN}/8)(sp) // riscv32: a4 = *(stackptr + (12*4)), riscv64: a4 = *(stackptr + (12*8)) + lx a5, 13*({XLEN}/8)(sp) // riscv32: a5 = *(stackptr + (13*4)), riscv64: a5 = *(stackptr + (13*8)) + lx a6, 14*({XLEN}/8)(sp) // riscv32: a6 = *(stackptr + (14*4)), riscv64: a6 = *(stackptr + (14*8)) + lx a7, 15*({XLEN}/8)(sp) // riscv32: a7 = *(stackptr + (15*4)), riscv64: a7 = *(stackptr + (15*8)) // Restore the one callee-saved register (s0), which used to hold the // address of the hart-specific 'are we in a trap handler flag': - lw s0, 16*4(sp) + lx s0, 16*({XLEN}/8)(sp) // riscv32: s0 = *(stackptr + (16*4)), riscv64: s0 = *(stackptr + (16*8)) // Reset the stack pointer. - addi sp, sp, 20*4 + addi sp, sp, 20*({XLEN}/8) // riscv32: sp = sp + (20*4), riscv32: sp = sp + (20*8) // mret returns from the trap handler. The PC is set to what is in // mepc and execution proceeds from there. Since we did not modify @@ -427,6 +452,8 @@ pub extern "C" fn _start_trap() -> ! { ", estack = sym _estack, sstack = sym _sstack, + XLEN = const XLEN, + XLEN_LOG2 = const XLEN_LOG2, ); } @@ -447,19 +474,19 @@ pub unsafe fn semihost_command(command: usize, arg0: usize, arg1: usize) -> usiz let res; asm!( " - .balign 16 - .option push - .option norelax - .option norvc - slli x0, x0, 0x1f - ebreak - srai x0, x0, 7 + .balign 16 // ensure 16 byte alignment + .option push // enable the following options: + .option norelax // - norelax: do not replace these instructions + .option norvc // - norvc: force full 32 bit instructions + slli x0, x0, 0x1f // useless instruction (writes to x0), but serves as sentinel for semihosting + ebreak // trap to debugger + srai x0, x0, 7 // useless instruction (writes to x0), but serves as second sentinel .option pop ", - in("a0") command, - in("a1") arg0, - in("a2") arg1, - lateout("a0") res, + in("a0") command, // a0 holds command (and return code) + in("a1") arg0, // a1 holds first argument + in("a2") arg1, // a2 holds second argument + lateout("a0") res, // semihosting replaces a0 with return code ); res } @@ -472,87 +499,38 @@ pub unsafe fn semihost_command(_command: usize, _arg0: usize, _arg1: usize) -> u /// Print a readable string for an mcause reason. pub unsafe fn print_mcause(mcval: csr::mcause::Trap, writer: &mut dyn Write) { - match mcval { + let s = match mcval { csr::mcause::Trap::Interrupt(interrupt) => match interrupt { - csr::mcause::Interrupt::UserSoft => { - let _ = writer.write_fmt(format_args!("User software interrupt")); - } - csr::mcause::Interrupt::SupervisorSoft => { - let _ = writer.write_fmt(format_args!("Supervisor software interrupt")); - } - csr::mcause::Interrupt::MachineSoft => { - let _ = writer.write_fmt(format_args!("Machine software interrupt")); - } - csr::mcause::Interrupt::UserTimer => { - let _ = writer.write_fmt(format_args!("User timer interrupt")); - } - csr::mcause::Interrupt::SupervisorTimer => { - let _ = writer.write_fmt(format_args!("Supervisor timer interrupt")); - } - csr::mcause::Interrupt::MachineTimer => { - let _ = writer.write_fmt(format_args!("Machine timer interrupt")); - } - csr::mcause::Interrupt::UserExternal => { - let _ = writer.write_fmt(format_args!("User external interrupt")); - } - csr::mcause::Interrupt::SupervisorExternal => { - let _ = writer.write_fmt(format_args!("Supervisor external interrupt")); - } - csr::mcause::Interrupt::MachineExternal => { - let _ = writer.write_fmt(format_args!("Machine external interrupt")); - } - csr::mcause::Interrupt::Unknown(_) => { - let _ = writer.write_fmt(format_args!("Reserved/Unknown")); - } + csr::mcause::Interrupt::UserSoft => "User software interrupt", + csr::mcause::Interrupt::SupervisorSoft => "Supervisor software interrupt", + csr::mcause::Interrupt::MachineSoft => "Machine software interrupt", + csr::mcause::Interrupt::UserTimer => "User timer interrupt", + csr::mcause::Interrupt::SupervisorTimer => "Supervisor timer interrupt", + csr::mcause::Interrupt::MachineTimer => "Machine timer interrupt", + csr::mcause::Interrupt::UserExternal => "User external interrupt", + csr::mcause::Interrupt::SupervisorExternal => "Supervisor external interrupt", + csr::mcause::Interrupt::MachineExternal => "Machine external interrupt", + csr::mcause::Interrupt::Unknown(_) => "Reserved/Unknown", }, csr::mcause::Trap::Exception(exception) => match exception { - csr::mcause::Exception::InstructionMisaligned => { - let _ = writer.write_fmt(format_args!("Instruction access misaligned")); - } - csr::mcause::Exception::InstructionFault => { - let _ = writer.write_fmt(format_args!("Instruction access fault")); - } - csr::mcause::Exception::IllegalInstruction => { - let _ = writer.write_fmt(format_args!("Illegal instruction")); - } - csr::mcause::Exception::Breakpoint => { - let _ = writer.write_fmt(format_args!("Breakpoint")); - } - csr::mcause::Exception::LoadMisaligned => { - let _ = writer.write_fmt(format_args!("Load address misaligned")); - } - csr::mcause::Exception::LoadFault => { - let _ = writer.write_fmt(format_args!("Load access fault")); - } - csr::mcause::Exception::StoreMisaligned => { - let _ = writer.write_fmt(format_args!("Store/AMO address misaligned")); - } - csr::mcause::Exception::StoreFault => { - let _ = writer.write_fmt(format_args!("Store/AMO access fault")); - } - csr::mcause::Exception::UserEnvCall => { - let _ = writer.write_fmt(format_args!("Environment call from U-mode")); - } - csr::mcause::Exception::SupervisorEnvCall => { - let _ = writer.write_fmt(format_args!("Environment call from S-mode")); - } - csr::mcause::Exception::MachineEnvCall => { - let _ = writer.write_fmt(format_args!("Environment call from M-mode")); - } - csr::mcause::Exception::InstructionPageFault => { - let _ = writer.write_fmt(format_args!("Instruction page fault")); - } - csr::mcause::Exception::LoadPageFault => { - let _ = writer.write_fmt(format_args!("Load page fault")); - } - csr::mcause::Exception::StorePageFault => { - let _ = writer.write_fmt(format_args!("Store/AMO page fault")); - } - csr::mcause::Exception::Unknown => { - let _ = writer.write_fmt(format_args!("Reserved")); - } + csr::mcause::Exception::InstructionMisaligned => "Instruction access misaligned", + csr::mcause::Exception::InstructionFault => "Instruction access fault", + csr::mcause::Exception::IllegalInstruction => "Illegal instruction", + csr::mcause::Exception::Breakpoint => "Breakpoint", + csr::mcause::Exception::LoadMisaligned => "Load address misaligned", + csr::mcause::Exception::LoadFault => "Load access fault", + csr::mcause::Exception::StoreMisaligned => "Store/AMO address misaligned", + csr::mcause::Exception::StoreFault => "Store/AMO access fault", + csr::mcause::Exception::UserEnvCall => "Environment call from U-mode", + csr::mcause::Exception::SupervisorEnvCall => "Environment call from S-mode", + csr::mcause::Exception::MachineEnvCall => "Environment call from M-mode", + csr::mcause::Exception::InstructionPageFault => "Instruction page fault", + csr::mcause::Exception::LoadPageFault => "Load page fault", + csr::mcause::Exception::StorePageFault => "Store/AMO page fault", + csr::mcause::Exception::Unknown => "Reserved", }, - } + }; + let _ = writer.write_str(s); } /// Prints out RISCV machine state, including basic system registers diff --git a/arch/riscv/src/pmp.rs b/arch/riscv/src/pmp.rs index be84a5c1a5..e2e7674943 100644 --- a/arch/riscv/src/pmp.rs +++ b/arch/riscv/src/pmp.rs @@ -327,7 +327,7 @@ pub mod misc_pmp_test { fn test_napot_region_spec_from_pmpaddr_csr() { use super::NAPOTRegionSpec; - // Unfortunatly, we can't run these unit tests for different platforms, + // Unfortunately, we can't run these unit tests for different platforms, // with arbitrary bit-widths (at least when using `usize` in the // `TORRegionSpec` internally. // @@ -419,7 +419,7 @@ pub mod misc_pmp_test { #[test] fn test_tor_region_spec_from_pmpaddr_csrs() { use super::TORRegionSpec; - // Unfortunatly, we can't run these unit tests for different platforms, + // Unfortunately, we can't run these unit tests for different platforms, // with arbitrary bit-widths (at least when using `usize` in the // `TORRegionSpec` internally. // @@ -577,7 +577,7 @@ pub mod misc_pmp_test { /// /// This function is unsafe, as it relies on the PMP CSRs to be accessible, and /// the hardware to feature `PHYSICAL_ENTRIES` PMP CSR entries. If these -/// conditions are not met, calling this function can result in undefinied +/// conditions are not met, calling this function can result in undefined /// behavior (e.g., cause a system trap). pub unsafe fn format_pmp_entries( f: &mut fmt::Formatter<'_>, @@ -763,11 +763,11 @@ pub trait TORUserPMP { /// configured either as a TOR region (`A = 0b01`), or disabled (all bits /// set to `0`). /// - /// - second value (`*const u8`): the region's start addres. As a PMP TOR + /// - second value (`*const u8`): the region's start address. As a PMP TOR /// region has a 4-byte address granularity, this address is rounded down /// to the next 4-byte boundary. /// - /// - third value (`*const u8`): the region's end addres. As a PMP TOR + /// - third value (`*const u8`): the region's end address. As a PMP TOR /// region has a 4-byte address granularity, this address is rounded down /// to the next 4-byte boundary. /// @@ -795,7 +795,7 @@ pub trait TORUserPMP { /// Disable the user-mode memory protection. /// /// Disables the memory protection for user-mode. If enabling the user-mode - /// memory protetion made user-mode accessible regions inaccessible to + /// memory protection made user-mode accessible regions inaccessible to /// machine-mode, this method should make these regions accessible again. /// /// For PMP implementations where configured regions are only enforced in @@ -995,7 +995,7 @@ unsafe impl + 'static> // that performed this check before adjusting the requested region // size to meet PMP region layout constraints (4 byte alignment for // start and end address). Existing applications whose end-address - // is aligned on a less than 4-byte bondary would thus be given + // is aligned on a less than 4-byte boundary would thus be given // access to additional memory which should be inaccessible. // Unfortunately, we can't fix this without breaking existing // applications. Thus, we perform the same insecure hack here, and @@ -1458,12 +1458,12 @@ pub mod simple { /// expected to be set to the number of available entries. /// /// [`SimplePMP`] implements [`TORUserPMP`] to expose all of its regions as - /// "top of range" (TOR) regions (each taking up two physical PMP entires) + /// "top of range" (TOR) regions (each taking up two physical PMP entries) /// for use as a user-mode memory protection mechanism. /// /// Notably, [`SimplePMP`] implements `TORUserPMP` over a /// generic `MPU_REGIONS` where `MPU_REGIONS <= (AVAILABLE_ENTRIES / 2)`. As - /// PMP re-configuration can have a significiant runtime overhead, users are + /// PMP re-configuration can have a significant runtime overhead, users are /// free to specify a small `MPU_REGIONS` const-generic parameter to reduce /// the runtime overhead induced through PMP configuration, at the cost of /// having less PMP regions available to use for userspace memory @@ -2278,7 +2278,7 @@ pub mod kernel_protection_mml_epmp { for KernelProtectionMMLEPMP { // Ensure that the MPU_REGIONS (starting at entry, and occupying two - // entries per region) don't overflow the available entires, excluding + // entries per region) don't overflow the available entries, excluding // the 7 entries used for implementing the kernel memory protection: const CONST_ASSERT_CHECK: () = assert!(MPU_REGIONS <= ((AVAILABLE_ENTRIES - 5) / 2)); diff --git a/arch/riscv/src/pseudo_instructions.rs b/arch/riscv/src/pseudo_instructions.rs new file mode 100644 index 0000000000..d85fe1cd11 --- /dev/null +++ b/arch/riscv/src/pseudo_instructions.rs @@ -0,0 +1,36 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2026. + +//! Define missing RISC-V pseudo instructions using assembly macros. +//! +//! The RISC-V spec includes numerous pseudo instructions to help with writing +//! RISC-V assembly. However, somewhat bafflingly, there are no pseudo +//! instructions for making operations XLEN bits long. This makes it difficult +//! to write general assembly code that works on both RV32 and RV64 systems, +//! where the assembly only needs to operate on different XLENs. +//! +//! `xlen_macros!` defines macros `lx` and `sx`, which function as +//! pseudoinstructions for loading and storing XLEN-sized values. + +#[cfg(any(doc, target_arch = "riscv32"))] +#[macro_export] +macro_rules! xlen_macros[() => [r" + .macro sx src, dest + sw \src, \dest + .endm + .macro lx dest, src + lw \dest, \src + .endm +"]]; + +#[cfg(target_arch = "riscv64")] +#[macro_export] +macro_rules! xlen_macros[() => [r" + .macro sx src, dest + sd \src, \dest + .endm + .macro lx dest, src + ld \dest, \src + .endm +"]]; diff --git a/arch/riscv/src/support.rs b/arch/riscv/src/support.rs index 83fb5ebdb3..032bcbfa3a 100644 --- a/arch/riscv/src/support.rs +++ b/arch/riscv/src/support.rs @@ -6,7 +6,7 @@ use crate::csr::{mstatus::mstatus, CSR}; -#[cfg(any(doc, all(target_arch = "riscv32", target_os = "none")))] +#[cfg(any(doc, target_arch = "riscv32", target_arch = "riscv64"))] #[inline(always)] /// NOP instruction pub fn nop() { @@ -16,9 +16,9 @@ pub fn nop() { } } -#[cfg(any(doc, all(target_arch = "riscv32", target_os = "none")))] +#[cfg(any(doc, target_arch = "riscv32", target_arch = "riscv64"))] #[inline(always)] -/// WFI instruction +/// Wait For Interrupt (WFI) instruction. pub unsafe fn wfi() { use core::arch::asm; asm!("wfi", options(nomem, nostack)); @@ -51,13 +51,13 @@ where } // Mock implementations for tests on Travis-CI. -#[cfg(not(any(doc, all(target_arch = "riscv32", target_os = "none"))))] +#[cfg(not(any(doc, target_arch = "riscv32", target_arch = "riscv64")))] /// NOP instruction (mock) pub fn nop() { unimplemented!() } -#[cfg(not(any(doc, all(target_arch = "riscv32", target_os = "none"))))] +#[cfg(not(any(doc, target_arch = "riscv32", target_arch = "riscv64")))] /// WFI instruction (mock) pub unsafe fn wfi() { unimplemented!() diff --git a/arch/riscv/src/syscall.rs b/arch/riscv/src/syscall.rs index a78dd30db2..6ca4ff5440 100644 --- a/arch/riscv/src/syscall.rs +++ b/arch/riscv/src/syscall.rs @@ -246,382 +246,383 @@ impl kernel::syscall::UserspaceKernelBoundary for SysCall { // is not set, hence the compiler has to assume the assembly // will issue arbitrary memory accesses (acting as a compiler // fence). - asm!(" - // Before switching to the app we need to save some kernel registers - // to the kernel stack, specifically ones which we can't mark as - // clobbered in the asm!() block. We then save the stack pointer in - // the mscratch CSR so we can retrieve it after returning to the - // kernel from the app. - // - // A few values get saved to the kernel stack, including an app - // register temporarily after entering the trap handler. Here is a - // memory map to make it easier to keep track: - // - // ``` - // 8*4(sp): <- original stack pointer - // 7*4(sp): - // 6*4(sp): x9 / s1 - // 5*4(sp): x8 / s0 / fp - // 4*4(sp): x4 / tp - // 3*4(sp): x3 / gp - // 2*4(sp): x10 / a0 (*state, Per-process StoredState struct) - // 1*4(sp): custom trap handler address - // 0*4(sp): scratch space, having s1 written to by the trap handler - // <- new stack pointer - // ``` - - addi sp, sp, -8*4 // Move the stack pointer down to make room. - - // Save all registers on the kernel stack which cannot be clobbered - // by an asm!() block. These are mostly registers which have a - // designated purpose (e.g. stack pointer) or are used internally - // by LLVM. - sw x9, 6*4(sp) // s1 (used internally by LLVM) - sw x8, 5*4(sp) // fp (can't be clobbered / used as an operand) - sw x4, 4*4(sp) // tp (can't be clobbered / used as an operand) - sw x3, 3*4(sp) // gp (can't be clobbered / used as an operand) - - sw x10, 2*4(sp) // Store process state pointer on stack as well. - // We need to have this available for after the - // app returns to the kernel so we can store its - // registers. - - // Load the address of `_start_app_trap` into `1*4(sp)`. We swap our - // stack pointer into the mscratch CSR and the trap handler will load - // and jump to the address at this offset. - la t0, 100f // t0 = _start_app_trap - sw t0, 1*4(sp) // 1*4(sp) = t0 - - // sw x0, 0*4(sp) // Reserved as scratch space for the trap handler - - // -----> All required registers saved to the stack. - // sp holds the updated stack pointer, a0 the per-process state - - // From here on we can't allow the CPU to take interrupts anymore, as - // we re-route traps to `_start_app_trap` below (by writing our stack - // pointer into the mscratch CSR), and we rely on certain CSRs to not - // be modified or used in their intermediate states (e.g., mepc). - // - // We atomically switch to user-mode and re-enable interrupts using - // the `mret` instruction below. - // - // If interrupts are disabled _after_ setting mscratch, this result in - // the race condition of [PR 2308](https://github.com/tock/tock/pull/2308) - - // Therefore, clear the following bits in mstatus first: - // 0x00000008 -> bit 3 -> MIE (disabling interrupts here) - // + 0x00001800 -> bits 11,12 -> MPP (switch to usermode on mret) - li t0, 0x00001808 - csrc mstatus, t0 // clear bits in mstatus - - // Afterwards, set the following bits in mstatus: - // 0x00000080 -> bit 7 -> MPIE (enable interrupts on mret) - li t0, 0x00000080 - csrs mstatus, t0 // set bits in mstatus - - // Execute `_start_app_trap` on a trap by setting the mscratch trap - // handler address to our current stack pointer. This stack pointer, - // at `1*4(sp)`, holds the address of `_start_app_trap`. - // - // Upon a trap, the global trap handler (_start_trap) will swap `s0` - // with the `mscratch` CSR and, if it contains a non-zero address, - // jump to the address that is now at `1*4(s0)`. This allows us to - // hook a custom trap handler that saves all userspace state: - // - csrw mscratch, sp // Store `sp` in mscratch CSR. Discard the - // prior value, must have been set to zero. - - // We have to set the mepc CSR with the PC we want the app to start - // executing at. This has been saved in Riscv32iStoredState for us - // (either when the app returned back to the kernel or in the - // `set_process_function()` function). - lw t0, 31*4(a0) // Retrieve the PC from Riscv32iStoredState - csrw mepc, t0 // Set mepc CSR to the app's PC. - - // Restore all of the app registers from what we saved. If this is the - // first time running the app then most of these values are - // irrelevant, However we do need to set the four arguments to the - // `_start_ function in the app. If the app has been executing then - // this allows the app to correctly resume. - - // We do a little switcheroo here, and place the per-process stored - // state pointer into the `sp` register instead of `a0`. Doing so - // allows us to use compressed instructions for all of these loads: - mv sp, a0 // sp <- a0 (per-process stored state) - - lw x1, 0*4(sp) // ra - // ------------------------> sp, do last since we overwrite our pointer - lw x3, 2*4(sp) // gp - lw x4, 3*4(sp) // tp - lw x5, 4*4(sp) // t0 - lw x6, 5*4(sp) // t1 - lw x7, 6*4(sp) // t2 - lw x8, 7*4(sp) // s0,fp - lw x9, 8*4(sp) // s1 - lw x10, 9*4(sp) // a0 - lw x11, 10*4(sp) // a1 - lw x12, 11*4(sp) // a2 - lw x13, 12*4(sp) // a3 - lw x14, 13*4(sp) // a4 - lw x15, 14*4(sp) // a5 - lw x16, 15*4(sp) // a6 - lw x17, 16*4(sp) // a7 - lw x18, 17*4(sp) // s2 - lw x19, 18*4(sp) // s3 - lw x20, 19*4(sp) // s4 - lw x21, 20*4(sp) // s5 - lw x22, 21*4(sp) // s6 - lw x23, 22*4(sp) // s7 - lw x24, 23*4(sp) // s8 - lw x25, 24*4(sp) // s9 - lw x26, 25*4(sp) // s10 - lw x27, 26*4(sp) // s11 - lw x28, 27*4(sp) // t3 - lw x29, 28*4(sp) // t4 - lw x30, 29*4(sp) // t5 - lw x31, 30*4(sp) // t6 - lw x2, 1*4(sp) // sp, overwriting our pointer - - // Call mret to jump to where mepc points, switch to user mode, and - // start running the app. - mret - - // The global trap handler will jump to this address when catching a - // trap while the app is executing (address loaded into the mscratch - // CSR). - // - // This custom trap handler is responsible for saving application - // state, clearing the custom trap handler (mscratch = 0), and - // restoring the kernel context. - 100: // _start_app_trap - - // At this point all we know is that we entered the trap handler from - // an app. We don't know _why_ we got a trap, it could be from an - // interrupt, syscall, or fault (or maybe something else). Therefore - // we have to be very careful not to overwrite any registers before we - // have saved them. - // - // The global trap handler has swapped the app's `s0` into the - // mscratch CSR, which now contains the address of our stack pointer. - // The global trap handler further clobbered `s1`, which now contains - // the address of `_start_app_trap`. The app's `s1` is saved at - // `0*4(s0)`. - // - // Thus we can clobber `s1` and load the address of the per-process - // stored state: - // - lw s1, 2*4(s0) - - // With the per-process stored state address in `t1`, save all - // non-clobbered registers. Save the `sp` first, then do the same - // switcheroo as above, moving the per-process stored state pointer - // into `sp`. This allows us to use compressed instructions for all - // these stores: - sw x2, 1*4(s1) // Save app's sp - mv sp, s1 // sp <- s1 (per-process stored state) - - // Now, store relative to `sp` (per-process stored state) with - // compressed instructions: - sw x1, 0*4(sp) // ra - // ------------------------> sp, saved above - sw x3, 2*4(sp) // gp - sw x4, 3*4(sp) // tp - sw x5, 4*4(sp) // t0 - sw x6, 5*4(sp) // t1 - sw x7, 6*4(sp) // t2 - // ------------------------> s0, in mscratch right now - // ------------------------> s1, stored at 0*4(s0) right now - sw x10, 9*4(sp) // a0 - sw x11, 10*4(sp) // a1 - sw x12, 11*4(sp) // a2 - sw x13, 12*4(sp) // a3 - sw x14, 13*4(sp) // a4 - sw x15, 14*4(sp) // a5 - sw x16, 15*4(sp) // a6 - sw x17, 16*4(sp) // a7 - sw x18, 17*4(sp) // s2 - sw x19, 18*4(sp) // s3 - sw x20, 19*4(sp) // s4 - sw x21, 20*4(sp) // s5 - sw x22, 21*4(sp) // s6 - sw x23, 22*4(sp) // s7 - sw x24, 23*4(sp) // s8 - sw x25, 24*4(sp) // s9 - sw x26, 25*4(sp) // s10 - sw x27, 26*4(sp) // s11 - sw x28, 27*4(sp) // t3 - sw x29, 28*4(sp) // t4 - sw x30, 29*4(sp) // t5 - sw x31, 30*4(sp) // t6 - - // At this point, we can restore s0 into our stack pointer: - mv sp, s0 - - // Now retrieve the original value of s1 and save that as well. We - // must not clobber s1, our per-process stored state pointer. - lw s0, 0*4(sp) // s0 = app s1 (from trap handler scratch space) - sw s0, 8*4(s1) // Save app s1 to per-process state - - // Retrieve the original value of s0 from the mscratch CSR, save it. - // - // This will also restore the kernel trap handler by writing zero to - // the CSR. `csrrw` allows us to read and write the CSR in a single - // instruction: - csrrw s0, mscratch, zero // s0 <- mscratch[app s0] <- zero - sw s0, 7*4(s1) // Save app s0 to per-process state - - // ------------------------------------------------------------------- - // At this point, the entire app register file is saved. We also - // restored the kernel trap handler. We have restored the following - // kernel registers: - // - // - sp: kernel stack pointer - // - s1: per-process stored state pointer - // - // We avoid clobbering those registers from this point onward. - // ------------------------------------------------------------------- - - // We also need to store some other information about the trap reason, - // present in CSRs: - // - // - the app's PC (mepc), - // - the trap reason (mcause), - // - the trap 'value' (mtval, e.g., faulting address). - // - // We need to store mcause because we use that to determine why the - // app stopped executing and returned to the kernel. We store mepc - // because it is where we need to return to in the app at some - // point. We need to store mtval in case the app faulted and we need - // mtval to help with debugging. - // - // We use `s0` as a scratch register, as it fits into the 3-bit - // register argument of RISC-V compressed loads / stores: - - // Save the PC to the stored state struct. We also load the address - // of _return_to_kernel into it, as this will be where we jump on - // the mret instruction, which leaves the trap handler. - la s0, 300f // Load _return_to_kernel into t0. - csrrw s0, mepc, s0 // s0 <- mepc[app pc] <- _return_to_kernel - sw s0, 31*4(s1) // Store app's pc in stored state struct. - - // Save mtval to the stored state struct - csrr s0, mtval - sw s0, 33*4(s1) - - // Save mcause and leave it loaded into a0, as we call a function - // with it below: - csrr a0, mcause - sw a0, 32*4(s1) - - // Depending on the value of a0, we might be calling into a function - // while still in the trap handler. The callee may rely on the `gp`, - // `tp`, and `fp` (s0) registers to be set correctly. Thus we restore - // them here, as we need to do anyways. They are saved registers, - // and so we avoid clobbering them beyond this point. - // - // We do not restore `s1`, as we need to move it back into `a0` - // _after_ potentially invoking the _disable_interrupt_... function. - // LLVM relies on it to not be clobbered internally, but it is not - // part of the RISC-V C ABI, which we need to follow here. - // - lw x8, 5*4(sp) // fp/s0: Restore the frame pointer - lw x4, 4*4(sp) // tp: Restore the thread pointer - lw x3, 3*4(sp) // gp: Restore the global pointer - - // -------------------------------------------------------------------- - // From this point onward, avoid clobbering the following registers: - // - // - x2 / sp: kernel stack pointer - // - x3 / gp: kernel global pointer - // - x4 / tp: kernel thread pointer - // - x8 / s0 / fp: kernel frame pointer - // - x9 / s1: per-process stored state pointer - // - // -------------------------------------------------------------------- - - // Now we need to check if this was an interrupt, and if it was, - // then we need to disable the interrupt before returning from this - // trap handler so that it does not fire again. - // - // If mcause is greater than or equal to zero this was not an - // interrupt (i.e. the most significant bit is not 1). In this case, - // jump to _start_app_trap_continue. - bge a0, zero, 200f - - // This was an interrupt. Call the interrupt disable function, with - // mcause already loaded in a0. - // - // This may clobber all caller-saved registers. However, at this - // stage, we only restored `sp`, `s1`, and the registers above, all of - // which are saved. Thus we don't have to worry about the function - // call clobbering these registers. - // - // However, before we can execute any Rust code within a trap handler - // context, we must set the hart-specific 'are we in a trap handler' - // flag as an offset to the `_trap_handler_active` symbol. The chip - // crate is responsible for defining this symbol, and ensuring it is - // large enough to fit `max(mhartid) * MXLEN` bytes. - // - // First, calculate its address and save it in a callee-saved - // register. We use `s2`: the app's `s2` has already been saved, and - // its not one of the register's we've already restored: - la s2, _trap_handler_active // s2 = addr(_trap_handler_active) - csrr t0, mhartid // t0 = hartid - slli t0, t0, 2 // t0 = t0 * 4 - add s2, s2, t0 // s2 = addr(_trap_handler_active[hartid]) - - // Indicate that we are in a trap handler on this hart: - li t0, 1 - sw t0, 0(s2) - - jal ra, _disable_interrupt_trap_rust_from_app - - // Indicate that we are no longer going to be in a trap handler on - // this hart: - sw x0, 0(s2) - - 200: // _start_app_trap_continue - - // Need to set mstatus.MPP to 0b11 so that we stay in machine mode. - // - // We use `a0` as a scratch register, as we are allowed to clobber it - // here, and it fits into a compressed load instruction. We must avoid - // using restored saved registers like `s0`, etc. - // - li a0, 0x1800 // Load 0b11 to the MPP bits location in a0 - csrs mstatus, a0 // mstatus |= a0 - - // Use mret to exit the trap handler and return to the context - // switching code. We loaded the address of _return_to_kernel - // into mepc above. - mret - - // This is where the trap handler jumps back to after the app stops - // executing. - 300: // _return_to_kernel - - // We have already stored the app registers in the trap handler. We - // have further restored `gp`, `tp`, `fp`/`s0` and the stack pointer. - // - // The only other non-clobbered registers are `s1` and `a0`, where - // `a0` needs to hold the per-process state pointer currently stored - // in `s1`, and the original value of `s1` is saved on the stack. - // Restore them: - // - mv a0, s1 // a0 = per-process stored state - lw s1, 6*4(sp) // restore s1 (used by LLVM internally) - - // We need thus need to mark all registers as clobbered, except: - // - // - x2 (sp) - // - x3 (gp) - // - x4 (tp) - // - x8 (fp) - // - x9 (s1) - // - x10 (a0) - - addi sp, sp, 8*4 // Reset kernel stack pointer - ", + asm!( + " + // Before switching to the app we need to save some kernel registers + // to the kernel stack, specifically ones which we can't mark as + // clobbered in the asm!() block. We then save the stack pointer in + // the mscratch CSR so we can retrieve it after returning to the + // kernel from the app. + // + // A few values get saved to the kernel stack, including an app + // register temporarily after entering the trap handler. Here is a + // memory map to make it easier to keep track: + // + // ``` + // 8*4(sp): <- original stack pointer + // 7*4(sp): + // 6*4(sp): x9 / s1 + // 5*4(sp): x8 / s0 / fp + // 4*4(sp): x4 / tp + // 3*4(sp): x3 / gp + // 2*4(sp): x10 / a0 (*state, Per-process StoredState struct) + // 1*4(sp): custom trap handler address + // 0*4(sp): scratch space, having s1 written to by the trap handler + // <- new stack pointer + // ``` + + addi sp, sp, -8*4 // Move the stack pointer down to make room. + + // Save all registers on the kernel stack which cannot be clobbered + // by an asm!() block. These are mostly registers which have a + // designated purpose (e.g. stack pointer) or are used internally + // by LLVM. + sw x9, 6*4(sp) // s1 (used internally by LLVM) + sw x8, 5*4(sp) // fp (can't be clobbered / used as an operand) + sw x4, 4*4(sp) // tp (can't be clobbered / used as an operand) + sw x3, 3*4(sp) // gp (can't be clobbered / used as an operand) + + sw x10, 2*4(sp) // Store process state pointer on stack as well. + // We need to have this available for after the + // app returns to the kernel so we can store its + // registers. + + // Load the address of `_start_app_trap` into `1*4(sp)`. We swap our + // stack pointer into the mscratch CSR and the trap handler will load + // and jump to the address at this offset. + la t0, 100f // t0 = _start_app_trap + sw t0, 1*4(sp) // 1*4(sp) = t0 + + // sw x0, 0*4(sp) // Reserved as scratch space for the trap handler + + // -----> All required registers saved to the stack. + // sp holds the updated stack pointer, a0 the per-process state + + // From here on we can't allow the CPU to take interrupts anymore, as + // we re-route traps to `_start_app_trap` below (by writing our stack + // pointer into the mscratch CSR), and we rely on certain CSRs to not + // be modified or used in their intermediate states (e.g., mepc). + // + // We atomically switch to user-mode and re-enable interrupts using + // the `mret` instruction below. + // + // If interrupts are disabled _after_ setting mscratch, this result in + // the race condition of [PR 2308](https://github.com/tock/tock/pull/2308) + + // Therefore, clear the following bits in mstatus first: + // 0x00000008 -> bit 3 -> MIE (disabling interrupts here) + // + 0x00001800 -> bits 11,12 -> MPP (switch to usermode on mret) + li t0, 0x00001808 + csrc mstatus, t0 // clear bits in mstatus + + // Afterwards, set the following bits in mstatus: + // 0x00000080 -> bit 7 -> MPIE (enable interrupts on mret) + li t0, 0x00000080 + csrs mstatus, t0 // set bits in mstatus + + // Execute `_start_app_trap` on a trap by setting the mscratch trap + // handler address to our current stack pointer. This stack pointer, + // at `1*4(sp)`, holds the address of `_start_app_trap`. + // + // Upon a trap, the global trap handler (_start_trap) will swap `s0` + // with the `mscratch` CSR and, if it contains a non-zero address, + // jump to the address that is now at `1*4(s0)`. This allows us to + // hook a custom trap handler that saves all userspace state: + // + csrw mscratch, sp // Store `sp` in mscratch CSR. Discard the + // prior value, must have been set to zero. + + // We have to set the mepc CSR with the PC we want the app to start + // executing at. This has been saved in Riscv32iStoredState for us + // (either when the app returned back to the kernel or in the + // `set_process_function()` function). + lw t0, 31*4(a0) // Retrieve the PC from Riscv32iStoredState + csrw mepc, t0 // Set mepc CSR to the app's PC. + + // Restore all of the app registers from what we saved. If this is the + // first time running the app then most of these values are + // irrelevant, However we do need to set the four arguments to the + // `_start_ function in the app. If the app has been executing then + // this allows the app to correctly resume. + + // We do a little switcheroo here, and place the per-process stored + // state pointer into the `sp` register instead of `a0`. Doing so + // allows us to use compressed instructions for all of these loads: + mv sp, a0 // sp <- a0 (per-process stored state) + + lw x1, 0*4(sp) // ra + // ------------------------> sp, do last since we overwrite our pointer + lw x3, 2*4(sp) // gp + lw x4, 3*4(sp) // tp + lw x5, 4*4(sp) // t0 + lw x6, 5*4(sp) // t1 + lw x7, 6*4(sp) // t2 + lw x8, 7*4(sp) // s0,fp + lw x9, 8*4(sp) // s1 + lw x10, 9*4(sp) // a0 + lw x11, 10*4(sp) // a1 + lw x12, 11*4(sp) // a2 + lw x13, 12*4(sp) // a3 + lw x14, 13*4(sp) // a4 + lw x15, 14*4(sp) // a5 + lw x16, 15*4(sp) // a6 + lw x17, 16*4(sp) // a7 + lw x18, 17*4(sp) // s2 + lw x19, 18*4(sp) // s3 + lw x20, 19*4(sp) // s4 + lw x21, 20*4(sp) // s5 + lw x22, 21*4(sp) // s6 + lw x23, 22*4(sp) // s7 + lw x24, 23*4(sp) // s8 + lw x25, 24*4(sp) // s9 + lw x26, 25*4(sp) // s10 + lw x27, 26*4(sp) // s11 + lw x28, 27*4(sp) // t3 + lw x29, 28*4(sp) // t4 + lw x30, 29*4(sp) // t5 + lw x31, 30*4(sp) // t6 + lw x2, 1*4(sp) // sp, overwriting our pointer + + // Call mret to jump to where mepc points, switch to user mode, and + // start running the app. + mret + + // The global trap handler will jump to this address when catching a + // trap while the app is executing (address loaded into the mscratch + // CSR). + // + // This custom trap handler is responsible for saving application + // state, clearing the custom trap handler (mscratch = 0), and + // restoring the kernel context. +100: // _start_app_trap + + // At this point all we know is that we entered the trap handler from + // an app. We don't know _why_ we got a trap, it could be from an + // interrupt, syscall, or fault (or maybe something else). Therefore + // we have to be very careful not to overwrite any registers before we + // have saved them. + // + // The global trap handler has swapped the app's `s0` into the + // mscratch CSR, which now contains the address of our stack pointer. + // The global trap handler further clobbered `s1`, which now contains + // the address of `_start_app_trap`. The app's `s1` is saved at + // `0*4(s0)`. + // + // Thus we can clobber `s1` and load the address of the per-process + // stored state: + // + lw s1, 2*4(s0) + + // With the per-process stored state address in `t1`, save all + // non-clobbered registers. Save the `sp` first, then do the same + // switcheroo as above, moving the per-process stored state pointer + // into `sp`. This allows us to use compressed instructions for all + // these stores: + sw x2, 1*4(s1) // Save app's sp + mv sp, s1 // sp <- s1 (per-process stored state) + + // Now, store relative to `sp` (per-process stored state) with + // compressed instructions: + sw x1, 0*4(sp) // ra + // ------------------------> sp, saved above + sw x3, 2*4(sp) // gp + sw x4, 3*4(sp) // tp + sw x5, 4*4(sp) // t0 + sw x6, 5*4(sp) // t1 + sw x7, 6*4(sp) // t2 + // ------------------------> s0, in mscratch right now + // ------------------------> s1, stored at 0*4(s0) right now + sw x10, 9*4(sp) // a0 + sw x11, 10*4(sp) // a1 + sw x12, 11*4(sp) // a2 + sw x13, 12*4(sp) // a3 + sw x14, 13*4(sp) // a4 + sw x15, 14*4(sp) // a5 + sw x16, 15*4(sp) // a6 + sw x17, 16*4(sp) // a7 + sw x18, 17*4(sp) // s2 + sw x19, 18*4(sp) // s3 + sw x20, 19*4(sp) // s4 + sw x21, 20*4(sp) // s5 + sw x22, 21*4(sp) // s6 + sw x23, 22*4(sp) // s7 + sw x24, 23*4(sp) // s8 + sw x25, 24*4(sp) // s9 + sw x26, 25*4(sp) // s10 + sw x27, 26*4(sp) // s11 + sw x28, 27*4(sp) // t3 + sw x29, 28*4(sp) // t4 + sw x30, 29*4(sp) // t5 + sw x31, 30*4(sp) // t6 + + // At this point, we can restore s0 into our stack pointer: + mv sp, s0 + + // Now retrieve the original value of s1 and save that as well. We + // must not clobber s1, our per-process stored state pointer. + lw s0, 0*4(sp) // s0 = app s1 (from trap handler scratch space) + sw s0, 8*4(s1) // Save app s1 to per-process state + + // Retrieve the original value of s0 from the mscratch CSR, save it. + // + // This will also restore the kernel trap handler by writing zero to + // the CSR. `csrrw` allows us to read and write the CSR in a single + // instruction: + csrrw s0, mscratch, zero // s0 <- mscratch[app s0] <- zero + sw s0, 7*4(s1) // Save app s0 to per-process state + + // ------------------------------------------------------------------- + // At this point, the entire app register file is saved. We also + // restored the kernel trap handler. We have restored the following + // kernel registers: + // + // - sp: kernel stack pointer + // - s1: per-process stored state pointer + // + // We avoid clobbering those registers from this point onward. + // ------------------------------------------------------------------- + + // We also need to store some other information about the trap reason, + // present in CSRs: + // + // - the app's PC (mepc), + // - the trap reason (mcause), + // - the trap 'value' (mtval, e.g., faulting address). + // + // We need to store mcause because we use that to determine why the + // app stopped executing and returned to the kernel. We store mepc + // because it is where we need to return to in the app at some + // point. We need to store mtval in case the app faulted and we need + // mtval to help with debugging. + // + // We use `s0` as a scratch register, as it fits into the 3-bit + // register argument of RISC-V compressed loads / stores: + + // Save the PC to the stored state struct. We also load the address + // of _return_to_kernel into it, as this will be where we jump on + // the mret instruction, which leaves the trap handler. + la s0, 300f // Load _return_to_kernel into t0. + csrrw s0, mepc, s0 // s0 <- mepc[app pc] <- _return_to_kernel + sw s0, 31*4(s1) // Store app's pc in stored state struct. + + // Save mtval to the stored state struct + csrr s0, mtval + sw s0, 33*4(s1) + + // Save mcause and leave it loaded into a0, as we call a function + // with it below: + csrr a0, mcause + sw a0, 32*4(s1) + + // Depending on the value of a0, we might be calling into a function + // while still in the trap handler. The callee may rely on the `gp`, + // `tp`, and `fp` (s0) registers to be set correctly. Thus we restore + // them here, as we need to do anyways. They are saved registers, + // and so we avoid clobbering them beyond this point. + // + // We do not restore `s1`, as we need to move it back into `a0` + // _after_ potentially invoking the _disable_interrupt_... function. + // LLVM relies on it to not be clobbered internally, but it is not + // part of the RISC-V C ABI, which we need to follow here. + // + lw x8, 5*4(sp) // fp/s0: Restore the frame pointer + lw x4, 4*4(sp) // tp: Restore the thread pointer + lw x3, 3*4(sp) // gp: Restore the global pointer + + // -------------------------------------------------------------------- + // From this point onward, avoid clobbering the following registers: + // + // - x2 / sp: kernel stack pointer + // - x3 / gp: kernel global pointer + // - x4 / tp: kernel thread pointer + // - x8 / s0 / fp: kernel frame pointer + // - x9 / s1: per-process stored state pointer + // + // -------------------------------------------------------------------- + + // Now we need to check if this was an interrupt, and if it was, + // then we need to disable the interrupt before returning from this + // trap handler so that it does not fire again. + // + // If mcause is greater than or equal to zero this was not an + // interrupt (i.e. the most significant bit is not 1). In this case, + // jump to _start_app_trap_continue. + bge a0, zero, 200f + + // This was an interrupt. Call the interrupt disable function, with + // mcause already loaded in a0. + // + // This may clobber all caller-saved registers. However, at this + // stage, we only restored `sp`, `s1`, and the registers above, all of + // which are saved. Thus we don't have to worry about the function + // call clobbering these registers. + // + // However, before we can execute any Rust code within a trap handler + // context, we must set the hart-specific 'are we in a trap handler' + // flag as an offset to the `_trap_handler_active` symbol. The chip + // crate is responsible for defining this symbol, and ensuring it is + // large enough to fit `max(mhartid) * MXLEN` bytes. + // + // First, calculate its address and save it in a callee-saved + // register. We use `s2`: the app's `s2` has already been saved, and + // its not one of the register's we've already restored: + la s2, _trap_handler_active // s2 = addr(_trap_handler_active) + csrr t0, mhartid // t0 = hartid + slli t0, t0, 2 // t0 = t0 * 4 + add s2, s2, t0 // s2 = addr(_trap_handler_active[hartid]) + + // Indicate that we are in a trap handler on this hart: + li t0, 1 + sw t0, 0(s2) + + jal ra, _disable_interrupt_trap_rust_from_app + + // Indicate that we are no longer going to be in a trap handler on + // this hart: + sw x0, 0(s2) + +200: // _start_app_trap_continue + + // Need to set mstatus.MPP to 0b11 so that we stay in machine mode. + // + // We use `a0` as a scratch register, as we are allowed to clobber it + // here, and it fits into a compressed load instruction. We must avoid + // using restored saved registers like `s0`, etc. + // + li a0, 0x1800 // Load 0b11 to the MPP bits location in a0 + csrs mstatus, a0 // mstatus |= a0 + + // Use mret to exit the trap handler and return to the context + // switching code. We loaded the address of _return_to_kernel + // into mepc above. + mret + + // This is where the trap handler jumps back to after the app stops + // executing. +300: // _return_to_kernel + + // We have already stored the app registers in the trap handler. We + // have further restored `gp`, `tp`, `fp`/`s0` and the stack pointer. + // + // The only other non-clobbered registers are `s1` and `a0`, where + // `a0` needs to hold the per-process state pointer currently stored + // in `s1`, and the original value of `s1` is saved on the stack. + // Restore them: + // + mv a0, s1 // a0 = per-process stored state + lw s1, 6*4(sp) // restore s1 (used by LLVM internally) + + // We need thus need to mark all registers as clobbered, except: + // + // - x2 (sp) + // - x3 (gp) + // - x4 (tp) + // - x8 (fp) + // - x9 (s1) + // - x10 (a0) + + addi sp, sp, 8*4 // Reset kernel stack pointer + ", // We pass the per-process state struct in a register we are allowed // to clobber (not s0 or s1), but still fits into 3-bit register diff --git a/arch/riscv/src/thread_id.rs b/arch/riscv/src/thread_id.rs index 5477e201dd..d04ed5cc87 100644 --- a/arch/riscv/src/thread_id.rs +++ b/arch/riscv/src/thread_id.rs @@ -15,74 +15,83 @@ pub enum RiscvThreadIdProvider {} // return the thread ID. On single-core platforms the thread ID only depends on // whether execution is in an interrupt service routine or not, which is what // this implementation checks for. +// +// The assembly (read `mhartid`, load the `_trap_handler_active` symbol address, +// read a `usize`) is XLEN-agnostic, so the same code serves both rv32 and rv64. +#[cfg(all( + any(target_arch = "riscv32", target_arch = "riscv64"), + target_os = "none" +))] unsafe impl ThreadIdProvider for RiscvThreadIdProvider { /// Return the current thread ID, computed using the `mhartid` (hardware thread /// ID), and a flag indicating whether the current hart is currently in a trap /// handler context. fn running_thread_id() -> usize { - // Mock implementation for non-rv32 target builds: - #[cfg(not(all(target_arch = "riscv32", target_os = "none")))] - { - unimplemented!() - } + let hartid: usize; + let trap_handler_active_addr: *mut usize; - // Proper rv32i-specific implementation: - #[cfg(all(target_arch = "riscv32", target_os = "none"))] - { - let hartid: usize; - let trap_handler_active_addr: *mut usize; + // # Safety + // + // This does not read any memory by itself, it merely loads a symbol + // address and reads the `mhartid` CSR: + unsafe { + core::arch::asm!( + " + // Determine the hart id and save in the appropriate output register. + csrr {hartid_reg}, mhartid // hartid_reg = hartid + // Load the `_trap_handler_active` symbol address. + la {trap_handler_active_addr_reg}, _trap_handler_active // trap_handler_active_addr = &_trap_handler_active + ", + hartid_reg = out(reg) hartid, + trap_handler_active_addr_reg = out(reg) trap_handler_active_addr, + options( + // The assembly code has no side effects, must eventually + // return, and its outputs depend only on its direct inputs + // (i.e. the values themselves, not what they point to) or + // values read from memory (unless the nomem options is also + // set). + pure, + // The assembly code does not read from or write to any memory + // accessible outside of the assembly code. + nomem, + // The assembly code does not modify the flags register (for + // RISC-V: `fflags`, `vtype`, `vl`, or `vcsr`). + preserves_flags, + // The assembly code does not push data to the stack, or write + // to the stack red-zone (if supported by the target). + nostack, + ), + ); + } - // Safety: - // - // This does not read any memory by itself, it merely loads a symbol - // address and reads the `mhartid` CSR: - unsafe { - core::arch::asm!( - // Determine the hart id and save in the appropriate output - // register: - "csrr {hartid_reg}, mhartid", - // Load the `_trap_handler_active` symbol address: - "la {trap_handler_active_addr_reg}, _trap_handler_active", - hartid_reg = out(reg) hartid, - trap_handler_active_addr_reg = out(reg) trap_handler_active_addr, - options( - // The assembly code has no side effects, must eventually - // return, and its outputs depend only on its direct inputs - // (i.e. the values themselves, not what they point to) or - // values read from memory (unless the nomem options is also - // set). - pure, - // The assembly code does not read from or write to any memory - // accessible outside of the assembly code. - nomem, - // The assembly code does not modify the flags register (for - // RISC-V: `fflags`, `vtype`, `vl`, or `vcsr`). - preserves_flags, - // The assembly code does not push data to the stack, or write - // to the stack red-zone (if supported by the target). - nostack, - ), - ); - } + // Load the hart's trap_handler_active value. + // + // # Safety + // + // `hartid` * core::mem::size_of::() must fit into an `isize`. By + // allocating the `_trap_handler_active` array, the chip crate ensures + // that this array can fit all hart IDs for all harts on the target + // machine. The maximum size of any Rust allocation is `isize::MAX` + // bytes, and as such, by allocating this array in the first place, the + // chip crate maintains that indexing into it with `hartid` must stay in + // this object, and an index of `hartid` elements will not produce an + // offset larger than `isize::MAX` bytes. + let hart_trap_handler_active = + unsafe { core::ptr::read(trap_handler_active_addr.add(hartid)) }; - // Load the hart's trap_handler_active value. - // - // Safety: - // - // `hartid` * core::mem::size_of::() must fit into an `isize`. By - // allocating the `_trap_handler_active` array, the chip crate ensures - // that this array can fit all hart IDs for all harts on the target - // machine. The maximum size of any Rust allocation is `isize::MAX` - // bytes, and as such, by allocating this arrary in the first place, the - // chip crate maintains that indexing into it with `hartid` must stay in - // this object, and an index of `hartid` elements will not produce an - // offset larger than `isize::MAX` bytes. - let hart_trap_handler_active = - unsafe { core::ptr::read(trap_handler_active_addr.add(hartid)) }; + // Determine the thread ID as a combination of the hart id, and whether + // it is currently in a trap handler context: + hartid.overflowing_shl(1).0 | ((hart_trap_handler_active != 0) as usize) + } +} - // Determine the thread ID as a combination of the hart id, and whether - // it is currently in a trap handler context: - hartid.overflowing_shl(1).0 | ((hart_trap_handler_active != 0) as usize) - } +// Mock implementation for non-RISC-V (host / doc) target builds +#[cfg(not(all( + any(target_arch = "riscv32", target_arch = "riscv64"), + target_os = "none" +)))] +unsafe impl ThreadIdProvider for RiscvThreadIdProvider { + fn running_thread_id() -> usize { + unimplemented!() } } diff --git a/boards/README.md b/boards/README.md index 29a1bd68e7..cb00aacacc 100644 --- a/boards/README.md +++ b/boards/README.md @@ -89,6 +89,7 @@ but the approximate definitions: | Board | Architecture | MCU | Interface | App deployment | QEMU Support? | |-------------------------------------------------------------------|------------------|----------------|------------|-----------------------------|---------------| | [WeAct F401CCU6 Core Board](weact_f401ccu6/README.md) | ARM Cortex-M4 | STM32F401CCU6 | openocd | custom | No | +| [ST Nucleo U545RE-Q (skeleton)](nucleo_u545re_q/README.md) | ARM Cortex-M33 | STM32U545RE-Q | openocd | custom | No | | [SparkFun RedBoard Red-V](redboard_redv/README.md) | RISC-V | FE310-G002 | openocd | tockloader | Yes (5.1) | | [SiFive HiFive1 Rev B](hifive1/README.md) | RISC-V | FE310-G002 | openocd | tockloader | Yes (5.1) | | [BBC HiFive Inventor](hifive_inventor/README.md) | RISC-V | FE310-G003 | tockloader | tockloader | No | diff --git a/boards/apollo3/lora_things_plus/src/main.rs b/boards/apollo3/lora_things_plus/src/main.rs index f88f89149a..5b3a448f2c 100644 --- a/boards/apollo3/lora_things_plus/src/main.rs +++ b/boards/apollo3/lora_things_plus/src/main.rs @@ -51,6 +51,7 @@ use kernel::hil::i2c::I2CMaster; use kernel::hil::led::LedHigh; use kernel::hil::spi::SpiMaster; use kernel::hil::time::Counter; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; use kernel::{create_capability, debug, static_init}; @@ -987,7 +988,7 @@ unsafe fn setup() -> ( /// setup and RAM initialization. #[no_mangle] pub unsafe fn main() { - apollo3::init(); + ChipHw::init(); #[cfg(test)] test_main(); diff --git a/boards/apollo3/redboard_artemis_atp/src/main.rs b/boards/apollo3/redboard_artemis_atp/src/main.rs index 7ebd6e4578..39c99eae95 100644 --- a/boards/apollo3/redboard_artemis_atp/src/main.rs +++ b/boards/apollo3/redboard_artemis_atp/src/main.rs @@ -24,6 +24,7 @@ use kernel::hil::i2c::I2CMaster; use kernel::hil::i2c::I2CSlave; use kernel::hil::led::LedHigh; use kernel::hil::time::Counter; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; use kernel::{create_capability, debug, static_init}; @@ -451,7 +452,7 @@ unsafe fn setup() -> ( /// setup and RAM initialization. #[no_mangle] pub unsafe fn main() { - apollo3::init(); + ChipHw::init(); #[cfg(test)] test_main(); diff --git a/boards/apollo3/redboard_artemis_nano/src/main.rs b/boards/apollo3/redboard_artemis_nano/src/main.rs index 3140eff279..dc01a1ec55 100644 --- a/boards/apollo3/redboard_artemis_nano/src/main.rs +++ b/boards/apollo3/redboard_artemis_nano/src/main.rs @@ -24,6 +24,7 @@ use kernel::debug::PanicResources; use kernel::hil::i2c::I2CMaster; use kernel::hil::led::LedHigh; use kernel::hil::time::Counter; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; use kernel::{create_capability, debug, static_init}; @@ -477,7 +478,7 @@ unsafe fn setup() -> ( /// setup and RAM initialization. #[no_mangle] pub unsafe fn main() { - apollo3::init(); + ChipHw::init(); #[cfg(test)] test_main(); diff --git a/boards/arty_e21/src/main.rs b/boards/arty_e21/src/main.rs index 438112b56f..6147fcb0f5 100644 --- a/boards/arty_e21/src/main.rs +++ b/boards/arty_e21/src/main.rs @@ -14,6 +14,7 @@ use crate::debug::PanicResources; use kernel::capabilities; use kernel::component::Component; use kernel::hil; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; use kernel::{create_capability, debug, static_init}; @@ -34,11 +35,10 @@ type ChipHw = arty_e21_chip::chip::ArtyExx<'static, ArtyExxDefaultPeripherals<'s const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = capsules_system::process_policies::PanicFaultPolicy {}; -type Chip = arty_e21_chip::chip::ArtyExx<'static, ArtyExxDefaultPeripherals<'static>>; type ProcessPrinter = capsules_system::process_printer::ProcessPrinterText; /// Resources for when a board panics used by io.rs. -static PANIC_RESOURCES: SingleThreadValue> = +static PANIC_RESOURCES: SingleThreadValue> = SingleThreadValue::new(); kernel::stack_size! {0x1000} @@ -131,13 +131,15 @@ unsafe fn start() -> ( ArtyE21, &'static arty_e21_chip::chip::ArtyExx<'static, ArtyExxDefaultPeripherals<'static>>, ) { + ChipHw::init(); + // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< ::ThreadIdProvider, >(); let _ = PANIC_RESOURCES - .bind_to_thread::<::ThreadIdProvider>( + .bind_to_thread::<::ThreadIdProvider>( PanicResources::new(), ); @@ -146,12 +148,11 @@ unsafe fn start() -> ( let chip = static_init!( arty_e21_chip::chip::ArtyExx, - arty_e21_chip::chip::ArtyExx::new(&peripherals.machinetimer, peripherals) + arty_e21_chip::chip::ArtyExx::new(peripherals) ); PANIC_RESOURCES.get().map(|resources| { resources.chip.put(chip); }); - chip.initialize(); let process_mgmt_cap = create_capability!(capabilities::ProcessManagementCapability); diff --git a/boards/clue_nrf52840/src/main.rs b/boards/clue_nrf52840/src/main.rs index 99dde88d06..0afeea4ac2 100644 --- a/boards/clue_nrf52840/src/main.rs +++ b/boards/clue_nrf52840/src/main.rs @@ -273,7 +273,7 @@ unsafe fn start() -> ( Platform, &'static nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>, ) { - nrf52840::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/boards/components/src/console.rs b/boards/components/src/console.rs index da184ab606..234c6386f8 100644 --- a/boards/components/src/console.rs +++ b/boards/components/src/console.rs @@ -124,6 +124,8 @@ macro_rules! console_component_static { }; } +pub type ConsoleComponentType = console::Console<'static>; + pub struct ConsoleComponent { board_kernel: &'static kernel::Kernel, driver_num: usize, diff --git a/boards/components/src/debug_writer.rs b/boards/components/src/debug_writer.rs index a5cedd043b..2abd1d4d2e 100644 --- a/boards/components/src/debug_writer.rs +++ b/boards/components/src/debug_writer.rs @@ -170,6 +170,11 @@ impl Component let (output_buf, internal_buf) = buf.split_at_mut(DEBUG_BUFFER_SPLIT); + // SAFETY: MaybeUninit has the same size and alignment as u8. + let internal_buf: &mut [core::mem::MaybeUninit] = unsafe { + core::slice::from_raw_parts_mut(internal_buf.as_mut_ptr().cast(), internal_buf.len()) + }; + // Create virtual device for kernel debug. let debugger_uart = s.0.write(UartDevice::new(self.uart_mux, false)); debugger_uart.setup(); @@ -226,6 +231,11 @@ impl< let buf = s.1.write([0; BUF_SIZE_BYTES]); let (output_buf, internal_buf) = buf.split_at_mut(DEBUG_BUFFER_SPLIT); + // SAFETY: MaybeUninit has the same size and alignment as u8. + let internal_buf: &mut [core::mem::MaybeUninit] = unsafe { + core::slice::from_raw_parts_mut(internal_buf.as_mut_ptr().cast(), internal_buf.len()) + }; + // Create virtual device for kernel debug. let ring_buffer = s.0.write(RingBuffer::new(internal_buf)); let debugger = diff --git a/boards/configurations/microbit_v2/microbit_v2-test-dynamic-app-load/src/main.rs b/boards/configurations/microbit_v2/microbit_v2-test-dynamic-app-load/src/main.rs index e041c94c6f..46e88a8770 100644 --- a/boards/configurations/microbit_v2/microbit_v2-test-dynamic-app-load/src/main.rs +++ b/boards/configurations/microbit_v2/microbit_v2-test-dynamic-app-load/src/main.rs @@ -16,6 +16,7 @@ use kernel::capabilities; use kernel::component::Component; use kernel::debug::PanicResources; use kernel::hil::time::Counter; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; @@ -233,7 +234,7 @@ unsafe fn start() -> ( MicroBit, &'static nrf52833::chip::NRF52<'static, Nrf52833DefaultPeripherals<'static>>, ) { - nrf52833::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-ecdsap256/Cargo.toml b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-ecdsap256/Cargo.toml index c54a1766f3..a687a5b4b2 100644 --- a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-ecdsap256/Cargo.toml +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-ecdsap256/Cargo.toml @@ -10,6 +10,8 @@ build = "../../../build.rs" edition.workspace = true [dependencies] +nrf52840dk-test-base = { path = "../nrf52840dk-test-base" } + components = { path = "../../../components" } cortexm4 = { path = "../../../../arch/cortex-m4" } kernel = { path = "../../../../kernel" } diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-ecdsap256/src/main.rs b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-ecdsap256/src/main.rs index a50c2ce5b2..5ba980cfe7 100644 --- a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-ecdsap256/src/main.rs +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-ecdsap256/src/main.rs @@ -9,48 +9,9 @@ #![deny(missing_docs)] use kernel::component::Component; +use kernel::create_capability; use kernel::deferred_call::DeferredCallClient; -use kernel::hil::led::LedLow; -use kernel::hil::time::Counter; -use kernel::platform::{KernelResources, SyscallDriverLookup}; -use kernel::{capabilities, create_capability, static_init}; -use nrf52840::gpio::Pin; use nrf52840::interrupt_service::Nrf52840DefaultPeripherals; -use nrf52_components::{UartChannel, UartPins}; - -// The nRF52840DK LEDs (see back of board) -const LED1_PIN: Pin = Pin::P0_13; -const LED2_PIN: Pin = Pin::P0_14; -const LED3_PIN: Pin = Pin::P0_15; -const LED4_PIN: Pin = Pin::P0_16; - -const BUTTON_RST_PIN: Pin = Pin::P0_18; - -const UART_RTS: Option = Some(Pin::P0_05); -const UART_TXD: Pin = Pin::P0_06; -const UART_CTS: Option = Some(Pin::P0_07); -const UART_RXD: Pin = Pin::P0_08; - -/// Debug Writer -pub mod io; - -// State for loading and holding applications. -// How should the kernel respond when a process faults. -const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = - capsules_system::process_policies::PanicFaultPolicy {}; - -// Number of concurrent processes this platform supports. -const NUM_PROCS: usize = 8; - -type ChipHw = nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>; - -kernel::stack_size! {0x2000} - -//------------------------------------------------------------------------------ -// SYSCALL DRIVER TYPE DEFINITIONS -//------------------------------------------------------------------------------ - -type AlarmDriver = components::alarm::AlarmDriverComponentType>; type Verifier = ecdsa_sw::p256_verifier::EcdsaP256SignatureVerifier<'static>; type SignatureVerifyInMemoryKeys = @@ -62,204 +23,11 @@ type SignatureVerifyInMemoryKeys = 64, >; -type SchedulerInUse = components::sched::round_robin::RoundRobinComponentType; - -/// Supported drivers by the platform -pub struct Platform { - console: &'static capsules_core::console::Console<'static>, - led: &'static capsules_core::led::LedDriver< - 'static, - kernel::hil::led::LedLow<'static, nrf52840::gpio::GPIOPin<'static>>, - 4, - >, - alarm: &'static AlarmDriver, - scheduler: &'static SchedulerInUse, - systick: cortexm4::systick::SysTick, -} - -impl SyscallDriverLookup for Platform { - fn with_driver(&self, driver_num: usize, f: F) -> R - where - F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, - { - match driver_num { - capsules_core::console::DRIVER_NUM => f(Some(self.console)), - capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), - capsules_core::led::DRIVER_NUM => f(Some(self.led)), - _ => f(None), - } - } -} - -/// This is in a separate, inline(never) function so that its stack frame is -/// removed when this function returns. Otherwise, the stack space used for -/// these static_inits is wasted. -#[inline(never)] -unsafe fn create_peripherals() -> &'static mut Nrf52840DefaultPeripherals<'static> { - let ieee802154_ack_buf = static_init!( - [u8; nrf52840::ieee802154_radio::ACK_BUF_SIZE], - [0; nrf52840::ieee802154_radio::ACK_BUF_SIZE] - ); - // Initialize chip peripheral drivers - let nrf52840_peripherals = static_init!( - Nrf52840DefaultPeripherals, - Nrf52840DefaultPeripherals::new(ieee802154_ack_buf) - ); - - nrf52840_peripherals -} - -impl KernelResources>> - for Platform -{ - type SyscallDriverLookup = Self; - type SyscallFilter = (); - type ProcessFault = (); - type Scheduler = SchedulerInUse; - type SchedulerTimer = cortexm4::systick::SysTick; - type WatchDog = (); - type ContextSwitchCallback = (); - - fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { - self - } - fn syscall_filter(&self) -> &Self::SyscallFilter { - &() - } - fn process_fault(&self) -> &Self::ProcessFault { - &() - } - fn scheduler(&self) -> &Self::Scheduler { - self.scheduler - } - fn scheduler_timer(&self) -> &Self::SchedulerTimer { - &self.systick - } - fn watchdog(&self) -> &Self::WatchDog { - &() - } - fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { - &() - } -} - /// Main function called after RAM initialized. #[no_mangle] pub unsafe fn main() { - //-------------------------------------------------------------------------- - // INITIAL SETUP - //-------------------------------------------------------------------------- - - // Apply errata fixes and enable interrupts. - nrf52840::init(); - - // Initialize deferred calls very early. - kernel::deferred_call::initialize_deferred_call_state::< - ::ThreadIdProvider, - >(); - - // Set up peripheral drivers. Called in separate function to reduce stack - // usage. - let nrf52840_peripherals = create_peripherals(); - - // Set up circular peripheral dependencies. - nrf52840_peripherals.init(); - let base_peripherals = &nrf52840_peripherals.nrf52; - - // Choose the channel for serial output. This board can be configured to use - // either the Segger RTT channel or via UART with traditional TX/RX GPIO - // pins. - let uart_channel = UartChannel::Pins(UartPins::new(UART_RTS, UART_TXD, UART_CTS, UART_RXD)); - - // Create an array to hold process references. - let processes = components::process_array::ProcessArrayComponent::new() - .finalize(components::process_array_component_static!(NUM_PROCS)); - - // Setup space to store the core kernel data structure. - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(processes.as_slice())); - - // Create (and save for panic debugging) a chip object to setup low-level - // resources (e.g. MPU, systick). - let chip = static_init!( - nrf52840::chip::NRF52, - nrf52840::chip::NRF52::new(nrf52840_peripherals) - ); - - // Do nRF configuration and setup. This is shared code with other nRF-based - // platforms. - nrf52_components::startup::NrfStartupComponent::new( - false, - BUTTON_RST_PIN, - nrf52840::uicr::Regulator0Output::DEFAULT, - &base_peripherals.nvmc, - ) - .finalize(()); - - //-------------------------------------------------------------------------- - // CAPABILITIES - //-------------------------------------------------------------------------- - - // Create capabilities that the board needs to call certain protected kernel - // functions. - let main_loop_capability = create_capability!(capabilities::MainLoopCapability); - - //-------------------------------------------------------------------------- - // LEDs - //-------------------------------------------------------------------------- - - let led = components::led::LedsComponent::new().finalize(components::led_component_static!( - LedLow<'static, nrf52840::gpio::GPIOPin>, - LedLow::new(&nrf52840_peripherals.gpio_port[LED1_PIN]), - LedLow::new(&nrf52840_peripherals.gpio_port[LED2_PIN]), - LedLow::new(&nrf52840_peripherals.gpio_port[LED3_PIN]), - LedLow::new(&nrf52840_peripherals.gpio_port[LED4_PIN]), - )); - - //-------------------------------------------------------------------------- - // TIMER - //-------------------------------------------------------------------------- - - let rtc = &base_peripherals.rtc; - let _ = rtc.start(); - let mux_alarm = components::alarm::AlarmMuxComponent::new(rtc) - .finalize(components::alarm_mux_component_static!(nrf52840::rtc::Rtc)); - let alarm = components::alarm::AlarmDriverComponent::new( - board_kernel, - capsules_core::alarm::DRIVER_NUM, - mux_alarm, - ) - .finalize(components::alarm_component_static!(nrf52840::rtc::Rtc)); - - //-------------------------------------------------------------------------- - // UART & CONSOLE & DEBUG - //-------------------------------------------------------------------------- - - let uart_channel = nrf52_components::UartChannelComponent::new( - uart_channel, - mux_alarm, - &base_peripherals.uarte0, - ) - .finalize(nrf52_components::uart_channel_component_static!( - nrf52840::rtc::Rtc - )); - - // Virtualize the UART channel for the console and for kernel debug. - let uart_mux = components::console::UartMuxComponent::new(uart_channel, 115200) - .finalize(components::uart_mux_component_static!()); - - // Setup the serial console for userspace. - let console = components::console::ConsoleComponent::new( - board_kernel, - capsules_core::console::DRIVER_NUM, - uart_mux, - ) - .finalize(components::console_component_static!()); - - //-------------------------------------------------------------------------- - // NRF CLOCK SETUP - //-------------------------------------------------------------------------- - - nrf52_components::NrfClockComponent::new(&base_peripherals.clock).finalize(()); + let (board_kernel, platform, chip, _default_peripherals, _mux_uart, _mux_alarm) = + nrf52840dk_test_base_lib::start(); //-------------------------------------------------------------------------- // Credential Checking @@ -415,7 +183,7 @@ pub unsafe fn main() { checker, board_kernel, chip, - &FAULT_RESPONSE, + &nrf52840dk_test_base_lib::FAULT_RESPONSE, assigner, storage_permissions_policy, app_flash, @@ -424,24 +192,14 @@ pub unsafe fn main() { .finalize(components::process_loader_sequential_component_static!( nrf52840::chip::NRF52, kernel::process::ProcessStandardDebugFull, - NUM_PROCS + nrf52840dk_test_base_lib::NUM_PROCS )); //-------------------------------------------------------------------------- // PLATFORM SETUP, SCHEDULER, AND START KERNEL LOOP //-------------------------------------------------------------------------- - let scheduler = components::sched::round_robin::RoundRobinComponent::new(processes) - .finalize(components::round_robin_component_static!(NUM_PROCS)); - - let platform = Platform { - console, - led, - alarm, - scheduler, - systick: cortexm4::systick::SysTick::new_with_calibration(64000000), - }; - + let main_loop_capability = create_capability!(kernel::capabilities::MainLoopCapability); board_kernel.kernel_loop( &platform, chip, diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/Cargo.toml b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/Cargo.toml index 56c495c0cf..f22b8a8445 100644 --- a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/Cargo.toml +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/Cargo.toml @@ -10,6 +10,8 @@ build = "../../../build.rs" edition.workspace = true [dependencies] +nrf52840dk-test-base = { path = "../nrf52840dk-test-base" } + components = { path = "../../../components" } cortexm4 = { path = "../../../../arch/cortex-m4" } kernel = { path = "../../../../kernel" } diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/src/io.rs b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/src/io.rs deleted file mode 100644 index 4a27492d6e..0000000000 --- a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/src/io.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Licensed under the Apache License, Version 2.0 or the MIT License. -// SPDX-License-Identifier: Apache-2.0 OR MIT -// Copyright Tock Contributors 2024. - -use core::panic::PanicInfo; -use nrf52840::gpio::Pin; - -#[cfg(not(test))] -#[panic_handler] -/// Panic handler -pub unsafe fn panic_fmt(_pi: &PanicInfo) -> ! { - // The nRF52840DK LEDs (see back of board) - let led_kernel_pin = &nrf52840::gpio::nrf52840_gpio_create_pin(Pin::P0_13); - let led = &mut kernel::hil::led::LedLow::new(led_kernel_pin); - kernel::debug::panic_blink_forever(&mut [led]) -} diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/src/main.rs b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/src/main.rs index 794aa5161d..faec9d7dd2 100644 --- a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/src/main.rs +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/src/main.rs @@ -9,246 +9,14 @@ #![deny(missing_docs)] use kernel::component::Component; -use kernel::hil::led::LedLow; -use kernel::hil::time::Counter; -use kernel::platform::{KernelResources, SyscallDriverLookup}; -use kernel::{capabilities, create_capability, static_init}; -use nrf52840::gpio::Pin; +use kernel::create_capability; use nrf52840::interrupt_service::Nrf52840DefaultPeripherals; -use nrf52_components::{UartChannel, UartPins}; - -// The nRF52840DK LEDs (see back of board) -const LED1_PIN: Pin = Pin::P0_13; -const LED2_PIN: Pin = Pin::P0_14; -const LED3_PIN: Pin = Pin::P0_15; -const LED4_PIN: Pin = Pin::P0_16; - -const BUTTON_RST_PIN: Pin = Pin::P0_18; - -const UART_RTS: Option = Some(Pin::P0_05); -const UART_TXD: Pin = Pin::P0_06; -const UART_CTS: Option = Some(Pin::P0_07); -const UART_RXD: Pin = Pin::P0_08; - -/// Debug Writer -pub mod io; - -// State for loading and holding applications. -// How should the kernel respond when a process faults. -const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = - capsules_system::process_policies::PanicFaultPolicy {}; - -// Number of concurrent processes this platform supports. -const NUM_PROCS: usize = 8; - -type ChipHw = nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>; - -kernel::stack_size! {0x2000} - -//------------------------------------------------------------------------------ -// SYSCALL DRIVER TYPE DEFINITIONS -//------------------------------------------------------------------------------ - -type AlarmDriver = components::alarm::AlarmDriverComponentType>; - -type SchedulerInUse = components::sched::round_robin::RoundRobinComponentType; - -/// Supported drivers by the platform -pub struct Platform { - console: &'static capsules_core::console::Console<'static>, - led: &'static capsules_core::led::LedDriver< - 'static, - kernel::hil::led::LedLow<'static, nrf52840::gpio::GPIOPin<'static>>, - 4, - >, - alarm: &'static AlarmDriver, - scheduler: &'static SchedulerInUse, - systick: cortexm4::systick::SysTick, -} - -impl SyscallDriverLookup for Platform { - fn with_driver(&self, driver_num: usize, f: F) -> R - where - F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, - { - match driver_num { - capsules_core::console::DRIVER_NUM => f(Some(self.console)), - capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), - capsules_core::led::DRIVER_NUM => f(Some(self.led)), - _ => f(None), - } - } -} - -/// This is in a separate, inline(never) function so that its stack frame is -/// removed when this function returns. Otherwise, the stack space used for -/// these static_inits is wasted. -#[inline(never)] -unsafe fn create_peripherals() -> &'static mut Nrf52840DefaultPeripherals<'static> { - let ieee802154_ack_buf = static_init!( - [u8; nrf52840::ieee802154_radio::ACK_BUF_SIZE], - [0; nrf52840::ieee802154_radio::ACK_BUF_SIZE] - ); - // Initialize chip peripheral drivers - let nrf52840_peripherals = static_init!( - Nrf52840DefaultPeripherals, - Nrf52840DefaultPeripherals::new(ieee802154_ack_buf) - ); - - nrf52840_peripherals -} - -impl KernelResources>> - for Platform -{ - type SyscallDriverLookup = Self; - type SyscallFilter = (); - type ProcessFault = (); - type Scheduler = SchedulerInUse; - type SchedulerTimer = cortexm4::systick::SysTick; - type WatchDog = (); - type ContextSwitchCallback = (); - - fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { - self - } - fn syscall_filter(&self) -> &Self::SyscallFilter { - &() - } - fn process_fault(&self) -> &Self::ProcessFault { - &() - } - fn scheduler(&self) -> &Self::Scheduler { - self.scheduler - } - fn scheduler_timer(&self) -> &Self::SchedulerTimer { - &self.systick - } - fn watchdog(&self) -> &Self::WatchDog { - &() - } - fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { - &() - } -} /// Main function called after RAM initialized. #[no_mangle] pub unsafe fn main() { - //-------------------------------------------------------------------------- - // INITIAL SETUP - //-------------------------------------------------------------------------- - - // Apply errata fixes and enable interrupts. - nrf52840::init(); - - // Initialize deferred calls very early. - kernel::deferred_call::initialize_deferred_call_state::< - ::ThreadIdProvider, - >(); - - // Set up peripheral drivers. Called in separate function to reduce stack - // usage. - let nrf52840_peripherals = create_peripherals(); - - // Set up circular peripheral dependencies. - nrf52840_peripherals.init(); - let base_peripherals = &nrf52840_peripherals.nrf52; - - // Choose the channel for serial output. This board can be configured to use - // either the Segger RTT channel or via UART with traditional TX/RX GPIO - // pins. - let uart_channel = UartChannel::Pins(UartPins::new(UART_RTS, UART_TXD, UART_CTS, UART_RXD)); - - // Create an array to hold process references. - let processes = components::process_array::ProcessArrayComponent::new() - .finalize(components::process_array_component_static!(NUM_PROCS)); - - // Setup space to store the core kernel data structure. - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(processes.as_slice())); - - // Create (and save for panic debugging) a chip object to setup low-level - // resources (e.g. MPU, systick). - let chip = static_init!( - nrf52840::chip::NRF52, - nrf52840::chip::NRF52::new(nrf52840_peripherals) - ); - - // Do nRF configuration and setup. This is shared code with other nRF-based - // platforms. - nrf52_components::startup::NrfStartupComponent::new( - false, - BUTTON_RST_PIN, - nrf52840::uicr::Regulator0Output::DEFAULT, - &base_peripherals.nvmc, - ) - .finalize(()); - - //-------------------------------------------------------------------------- - // CAPABILITIES - //-------------------------------------------------------------------------- - - // Create capabilities that the board needs to call certain protected kernel - // functions. - let main_loop_capability = create_capability!(capabilities::MainLoopCapability); - - //-------------------------------------------------------------------------- - // LEDs - //-------------------------------------------------------------------------- - - let led = components::led::LedsComponent::new().finalize(components::led_component_static!( - LedLow<'static, nrf52840::gpio::GPIOPin>, - LedLow::new(&nrf52840_peripherals.gpio_port[LED1_PIN]), - LedLow::new(&nrf52840_peripherals.gpio_port[LED2_PIN]), - LedLow::new(&nrf52840_peripherals.gpio_port[LED3_PIN]), - LedLow::new(&nrf52840_peripherals.gpio_port[LED4_PIN]), - )); - - //-------------------------------------------------------------------------- - // TIMER - //-------------------------------------------------------------------------- - - let rtc = &base_peripherals.rtc; - let _ = rtc.start(); - let mux_alarm = components::alarm::AlarmMuxComponent::new(rtc) - .finalize(components::alarm_mux_component_static!(nrf52840::rtc::Rtc)); - let alarm = components::alarm::AlarmDriverComponent::new( - board_kernel, - capsules_core::alarm::DRIVER_NUM, - mux_alarm, - ) - .finalize(components::alarm_component_static!(nrf52840::rtc::Rtc)); - - //-------------------------------------------------------------------------- - // UART & CONSOLE & DEBUG - //-------------------------------------------------------------------------- - - let uart_channel = nrf52_components::UartChannelComponent::new( - uart_channel, - mux_alarm, - &base_peripherals.uarte0, - ) - .finalize(nrf52_components::uart_channel_component_static!( - nrf52840::rtc::Rtc - )); - - // Virtualize the UART channel for the console and for kernel debug. - let uart_mux = components::console::UartMuxComponent::new(uart_channel, 115200) - .finalize(components::uart_mux_component_static!()); - - // Setup the serial console for userspace. - let console = components::console::ConsoleComponent::new( - board_kernel, - capsules_core::console::DRIVER_NUM, - uart_mux, - ) - .finalize(components::console_component_static!()); - - //-------------------------------------------------------------------------- - // NRF CLOCK SETUP - //-------------------------------------------------------------------------- - - nrf52_components::NrfClockComponent::new(&base_peripherals.clock).finalize(()); + let (board_kernel, platform, chip, _default_peripherals, _mux_uart, _mux_alarm) = + nrf52840dk_test_base_lib::start(); //-------------------------------------------------------------------------- // Credential Checking @@ -312,7 +80,7 @@ pub unsafe fn main() { checker, board_kernel, chip, - &FAULT_RESPONSE, + &nrf52840dk_test_base_lib::FAULT_RESPONSE, assigner, storage_permissions_policy, app_flash, @@ -321,24 +89,14 @@ pub unsafe fn main() { .finalize(components::process_loader_sequential_component_static!( nrf52840::chip::NRF52, kernel::process::ProcessStandardDebugFull, - NUM_PROCS + nrf52840dk_test_base_lib::NUM_PROCS )); //-------------------------------------------------------------------------- // PLATFORM SETUP, SCHEDULER, AND START KERNEL LOOP //-------------------------------------------------------------------------- - let scheduler = components::sched::round_robin::RoundRobinComponent::new(processes) - .finalize(components::round_robin_component_static!(NUM_PROCS)); - - let platform = Platform { - console, - led, - alarm, - scheduler, - systick: cortexm4::systick::SysTick::new_with_calibration(64000000), - }; - + let main_loop_capability = create_capability!(kernel::capabilities::MainLoopCapability); board_kernel.kernel_loop( &platform, chip, diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/Cargo.toml b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/Cargo.toml index 4b2796d31b..d7215f3d3a 100644 --- a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/Cargo.toml +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/Cargo.toml @@ -10,6 +10,8 @@ build = "../../../build.rs" edition.workspace = true [dependencies] +nrf52840dk-test-base = { path = "../nrf52840dk-test-base" } + components = { path = "../../../components" } cortexm4 = { path = "../../../../arch/cortex-m4" } kernel = { path = "../../../../kernel" } diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/src/io.rs b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/src/io.rs deleted file mode 100644 index 4a27492d6e..0000000000 --- a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/src/io.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Licensed under the Apache License, Version 2.0 or the MIT License. -// SPDX-License-Identifier: Apache-2.0 OR MIT -// Copyright Tock Contributors 2024. - -use core::panic::PanicInfo; -use nrf52840::gpio::Pin; - -#[cfg(not(test))] -#[panic_handler] -/// Panic handler -pub unsafe fn panic_fmt(_pi: &PanicInfo) -> ! { - // The nRF52840DK LEDs (see back of board) - let led_kernel_pin = &nrf52840::gpio::nrf52840_gpio_create_pin(Pin::P0_13); - let led = &mut kernel::hil::led::LedLow::new(led_kernel_pin); - kernel::debug::panic_blink_forever(&mut [led]) -} diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/src/main.rs b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/src/main.rs index 59fc392917..8d8c395536 100644 --- a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/src/main.rs +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/src/main.rs @@ -9,129 +9,19 @@ #![deny(missing_docs)] use kernel::component::Component; -use kernel::hil::led::LedLow; -use kernel::hil::time::Counter; -use kernel::platform::{KernelResources, SyscallDriverLookup}; -use kernel::process::{ProcessArray, ProcessLoadingAsync}; +use kernel::process::ProcessLoadingAsync; use kernel::{capabilities, create_capability, static_init}; -use nrf52840::gpio::Pin; use nrf52840::interrupt_service::Nrf52840DefaultPeripherals; -use nrf52_components::{UartChannel, UartPins}; - -// The nRF52840DK LEDs (see back of board) -const LED1_PIN: Pin = Pin::P0_13; -const LED2_PIN: Pin = Pin::P0_14; -const LED3_PIN: Pin = Pin::P0_15; -const LED4_PIN: Pin = Pin::P0_16; - -const BUTTON_RST_PIN: Pin = Pin::P0_18; - -const UART_RTS: Option = Some(Pin::P0_05); -const UART_TXD: Pin = Pin::P0_06; -const UART_CTS: Option = Some(Pin::P0_07); -const UART_RXD: Pin = Pin::P0_08; - -/// Debug Writer -pub mod io; - -// State for loading and holding applications. -// How should the kernel respond when a process faults. -const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = - capsules_system::process_policies::PanicFaultPolicy {}; - -// Number of concurrent processes this platform supports. -const NUM_PROCS: usize = 8; - -type ChipHw = nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>; - -kernel::stack_size! {0x2000} //------------------------------------------------------------------------------ // SYSCALL DRIVER TYPE DEFINITIONS //------------------------------------------------------------------------------ -type AlarmDriver = components::alarm::AlarmDriverComponentType>; - -type SchedulerInUse = components::sched::round_robin::RoundRobinComponentType; +type ChipHw = nrf52840dk_test_base_lib::ChipHw; /// Supported drivers by the platform pub struct Platform { - console: &'static capsules_core::console::Console<'static>, - led: &'static capsules_core::led::LedDriver< - 'static, - kernel::hil::led::LedLow<'static, nrf52840::gpio::GPIOPin<'static>>, - 4, - >, - alarm: &'static AlarmDriver, - scheduler: &'static SchedulerInUse, - systick: cortexm4::systick::SysTick, - processes: &'static ProcessArray, -} - -impl SyscallDriverLookup for Platform { - fn with_driver(&self, driver_num: usize, f: F) -> R - where - F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, - { - match driver_num { - capsules_core::console::DRIVER_NUM => f(Some(self.console)), - capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), - capsules_core::led::DRIVER_NUM => f(Some(self.led)), - _ => f(None), - } - } -} - -/// This is in a separate, inline(never) function so that its stack frame is -/// removed when this function returns. Otherwise, the stack space used for -/// these static_inits is wasted. -#[inline(never)] -unsafe fn create_peripherals() -> &'static mut Nrf52840DefaultPeripherals<'static> { - let ieee802154_ack_buf = static_init!( - [u8; nrf52840::ieee802154_radio::ACK_BUF_SIZE], - [0; nrf52840::ieee802154_radio::ACK_BUF_SIZE] - ); - // Initialize chip peripheral drivers - let nrf52840_peripherals = static_init!( - Nrf52840DefaultPeripherals, - Nrf52840DefaultPeripherals::new(ieee802154_ack_buf) - ); - - nrf52840_peripherals -} - -impl KernelResources>> - for Platform -{ - type SyscallDriverLookup = Self; - type SyscallFilter = (); - type ProcessFault = (); - type Scheduler = SchedulerInUse; - type SchedulerTimer = cortexm4::systick::SysTick; - type WatchDog = (); - type ContextSwitchCallback = (); - - fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { - self - } - fn syscall_filter(&self) -> &Self::SyscallFilter { - &() - } - fn process_fault(&self) -> &Self::ProcessFault { - &() - } - fn scheduler(&self) -> &Self::Scheduler { - self.scheduler - } - fn scheduler_timer(&self) -> &Self::SchedulerTimer { - &self.systick - } - fn watchdog(&self) -> &Self::WatchDog { - &() - } - fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { - &() - } + kernel: &'static kernel::Kernel, } impl kernel::process::ProcessLoadingAsyncClient for Platform { @@ -140,11 +30,14 @@ impl kernel::process::ProcessLoadingAsyncClient for Platform { fn process_loading_finished(&self) { kernel::debug!("Processes Loaded:"); - for (i, proc) in self.processes.as_slice().iter().enumerate() { - proc.get().map(|p| { - kernel::debug!("[{}] {}", i, p.get_process_name()); - kernel::debug!(" ShortId: {}", p.short_app_id()); - }); + let process_manage_cap = create_capability!(capabilities::ProcessManagementCapability); + for (i, proc) in self + .kernel + .process_iter_capability(&process_manage_cap) + .enumerate() + { + kernel::debug!("[{}] {}", i, proc.get_process_name()); + kernel::debug!(" ShortId: {}", proc.short_app_id()); } } } @@ -152,115 +45,13 @@ impl kernel::process::ProcessLoadingAsyncClient for Platform { /// Main function called after RAM initialized. #[no_mangle] pub unsafe fn main() { - //-------------------------------------------------------------------------- - // INITIAL SETUP - //-------------------------------------------------------------------------- - - // Apply errata fixes and enable interrupts. - nrf52840::init(); - - // Initialize deferred calls very early. - kernel::deferred_call::initialize_deferred_call_state::< - ::ThreadIdProvider, - >(); - - // Set up peripheral drivers. Called in separate function to reduce stack - // usage. - let nrf52840_peripherals = create_peripherals(); - - // Set up circular peripheral dependencies. - nrf52840_peripherals.init(); - let base_peripherals = &nrf52840_peripherals.nrf52; - - // Choose the channel for serial output. This board can be configured to use - // either the Segger RTT channel or via UART with traditional TX/RX GPIO - // pins. - let uart_channel = UartChannel::Pins(UartPins::new(UART_RTS, UART_TXD, UART_CTS, UART_RXD)); - - // Create an array to hold process references. - let processes = components::process_array::ProcessArrayComponent::new() - .finalize(components::process_array_component_static!(NUM_PROCS)); - - // Setup space to store the core kernel data structure. - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(processes.as_slice())); - - // Create (and save for panic debugging) a chip object to setup low-level - // resources (e.g. MPU, systick). - let chip = static_init!( - nrf52840::chip::NRF52, - nrf52840::chip::NRF52::new(nrf52840_peripherals) - ); - - // Do nRF configuration and setup. This is shared code with other nRF-based - // platforms. - nrf52_components::startup::NrfStartupComponent::new( - false, - BUTTON_RST_PIN, - nrf52840::uicr::Regulator0Output::DEFAULT, - &base_peripherals.nvmc, - ) - .finalize(()); + let (board_kernel, base_platform, chip, _default_peripherals, mux_uart, mux_alarm) = + nrf52840dk_test_base_lib::start(); //-------------------------------------------------------------------------- - // CAPABILITIES + // PROCESS CONSOLE //-------------------------------------------------------------------------- - // Create capabilities that the board needs to call certain protected kernel - // functions. - let main_loop_capability = create_capability!(capabilities::MainLoopCapability); - - //-------------------------------------------------------------------------- - // LEDs - //-------------------------------------------------------------------------- - - let led = components::led::LedsComponent::new().finalize(components::led_component_static!( - LedLow<'static, nrf52840::gpio::GPIOPin>, - LedLow::new(&nrf52840_peripherals.gpio_port[LED1_PIN]), - LedLow::new(&nrf52840_peripherals.gpio_port[LED2_PIN]), - LedLow::new(&nrf52840_peripherals.gpio_port[LED3_PIN]), - LedLow::new(&nrf52840_peripherals.gpio_port[LED4_PIN]), - )); - - //-------------------------------------------------------------------------- - // TIMER - //-------------------------------------------------------------------------- - - let rtc = &base_peripherals.rtc; - let _ = rtc.start(); - let mux_alarm = components::alarm::AlarmMuxComponent::new(rtc) - .finalize(components::alarm_mux_component_static!(nrf52840::rtc::Rtc)); - let alarm = components::alarm::AlarmDriverComponent::new( - board_kernel, - capsules_core::alarm::DRIVER_NUM, - mux_alarm, - ) - .finalize(components::alarm_component_static!(nrf52840::rtc::Rtc)); - - //-------------------------------------------------------------------------- - // UART & CONSOLE & DEBUG - //-------------------------------------------------------------------------- - - let uart_channel = nrf52_components::UartChannelComponent::new( - uart_channel, - mux_alarm, - &base_peripherals.uarte0, - ) - .finalize(nrf52_components::uart_channel_component_static!( - nrf52840::rtc::Rtc - )); - - // Virtualize the UART channel for the console and for kernel debug. - let uart_mux = components::console::UartMuxComponent::new(uart_channel, 115200) - .finalize(components::uart_mux_component_static!()); - - // Setup the serial console for userspace. - let console = components::console::ConsoleComponent::new( - board_kernel, - capsules_core::console::DRIVER_NUM, - uart_mux, - ) - .finalize(components::console_component_static!()); - // Tool for displaying information about processes. let process_printer = components::process_printer::ProcessPrinterTextComponent::new() .finalize(components::process_printer_text_component_static!()); @@ -269,7 +60,7 @@ pub unsafe fn main() { // processes. let pconsole = components::process_console::ProcessConsoleComponent::new( board_kernel, - uart_mux, + mux_uart, mux_alarm, process_printer, Some(cortexm4::support::reset), @@ -282,17 +73,11 @@ pub unsafe fn main() { components::debug_writer::DebugWriterComponent::new::< ::ThreadIdProvider, >( - uart_mux, + mux_uart, create_capability!(capabilities::SetDebugWriterCapability), ) .finalize(components::debug_writer_component_static!()); - //-------------------------------------------------------------------------- - // NRF CLOCK SETUP - //-------------------------------------------------------------------------- - - nrf52_components::NrfClockComponent::new(&base_peripherals.clock).finalize(()); - //-------------------------------------------------------------------------- // Credential Checking //-------------------------------------------------------------------------- @@ -351,7 +136,7 @@ pub unsafe fn main() { checker, board_kernel, chip, - &FAULT_RESPONSE, + &nrf52840dk_test_base_lib::FAULT_RESPONSE, assigner, storage_permissions_policy, app_flash, @@ -360,33 +145,26 @@ pub unsafe fn main() { .finalize(components::process_loader_sequential_component_static!( nrf52840::chip::NRF52, kernel::process::ProcessStandardDebugFull, - NUM_PROCS + nrf52840dk_test_base_lib::NUM_PROCS )); //-------------------------------------------------------------------------- // PLATFORM SETUP, SCHEDULER, AND START KERNEL LOOP //-------------------------------------------------------------------------- - let scheduler = components::sched::round_robin::RoundRobinComponent::new(processes) - .finalize(components::round_robin_component_static!(NUM_PROCS)); - let platform = static_init!( Platform, Platform { - console, - led, - alarm, - scheduler, - systick: cortexm4::systick::SysTick::new_with_calibration(64000000), - processes, + kernel: board_kernel, } ); loader.set_client(platform); let _ = pconsole.start(); + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); board_kernel.kernel_loop( - platform, + &base_platform, chip, None::<&kernel::ipc::IPC<0>>, &main_loop_capability, diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-base/Cargo.toml b/boards/configurations/nrf52840dk/nrf52840dk-test-base/Cargo.toml new file mode 100644 index 0000000000..7273f15d51 --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-base/Cargo.toml @@ -0,0 +1,27 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2026. + +[package] +name = "nrf52840dk-test-base" +version.workspace = true +authors.workspace = true +edition.workspace = true + +[lib] +name = "nrf52840dk_test_base_lib" + +[dependencies] +components = { path = "../../../components" } +cortexm4 = { path = "../../../../arch/cortex-m4" } +kernel = { path = "../../../../kernel" } +nrf52840 = { path = "../../../../chips/nrf52840" } +segger = { path = "../../../../chips/segger" } +nrf52_components = { path = "../../../nordic/nrf52_components" } + +capsules-core = { path = "../../../../capsules/core" } +capsules-extra = { path = "../../../../capsules/extra" } +capsules-system = { path = "../../../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-base/README.md b/boards/configurations/nrf52840dk/nrf52840dk-test-base/README.md new file mode 100644 index 0000000000..c5f90a2147 --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-base/README.md @@ -0,0 +1,4 @@ +nRF52840-DK Test Base Board +=================================== + +Use to more easily create other testing configuration boards. diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-ecdsap256/src/io.rs b/boards/configurations/nrf52840dk/nrf52840dk-test-base/src/io.rs similarity index 100% rename from boards/configurations/nrf52840dk/nrf52840dk-test-appid-ecdsap256/src/io.rs rename to boards/configurations/nrf52840dk/nrf52840dk-test-base/src/io.rs diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-base/src/lib.rs b/boards/configurations/nrf52840dk/nrf52840dk-test-base/src/lib.rs new file mode 100644 index 0000000000..41a0baaf4a --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-base/src/lib.rs @@ -0,0 +1,278 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2026. + +//! Tock kernel for the Nordic Semiconductor nRF52840 development kit (DK). + +#![no_std] +#![deny(missing_docs)] + +use capsules_core::virtualizers::virtual_alarm::MuxAlarm; +use capsules_core::virtualizers::virtual_uart::MuxUart; +use kernel::component::Component; +use kernel::hil::led::LedLow; +use kernel::hil::time::Counter; +use kernel::platform::chip::Chip; +use kernel::platform::{KernelResources, SyscallDriverLookup}; +use kernel::static_init; +use nrf52840::gpio::Pin; +use nrf52840::interrupt_service::Nrf52840DefaultPeripherals; +use nrf52_components::{UartChannel, UartPins}; + +// The nRF52840DK LEDs (see back of board) +const LED1_PIN: Pin = Pin::P0_13; +const LED2_PIN: Pin = Pin::P0_14; +const LED3_PIN: Pin = Pin::P0_15; +const LED4_PIN: Pin = Pin::P0_16; + +const BUTTON_RST_PIN: Pin = Pin::P0_18; + +const UART_RTS: Option = Some(Pin::P0_05); +const UART_TXD: Pin = Pin::P0_06; +const UART_CTS: Option = Some(Pin::P0_07); +const UART_RXD: Pin = Pin::P0_08; + +/// Debug Writer +pub mod io; + +/// How should the kernel respond when a process faults. +pub const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; + +/// Number of concurrent processes this platform supports. +pub const NUM_PROCS: usize = 8; + +/// Type of the nrf52840 chip. +pub type ChipHw = nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>; + +kernel::stack_size! {0x2000} + +//------------------------------------------------------------------------------ +// SYSCALL DRIVER TYPE DEFINITIONS +//------------------------------------------------------------------------------ + +type ConsoleDriver = components::console::ConsoleComponentType; + +type Led = kernel::hil::led::LedLow<'static, nrf52840::gpio::GPIOPin<'static>>; +type LedDriver = components::led::LedsComponentType; + +type AlarmDriver = components::alarm::AlarmDriverComponentType>; + +type SchedulerInUse = components::sched::round_robin::RoundRobinComponentType; + +/// Supported drivers by the platform +pub struct Platform { + console: &'static ConsoleDriver, + led: &'static LedDriver, + alarm: &'static AlarmDriver, + scheduler: &'static SchedulerInUse, + systick: cortexm4::systick::SysTick, +} + +impl SyscallDriverLookup for Platform { + fn with_driver(&self, driver_num: usize, f: F) -> R + where + F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, + { + match driver_num { + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + _ => f(None), + } + } +} + +/// This is in a separate, inline(never) function so that its stack frame is +/// removed when this function returns. Otherwise, the stack space used for +/// these static_inits is wasted. +#[inline(never)] +unsafe fn create_peripherals() -> &'static mut Nrf52840DefaultPeripherals<'static> { + let ieee802154_ack_buf = static_init!( + [u8; nrf52840::ieee802154_radio::ACK_BUF_SIZE], + [0; nrf52840::ieee802154_radio::ACK_BUF_SIZE] + ); + // Initialize chip peripheral drivers + let nrf52840_peripherals = static_init!( + Nrf52840DefaultPeripherals, + Nrf52840DefaultPeripherals::new(ieee802154_ack_buf) + ); + + nrf52840_peripherals +} + +impl KernelResources>> + for Platform +{ + type SyscallDriverLookup = Self; + type SyscallFilter = (); + type ProcessFault = (); + type Scheduler = SchedulerInUse; + type SchedulerTimer = cortexm4::systick::SysTick; + type WatchDog = (); + type ContextSwitchCallback = (); + + fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { + self + } + fn syscall_filter(&self) -> &Self::SyscallFilter { + &() + } + fn process_fault(&self) -> &Self::ProcessFault { + &() + } + fn scheduler(&self) -> &Self::Scheduler { + self.scheduler + } + fn scheduler_timer(&self) -> &Self::SchedulerTimer { + &self.systick + } + fn watchdog(&self) -> &Self::WatchDog { + &() + } + fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { + &() + } +} + +/// Do baseline setup for a testing configuration board. +#[inline(never)] +pub unsafe fn start() -> ( + &'static kernel::Kernel, + Platform, + &'static ChipHw, + &'static Nrf52840DefaultPeripherals<'static>, + &'static MuxUart<'static>, + &'static MuxAlarm<'static, nrf52840::rtc::Rtc<'static>>, +) { + //-------------------------------------------------------------------------- + // INITIAL SETUP + //-------------------------------------------------------------------------- + + // Apply errata fixes and enable interrupts. + ChipHw::init(); + + // Initialize deferred calls very early. + kernel::deferred_call::initialize_deferred_call_state::< + ::ThreadIdProvider, + >(); + + // Set up peripheral drivers. Called in separate function to reduce stack + // usage. + let nrf52840_peripherals = create_peripherals(); + + // Set up circular peripheral dependencies. + nrf52840_peripherals.init(); + let base_peripherals = &nrf52840_peripherals.nrf52; + + // Choose the channel for serial output. This board can be configured to use + // either the Segger RTT channel or via UART with traditional TX/RX GPIO + // pins. + let uart_channel = UartChannel::Pins(UartPins::new(UART_RTS, UART_TXD, UART_CTS, UART_RXD)); + + // Create an array to hold process references. + let processes = components::process_array::ProcessArrayComponent::new() + .finalize(components::process_array_component_static!(NUM_PROCS)); + + // Setup space to store the core kernel data structure. + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(processes.as_slice())); + + // Create (and save for panic debugging) a chip object to setup low-level + // resources (e.g. MPU, systick). + let chip = static_init!( + nrf52840::chip::NRF52, + nrf52840::chip::NRF52::new(nrf52840_peripherals) + ); + + // Do nRF configuration and setup. This is shared code with other nRF-based + // platforms. + nrf52_components::startup::NrfStartupComponent::new( + false, + BUTTON_RST_PIN, + nrf52840::uicr::Regulator0Output::DEFAULT, + &base_peripherals.nvmc, + ) + .finalize(()); + + //-------------------------------------------------------------------------- + // LEDs + //-------------------------------------------------------------------------- + + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( + Led, + LedLow::new(&nrf52840_peripherals.gpio_port[LED1_PIN]), + LedLow::new(&nrf52840_peripherals.gpio_port[LED2_PIN]), + LedLow::new(&nrf52840_peripherals.gpio_port[LED3_PIN]), + LedLow::new(&nrf52840_peripherals.gpio_port[LED4_PIN]), + )); + + //-------------------------------------------------------------------------- + // TIMER + //-------------------------------------------------------------------------- + + let rtc = &base_peripherals.rtc; + let _ = rtc.start(); + let mux_alarm = components::alarm::AlarmMuxComponent::new(rtc) + .finalize(components::alarm_mux_component_static!(nrf52840::rtc::Rtc)); + let alarm = components::alarm::AlarmDriverComponent::new( + board_kernel, + capsules_core::alarm::DRIVER_NUM, + mux_alarm, + ) + .finalize(components::alarm_component_static!(nrf52840::rtc::Rtc)); + + //-------------------------------------------------------------------------- + // UART & CONSOLE & DEBUG + //-------------------------------------------------------------------------- + + let uart_channel = nrf52_components::UartChannelComponent::new( + uart_channel, + mux_alarm, + &base_peripherals.uarte0, + ) + .finalize(nrf52_components::uart_channel_component_static!( + nrf52840::rtc::Rtc + )); + + // Virtualize the UART channel for the console and for kernel debug. + let mux_uart = components::console::UartMuxComponent::new(uart_channel, 115200) + .finalize(components::uart_mux_component_static!()); + + // Setup the serial console for userspace. + let console = components::console::ConsoleComponent::new( + board_kernel, + capsules_core::console::DRIVER_NUM, + mux_uart, + ) + .finalize(components::console_component_static!()); + + //-------------------------------------------------------------------------- + // NRF CLOCK SETUP + //-------------------------------------------------------------------------- + + nrf52_components::NrfClockComponent::new(&base_peripherals.clock).finalize(()); + + //-------------------------------------------------------------------------- + // PLATFORM SETUP, SCHEDULER, AND START KERNEL LOOP + //-------------------------------------------------------------------------- + + let scheduler = components::sched::round_robin::RoundRobinComponent::new(processes) + .finalize(components::round_robin_component_static!(NUM_PROCS)); + + let platform = Platform { + console, + led, + alarm, + scheduler, + systick: cortexm4::systick::SysTick::new_with_calibration(64000000), + }; + + ( + board_kernel, + platform, + chip, + nrf52840_peripherals, + mux_uart, + mux_alarm, + ) +} diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-dynamic-app-load/src/main.rs b/boards/configurations/nrf52840dk/nrf52840dk-test-dynamic-app-load/src/main.rs index 8ca86f0a2f..44fdac7922 100644 --- a/boards/configurations/nrf52840dk/nrf52840dk-test-dynamic-app-load/src/main.rs +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-dynamic-app-load/src/main.rs @@ -12,6 +12,7 @@ use kernel::component::Component; use kernel::debug::PanicResources; use kernel::hil::led::LedLow; use kernel::hil::time::Counter; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::process::ProcessArray; use kernel::process::ProcessLoadingAsync; @@ -188,7 +189,7 @@ pub unsafe fn main() { //-------------------------------------------------------------------------- // Apply errata fixes and enable interrupts. - nrf52840::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-invs/Cargo.toml b/boards/configurations/nrf52840dk/nrf52840dk-test-invs/Cargo.toml index fc06874959..9aeec4e5d7 100644 --- a/boards/configurations/nrf52840dk/nrf52840dk-test-invs/Cargo.toml +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-invs/Cargo.toml @@ -10,6 +10,8 @@ build = "../../../build.rs" edition.workspace = true [dependencies] +nrf52840dk-test-base = { path = "../nrf52840dk-test-base" } + components = { path = "../../../components" } cortexm4 = { path = "../../../../arch/cortex-m4" } kernel = { path = "../../../../kernel" } diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-invs/src/io.rs b/boards/configurations/nrf52840dk/nrf52840dk-test-invs/src/io.rs deleted file mode 100644 index 4a27492d6e..0000000000 --- a/boards/configurations/nrf52840dk/nrf52840dk-test-invs/src/io.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Licensed under the Apache License, Version 2.0 or the MIT License. -// SPDX-License-Identifier: Apache-2.0 OR MIT -// Copyright Tock Contributors 2024. - -use core::panic::PanicInfo; -use nrf52840::gpio::Pin; - -#[cfg(not(test))] -#[panic_handler] -/// Panic handler -pub unsafe fn panic_fmt(_pi: &PanicInfo) -> ! { - // The nRF52840DK LEDs (see back of board) - let led_kernel_pin = &nrf52840::gpio::nrf52840_gpio_create_pin(Pin::P0_13); - let led = &mut kernel::hil::led::LedLow::new(led_kernel_pin); - kernel::debug::panic_blink_forever(&mut [led]) -} diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-invs/src/main.rs b/boards/configurations/nrf52840dk/nrf52840dk-test-invs/src/main.rs index b8fe937e0d..6385f69434 100644 --- a/boards/configurations/nrf52840dk/nrf52840dk-test-invs/src/main.rs +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-invs/src/main.rs @@ -9,29 +9,13 @@ #![deny(missing_docs)] use kernel::component::Component; -use kernel::hil::led::LedLow; -use kernel::hil::time::Counter; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::{capabilities, create_capability, static_init}; use nrf52840::gpio::Pin; use nrf52840::interrupt_service::Nrf52840DefaultPeripherals; -use nrf52_components::{UartChannel, UartPins}; mod invs_permissions; -// The nRF52840DK LEDs (see back of board) -const LED1_PIN: Pin = Pin::P0_13; -const LED2_PIN: Pin = Pin::P0_14; -const LED3_PIN: Pin = Pin::P0_15; -const LED4_PIN: Pin = Pin::P0_16; - -const BUTTON_RST_PIN: Pin = Pin::P0_18; - -const UART_RTS: Option = Some(Pin::P0_05); -const UART_TXD: Pin = Pin::P0_06; -const UART_CTS: Option = Some(Pin::P0_07); -const UART_RXD: Pin = Pin::P0_08; - const SPI_MOSI: Pin = Pin::P0_20; const SPI_MISO: Pin = Pin::P0_21; const SPI_CLK: Pin = Pin::P0_19; @@ -40,20 +24,7 @@ const SPI_MX25R6435F_CHIP_SELECT: Pin = Pin::P0_17; const SPI_MX25R6435F_WRITE_PROTECT_PIN: Pin = Pin::P0_22; const SPI_MX25R6435F_HOLD_PIN: Pin = Pin::P0_23; -/// Debug Writer -pub mod io; - -// State for loading and holding applications. -// How should the kernel respond when a process faults. -const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = - capsules_system::process_policies::PanicFaultPolicy {}; - -// Number of concurrent processes this platform supports. -const NUM_PROCS: usize = 8; - -type ChipHw = nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>; - -kernel::stack_size! {0x2000} +type ChipHw = nrf52840dk_test_base_lib::ChipHw; const APP_STORAGE_REGION_SIZE: usize = 4096; @@ -61,8 +32,6 @@ const APP_STORAGE_REGION_SIZE: usize = 4096; // SYSCALL DRIVER TYPE DEFINITIONS //------------------------------------------------------------------------------ -type AlarmDriver = components::alarm::AlarmDriverComponentType>; - type Mx25r6435f = components::mx25r6435f::Mx25r6435fComponentType< nrf52840::spi::SPIM<'static>, nrf52840::gpio::GPIOPin<'static>, @@ -72,20 +41,10 @@ type InvsDriver = components::isolated_nonvolatile_storage::IsolatedNonvolatileS APP_STORAGE_REGION_SIZE, >; -type SchedulerInUse = components::sched::round_robin::RoundRobinComponentType; - /// Supported drivers by the platform pub struct Platform { - console: &'static capsules_core::console::Console<'static>, - led: &'static capsules_core::led::LedDriver< - 'static, - kernel::hil::led::LedLow<'static, nrf52840::gpio::GPIOPin<'static>>, - 4, - >, - alarm: &'static AlarmDriver, + base: nrf52840dk_test_base_lib::Platform, invs: &'static InvsDriver, - scheduler: &'static SchedulerInUse, - systick: cortexm4::systick::SysTick, } impl SyscallDriverLookup for Platform { @@ -94,179 +53,55 @@ impl SyscallDriverLookup for Platform { F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules_core::console::DRIVER_NUM => f(Some(self.console)), - capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), - capsules_core::led::DRIVER_NUM => f(Some(self.led)), capsules_extra::isolated_nonvolatile_storage_driver::DRIVER_NUM => f(Some(self.invs)), - _ => f(None), + _ => self.base.with_driver(driver_num, f), } } } -/// This is in a separate, inline(never) function so that its stack frame is -/// removed when this function returns. Otherwise, the stack space used for -/// these static_inits is wasted. -#[inline(never)] -unsafe fn create_peripherals() -> &'static mut Nrf52840DefaultPeripherals<'static> { - let ieee802154_ack_buf = static_init!( - [u8; nrf52840::ieee802154_radio::ACK_BUF_SIZE], - [0; nrf52840::ieee802154_radio::ACK_BUF_SIZE] - ); - // Initialize chip peripheral drivers - let nrf52840_peripherals = static_init!( - Nrf52840DefaultPeripherals, - Nrf52840DefaultPeripherals::new(ieee802154_ack_buf) - ); - - nrf52840_peripherals -} - -impl KernelResources>> - for Platform -{ +impl KernelResources for Platform { type SyscallDriverLookup = Self; - type SyscallFilter = (); - type ProcessFault = (); - type Scheduler = SchedulerInUse; - type SchedulerTimer = cortexm4::systick::SysTick; - type WatchDog = (); - type ContextSwitchCallback = (); + type SyscallFilter = + >::SyscallFilter; + type ProcessFault = + >::ProcessFault; + type Scheduler = >::Scheduler; + type SchedulerTimer = + >::SchedulerTimer; + type WatchDog = >::WatchDog; + type ContextSwitchCallback = + >::ContextSwitchCallback; fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { self } fn syscall_filter(&self) -> &Self::SyscallFilter { - &() + self.base.syscall_filter() } fn process_fault(&self) -> &Self::ProcessFault { - &() + self.base.process_fault() } fn scheduler(&self) -> &Self::Scheduler { - self.scheduler + self.base.scheduler() } fn scheduler_timer(&self) -> &Self::SchedulerTimer { - &self.systick + self.base.scheduler_timer() } fn watchdog(&self) -> &Self::WatchDog { - &() + self.base.watchdog() } fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { - &() + self.base.context_switch_callback() } } /// Main function called after RAM initialized. #[no_mangle] pub unsafe fn main() { - //-------------------------------------------------------------------------- - // INITIAL SETUP - //-------------------------------------------------------------------------- - - // Apply errata fixes and enable interrupts. - nrf52840::init(); - - // Initialize deferred calls very early. - kernel::deferred_call::initialize_deferred_call_state::< - ::ThreadIdProvider, - >(); - - // Set up peripheral drivers. Called in separate function to reduce stack - // usage. - let nrf52840_peripherals = create_peripherals(); - - // Set up circular peripheral dependencies. - nrf52840_peripherals.init(); + let (board_kernel, base_platform, chip, nrf52840_peripherals, _mux_uart, mux_alarm) = + nrf52840dk_test_base_lib::start(); let base_peripherals = &nrf52840_peripherals.nrf52; - // Choose the channel for serial output. This board can be configured to use - // either the Segger RTT channel or via UART with traditional TX/RX GPIO - // pins. - let uart_channel = UartChannel::Pins(UartPins::new(UART_RTS, UART_TXD, UART_CTS, UART_RXD)); - - // Create an array to hold process references. - let processes = components::process_array::ProcessArrayComponent::new() - .finalize(components::process_array_component_static!(NUM_PROCS)); - - // Setup space to store the core kernel data structure. - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(processes.as_slice())); - - // Create (and save for panic debugging) a chip object to setup low-level - // resources (e.g. MPU, systick). - let chip = static_init!( - nrf52840::chip::NRF52, - nrf52840::chip::NRF52::new(nrf52840_peripherals) - ); - - // Do nRF configuration and setup. This is shared code with other nRF-based - // platforms. - nrf52_components::startup::NrfStartupComponent::new( - false, - BUTTON_RST_PIN, - nrf52840::uicr::Regulator0Output::DEFAULT, - &base_peripherals.nvmc, - ) - .finalize(()); - - //-------------------------------------------------------------------------- - // CAPABILITIES - //-------------------------------------------------------------------------- - - // Create capabilities that the board needs to call certain protected kernel - // functions. - let main_loop_capability = create_capability!(capabilities::MainLoopCapability); - - //-------------------------------------------------------------------------- - // LEDs - //-------------------------------------------------------------------------- - - let led = components::led::LedsComponent::new().finalize(components::led_component_static!( - LedLow<'static, nrf52840::gpio::GPIOPin>, - LedLow::new(&nrf52840_peripherals.gpio_port[LED1_PIN]), - LedLow::new(&nrf52840_peripherals.gpio_port[LED2_PIN]), - LedLow::new(&nrf52840_peripherals.gpio_port[LED3_PIN]), - LedLow::new(&nrf52840_peripherals.gpio_port[LED4_PIN]), - )); - - //-------------------------------------------------------------------------- - // TIMER - //-------------------------------------------------------------------------- - - let rtc = &base_peripherals.rtc; - let _ = rtc.start(); - let mux_alarm = components::alarm::AlarmMuxComponent::new(rtc) - .finalize(components::alarm_mux_component_static!(nrf52840::rtc::Rtc)); - let alarm = components::alarm::AlarmDriverComponent::new( - board_kernel, - capsules_core::alarm::DRIVER_NUM, - mux_alarm, - ) - .finalize(components::alarm_component_static!(nrf52840::rtc::Rtc)); - - //-------------------------------------------------------------------------- - // UART & CONSOLE & DEBUG - //-------------------------------------------------------------------------- - - let uart_channel = nrf52_components::UartChannelComponent::new( - uart_channel, - mux_alarm, - &base_peripherals.uarte0, - ) - .finalize(nrf52_components::uart_channel_component_static!( - nrf52840::rtc::Rtc - )); - - // Virtualize the UART channel for the console and for kernel debug. - let uart_mux = components::console::UartMuxComponent::new(uart_channel, 115200) - .finalize(components::uart_mux_component_static!()); - - // Setup the serial console for userspace. - let console = components::console::ConsoleComponent::new( - board_kernel, - capsules_core::console::DRIVER_NUM, - uart_mux, - ) - .finalize(components::console_component_static!()); - //-------------------------------------------------------------------------- // ONBOARD EXTERNAL FLASH //-------------------------------------------------------------------------- @@ -309,12 +144,6 @@ pub unsafe fn main() { APP_STORAGE_REGION_SIZE )); - //-------------------------------------------------------------------------- - // NRF CLOCK SETUP - //-------------------------------------------------------------------------- - - nrf52_components::NrfClockComponent::new(&base_peripherals.clock).finalize(()); - //-------------------------------------------------------------------------- // Credential Checking //-------------------------------------------------------------------------- @@ -386,7 +215,7 @@ pub unsafe fn main() { checker, board_kernel, chip, - &FAULT_RESPONSE, + &nrf52840dk_test_base_lib::FAULT_RESPONSE, assigner, storage_permissions_policy, app_flash, @@ -395,25 +224,19 @@ pub unsafe fn main() { .finalize(components::process_loader_sequential_component_static!( nrf52840::chip::NRF52, kernel::process::ProcessStandardDebugFull, - NUM_PROCS + nrf52840dk_test_base_lib::NUM_PROCS )); //-------------------------------------------------------------------------- // PLATFORM SETUP, SCHEDULER, AND START KERNEL LOOP //-------------------------------------------------------------------------- - let scheduler = components::sched::round_robin::RoundRobinComponent::new(processes) - .finalize(components::round_robin_component_static!(NUM_PROCS)); - let platform = Platform { - console, - led, - alarm, + base: base_platform, invs, - scheduler, - systick: cortexm4::systick::SysTick::new_with_calibration(64000000), }; + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); board_kernel.kernel_loop( &platform, chip, diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/src/main.rs b/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/src/main.rs index 0e906faa95..9226752a1a 100644 --- a/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/src/main.rs +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/src/main.rs @@ -13,6 +13,7 @@ use core::cell::Cell; use kernel::component::Component; use kernel::debug::PanicResources; use kernel::hil::time::Counter; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::cells::NumericCellExt; use kernel::utilities::single_thread_value::SingleThreadValue; @@ -164,7 +165,7 @@ pub unsafe fn main() { //-------------------------------------------------------------------------- // Apply errata fixes and enable interrupts. - nrf52840::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/boards/cy8cproto_62_4343_w/src/main.rs b/boards/cy8cproto_62_4343_w/src/main.rs index be1b13f6dc..47cb7459aa 100644 --- a/boards/cy8cproto_62_4343_w/src/main.rs +++ b/boards/cy8cproto_62_4343_w/src/main.rs @@ -19,6 +19,7 @@ use components::led::LedsComponent; use kernel::component::Component; use kernel::debug::PanicResources; use kernel::hil::led::LedHigh; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; use kernel::{capabilities, create_capability, static_init}; @@ -120,8 +121,7 @@ fn init_clocks(peripherals: &PsoC62xaDefaultPeripherals) { /// Main function called after RAM initialized. #[no_mangle] pub unsafe fn main() { - // Set the offset of the vector table - cortexm0p::scb::set_vector_table_offset(0x10000000 as *const ()); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state_unsafe::< diff --git a/boards/hail/src/main.rs b/boards/hail/src/main.rs index 23b334cfb9..4cb24234f3 100644 --- a/boards/hail/src/main.rs +++ b/boards/hail/src/main.rs @@ -17,6 +17,7 @@ use kernel::debug::PanicResources; use kernel::hil; use kernel::hil::led::LedLow; use kernel::hil::Controller; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; #[allow(unused_imports)] @@ -228,7 +229,7 @@ unsafe fn start() -> ( Hail, &'static sam4l::chip::Sam4l, ) { - sam4l::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/boards/imix/src/main.rs b/boards/imix/src/main.rs index 9d2547ca49..b969b051f8 100644 --- a/boards/imix/src/main.rs +++ b/boards/imix/src/main.rs @@ -30,6 +30,7 @@ use kernel::hil::radio; #[allow(unused_imports)] use kernel::hil::radio::{RadioConfig, RadioData}; use kernel::hil::symmetric_encryption::AES128; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; @@ -316,7 +317,7 @@ unsafe fn start() -> ( Imix, &'static sam4l::chip::Sam4l, ) { - sam4l::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/boards/imxrt1050-evkb/src/main.rs b/boards/imxrt1050-evkb/src/main.rs index 990ee80feb..31b069c9b1 100644 --- a/boards/imxrt1050-evkb/src/main.rs +++ b/boards/imxrt1050-evkb/src/main.rs @@ -20,6 +20,7 @@ use kernel::debug; use kernel::debug::PanicResources; use kernel::hil::gpio::Configure; use kernel::hil::led::LedLow; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; use kernel::{create_capability, static_init}; @@ -229,7 +230,7 @@ unsafe fn start() -> ( Imxrt1050EVKB, &'static imxrt10xx::chip::Imxrt10xx, ) { - imxrt10xx::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/boards/lora_e5_mini/src/main.rs b/boards/lora_e5_mini/src/main.rs index 2006b8f36f..758c4440f4 100644 --- a/boards/lora_e5_mini/src/main.rs +++ b/boards/lora_e5_mini/src/main.rs @@ -20,6 +20,7 @@ use kernel::component::Component; use kernel::debug::PanicResources; use kernel::hil::led::LedLow; use kernel::hil::time::Counter; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; use kernel::{create_capability, debug, static_init}; @@ -216,7 +217,7 @@ pub unsafe fn main() { PanicResources::new(), ); - stm32wle5jc::init(); + ChipHw::init(); let peripherals = create_peripherals(); peripherals.init(); diff --git a/boards/lpc55s69-evk/src/main.rs b/boards/lpc55s69-evk/src/main.rs index 2a615258cb..b3327461c9 100644 --- a/boards/lpc55s69-evk/src/main.rs +++ b/boards/lpc55s69-evk/src/main.rs @@ -16,6 +16,7 @@ use kernel::component::Component; use kernel::debug::PanicResources; use kernel::hil::led::LedLow; use kernel::hil::uart::{Configure, Parameters, Parity, StopBits, Width}; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; use kernel::{capabilities, create_capability, static_init}; @@ -125,7 +126,7 @@ unsafe fn start() -> ( Lpc55s69evk, &'static Lpc55s69<'static, Lpc55s69DefaultPeripheral<'static>>, ) { - lpc55s6x::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/boards/makepython-nrf52840/src/main.rs b/boards/makepython-nrf52840/src/main.rs index e09df7548c..9a263cc4fa 100644 --- a/boards/makepython-nrf52840/src/main.rs +++ b/boards/makepython-nrf52840/src/main.rs @@ -18,6 +18,7 @@ use kernel::debug::PanicResources; use kernel::hil::led::LedLow; use kernel::hil::time::Counter; use kernel::hil::usb::Client; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; #[allow(unused_imports)] @@ -227,7 +228,7 @@ pub unsafe fn start() -> ( Platform, &'static nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>, ) { - nrf52840::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/boards/microbit_v2/src/main.rs b/boards/microbit_v2/src/main.rs index 6e7378ca62..d2e4b46eee 100644 --- a/boards/microbit_v2/src/main.rs +++ b/boards/microbit_v2/src/main.rs @@ -16,6 +16,7 @@ use kernel::capabilities; use kernel::component::Component; use kernel::debug::PanicResources; use kernel::hil::time::Counter; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; @@ -221,7 +222,7 @@ unsafe fn start() -> ( MicroBit, &'static nrf52833::chip::NRF52<'static, Nrf52833DefaultPeripherals<'static>>, ) { - nrf52833::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/boards/msp_exp432p401r/src/main.rs b/boards/msp_exp432p401r/src/main.rs index 90cd0e6685..34c4db61bf 100644 --- a/boards/msp_exp432p401r/src/main.rs +++ b/boards/msp_exp432p401r/src/main.rs @@ -15,6 +15,7 @@ use kernel::capabilities; use kernel::component::Component; use kernel::debug::PanicResources; use kernel::hil::gpio::Configure; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; use kernel::{create_capability, debug, static_init}; @@ -125,7 +126,7 @@ impl SyscallDriverLookup for MspExp432P401R { /// (which is necessary for 48MHz operation) and enables waitstates and buffering in a way that /// the flash returns valid data with 48MHz CPU frequency. unsafe fn startup_intilialisation() { - msp432::init(); + ChipHw::init(); // For now, these peripherals are only used at startup, so we do not // allocate them for the life of the program. If these are later used by the diff --git a/boards/nano33ble/src/main.rs b/boards/nano33ble/src/main.rs index fe2a4f426d..a1cf4c1785 100644 --- a/boards/nano33ble/src/main.rs +++ b/boards/nano33ble/src/main.rs @@ -244,7 +244,7 @@ pub unsafe fn start() -> ( Platform, &'static nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>, ) { - nrf52840::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/boards/nano33ble_rev2/src/main.rs b/boards/nano33ble_rev2/src/main.rs index 907aa8771b..7e17e78da3 100644 --- a/boards/nano33ble_rev2/src/main.rs +++ b/boards/nano33ble_rev2/src/main.rs @@ -253,7 +253,7 @@ pub unsafe fn start() -> ( Platform, &'static nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>, ) { - nrf52840::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/boards/nano_rp2040_connect/src/main.rs b/boards/nano_rp2040_connect/src/main.rs index 7525c707e7..c0d8479a22 100644 --- a/boards/nano_rp2040_connect/src/main.rs +++ b/boards/nano_rp2040_connect/src/main.rs @@ -21,6 +21,7 @@ use kernel::debug; use kernel::debug::PanicResources; use kernel::hil::led::LedHigh; use kernel::hil::usb::Client; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::syscall::SyscallDriver; use kernel::utilities::single_thread_value::SingleThreadValue; @@ -244,7 +245,7 @@ pub unsafe fn start() -> ( &'static rp2040::chip::Rp2040<'static, Rp2040DefaultPeripherals<'static>>, ) { // Loads relocations and clears BSS - rp2040::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state_unsafe::< diff --git a/boards/nordic/nrf52840_dongle/src/main.rs b/boards/nordic/nrf52840_dongle/src/main.rs index a657cd2a14..d08a09b6ef 100644 --- a/boards/nordic/nrf52840_dongle/src/main.rs +++ b/boards/nordic/nrf52840_dongle/src/main.rs @@ -21,6 +21,7 @@ use kernel::deferred_call::DeferredCallClient; use kernel::hil::led::LedLow; use kernel::hil::symmetric_encryption::AES128; use kernel::hil::time::Counter; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; #[allow(unused_imports)] @@ -191,7 +192,7 @@ pub unsafe fn start() -> ( Platform, &'static nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>, ) { - nrf52840::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/boards/nordic/nrf52840dk/src/lib.rs b/boards/nordic/nrf52840dk/src/lib.rs index d848380983..c880780981 100644 --- a/boards/nordic/nrf52840dk/src/lib.rs +++ b/boards/nordic/nrf52840dk/src/lib.rs @@ -81,6 +81,7 @@ use kernel::hil::led::LedLow; use kernel::hil::time::Counter; #[allow(unused_imports)] use kernel::hil::usb::Client; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::cells::MapCell; #[allow(unused_imports)] @@ -413,7 +414,7 @@ pub unsafe fn start_no_pconsole() -> ( //-------------------------------------------------------------------------- // Apply errata fixes and enable interrupts. - nrf52840::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/boards/nordic/nrf52dk/src/main.rs b/boards/nordic/nrf52dk/src/main.rs index ae20f874e6..9b9459491c 100644 --- a/boards/nordic/nrf52dk/src/main.rs +++ b/boards/nordic/nrf52dk/src/main.rs @@ -76,6 +76,7 @@ use kernel::component::Component; use kernel::debug::PanicResources; use kernel::hil::led::LedLow; use kernel::hil::time::Counter; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; #[allow(unused_imports)] @@ -243,7 +244,7 @@ pub unsafe fn start() -> ( Platform, &'static nrf52832::chip::NRF52<'static, Nrf52832DefaultPeripherals<'static>>, ) { - nrf52832::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/boards/nucleo_f429zi/src/main.rs b/boards/nucleo_f429zi/src/main.rs index ee8492c0c6..e2f55ce99c 100644 --- a/boards/nucleo_f429zi/src/main.rs +++ b/boards/nucleo_f429zi/src/main.rs @@ -18,6 +18,7 @@ use kernel::capabilities; use kernel::component::Component; use kernel::debug::PanicResources; use kernel::hil::led::LedHigh; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; use kernel::{create_capability, debug, static_init}; @@ -318,7 +319,7 @@ unsafe fn start() -> ( NucleoF429ZI, &'static stm32f429zi::chip::Stm32f4xx<'static, Stm32f429ziDefaultPeripherals<'static>>, ) { - stm32f429zi::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/boards/nucleo_f446re/src/main.rs b/boards/nucleo_f446re/src/main.rs index 590b360cbf..8a5b7f55ac 100644 --- a/boards/nucleo_f446re/src/main.rs +++ b/boards/nucleo_f446re/src/main.rs @@ -19,6 +19,7 @@ use kernel::component::Component; use kernel::debug::PanicResources; use kernel::hil::gpio::Configure; use kernel::hil::led::LedHigh; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; use kernel::{create_capability, debug, static_init}; @@ -266,7 +267,7 @@ unsafe fn start() -> ( NucleoF446RE, &'static stm32f446re::chip::Stm32f4xx<'static, Stm32f446reDefaultPeripherals<'static>>, ) { - stm32f446re::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/boards/nucleo_u545re_q/.cargo/config.toml b/boards/nucleo_u545re_q/.cargo/config.toml new file mode 100644 index 0000000000..d2588f07d0 --- /dev/null +++ b/boards/nucleo_u545re_q/.cargo/config.toml @@ -0,0 +1,14 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +include = [ + "../../cargo/tock_flags.toml", + "../../cargo/unstable_flags.toml", +] + +[build] +target = "thumbv8m.main-none-eabi" + +[unstable] +config-include = true diff --git a/boards/nucleo_u545re_q/Cargo.toml b/boards/nucleo_u545re_q/Cargo.toml new file mode 100644 index 0000000000..1b4e7475ad --- /dev/null +++ b/boards/nucleo_u545re_q/Cargo.toml @@ -0,0 +1,26 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +[package] +name = "nucleo_u545re_q" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true + +[dependencies] +components = { path = "../components" } +cortexm33 = { path = "../../arch/cortex-m33" } +kernel = { path = "../../kernel" } +stm32u545 = { path = "../../chips/stm32u545" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[build-dependencies] +tock_build_scripts = { path = "../build_scripts" } + +[lints] +workspace = true diff --git a/boards/nucleo_u545re_q/Makefile b/boards/nucleo_u545re_q/Makefile new file mode 100644 index 0000000000..5420acee07 --- /dev/null +++ b/boards/nucleo_u545re_q/Makefile @@ -0,0 +1,50 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright OxidOS Automotive 2026. + +# Makefile for building the tock kernel for the NUCLEO-U545RE-Q platform +# + +include ../Makefile.common + +# Toolchain Variables +PROBE_RS ?= probe-rs +CHIP = STM32U545RETx +OBJCOPY ?= arm-none-eabi-objcopy + +# NOTE: STM32U5 Metadata Stripping +# The standard ELF file produced by the compiler contains metadata sections +# (like .ARM.attributes) that sometimes point to System Memory addresses +# (e.g., 0x0BFA0000). Many flashing tools, including probe-rs, will attempt +# to write to these addresses, which are protected on the STM32U5, causing +# the flash process to crash. +# +# We use 'objcopy -j' to extract ONLY the sections that belong +# in the physical Flash memory (0x0C000000). This ensures a clean, +# safe deployment to the hardware. + +# Default target for installing the kernel. +.PHONY: install +install: flash + +.PHONY: flash-debug +flash-debug: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/debug/$(PLATFORM).elf + @echo "--- Preparing Clean Debug Kernel ELF ---" + $(OBJCOPY) -j .text -j .ARM.exidx -j .storage -j .relocate $< $<.clean + @echo "--- Flashing Debug Kernel via probe-rs ---" + $(PROBE_RS) download --chip $(CHIP) --allow-erase-all $<.clean + $(PROBE_RS) reset --chip $(CHIP) + +.PHONY: flash +flash: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf + @echo "--- Preparing Clean Release Kernel ELF ---" + $(OBJCOPY) -j .text -j .ARM.exidx -j .storage -j .relocate $< $<.clean + @echo "--- Flashing Release Kernel via probe-rs ---" + $(PROBE_RS) download --chip $(CHIP) --allow-erase-all $<.clean + $(PROBE_RS) reset --chip $(CHIP) + +.PHONY: program +program: + # Note: tockloader does not natively wrap probe-rs yet. + # The --openocd flag was removed; tockloader will use its default method (usually serial/jlink). + tockloader install --board nucleo_u545re_q $(APP) diff --git a/boards/nucleo_u545re_q/README.md b/boards/nucleo_u545re_q/README.md new file mode 100644 index 0000000000..cf4eaef9f2 --- /dev/null +++ b/boards/nucleo_u545re_q/README.md @@ -0,0 +1,90 @@ +# NUCLEO-U545RE-Q + +The [NUCLEO-U545RE-Q](https://www.st.com/en/evaluation-tools/nucleo-u545re-q.html) +is a development board based on the STM32U545RE microcontroller. + +## Building + +To build the kernel for this board, run `make` in this directory: + +```bash +cd boards/nucleo_u545re_q +make +``` + +## Programming and Deployment + +The Makefile for this board is simplified to focus on OpenOCD-based deployment. + +### Makefile Targets + +| Target | Description | +| :--- | :--- | +| **`make`** | Compiles the Tock kernel for the Nucleo-U545RE-Q. | +| **`make flash`** | Flashes the release kernel using `openocd`. | +| **`make flash-debug`** | Flashes the debug kernel using `openocd`. | +| **`make program`** | Installs apps using `tockloader` via `openocd`. Requires `APP`. | +| **`make install`** | Alias for `make flash`. | + +### Usage Examples + +**1. Flash the release kernel:** +```bash +make flash +``` + +**2. Flash the debug kernel:** +```bash +make flash-debug +``` + +**3. Install an application:** +Note: This requires a `.tab` (Tock Application Bundle) file. +```bash +make program APP=/path/to/your/app.tab +``` + +## Flashing Notes + +This board is flashed using **`openocd`**. The Makefile handles the necessary +initialization and mass erase before programming. + +### OpenOCD Requirements + +The STM32U545 is a newer chip and is **not supported** by OpenOCD 0.12.0 or +earlier. To use the OpenOCD targets, you must use **OpenOCD 0.12.0+dev** built +from source. + +#### Compiling OpenOCD from Source + +If your system's OpenOCD is too old, follow these steps to compile a +compatible version: + +1. **Install dependencies**: + ```bash + sudo apt update + sudo apt install build-essential libusb-1.0-0-dev libftdi1-dev \ + libtool autoconf automake texinfo pkg-config + ``` + +2. **Clone and build**: + ```bash + git clone https://github.com/openocd-org/openocd.git + cd openocd + git submodule update --init --recursive + ./bootstrap + ./configure --enable-stlink + make -j$(nproc) + sudo make install + ``` + +3. **Verify version**: + ```bash + openocd --version + # Should report 0.12.0+dev + ``` + +## Console + +The kernel console is available on USART1 via the ST-LINK USB connection at +115200 baud. diff --git a/boards/nucleo_u545re_q/layout.ld b/boards/nucleo_u545re_q/layout.ld new file mode 100644 index 0000000000..e382fe3f78 --- /dev/null +++ b/boards/nucleo_u545re_q/layout.ld @@ -0,0 +1,30 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2024. */ +/* Copyright OxidOS Automotive 2026. */ + +/* Memory layout for the STM32U545 (Secure Mode Aliases) + * FLASH: 512 KB Total + * KERNEL: 256 KB + * APPS: 256 KB + * RAM: 160 KB */ + +MEMORY +{ + rom (rx) : ORIGIN = 0x0C000000, LENGTH = 0x00040000 /* 256 KB Kernel */ + prog (rx) : ORIGIN = 0x0C040000, LENGTH = 0x00040000 /* 256 KB Apps */ + ram (rwx) : ORIGIN = 0x30000000, LENGTH = 0x00028000 /* 160 KB */ +} + +/* + * Tock's generic linker script (tock_kernel_layout.ld) usually defines + * _estack at the START of RAM for overflow protection. + * + * However, for this board in Secure Mode, we need it at the END of RAM + * to prevent an immediate HardFault. + */ +_estack = ORIGIN(ram) + LENGTH(ram); + +PAGE_SIZE = 8K; + +INCLUDE tock_kernel_layout.ld diff --git a/boards/nucleo_u545re_q/src/io.rs b/boards/nucleo_u545re_q/src/io.rs new file mode 100644 index 0000000000..45bb571cee --- /dev/null +++ b/boards/nucleo_u545re_q/src/io.rs @@ -0,0 +1,24 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. +// Copyright OxidOS Automotive 2026. + +use core::panic::PanicInfo; +use kernel::debug; + +/// Panic handler. +#[panic_handler] +pub unsafe fn panic_fmt(info: &PanicInfo) -> ! { + let writer_config = stm32u545::usart::UsartPanicWriterConfig { + base: stm32u545::usart::USART1_BASE, + }; + + debug::panic_print::( + writer_config, + info, + &cortexm33::support::nop, + crate::PANIC_RESOURCES.get(), + ); + + loop {} +} diff --git a/boards/nucleo_u545re_q/src/main.rs b/boards/nucleo_u545re_q/src/main.rs new file mode 100644 index 0000000000..7f8403b6b2 --- /dev/null +++ b/boards/nucleo_u545re_q/src/main.rs @@ -0,0 +1,302 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. +// Copyright OxidOS Automotive 2026. + +#![no_std] +#![no_main] + +use kernel::capabilities; +use kernel::component::Component; +use kernel::debug::PanicResources; +use kernel::deferred_call::DeferredCallClient; +use kernel::platform::chip::Chip; +use kernel::platform::{KernelResources, SyscallDriverLookup}; +use kernel::utilities::single_thread_value::SingleThreadValue; +use kernel::{create_capability, static_init}; + +use stm32u545::gpio::PinId; + +pub mod io; + +extern "C" { + static _sappmem: u8; + static _eappmem: u8; +} + +const NUM_PROCS: usize = 4; + +type ChipHw = + stm32u545::chip::Stm32u5xx<'static, stm32u545::chip::Stm32u5xxDefaultPeripherals<'static>>; +type ProcessPrinterInUse = capsules_system::process_printer::ProcessPrinterText; + +static PANIC_RESOURCES: SingleThreadValue> = + SingleThreadValue::new(); + +kernel::stack_size! {0x2000} + +struct NucleoU545RE { + console: &'static capsules_core::console::Console<'static>, + scheduler: &'static components::sched::round_robin::RoundRobinComponentType, + systick: cortexm33::systick::SysTick, + led: &'static capsules_core::led::LedDriver< + 'static, + kernel::hil::led::LedHigh<'static, stm32u545::gpio::Pin<'static>>, + 1, + >, + button: &'static capsules_core::button::Button<'static, stm32u545::gpio::Pin<'static>>, + alarm: &'static capsules_core::alarm::AlarmDriver< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + stm32u545::tim::Tim2<'static>, + >, + >, +} + +impl SyscallDriverLookup for NucleoU545RE { + fn with_driver(&self, driver_num: usize, f: F) -> R + where + F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, + { + match driver_num { + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::button::DRIVER_NUM => f(Some(self.button)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + _ => f(None), + } + } +} + +impl KernelResources for NucleoU545RE { + type SyscallDriverLookup = Self; + type SyscallFilter = (); + type ProcessFault = (); + type Scheduler = components::sched::round_robin::RoundRobinComponentType; + type SchedulerTimer = cortexm33::systick::SysTick; + type WatchDog = (); + type ContextSwitchCallback = (); + + fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { + self + } + fn syscall_filter(&self) -> &Self::SyscallFilter { + &() + } + fn process_fault(&self) -> &Self::ProcessFault { + &() + } + fn scheduler(&self) -> &Self::Scheduler { + self.scheduler + } + fn scheduler_timer(&self) -> &Self::SchedulerTimer { + &self.systick + } + fn watchdog(&self) -> &Self::WatchDog { + &() + } + fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { + &() + } +} + +/// Helper function for board-specific pin muxing +unsafe fn set_pin_primary_functions(periphs: &stm32u545::chip::Stm32u5xxDefaultPeripherals) { + use kernel::hil::gpio::Configure; + + // USART1 Pins (PA9/10) + let pin9 = periphs.gpio_a.pin(PinId::Pin09); + let pin10 = periphs.gpio_a.pin(PinId::Pin10); + pin9.set_mode(stm32u545::gpio::Mode::AlternateFunction); + pin9.set_alternate_function(7); + pin9.set_speed_high(); + pin10.set_mode(stm32u545::gpio::Mode::AlternateFunction); + pin10.set_alternate_function(7); + pin10.set_speed_high(); + + // LED Pin (PA5) + periphs.gpio_a.pin(PinId::Pin05).make_output(); + + // Button Pin (PC13) - Hardware is Active High + let btn = periphs.gpio_c.pin(PinId::Pin13); + btn.make_input(); + btn.set_floating_state(kernel::hil::gpio::FloatingState::PullDown); +} + +#[inline(never)] +#[allow(clippy::large_stack_arrays)] +unsafe fn start() -> ( + &'static kernel::Kernel, + &'static NucleoU545RE, + &'static ChipHw, +) { + ChipHw::init(); + + kernel::deferred_call::initialize_deferred_call_state::< + ::ThreadIdProvider, + >(); + + // Create Individual Drivers + let exti = static_init!( + stm32u545::exti::Exti<'static>, + stm32u545::exti::Exti::new(stm32u545::exti::EXTI_BASE) + ); + let dma1 = static_init!( + stm32u545::dma::Dma, + stm32u545::dma::Dma::new(stm32u545::dma::DMA1_BASE) + ); + let usart1 = static_init!( + stm32u545::usart::Usart<'static>, + stm32u545::usart::Usart::new(stm32u545::usart::USART1_BASE) + ); + usart1.register(); + + // Load Peripherals Bundle + let periphs = static_init!( + stm32u545::chip::Stm32u5xxDefaultPeripherals<'static>, + stm32u545::chip::Stm32u5xxDefaultPeripherals::new(usart1, exti, dma1) + ); + + // Initialize wiring (DMA, clocks) + periphs.init(); + + // Board specific wiring + periphs.tim2.start(); + set_pin_primary_functions(periphs); + + // Kernel and Muxes + let processes = components::process_array::ProcessArrayComponent::new() + .finalize(components::process_array_component_static!(NUM_PROCS)); + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(processes.as_slice())); + + let uart_mux = components::console::UartMuxComponent::new(periphs.usart1, 115200) + .finalize(components::uart_mux_component_static!()); + + let alarm_mux = components::alarm::AlarmMuxComponent::new(&periphs.tim2).finalize( + components::alarm_mux_component_static!(stm32u545::tim::Tim2), + ); + + // Capsules + let console = components::console::ConsoleComponent::new( + board_kernel, + capsules_core::console::DRIVER_NUM, + uart_mux, + ) + .finalize(components::console_component_static!()); + + components::debug_writer::DebugWriterComponent::new::< + ::ThreadIdProvider, + >( + uart_mux, + create_capability!(capabilities::SetDebugWriterCapability), + ) + .finalize(components::debug_writer_component_static!()); + + let process_console = components::process_console::ProcessConsoleComponent::new( + board_kernel, + uart_mux, + alarm_mux, + components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()), + None, + ) + .finalize(components::process_console_component_static!( + stm32u545::tim::Tim2 + )); + let _ = process_console.start(); + + let alarm = components::alarm::AlarmDriverComponent::new( + board_kernel, + capsules_core::alarm::DRIVER_NUM, + alarm_mux, + ) + .finalize(components::alarm_component_static!(stm32u545::tim::Tim2)); + + let led_pin = static_init!(stm32u545::gpio::Pin, periphs.gpio_a.pin(PinId::Pin05)); + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( + kernel::hil::led::LedHigh<'static, stm32u545::gpio::Pin>, + kernel::hil::led::LedHigh::new(led_pin) + )); + + let button = components::button::ButtonComponent::new( + board_kernel, + capsules_core::button::DRIVER_NUM, + components::button_component_helper!( + stm32u545::gpio::Pin, + ( + static_init!(stm32u545::gpio::Pin, periphs.gpio_c.pin(PinId::Pin13)), + kernel::hil::gpio::ActivationMode::ActiveHigh, + kernel::hil::gpio::FloatingState::PullDown + ) + ), + ) + .finalize(components::button_component_static!(stm32u545::gpio::Pin)); + + // Platform and Interrupts + let platform = static_init!( + NucleoU545RE, + NucleoU545RE { + console, + scheduler: components::sched::round_robin::RoundRobinComponent::new(processes) + .finalize(components::round_robin_component_static!(NUM_PROCS)), + systick: cortexm33::systick::SysTick::new(), + led, + button, + alarm, + } + ); + + let chip = static_init!( + stm32u545::chip::Stm32u5xx, + stm32u545::chip::Stm32u5xx::new(periphs) + ); + + // Symbols for linker + extern "C" { + /// Beginning of the ROM region containing app images. + static _sapps: u8; + /// End of the ROM region containing app images. + static _eapps: u8; + /// Beginning of the RAM region for app memory. + static mut _sappmem: u8; + /// End of the RAM region for app memory. + static _eappmem: u8; + } + + // Load processes + let app_flash = core::slice::from_raw_parts( + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, + ); + + let app_memory = core::slice::from_raw_parts_mut( + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, + ); + + let _ = kernel::process::load_processes( + board_kernel, + chip, + app_flash, + app_memory, + &capsules_system::process_policies::PanicFaultPolicy {}, + &create_capability!(capabilities::ProcessManagementCapability), + ); + + (board_kernel, platform, chip) +} + +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start(); + // Hand over control to the Tock Kernel Loop + board_kernel.kernel_loop::( + platform, + chip, + None, + &main_loop_capability, + ); +} diff --git a/boards/particle_boron/src/main.rs b/boards/particle_boron/src/main.rs index 9212f4ee87..17d2b75d89 100644 --- a/boards/particle_boron/src/main.rs +++ b/boards/particle_boron/src/main.rs @@ -23,6 +23,7 @@ use kernel::hil::i2c::{I2CMaster, I2CSlave}; use kernel::hil::led::LedLow; use kernel::hil::symmetric_encryption::AES128; use kernel::hil::time::Counter; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; #[allow(unused_imports)] @@ -213,7 +214,7 @@ pub unsafe fn start_particle_boron() -> ( Platform, &'static nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>, ) { - nrf52840::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/boards/pico_explorer_base/src/main.rs b/boards/pico_explorer_base/src/main.rs index 6724be3fb3..1bb99ce996 100644 --- a/boards/pico_explorer_base/src/main.rs +++ b/boards/pico_explorer_base/src/main.rs @@ -20,6 +20,7 @@ use kernel::component::Component; use kernel::debug::PanicResources; use kernel::hil::led::LedHigh; use kernel::hil::usb::Client; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; use kernel::{capabilities, create_capability, static_init}; @@ -256,7 +257,7 @@ pub unsafe fn start() -> ( &'static rp2040::chip::Rp2040<'static, Rp2040DefaultPeripherals<'static>>, ) { // Loads relocations and clears BSS - rp2040::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state_unsafe::< diff --git a/boards/raspberry_pi_pico/src/lib.rs b/boards/raspberry_pi_pico/src/lib.rs index e3f524f787..67c13a7e76 100644 --- a/boards/raspberry_pi_pico/src/lib.rs +++ b/boards/raspberry_pi_pico/src/lib.rs @@ -18,6 +18,7 @@ use kernel::hil::gpio::Configure; use kernel::hil::gpio::FloatingState; use kernel::hil::i2c::I2CMaster; use kernel::hil::usb::Client; +use kernel::platform::chip::Chip; use kernel::platform::SyscallDriverLookup; use kernel::syscall::SyscallDriver; use kernel::utilities::single_thread_value::SingleThreadValue; @@ -238,7 +239,7 @@ pub unsafe fn setup( &'static Rp2040<'static, Rp2040DefaultPeripherals<'static>>, ) { // Loads relocations and clears BSS - rp2040::init(); + ChipHw::init(); // Bind global variables to this thread. let _ = PANIC_RESOURCES diff --git a/boards/raspberry_pi_pico_2/src/main.rs b/boards/raspberry_pi_pico_2/src/main.rs index fa726c4c82..8e2a73a63d 100644 --- a/boards/raspberry_pi_pico_2/src/main.rs +++ b/boards/raspberry_pi_pico_2/src/main.rs @@ -21,6 +21,7 @@ use enum_primitive::cast::FromPrimitive; use kernel::component::Component; use kernel::debug::PanicResources; use kernel::hil::led::LedHigh; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::syscall::SyscallDriver; use kernel::utilities::single_thread_value::SingleThreadValue; @@ -246,7 +247,7 @@ unsafe fn get_peripherals() -> ( /// Main function called after RAM initialized. #[no_mangle] pub unsafe fn main() { - rp2350::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/boards/sma_q3/src/main.rs b/boards/sma_q3/src/main.rs index 036d41b9bf..3c70995adf 100644 --- a/boards/sma_q3/src/main.rs +++ b/boards/sma_q3/src/main.rs @@ -27,6 +27,7 @@ use kernel::hil::led::LedHigh; use kernel::hil::screen::Screen; use kernel::hil::symmetric_encryption::AES128; use kernel::hil::time::Counter; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; #[allow(unused_imports)] @@ -194,7 +195,7 @@ pub unsafe fn start() -> ( Platform, &'static nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>, ) { - nrf52840::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/boards/stm32f3discovery/src/main.rs b/boards/stm32f3discovery/src/main.rs index 909bae85d6..31f3079c01 100644 --- a/boards/stm32f3discovery/src/main.rs +++ b/boards/stm32f3discovery/src/main.rs @@ -22,6 +22,7 @@ use kernel::hil::gpio::Configure; use kernel::hil::gpio::Output; use kernel::hil::led::LedHigh; use kernel::hil::time::Counter; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; use kernel::{create_capability, debug, static_init}; @@ -372,7 +373,7 @@ unsafe fn start() -> ( STM32F3Discovery, &'static stm32f303xc::chip::Stm32f3xx<'static, Stm32f3xxDefaultPeripherals<'static>>, ) { - stm32f303xc::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/boards/stm32f412gdiscovery/src/main.rs b/boards/stm32f412gdiscovery/src/main.rs index dd2484ac51..6d8137c7fe 100644 --- a/boards/stm32f412gdiscovery/src/main.rs +++ b/boards/stm32f412gdiscovery/src/main.rs @@ -21,6 +21,7 @@ use kernel::debug::PanicResources; use kernel::hil::gpio; use kernel::hil::led::LedLow; use kernel::hil::screen::ScreenRotation; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; use kernel::{create_capability, debug, static_init}; @@ -398,7 +399,7 @@ unsafe fn start() -> ( STM32F412GDiscovery, &'static stm32f412g::chip::Stm32f4xx<'static, Stm32f412gDefaultPeripherals<'static>>, ) { - stm32f412g::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/boards/stm32f429idiscovery/src/main.rs b/boards/stm32f429idiscovery/src/main.rs index d02fa28039..d050bdc403 100644 --- a/boards/stm32f429idiscovery/src/main.rs +++ b/boards/stm32f429idiscovery/src/main.rs @@ -18,6 +18,7 @@ use kernel::capabilities; use kernel::component::Component; use kernel::debug::PanicResources; use kernel::hil::led::LedHigh; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; use kernel::{create_capability, debug, static_init}; @@ -279,7 +280,7 @@ unsafe fn start() -> ( STM32F429IDiscovery, &'static stm32f429zi::chip::Stm32f4xx<'static, Stm32f429ziDefaultPeripherals<'static>>, ) { - stm32f429zi::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/boards/teensy40/src/main.rs b/boards/teensy40/src/main.rs index 171b6e9b17..a53f32cf1c 100644 --- a/boards/teensy40/src/main.rs +++ b/boards/teensy40/src/main.rs @@ -23,6 +23,7 @@ use kernel::capabilities; use kernel::component::Component; use kernel::debug::PanicResources; use kernel::hil::{gpio::Configure, led::LedHigh}; +use kernel::platform::chip::Chip; use kernel::platform::chip::ClockInterface; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; @@ -179,7 +180,7 @@ fn set_arm_clock(ccm: &imxrt1060::ccm::Ccm, ccm_analog: &imxrt1060::ccm_analog:: /// these static_inits is wasted. #[inline(never)] unsafe fn start() -> (&'static kernel::Kernel, Teensy40, &'static ChipHw) { - imxrt1060::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/boards/weact_f401ccu6/src/main.rs b/boards/weact_f401ccu6/src/main.rs index 9d7c654970..0d731b465a 100644 --- a/boards/weact_f401ccu6/src/main.rs +++ b/boards/weact_f401ccu6/src/main.rs @@ -18,6 +18,7 @@ use kernel::capabilities; use kernel::component::Component; use kernel::debug::PanicResources; use kernel::hil::led::LedLow; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; use kernel::{create_capability, debug, static_init}; @@ -224,7 +225,7 @@ unsafe fn start() -> ( WeactF401CC, &'static stm32f401cc::chip::Stm32f4xx<'static, Stm32f401ccDefaultPeripherals<'static>>, ) { - stm32f401cc::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/boards/wm1110dev/src/main.rs b/boards/wm1110dev/src/main.rs index 299036ca35..72897968cd 100644 --- a/boards/wm1110dev/src/main.rs +++ b/boards/wm1110dev/src/main.rs @@ -19,6 +19,7 @@ use kernel::hil::gpio::Output; use kernel::hil::led::LedHigh; use kernel::hil::spi::SpiMaster; use kernel::hil::time::Counter; +use kernel::platform::chip::Chip; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::utilities::single_thread_value::SingleThreadValue; #[allow(unused_imports)] @@ -196,7 +197,7 @@ pub unsafe fn start() -> ( Platform, &'static nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>, ) { - nrf52840::init(); + ChipHw::init(); // Initialize deferred calls very early. kernel::deferred_call::initialize_deferred_call_state::< diff --git a/chips/apollo3/src/chip.rs b/chips/apollo3/src/chip.rs index 870cb63461..3ad232fff0 100644 --- a/chips/apollo3/src/chip.rs +++ b/chips/apollo3/src/chip.rs @@ -97,6 +97,37 @@ impl Chip for Apollo3 { type UserspaceKernelBoundary = cortexm4f::syscall::SysCall; type ThreadIdProvider = cortexm4f::thread_id::CortexMThreadIdProvider; + fn init() { + let cache_ctrl = crate::cachectrl::CacheCtrl::new(); + cache_ctrl.enable_cache(); + + // Explicitly tell the core where Tock's vector table is located. If Tock is the + // only thing on the chip then this is effectively a no-op. If, however, there is + // a bootloader present then we want to ensure that the vector table is set + // correctly for Tock. The bootloader _may_ set this for us, but it may not + // so that any errors early in the Tock boot process trap back to the bootloader. + // To be safe we unconditionally set the vector table. + unsafe { + crate::scb::set_vector_table_offset(crate::BASE_VECTORS.as_ptr().cast::<()>()); + } + + // Disable the FPU (it might be enabled by a prior stage) + unsafe { + crate::scb::disable_fpca(); + } + + // This ensures the FPU is actually disabled + unsafe { + crate::actually_disable_fpu(); + } + + unsafe { + cortexm4f::nvic::disable_all(); + cortexm4f::nvic::clear_all_pending(); + cortexm4f::nvic::enable_all(); + } + } + fn service_pending_interrupts(&self) { unsafe { while let Some(interrupt) = cortexm4f::nvic::next_pending() { diff --git a/chips/apollo3/src/lib.rs b/chips/apollo3/src/lib.rs index 82045dc023..b3f3526733 100644 --- a/chips/apollo3/src/lib.rs +++ b/chips/apollo3/src/lib.rs @@ -78,33 +78,16 @@ pub static PATCH: [unsafe extern "C" fn(); 16] = [unhandled_interrupt; 16]; // `main()` otherwise we end up with a clobbered stack. #[cfg(any(doc, all(target_arch = "arm", target_os = "none")))] #[inline(always)] -pub unsafe fn init() { +pub unsafe fn actually_disable_fpu() { use core::arch::asm; - let cache_ctrl = crate::cachectrl::CacheCtrl::new(); - cache_ctrl.enable_cache(); - - // Explicitly tell the core where Tock's vector table is located. If Tock is the - // only thing on the chip then this is effectively a no-op. If, however, there is - // a bootloader present then we want to ensure that the vector table is set - // correctly for Tock. The bootloader _may_ set this for us, but it may not - // so that any errors early in the Tock boot process trap back to the bootloader. - // To be safe we unconditionally set the vector table. - scb::set_vector_table_offset(BASE_VECTORS.as_ptr() as *const ()); - - // Disable the FPU (it might be enalbed by a prior stage) - scb::disable_fpca(); // This ensures the FPU is actually disabled asm!("svc 0xff", out("r0") _, out("r1") _, out("r2") _, out("r3") _, out("r12") _); - - cortexm4f::nvic::disable_all(); - cortexm4f::nvic::clear_all_pending(); - cortexm4f::nvic::enable_all(); } // Mock implementation for tests #[cfg(not(any(doc, all(target_arch = "arm", target_os = "none"))))] -pub unsafe fn init() { +pub unsafe fn actually_disable_fpu() { // Prevent unused code warning. scb::disable_fpca(); diff --git a/chips/apollo3/src/stimer.rs b/chips/apollo3/src/stimer.rs index 820afba9e8..fefc5f10cc 100644 --- a/chips/apollo3/src/stimer.rs +++ b/chips/apollo3/src/stimer.rs @@ -4,9 +4,7 @@ //! STimer driver for the Apollo3 -use kernel::hil::time::{ - Alarm, AlarmClient, Counter, Freq16KHz, OverflowClient, Ticks, Ticks32, Time, -}; +use kernel::hil::time::{Alarm, AlarmClient, Counter, Freq16KHz, Ticks, Ticks32, Time}; use kernel::utilities::cells::OptionalCell; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; use kernel::utilities::registers::{register_bitfields, register_structs, ReadWrite}; @@ -143,10 +141,6 @@ impl Time for STimer<'_> { } impl<'a> Counter<'a> for STimer<'a> { - fn set_overflow_client(&self, _client: &'a dyn OverflowClient) { - //self.overflow_client.set(client); - } - fn start(&self) -> Result<(), ErrorCode> { // Set the clock source self.registers.stcfg.write(STCFG::CLKSEL::XTAL_DIV2); diff --git a/chips/arty_e21_chip/src/chip.rs b/chips/arty_e21_chip/src/chip.rs index 52acc2c4b9..96289cc614 100644 --- a/chips/arty_e21_chip/src/chip.rs +++ b/chips/arty_e21_chip/src/chip.rs @@ -18,7 +18,6 @@ pub struct ArtyExx<'a, I: InterruptService + 'a> { pmp: PMPUserMPU<2, SimplePMP<4>>, userspace_kernel_boundary: rv32i::syscall::SysCall, clic: rv32i::clic::Clic, - machinetimer: &'a ArtyExxClint<'a>, interrupt_service: &'a I, } @@ -39,6 +38,12 @@ impl ArtyExxDefaultPeripherals<'_> { // Resolves any circular dependencies and sets up deferred calls pub fn init(&'static self) { + // By default the machine timer is enabled and will trigger interrupts. To + // prevent that we can make the compare register very large to effectively + // stop the interrupt from triggering, and then the machine timer can be + // used later as needed. + self.machinetimer.disable_machine_timer(); + kernel::deferred_call::DeferredCallClient::register(&self.uart0); } } @@ -74,7 +79,7 @@ impl InterruptService for ArtyExxDefaultPeripherals<'_> { } impl<'a, I: InterruptService + 'a> ArtyExx<'a, I> { - pub unsafe fn new(machinetimer: &'a ArtyExxClint<'a>, interrupt_service: &'a I) -> Self { + pub unsafe fn new(interrupt_service: &'a I) -> Self { // Make a bit-vector of all interrupt locations that we actually intend // to use on this chip. // 0001 1111 1111 1111 1111 0000 0000 1000 0000 @@ -84,7 +89,6 @@ impl<'a, I: InterruptService + 'a> ArtyExx<'a, I> { pmp: PMPUserMPU::new(SimplePMP::new().unwrap()), userspace_kernel_boundary: rv32i::syscall::SysCall::new(), clic: rv32i::clic::Clic::new(in_use_interrupts), - machinetimer, interrupt_service, } } @@ -92,55 +96,6 @@ impl<'a, I: InterruptService + 'a> ArtyExx<'a, I> { pub fn enable_all_interrupts(&self) { self.clic.enable_all(); } - - /// By default the machine timer is enabled and will trigger interrupts. To - /// prevent that we can make the compare register very large to effectively - /// stop the interrupt from triggering, and then the machine timer can be - /// used later as needed. - pub unsafe fn disable_machine_timer(&self) { - self.machinetimer.disable_machine_timer(); - } - - /// Setup the function that should run when a trap happens. - /// - /// This needs to be chip specific because how the CLIC works is configured - /// when the trap handler address is specified in mtvec, and that is only - /// valid for platforms with a CLIC. - #[cfg(any(doc, all(target_arch = "riscv32", target_os = "none")))] - pub unsafe fn configure_trap_handler(&self) { - use core::arch::asm; - asm!( - " - // The csrw instruction writes a Control and Status Register (CSR) - // with a new value. - // - // CSR 0x305 (mtvec, 'Machine trap-handler base address.') sets the - // address of the trap handler. We do not care about its old value, - // so we don't bother reading it. We want to enable direct CLIC mode - // so we set the second lowest bit. - lui t0, %hi({start_trap}) - addi t0, t0, %lo({start_trap}) - ori t0, t0, 0x02 // Set CLIC direct mode - csrw 0x305, t0 // Write the mtvec CSR. - ", - start_trap = sym rv32i::_start_trap, - out("t0") _, - ); - } - - // Mock implementation for tests on Travis-CI. - #[cfg(not(any(doc, all(target_arch = "riscv32", target_os = "none"))))] - pub unsafe fn configure_trap_handler(&self) { - unimplemented!() - } - - /// Generic helper initialize function to setup all of the chip specific - /// operations. Different boards can call the functions that `initialize()` - /// calls directly if it needs to use a custom setup operation. - pub unsafe fn initialize(&self) { - self.disable_machine_timer(); - self.configure_trap_handler(); - } } impl<'a, I: InterruptService + 'a> kernel::platform::chip::Chip for ArtyExx<'a, I> { @@ -148,6 +103,12 @@ impl<'a, I: InterruptService + 'a> kernel::platform::chip::Chip for ArtyExx<'a, type UserspaceKernelBoundary = rv32i::syscall::SysCall; type ThreadIdProvider = rv32i::thread_id::RiscvThreadIdProvider; + fn init() { + unsafe { + configure_trap_handler(); + } + } + fn mpu(&self) -> &Self::MPU { &self.pmp } @@ -192,6 +153,39 @@ impl<'a, I: InterruptService + 'a> kernel::platform::chip::Chip for ArtyExx<'a, } } +// Setup the function that should run when a trap happens. +// +// This needs to be chip specific because how the CLIC works is configured +// when the trap handler address is specified in mtvec, and that is only +// valid for platforms with a CLIC. +#[cfg(any(doc, all(target_arch = "riscv32", target_os = "none")))] +pub unsafe fn configure_trap_handler() { + use core::arch::asm; + asm!( + " + // The csrw instruction writes a Control and Status Register (CSR) + // with a new value. + // + // CSR 0x305 (mtvec, 'Machine trap-handler base address.') sets the + // address of the trap handler. We do not care about its old value, + // so we don't bother reading it. We want to enable direct CLIC mode + // so we set the second lowest bit. + lui t0, %hi({start_trap}) + addi t0, t0, %lo({start_trap}) + ori t0, t0, 0x02 // Set CLIC direct mode + csrw 0x305, t0 // Write the mtvec CSR. + ", + start_trap = sym rv32i::_start_trap, + out("t0") _, + ); +} + +// Mock implementation for tests on Travis-CI. +#[cfg(not(any(doc, all(target_arch = "riscv32", target_os = "none"))))] +pub unsafe fn configure_trap_handler() { + unimplemented!() +} + /// Trap handler for board/chip specific code. /// /// For the arty-e21 this gets called when an interrupt occurs while the chip is diff --git a/chips/e310x/src/chip.rs b/chips/e310x/src/chip.rs index 00debdf419..6368a21cad 100644 --- a/chips/e310x/src/chip.rs +++ b/chips/e310x/src/chip.rs @@ -117,6 +117,8 @@ impl<'a, I: InterruptService + 'a> kernel::platform::chip::Chip for E310x<'a, I> type UserspaceKernelBoundary = rv32i::syscall::SysCall; type ThreadIdProvider = rv32i::thread_id::RiscvThreadIdProvider; + fn init() {} + fn mpu(&self) -> &Self::MPU { &self.pmp } diff --git a/chips/earlgrey/src/chip.rs b/chips/earlgrey/src/chip.rs index 234b21e9b9..165b96f75d 100644 --- a/chips/earlgrey/src/chip.rs +++ b/chips/earlgrey/src/chip.rs @@ -266,6 +266,8 @@ impl< type UserspaceKernelBoundary = SysCall; type ThreadIdProvider = rv32i::thread_id::RiscvThreadIdProvider; + fn init() {} + fn mpu(&self) -> &Self::MPU { &self.mpu } diff --git a/chips/earlgrey/src/timer.rs b/chips/earlgrey/src/timer.rs index 2d0a6daa63..154a6359c2 100644 --- a/chips/earlgrey/src/timer.rs +++ b/chips/earlgrey/src/timer.rs @@ -57,7 +57,6 @@ register_bitfields![u32, pub struct RvTimer<'a, CFG: EarlGreyConfig> { registers: StaticRef, alarm_client: OptionalCell<&'a dyn time::AlarmClient>, - overflow_client: OptionalCell<&'a dyn time::OverflowClient>, mtimer: MachineTimer<'a>, _cfg: PhantomData, } @@ -67,7 +66,6 @@ impl RvTimer<'_, CFG> { Self { registers: TIMER_BASE, alarm_client: OptionalCell::empty(), - overflow_client: OptionalCell::empty(), mtimer: MachineTimer::new( &TIMER_BASE.compare_low, &TIMER_BASE.compare_high, @@ -111,10 +109,6 @@ impl time::Time for RvTimer<'_, CFG> { } impl<'a, CFG: EarlGreyConfig> time::Counter<'a> for RvTimer<'a, CFG> { - fn set_overflow_client(&self, client: &'a dyn time::OverflowClient) { - self.overflow_client.set(client); - } - fn start(&self) -> Result<(), ErrorCode> { Ok(()) } diff --git a/chips/esp32-c3/src/chip.rs b/chips/esp32-c3/src/chip.rs index 14f98b40d2..609cb0eef2 100644 --- a/chips/esp32-c3/src/chip.rs +++ b/chips/esp32-c3/src/chip.rs @@ -113,6 +113,8 @@ impl<'a, I: InterruptService + 'a> Chip for Esp32C3<'a, I> { type UserspaceKernelBoundary = SysCall; type ThreadIdProvider = rv32i::thread_id::RiscvThreadIdProvider; + fn init() {} + fn service_pending_interrupts(&self) { loop { if self.intc.get_saved_interrupts().is_some() { diff --git a/chips/esp32/src/timg.rs b/chips/esp32/src/timg.rs index 513bffda4f..0a81dd4ea3 100644 --- a/chips/esp32/src/timg.rs +++ b/chips/esp32/src/timg.rs @@ -206,10 +206,6 @@ impl time::Time for TimG<'_, F, C3> { } impl<'a, F: time::Frequency, const C3: bool> Counter<'a> for TimG<'a, F, C3> { - fn set_overflow_client(&self, _client: &'a dyn time::OverflowClient) { - // We have no way to know when this happens - } - fn start(&self) -> Result<(), ErrorCode> { self.registers.t0config.write(CONFIG::EN::SET); diff --git a/chips/imxrt10xx/src/chip.rs b/chips/imxrt10xx/src/chip.rs index 67533bc197..e3e59efbda 100644 --- a/chips/imxrt10xx/src/chip.rs +++ b/chips/imxrt10xx/src/chip.rs @@ -107,6 +107,20 @@ impl Chip for Imxrt10xx { type UserspaceKernelBoundary = cortexm7::syscall::SysCall; type ThreadIdProvider = cortexm7::thread_id::CortexMThreadIdProvider; + fn init() { + unsafe { + cortexm7::nvic::disable_all(); + cortexm7::nvic::clear_all_pending(); + } + + // Set the vector table offset. + crate::initialize_vector_table(); + + unsafe { + cortexm7::nvic::enable_all(); + } + } + fn service_pending_interrupts(&self) { unsafe { while let Some(interrupt) = cortexm7::nvic::next_pending() { diff --git a/chips/imxrt10xx/src/lib.rs b/chips/imxrt10xx/src/lib.rs index 30863baed4..56e1baabe1 100644 --- a/chips/imxrt10xx/src/lib.rs +++ b/chips/imxrt10xx/src/lib.rs @@ -56,6 +56,17 @@ pub static BASE_VECTORS: [unsafe extern "C" fn(); 16] = [ CortexM7::SYSTICK_HANDLER, // SysTick ]; +pub(crate) fn initialize_vector_table() { + // # Safety + // + // The vector table must setup function pointers for the thumb core + // correctly. Because `BASE_VECTORS` is the correct data type this is + // safe. + unsafe { + cortexm7::scb::set_vector_table_offset(BASE_VECTORS.as_ptr().cast::<()>()); + } +} + // imxrt 1050 has total of 160 interrupts #[cfg_attr(all(target_arch = "arm", target_os = "none"), link_section = ".irqs")] // used Ensures that the symbol is kept until the final binary @@ -222,16 +233,3 @@ pub static IRQS: [unsafe extern "C" fn(); 160] = [ CortexM7::GENERIC_ISR, // Reserved (158) CortexM7::GENERIC_ISR, // Reserved (159) ]; - -pub unsafe fn init() { - cortexm7::nvic::disable_all(); - cortexm7::nvic::clear_all_pending(); - - // Set the vector table offset, which requires casting from a BASE_VECTORS to a *const () - // pointer. - let vector_table: *const [unsafe extern "C" fn(); 16] = core::ptr::addr_of!(BASE_VECTORS); - let vector_table: *const () = vector_table.cast(); - cortexm7::scb::set_vector_table_offset(vector_table); - - cortexm7::nvic::enable_all(); -} diff --git a/chips/litex_vexriscv/src/chip.rs b/chips/litex_vexriscv/src/chip.rs index c0ee333ec6..0f327e8f17 100644 --- a/chips/litex_vexriscv/src/chip.rs +++ b/chips/litex_vexriscv/src/chip.rs @@ -64,6 +64,8 @@ impl kernel::platform::chip::Chip for LiteXVexRis type UserspaceKernelBoundary = SysCall; type ThreadIdProvider = rv32i::thread_id::RiscvThreadIdProvider; + fn init() {} + fn mpu(&self) -> &Self::MPU { &self.pmp_mpu } diff --git a/chips/lpc55s6x/src/chip.rs b/chips/lpc55s6x/src/chip.rs index 7c52c7eb6a..5a5114ae9a 100644 --- a/chips/lpc55s6x/src/chip.rs +++ b/chips/lpc55s6x/src/chip.rs @@ -44,6 +44,22 @@ impl Chip for Lpc55s69<'_, I> { type UserspaceKernelBoundary = cortexm33::syscall::SysCall; type ThreadIdProvider = cortexm33::thread_id::CortexMThreadIdProvider; + fn init() { + unsafe { + cortexm33::nvic::disable_all(); + cortexm33::nvic::clear_all_pending(); + + // Set the vector table offset, which requires casting from a BASE_VECTORS to a *const () + // pointer. + let vector_table: *const [unsafe extern "C" fn(); 16] = + core::ptr::addr_of!(crate::BASE_VECTORS); + let vector_table: *const () = vector_table.cast(); + cortexm33::scb::set_vector_table_offset(vector_table); + + cortexm33::nvic::enable_all(); + } + } + fn service_pending_interrupts(&self) { unsafe { while let Some(interrupt) = cortexm33::nvic::next_pending() { diff --git a/chips/lpc55s6x/src/lib.rs b/chips/lpc55s6x/src/lib.rs index f4eb27a03a..13f705bf0b 100644 --- a/chips/lpc55s6x/src/lib.rs +++ b/chips/lpc55s6x/src/lib.rs @@ -112,16 +112,3 @@ pub static IRQS: [unsafe extern "C" fn(); 60] = [ CortexM33::GENERIC_ISR, CortexM33::GENERIC_ISR, ]; - -pub unsafe fn init() { - cortexm33::nvic::disable_all(); - cortexm33::nvic::clear_all_pending(); - - // Set the vector table offset, which requires casting from a BASE_VECTORS to a *const () - // pointer. - let vector_table: *const [unsafe extern "C" fn(); 16] = core::ptr::addr_of!(BASE_VECTORS); - let vector_table: *const () = vector_table.cast(); - cortexm33::scb::set_vector_table_offset(vector_table); - - cortexm33::nvic::enable_all(); -} diff --git a/chips/msp432/src/chip.rs b/chips/msp432/src/chip.rs index e079156e65..b4ae30cf73 100644 --- a/chips/msp432/src/chip.rs +++ b/chips/msp432/src/chip.rs @@ -109,6 +109,14 @@ impl<'a, I: InterruptService + 'a> Chip for Msp432<'a, I> { type UserspaceKernelBoundary = cortexm4::syscall::SysCall; type ThreadIdProvider = cortexm4::thread_id::CortexMThreadIdProvider; + fn init() { + unsafe { + cortexm4::nvic::disable_all(); + cortexm4::nvic::clear_all_pending(); + cortexm4::nvic::enable_all(); + } + } + fn service_pending_interrupts(&self) { unsafe { while let Some(interrupt) = cortexm4::nvic::next_pending() { diff --git a/chips/msp432/src/lib.rs b/chips/msp432/src/lib.rs index 53e34294dd..7676691b56 100644 --- a/chips/msp432/src/lib.rs +++ b/chips/msp432/src/lib.rs @@ -122,9 +122,3 @@ pub static IRQS: [unsafe extern "C" fn(); 64] = [ unhandled_interrupt, // Reserved (62) unhandled_interrupt, // Reserved (63) ]; - -pub unsafe fn init() { - cortexm4::nvic::disable_all(); - cortexm4::nvic::clear_all_pending(); - cortexm4::nvic::enable_all(); -} diff --git a/chips/msp432/src/timer.rs b/chips/msp432/src/timer.rs index 6d884c19c9..a7c5e5c321 100644 --- a/chips/msp432/src/timer.rs +++ b/chips/msp432/src/timer.rs @@ -5,9 +5,7 @@ //! Timer (TIMER_Ax) use core::cell::Cell; -use kernel::hil::time::{ - Alarm, AlarmClient, Counter, Frequency, OverflowClient, Ticks, Ticks16, Time, -}; +use kernel::hil::time::{Alarm, AlarmClient, Counter, Frequency, Ticks, Ticks16, Time}; use kernel::utilities::cells::OptionalCell; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; use kernel::utilities::registers::{register_bitfields, register_structs, ReadWrite}; @@ -345,8 +343,6 @@ impl Time for TimerA<'_> { } impl<'a> Counter<'a> for TimerA<'a> { - fn set_overflow_client(&self, _client: &'a dyn OverflowClient) {} - fn start(&self) -> Result<(), ErrorCode> { self.setup_for_alarm(); Ok(()) diff --git a/chips/nrf52/src/chip.rs b/chips/nrf52/src/chip.rs index 29854d0685..58d80090df 100644 --- a/chips/nrf52/src/chip.rs +++ b/chips/nrf52/src/chip.rs @@ -132,6 +132,24 @@ impl<'a, I: InterruptService + 'a> kernel::platform::chip::Chip for NRF52<'a, I> type UserspaceKernelBoundary = cortexm4f::syscall::SysCall; type ThreadIdProvider = cortexm4f::thread_id::CortexMThreadIdProvider; + fn init() { + // # Safety + // + // We are setting up the nRF52 chip, so these memory locations are valid + // on the nRF52 and this is OK to call. + unsafe { + crate::crt1::fix_errata(); + } + crate::crt1::initialize_vector_table(); + + // # Safety + // + // Need to enable interrupts. + unsafe { + nvic::enable_all(); + } + } + fn mpu(&self) -> &Self::MPU { &self.mpu } diff --git a/chips/nrf52/src/crt1.rs b/chips/nrf52/src/crt1.rs index dbf26082b2..0e46d28b95 100644 --- a/chips/nrf52/src/crt1.rs +++ b/chips/nrf52/src/crt1.rs @@ -2,9 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // Copyright Tock Contributors 2022. -use cortexm4f::{ - initialize_ram_jump_to_main, nvic, scb, unhandled_interrupt, CortexM4F, CortexMVariant, -}; +use cortexm4f::{initialize_ram_jump_to_main, scb, unhandled_interrupt, CortexM4F, CortexMVariant}; /* * Adapted from crt1.c which was relicensed by the original author from @@ -71,8 +69,13 @@ pub static BASE_VECTORS: [unsafe extern "C" fn(); 16] = [ #[cfg_attr(all(target_arch = "arm", target_os = "none"), used)] pub static IRQS: [unsafe extern "C" fn(); 80] = [CortexM4F::GENERIC_ISR; 80]; -#[no_mangle] -pub unsafe extern "C" fn init() { +/// Apply fixes for various nRF52 errata +/// +/// # Safety +/// +/// Fixing these errata requires writing to various memory locations. These +/// operations are safe as long as this is only run on an nRF52 MCU. +pub(crate) unsafe fn fix_errata() { // Apply early initialization workarounds for anomalies documented on // 2015-12-11 nRF52832 Errata v1.2 // http://infocenter.nordicsemi.com/pdf/nRF52832_Errata_v1.2.pdf @@ -148,14 +151,23 @@ pub unsafe extern "C" fn init() { 0x40000ee4i32 as *mut u32, core::ptr::read_volatile(0x10000258i32 as *mut u32) & 0x4fu32, ); +} - // Explicitly tell the core where Tock's vector table is located. If Tock is the - // only thing on the chip then this is effectively a no-op. If, however, there is - // a bootloader present then we want to ensure that the vector table is set - // correctly for Tock. The bootloader _may_ set this for us, but it may not - // so that any errors early in the Tock boot process trap back to the bootloader. - // To be safe we unconditionally set the vector table. - scb::set_vector_table_offset(BASE_VECTORS.as_ptr().cast::<()>()); - - nvic::enable_all(); +/// Explicitly tell the core where Tock's vector table is located. +/// +/// If Tock is the +/// only thing on the chip then this is effectively a no-op. If, however, there is +/// a bootloader present then we want to ensure that the vector table is set +/// correctly for Tock. The bootloader _may_ set this for us, but it may not +/// so that any errors early in the Tock boot process trap back to the bootloader. +/// To be safe we unconditionally set the vector table. +pub(crate) fn initialize_vector_table() { + // # Safety + // + // The vector table must setup function pointers for the thumb core + // correctly. Because `BASE_VECTORS` is the correct data type this is + // safe. + unsafe { + scb::set_vector_table_offset(BASE_VECTORS.as_ptr().cast::<()>()); + } } diff --git a/chips/nrf52/src/lib.rs b/chips/nrf52/src/lib.rs index 6c7b465ca0..07462abedd 100644 --- a/chips/nrf52/src/lib.rs +++ b/chips/nrf52/src/lib.rs @@ -23,7 +23,6 @@ pub mod uart; pub mod uicr; pub mod usbd; -pub use crate::crt1::init; pub use nrf5x::{ aes, constants, gpio, peripheral_interrupts, pinmux, rtc, temperature, timer, trng, }; diff --git a/chips/nrf52832/src/lib.rs b/chips/nrf52832/src/lib.rs index 864ea00b29..dc0adb9cdc 100644 --- a/chips/nrf52832/src/lib.rs +++ b/chips/nrf52832/src/lib.rs @@ -5,7 +5,7 @@ #![no_std] pub use nrf52::{ - acomp, adc, aes, ble_radio, chip, clock, constants, crt1, ficr, i2c, init, nvmc, + acomp, adc, aes, ble_radio, chip, clock, constants, crt1, ficr, i2c, nvmc, peripheral_interrupts as base_interrupts, pinmux, power, ppi, pwm, rtc, spi, temperature, timer, trng, uart, uicr, }; diff --git a/chips/nrf52833/src/lib.rs b/chips/nrf52833/src/lib.rs index 3201a9d6ae..aedef1b48e 100644 --- a/chips/nrf52833/src/lib.rs +++ b/chips/nrf52833/src/lib.rs @@ -7,8 +7,8 @@ // FIXME: Move ieee802154_radio to an nrf528xx crate so this can access it. pub use nrf52::{ - acomp, adc, aes, ble_radio, chip, clock, constants, crt1, ficr, i2c, ieee802154_radio, init, - nvmc, peripheral_interrupts as base_interrupts, pinmux, power, ppi, pwm, rtc, spi, temperature, + acomp, adc, aes, ble_radio, chip, clock, constants, crt1, ficr, i2c, ieee802154_radio, nvmc, + peripheral_interrupts as base_interrupts, pinmux, power, ppi, pwm, rtc, spi, temperature, timer, trng, uart, uicr, }; pub mod gpio; diff --git a/chips/nrf52840/src/lib.rs b/chips/nrf52840/src/lib.rs index 2e6e6e495c..fe289b2ec3 100644 --- a/chips/nrf52840/src/lib.rs +++ b/chips/nrf52840/src/lib.rs @@ -4,8 +4,8 @@ #![no_std] pub use nrf52::{ - acomp, adc, aes, ble_radio, chip, clock, constants, crt1, ficr, i2c, ieee802154_radio, init, - nvmc, peripheral_interrupts as base_interrupts, pinmux, power, ppi, pwm, rtc, spi, temperature, + acomp, adc, aes, ble_radio, chip, clock, constants, crt1, ficr, i2c, ieee802154_radio, nvmc, + peripheral_interrupts as base_interrupts, pinmux, power, ppi, pwm, rtc, spi, temperature, timer, trng, uart, uicr, usbd, }; pub mod gpio; diff --git a/chips/nrf5x/src/rtc.rs b/chips/nrf5x/src/rtc.rs index f565eda239..993e543aa8 100644 --- a/chips/nrf5x/src/rtc.rs +++ b/chips/nrf5x/src/rtc.rs @@ -127,11 +127,6 @@ impl Time for Rtc<'_> { } impl<'a> time::Counter<'a> for Rtc<'a> { - fn set_overflow_client(&self, client: &'a dyn time::OverflowClient) { - self.overflow_client.set(client); - self.registers.intenset.write(Inte::OVRFLW::SET); - } - fn start(&self) -> Result<(), ErrorCode> { self.registers.prescaler.write(Prescaler::PRESCALER.val(0)); self.registers.tasks_start.write(Task::ENABLE::SET); @@ -156,6 +151,14 @@ impl<'a> time::Counter<'a> for Rtc<'a> { } } +#[allow(deprecated)] +impl<'a> time::CounterOverflow<'a> for Rtc<'a> { + fn set_overflow_client(&self, client: &'a dyn time::OverflowClient) { + self.overflow_client.set(client); + self.registers.intenset.write(Inte::OVRFLW::SET); + } +} + impl<'a> Alarm<'a> for Rtc<'a> { fn set_alarm_client(&self, client: &'a dyn time::AlarmClient) { self.alarm_client.set(client); diff --git a/chips/psoc62xa/src/chip.rs b/chips/psoc62xa/src/chip.rs index b40e92c8e2..b710393cd3 100644 --- a/chips/psoc62xa/src/chip.rs +++ b/chips/psoc62xa/src/chip.rs @@ -29,6 +29,11 @@ impl Chip for Psoc62xa<'_, I> { type UserspaceKernelBoundary = cortexm0p::syscall::SysCall; type ThreadIdProvider = cortexm0p::thread_id::CortexMThreadIdProvider; + fn init() { + // Set the offset of the vector table + crate::initialize_vector_table(); + } + fn mpu(&self) -> &Self::MPU { &self.mpu } diff --git a/chips/psoc62xa/src/lib.rs b/chips/psoc62xa/src/lib.rs index facd422004..b14132c914 100644 --- a/chips/psoc62xa/src/lib.rs +++ b/chips/psoc62xa/src/lib.rs @@ -40,6 +40,17 @@ pub static BASE_VECTORS: [unsafe extern "C" fn(); 16] = [ CortexM0P::SYSTICK_HANDLER, // SysTick ]; +pub(crate) fn initialize_vector_table() { + // # Safety + // + // The vector table must setup function pointers for the thumb core + // correctly. Because `BASE_VECTORS` is the correct data type this is + // safe. + unsafe { + cortexm0p::scb::set_vector_table_offset(BASE_VECTORS.as_ptr().cast::<()>()); + } +} + #[cfg_attr( all(target_arch = "arm", target_os = "none"), link_section = ".vectors" diff --git a/chips/qemu_rv32_virt_chip/src/chip.rs b/chips/qemu_rv32_virt_chip/src/chip.rs index d38b39ea66..6de8dd4fc3 100644 --- a/chips/qemu_rv32_virt_chip/src/chip.rs +++ b/chips/qemu_rv32_virt_chip/src/chip.rs @@ -116,6 +116,8 @@ impl<'a, I: InterruptService + 'a> Chip for QemuRv32VirtChip<'a, I> { type UserspaceKernelBoundary = rv32i::syscall::SysCall; type ThreadIdProvider = rv32i::thread_id::RiscvThreadIdProvider; + fn init() {} + fn mpu(&self) -> &Self::MPU { &self.pmp } diff --git a/chips/rp2040/src/chip.rs b/chips/rp2040/src/chip.rs index d32b7a2381..44ac48e747 100644 --- a/chips/rp2040/src/chip.rs +++ b/chips/rp2040/src/chip.rs @@ -60,6 +60,23 @@ impl Chip for Rp2040<'_, I> { type UserspaceKernelBoundary = cortexm0p::syscall::SysCall; type ThreadIdProvider = cortexm0p::thread_id::CortexMThreadIdProvider; + fn init() { + unsafe { + cortexm0p::nvic::disable_all(); + cortexm0p::nvic::clear_all_pending(); + } + + let sio = crate::gpio::SIO::new(); + let processor = sio.get_processor(); + match processor { + crate::chip::Processor::Processor0 => {} + _ => panic!( + "Kernel should run only using processor 0 (now processor {})", + processor as u8 + ), + } + } + fn service_pending_interrupts(&self) { unsafe { let mask = match self.sio.get_processor() { diff --git a/chips/rp2040/src/lib.rs b/chips/rp2040/src/lib.rs index 1b54794c47..4500806b85 100644 --- a/chips/rp2040/src/lib.rs +++ b/chips/rp2040/src/lib.rs @@ -107,17 +107,3 @@ extern "C" { static mut _srelocate: usize; static mut _erelocate: usize; } - -pub unsafe fn init() { - cortexm0p::nvic::disable_all(); - cortexm0p::nvic::clear_all_pending(); - let sio = gpio::SIO::new(); - let processor = sio.get_processor(); - match processor { - chip::Processor::Processor0 => {} - _ => panic!( - "Kernel should run only using processor 0 (now processor {})", - processor as u8 - ), - } -} diff --git a/chips/rp2350/src/chip.rs b/chips/rp2350/src/chip.rs index 2e40f3b0e5..18312a3580 100644 --- a/chips/rp2350/src/chip.rs +++ b/chips/rp2350/src/chip.rs @@ -50,6 +50,22 @@ impl Chip for Rp2350<'_, I> { type UserspaceKernelBoundary = cortexm33::syscall::SysCall; type ThreadIdProvider = cortexm33::thread_id::CortexMThreadIdProvider; + fn init() { + unsafe { + cortexm33::nvic::disable_all(); + cortexm33::nvic::clear_all_pending(); + } + let sio = crate::gpio::SIO::new(); + let processor = sio.get_processor(); + match processor { + crate::chip::Processor::Processor0 => {} + _ => panic!( + "Kernel should run only using processor 0 (now processor {})", + processor as u8 + ), + } + } + fn service_pending_interrupts(&self) { unsafe { let mask = match self.sio.get_processor() { diff --git a/chips/rp2350/src/lib.rs b/chips/rp2350/src/lib.rs index 33d16bb5d2..aa32e6e24d 100644 --- a/chips/rp2350/src/lib.rs +++ b/chips/rp2350/src/lib.rs @@ -115,17 +115,3 @@ extern "C" { static mut _srelocate: usize; static mut _erelocate: usize; } - -pub unsafe fn init() { - cortexm33::nvic::disable_all(); - cortexm33::nvic::clear_all_pending(); - let sio = gpio::SIO::new(); - let processor = sio.get_processor(); - match processor { - chip::Processor::Processor0 => {} - _ => panic!( - "Kernel should run only using processor 0 (now processor {})", - processor as u8 - ), - } -} diff --git a/chips/sam4l/src/ast.rs b/chips/sam4l/src/ast.rs index a8848e8998..119bba9735 100644 --- a/chips/sam4l/src/ast.rs +++ b/chips/sam4l/src/ast.rs @@ -321,8 +321,6 @@ impl time::Time for Ast<'_> { } impl<'a> time::Counter<'a> for Ast<'a> { - fn set_overflow_client(&self, _client: &'a dyn time::OverflowClient) {} - fn start(&self) -> Result<(), ErrorCode> { self.enable(); Ok(()) diff --git a/chips/sam4l/src/chip.rs b/chips/sam4l/src/chip.rs index 1346d44c39..9b2010d7df 100644 --- a/chips/sam4l/src/chip.rs +++ b/chips/sam4l/src/chip.rs @@ -240,6 +240,14 @@ impl Chip for Sam4l { type UserspaceKernelBoundary = cortexm4::syscall::SysCall; type ThreadIdProvider = cortexm4::thread_id::CortexMThreadIdProvider; + fn init() { + unsafe { + cortexm4::nvic::disable_all(); + cortexm4::nvic::clear_all_pending(); + cortexm4::nvic::enable_all(); + } + } + fn service_pending_interrupts(&self) { unsafe { while let Some(interrupt) = cortexm4::nvic::next_pending() { diff --git a/chips/sam4l/src/lib.rs b/chips/sam4l/src/lib.rs index 6dee6bdef7..8d43e1aea4 100644 --- a/chips/sam4l/src/lib.rs +++ b/chips/sam4l/src/lib.rs @@ -73,9 +73,3 @@ pub static BASE_VECTORS: [unsafe extern "C" fn(); 16] = [ // used Ensures that the symbol is kept until the final binary #[cfg_attr(all(target_arch = "arm", target_os = "none"), used)] pub static IRQS: [unsafe extern "C" fn(); 80] = [CortexM4::GENERIC_ISR; 80]; - -pub unsafe fn init() { - cortexm4::nvic::disable_all(); - cortexm4::nvic::clear_all_pending(); - cortexm4::nvic::enable_all(); -} diff --git a/chips/stm32f303xc/src/chip.rs b/chips/stm32f303xc/src/chip.rs index 65966344bc..3f080579a2 100644 --- a/chips/stm32f303xc/src/chip.rs +++ b/chips/stm32f303xc/src/chip.rs @@ -106,6 +106,14 @@ impl<'a, I: InterruptService + 'a> Chip for Stm32f3xx<'a, I> { type UserspaceKernelBoundary = cortexm4f::syscall::SysCall; type ThreadIdProvider = cortexm4f::thread_id::CortexMThreadIdProvider; + fn init() { + unsafe { + cortexm4f::nvic::disable_all(); + cortexm4f::nvic::clear_all_pending(); + cortexm4f::nvic::enable_all(); + } + } + fn service_pending_interrupts(&self) { unsafe { while let Some(interrupt) = cortexm4f::nvic::next_pending() { diff --git a/chips/stm32f303xc/src/lib.rs b/chips/stm32f303xc/src/lib.rs index 24a7deb6a1..5ce83dbef3 100644 --- a/chips/stm32f303xc/src/lib.rs +++ b/chips/stm32f303xc/src/lib.rs @@ -148,9 +148,3 @@ pub static IRQS: [unsafe extern "C" fn(); 82] = [ unhandled_interrupt, // (80) CortexM4F::GENERIC_ISR, // FPU (81) ]; - -pub unsafe fn init() { - cortexm4f::nvic::disable_all(); - cortexm4f::nvic::clear_all_pending(); - cortexm4f::nvic::enable_all(); -} diff --git a/chips/stm32f303xc/src/tim2.rs b/chips/stm32f303xc/src/tim2.rs index 7ffc3de8a8..bc1a4fc10d 100644 --- a/chips/stm32f303xc/src/tim2.rs +++ b/chips/stm32f303xc/src/tim2.rs @@ -3,9 +3,7 @@ // Copyright Tock Contributors 2022. use cortexm4f::support::with_interrupts_disabled; -use kernel::hil::time::{ - Alarm, AlarmClient, Counter, Freq16KHz, OverflowClient, Ticks, Ticks32, Time, -}; +use kernel::hil::time::{Alarm, AlarmClient, Counter, Freq16KHz, Ticks, Ticks32, Time}; use kernel::platform::chip::ClockInterface; use kernel::utilities::cells::OptionalCell; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; @@ -376,8 +374,6 @@ impl Time for Tim2<'_> { } impl<'a> Counter<'a> for Tim2<'a> { - fn set_overflow_client(&self, _client: &'a dyn OverflowClient) {} - // starts the timer fn start(&self) -> Result<(), ErrorCode> { self.start_counter(); diff --git a/chips/stm32f401cc/src/lib.rs b/chips/stm32f401cc/src/lib.rs index 2d973a3c2e..dd77ec1c58 100644 --- a/chips/stm32f401cc/src/lib.rs +++ b/chips/stm32f401cc/src/lib.rs @@ -104,7 +104,3 @@ pub static IRQS: [unsafe extern "C" fn(); 85] = [ unhandled_interrupt, // (83) CortexM4F::GENERIC_ISR, // SPI4 (84) ]; - -pub unsafe fn init() { - stm32f4xx::init(); -} diff --git a/chips/stm32f412g/src/lib.rs b/chips/stm32f412g/src/lib.rs index 27165048c9..6a84ecce22 100644 --- a/chips/stm32f412g/src/lib.rs +++ b/chips/stm32f412g/src/lib.rs @@ -124,7 +124,3 @@ pub static IRQS: [unsafe extern "C" fn(); 97] = [ CortexM4F::GENERIC_ISR, // I2CFMP1 event (95) CortexM4F::GENERIC_ISR, // I2CFMP1 error (96) ]; - -pub unsafe fn init() { - stm32f4xx::init(); -} diff --git a/chips/stm32f429zi/src/lib.rs b/chips/stm32f429zi/src/lib.rs index c20e67d5e1..4c57d7a956 100644 --- a/chips/stm32f429zi/src/lib.rs +++ b/chips/stm32f429zi/src/lib.rs @@ -122,7 +122,3 @@ pub static IRQS: [unsafe extern "C" fn(); 91] = [ CortexM4F::GENERIC_ISR, // LCD-TFT (89) CortexM4F::GENERIC_ISR, // DMA2D(90) ]; - -pub unsafe fn init() { - stm32f4xx::init(); -} diff --git a/chips/stm32f446re/src/lib.rs b/chips/stm32f446re/src/lib.rs index e430231b48..1654b89bd4 100644 --- a/chips/stm32f446re/src/lib.rs +++ b/chips/stm32f446re/src/lib.rs @@ -124,7 +124,3 @@ pub static IRQS: [unsafe extern "C" fn(); 97] = [ CortexM4F::GENERIC_ISR, // FMPI2C1_EV (95) CortexM4F::GENERIC_ISR, // FMPI2C1_ER (96) ]; - -pub unsafe fn init() { - stm32f4xx::init(); -} diff --git a/chips/stm32f4xx/src/chip.rs b/chips/stm32f4xx/src/chip.rs index 69eb4c001d..d96eecef05 100644 --- a/chips/stm32f4xx/src/chip.rs +++ b/chips/stm32f4xx/src/chip.rs @@ -167,6 +167,14 @@ impl<'a, I: InterruptService + 'a> Chip for Stm32f4xx<'a, I> { type UserspaceKernelBoundary = cortexm4f::syscall::SysCall; type ThreadIdProvider = cortexm4f::thread_id::CortexMThreadIdProvider; + fn init() { + unsafe { + cortexm4f::nvic::disable_all(); + cortexm4f::nvic::clear_all_pending(); + cortexm4f::nvic::enable_all(); + } + } + fn service_pending_interrupts(&self) { unsafe { while let Some(interrupt) = cortexm4f::nvic::next_pending() { diff --git a/chips/stm32f4xx/src/lib.rs b/chips/stm32f4xx/src/lib.rs index a56535553f..79fd470254 100644 --- a/chips/stm32f4xx/src/lib.rs +++ b/chips/stm32f4xx/src/lib.rs @@ -65,9 +65,3 @@ pub static BASE_VECTORS: [unsafe extern "C" fn(); 16] = [ unhandled_interrupt, // PendSV CortexM4F::SYSTICK_HANDLER, // SysTick ]; - -pub unsafe fn init() { - cortexm4f::nvic::disable_all(); - cortexm4f::nvic::clear_all_pending(); - cortexm4f::nvic::enable_all(); -} diff --git a/chips/stm32f4xx/src/tim2.rs b/chips/stm32f4xx/src/tim2.rs index 16b54355c6..f60e4bf6c1 100644 --- a/chips/stm32f4xx/src/tim2.rs +++ b/chips/stm32f4xx/src/tim2.rs @@ -3,9 +3,7 @@ // Copyright Tock Contributors 2022. use cortexm4f::support::with_interrupts_disabled; -use kernel::hil::time::{ - Alarm, AlarmClient, Counter, Freq16KHz, Frequency, OverflowClient, Ticks, Ticks32, Time, -}; +use kernel::hil::time::{Alarm, AlarmClient, Counter, Freq16KHz, Frequency, Ticks, Ticks32, Time}; use kernel::platform::chip::ClockInterface; use kernel::utilities::cells::OptionalCell; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; @@ -397,8 +395,6 @@ impl Time for Tim2<'_> { } impl<'a> Counter<'a> for Tim2<'a> { - fn set_overflow_client(&self, _client: &'a dyn OverflowClient) {} - // starts the timer fn start(&self) -> Result<(), ErrorCode> { self.start(); diff --git a/chips/stm32u545/Cargo.toml b/chips/stm32u545/Cargo.toml new file mode 100644 index 0000000000..77958cc6fa --- /dev/null +++ b/chips/stm32u545/Cargo.toml @@ -0,0 +1,17 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +[package] +name = "stm32u545" +version.workspace = true +authors.workspace = true +edition.workspace = true + +[dependencies] +cortexm33 = { path = "../../arch/cortex-m33" } +kernel = { path = "../../kernel" } +stm32u5xx = { path = "../stm32u5xx" } + +[lints] +workspace = true diff --git a/chips/stm32u545/README.md b/chips/stm32u545/README.md new file mode 100644 index 0000000000..e3fcc21806 --- /dev/null +++ b/chips/stm32u545/README.md @@ -0,0 +1,4 @@ +# STM32U545 Tock Chip Crate + +This crate provides support for the STM32U545 line of microcontrollers. +It depends on the common STM32U5xx drivers in `chips/stm32u5xx`. diff --git a/chips/stm32u545/src/lib.rs b/chips/stm32u545/src/lib.rs new file mode 100644 index 0000000000..9bb37ae830 --- /dev/null +++ b/chips/stm32u545/src/lib.rs @@ -0,0 +1,18 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright OxidOS Automotive 2026. + +#![no_std] + +pub use stm32u5xx::{chip, dma, exti, gpio, rcc, tim, usart}; + +use cortexm33::{CortexM33, CortexMVariant}; + +#[cfg_attr(all(target_arch = "arm", target_os = "none"), used)] +#[cfg_attr(all(target_arch = "arm", target_os = "none"), link_section = ".irqs")] +// Link to the STM32U5 series reference manual (RM0456): +// Table 186 "STM32U5 series vector table" +// https://www.st.com/resource/en/reference_manual/rm0456-stm32u5-series-armbased-32bit-mcus-stmicroelectronics.pdf +// Link to the STM32U545RE datasheet confirming 141 maskable interrupt channels: +// https://www.st.com/resource/en/datasheet/stm32u545re.pdf (Section 3.19.1) +pub static IRQS: [unsafe extern "C" fn(); 141] = [::GENERIC_ISR; 141]; diff --git a/chips/stm32u5xx/Cargo.toml b/chips/stm32u5xx/Cargo.toml new file mode 100644 index 0000000000..33a6228555 --- /dev/null +++ b/chips/stm32u5xx/Cargo.toml @@ -0,0 +1,18 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +[package] +name = "stm32u5xx" +version.workspace = true +authors.workspace = true +edition.workspace = true + +[dependencies] +cortexm33 = { path = "../../arch/cortex-m33" } +kernel = { path = "../../kernel" } + +[features] + +[lints] +workspace = true diff --git a/chips/stm32u5xx/README.md b/chips/stm32u5xx/README.md new file mode 100644 index 0000000000..7679a6c50f --- /dev/null +++ b/chips/stm32u5xx/README.md @@ -0,0 +1,14 @@ +# STM32U5xx Tock Chip Crate + +This crate provides support for the STM32U5 series of ultra-low-power +microcontrollers from STMicroelectronics. + +## Status + +Currently supported peripherals: +- RCC (Reset and Clock Control) +- GPIO (General Purpose I/O) +- EXTI (External Interrupts) +- USART (Universal Synchronous/Asynchronous Receiver Transmitter) +- GPDMA (Global Programmable DMA) +- TIM2 (Timer) diff --git a/chips/stm32u5xx/src/chip.rs b/chips/stm32u5xx/src/chip.rs new file mode 100644 index 0000000000..193fd197db --- /dev/null +++ b/chips/stm32u5xx/src/chip.rs @@ -0,0 +1,229 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. +// Copyright OxidOS Automotive 2026. + +use crate::dma::{ChannelId, Dma}; +use crate::exti; +use crate::gpio; +use crate::nvic::{ + EXTI13_IRQ, GPDMA1_CH0_IRQ, GPDMA1_CH10_IRQ, GPDMA1_CH11_IRQ, GPDMA1_CH12_IRQ, GPDMA1_CH13_IRQ, + GPDMA1_CH14_IRQ, GPDMA1_CH15_IRQ, GPDMA1_CH1_IRQ, GPDMA1_CH2_IRQ, GPDMA1_CH3_IRQ, + GPDMA1_CH4_IRQ, GPDMA1_CH5_IRQ, GPDMA1_CH6_IRQ, GPDMA1_CH7_IRQ, GPDMA1_CH8_IRQ, GPDMA1_CH9_IRQ, + TIM2_IRQ, USART1_IRQ, +}; +use crate::rcc; +use crate::tim; +use crate::usart; + +use core::fmt::Write; +use kernel::platform::chip::Chip; +use kernel::platform::chip::InterruptService; + +pub struct Stm32u5xx<'a, I: InterruptService + 'a> { + mpu: cortexm33::mpu::MPU<8>, + userspace_kernel_boundary: cortexm33::syscall::SysCall, + interrupt_service: &'a I, +} + +pub struct Stm32u5xxDefaultPeripherals<'a> { + pub rcc: rcc::Rcc, + pub tim2: tim::Tim2<'a>, + pub usart1: &'a usart::Usart<'a>, + pub exti: &'a exti::Exti<'a>, + pub dma1: &'a Dma, + pub gpio_a: gpio::Port<'a>, + pub gpio_c: gpio::Port<'a>, +} + +fn enable_tim2_clock() { + let rcc = rcc::Rcc::new(rcc::RCC_BASE); + rcc.enable_tim2(); +} + +impl<'a> Stm32u5xxDefaultPeripherals<'a> { + pub fn new(usart1: &'a usart::Usart<'a>, exti: &'a exti::Exti<'a>, dma1: &'a Dma) -> Self { + Self { + rcc: rcc::Rcc::new(rcc::RCC_BASE), + tim2: tim::Tim2::new(tim::TIM2_BASE, enable_tim2_clock), + usart1, + exti, + dma1, + gpio_a: gpio::Port::new(gpio::GPIO_A_BASE, exti, gpio::GpioPort::PortA), + gpio_c: gpio::Port::new(gpio::GPIO_C_BASE, exti, gpio::GpioPort::PortC), + } + } + + pub fn init(&'static self) { + // Power and Wires + self.rcc.enable_dma1(); + self.rcc.enable_gpioa(); + self.rcc.enable_gpioc(); + self.rcc.enable_usart1(); + self.rcc.enable_syscfg(); + self.rcc.set_usart1_source_pclk(); + // Link DMA to USART1 + let usart1_channel_tx = self.dma1.request_channel(); + let usart1_channel_rx = self.dma1.request_channel(); + + if let (Some(tx), Some(rx)) = (usart1_channel_tx, usart1_channel_rx) { + usart::Usart::set_dma(self.usart1, self.dma1, tx, rx); + } + } +} + +impl InterruptService for Stm32u5xxDefaultPeripherals<'_> { + unsafe fn service_interrupt(&self, interrupt: u32) -> bool { + match interrupt { + TIM2_IRQ => { + // TIM2 + self.tim2.handle_interrupt(); + true + } + USART1_IRQ => { + // USART1 + self.usart1.handle_interrupt(); + true + } + EXTI13_IRQ => { + // EXTI13 (Button) + self.exti.handle_interrupt(crate::exti::LineId::Line13); + true + } + // Route all 16 GPDMA1 Channels to the DMA manager + GPDMA1_CH0_IRQ => { + self.dma1.handle_interrupt(ChannelId::Channel00); + true + } + GPDMA1_CH1_IRQ => { + self.dma1.handle_interrupt(ChannelId::Channel01); + true + } + GPDMA1_CH2_IRQ => { + self.dma1.handle_interrupt(ChannelId::Channel02); + true + } + GPDMA1_CH3_IRQ => { + self.dma1.handle_interrupt(ChannelId::Channel03); + true + } + GPDMA1_CH4_IRQ => { + self.dma1.handle_interrupt(ChannelId::Channel04); + true + } + GPDMA1_CH5_IRQ => { + self.dma1.handle_interrupt(ChannelId::Channel05); + true + } + GPDMA1_CH6_IRQ => { + self.dma1.handle_interrupt(ChannelId::Channel06); + true + } + GPDMA1_CH7_IRQ => { + self.dma1.handle_interrupt(ChannelId::Channel07); + true + } + GPDMA1_CH8_IRQ => { + self.dma1.handle_interrupt(ChannelId::Channel08); + true + } + GPDMA1_CH9_IRQ => { + self.dma1.handle_interrupt(ChannelId::Channel09); + true + } + GPDMA1_CH10_IRQ => { + self.dma1.handle_interrupt(ChannelId::Channel10); + true + } + GPDMA1_CH11_IRQ => { + self.dma1.handle_interrupt(ChannelId::Channel11); + true + } + GPDMA1_CH12_IRQ => { + self.dma1.handle_interrupt(ChannelId::Channel12); + true + } + GPDMA1_CH13_IRQ => { + self.dma1.handle_interrupt(ChannelId::Channel13); + true + } + GPDMA1_CH14_IRQ => { + self.dma1.handle_interrupt(ChannelId::Channel14); + true + } + GPDMA1_CH15_IRQ => { + self.dma1.handle_interrupt(ChannelId::Channel15); + true + } + _ => false, + } + } +} + +impl<'a, I: InterruptService + 'a> Stm32u5xx<'a, I> { + pub unsafe fn new(interrupt_service: &'a I) -> Self { + Self { + mpu: cortexm33::mpu::new::<8>(), + userspace_kernel_boundary: cortexm33::syscall::SysCall::new(), + interrupt_service, + } + } +} + +impl<'a, I: InterruptService + 'a> Chip for Stm32u5xx<'a, I> { + type MPU = cortexm33::mpu::MPU<8>; + type UserspaceKernelBoundary = cortexm33::syscall::SysCall; + type ThreadIdProvider = cortexm33::thread_id::CortexMThreadIdProvider; + + fn init() { + unsafe { + cortexm33::nvic::disable_all(); + cortexm33::nvic::clear_all_pending(); + cortexm33::nvic::enable_all(); + } + } + + fn service_pending_interrupts(&self) { + unsafe { + while let Some(interrupt) = cortexm33::nvic::next_pending() { + if !self.interrupt_service.service_interrupt(interrupt) { + panic!("unhandled interrupt {}", interrupt); + } + + let n = cortexm33::nvic::Nvic::new(interrupt); + n.clear_pending(); + n.enable(); + } + } + } + + fn has_pending_interrupts(&self) -> bool { + unsafe { cortexm33::nvic::has_pending() } + } + + fn mpu(&self) -> &cortexm33::mpu::MPU<8> { + &self.mpu + } + + fn userspace_kernel_boundary(&self) -> &cortexm33::syscall::SysCall { + &self.userspace_kernel_boundary + } + + fn sleep(&self) { + unsafe { + cortexm33::scb::unset_sleepdeep(); + cortexm33::support::wfi(); + } + } + + unsafe fn with_interrupts_disabled(&self, f: F) -> R + where + F: FnOnce() -> R, + { + cortexm33::support::with_interrupts_disabled(f) + } + + unsafe fn print_state(_this: Option<&Self>, write: &mut dyn Write) { + let _ = write.write_str("Cortex-M33 state\n"); + } +} diff --git a/chips/stm32u5xx/src/dma.rs b/chips/stm32u5xx/src/dma.rs new file mode 100644 index 0000000000..d3c51c0cea --- /dev/null +++ b/chips/stm32u5xx/src/dma.rs @@ -0,0 +1,389 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright OxidOS Automotive 2026. + +use core::cell::Cell; +use kernel::utilities::cells::OptionalCell; +use kernel::utilities::registers::interfaces::{ReadWriteable, Writeable}; +use kernel::utilities::registers::{ + register_bitfields, register_structs, Field, ReadOnly, ReadWrite, +}; +use kernel::utilities::StaticRef; + +/// Base address for USART1 in Secure Alias mode. +const USART1_BASE_ADDR: u32 = 0x50013800; +/// USART1 Receive Data Register (RDR) address. +const USART1_RDR: u32 = USART1_BASE_ADDR + 0x24; +/// USART1 Transmit Data Register (TDR) address. +const USART1_TDR: u32 = USART1_BASE_ADDR + 0x28; + +/// GPDMA Request Selection IDs (REQSEL) +/// Found in the GPDMA request multiplexer table of the STM32U5 reference manual. +const GPDMA_REQ_USART1_RX: u32 = 24; +const GPDMA_REQ_USART1_TX: u32 = 25; + +register_bitfields! [ + u32, + pub DmaChannelTR1 [ + /// Destination security + DSEC OFFSET(31) NUMBITS(1) [], + /// Destination allocated port + DAP OFFSET(30) NUMBITS(1) [], + /// Destination increment + DINC OFFSET(19) NUMBITS(1) [], + /// Source security + SSEC OFFSET(15) NUMBITS(1) [], + /// Source allocated port + SAP OFFSET(14) NUMBITS(1) [], + /// Source increment + SINC OFFSET(3) NUMBITS(1) [], + ], + pub DmaChannelTR2 [ + /// Destination request + DREQ OFFSET(10) NUMBITS(1) [], + /// Request selection + REQSEL OFFSET(0) NUMBITS(7) [], + ], + pub DmaChannelBR1 [ + /// Block number of data bytes to transfer + BNDT OFFSET(0) NUMBITS(16) [] + ], + pub DmaChannelSAR [ + /// Source address + SAR OFFSET(0) NUMBITS(32) [] + ], + pub DmaChannelDAR [ + /// Destination address + DAR OFFSET(0) NUMBITS(32) [] + ], + pub DmaChannelCR [ + /// Transfer complete interrupt enable + TCIE OFFSET(8) NUMBITS(1) [], + /// Enable + EN OFFSET(0) NUMBITS(1) [], + ], + pub DmaChannelFCR [ + /// Completed suspension flag clear + SUSPF OFFSET(13) NUMBITS(1) [], + /// User setting error flag clear + USEF OFFSET(12) NUMBITS(1) [], + /// Update link error flag clear + ULEF OFFSET(11) NUMBITS(1) [], + /// Data transfer error flag clear + DTEF OFFSET(10) NUMBITS(1) [], + /// Half transfer flag clear + HTF OFFSET(9) NUMBITS(1) [], + /// Transfer complete flag clear + TCF OFFSET(8) NUMBITS(1) [], + ], + pub DmaChannelEnable [ + CH0 OFFSET(0) NUMBITS(1) [], + CH1 OFFSET(1) NUMBITS(1) [], + CH2 OFFSET(2) NUMBITS(1) [], + CH3 OFFSET(3) NUMBITS(1) [], + CH4 OFFSET(4) NUMBITS(1) [], + CH5 OFFSET(5) NUMBITS(1) [], + CH6 OFFSET(6) NUMBITS(1) [], + CH7 OFFSET(7) NUMBITS(1) [], + CH8 OFFSET(8) NUMBITS(1) [], + CH9 OFFSET(9) NUMBITS(1) [], + CH10 OFFSET(10) NUMBITS(1) [], + CH11 OFFSET(11) NUMBITS(1) [], + CH12 OFFSET(12) NUMBITS(1) [], + CH13 OFFSET(13) NUMBITS(1) [], + CH14 OFFSET(14) NUMBITS(1) [], + CH15 OFFSET(15) NUMBITS(1) [], + ] +]; + +const CH_FIELDS: [Field; 16] = [ + DmaChannelEnable::CH0, + DmaChannelEnable::CH1, + DmaChannelEnable::CH2, + DmaChannelEnable::CH3, + DmaChannelEnable::CH4, + DmaChannelEnable::CH5, + DmaChannelEnable::CH6, + DmaChannelEnable::CH7, + DmaChannelEnable::CH8, + DmaChannelEnable::CH9, + DmaChannelEnable::CH10, + DmaChannelEnable::CH11, + DmaChannelEnable::CH12, + DmaChannelEnable::CH13, + DmaChannelEnable::CH14, + DmaChannelEnable::CH15, +]; + +register_structs! { + pub DmaChannelRegisters { + /// Channel x linked-list base address register (Relative 0x00) + (0x000 => pub l_bar: ReadWrite), + /// Channel x flag clear register (Relative 0x04) + (0x004 => _reserved0: [u32; 2]), + (0x00C => pub f_cr: ReadWrite), + /// Channel x status register (Relative 0x08) + (0x010 => pub s_r: ReadOnly), + /// Channel x control register (Relative 0x0C) + (0x014 => pub c_r: ReadWrite), + (0x018 => _reserved1: [u32; 10]), + /// Channel x transfer register 1 (Relative 0x40) + (0x040 => pub t_r1: ReadWrite), + /// Channel x transfer register 2 (Relative 0x44) + (0x044 => pub t_r2: ReadWrite), + /// Channel x block register 1 (Relative 0x48) + (0x048 => pub b_r1: ReadWrite), + /// Channel x source address register (Relative 0x4C) + (0x04C => pub s_ar: ReadWrite), + /// Channel x destination address register (Relative 0x50) + (0x050 => pub d_ar: ReadWrite), + (0x054 => _reserved2: [u32; 10]), + /// Channel x linked-list address register (Relative 0x7C) + (0x07C => pub l_lr: ReadWrite), + (0x080 => @END), + } +} + +register_structs! { + pub DmaRegisters { + /// GPDMA secure configuration register (0x00) + (0x000 => pub seccfgr: ReadWrite), + /// GPDMA privileged configuration register (0x04) + (0x004 => pub privcfgr: ReadWrite), + (0x008 => _reserved0: [u32; 1]), + /// Masked interrupt status register (0x0C) + (0x00C => pub misr: ReadOnly), + (0x010 => pub smisr: ReadOnly), + (0x014 => _reserved1: [u32; 15]), + /// Channels starting at 0x50. Each channel is 0x80 bytes long. + (0x050 => pub channels: [DmaChannelRegisters; 16]), + (0x850 => @END), + } +} + +/// Base address for DMA1 in Secure Alias mode. +pub const DMA1_BASE: StaticRef = + unsafe { StaticRef::new(0x50020000 as *const DmaRegisters) }; + +pub trait DmaClient { + fn transfer_done(&self, channel: ChannelId); +} + +#[repr(usize)] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum ChannelId { + Channel00 = 0, + Channel01 = 1, + Channel02 = 2, + Channel03 = 3, + Channel04 = 4, + Channel05 = 5, + Channel06 = 6, + Channel07 = 7, + Channel08 = 8, + Channel09 = 9, + Channel10 = 10, + Channel11 = 11, + Channel12 = 12, + Channel13 = 13, + Channel14 = 14, + Channel15 = 15, +} + +impl From for usize { + fn from(val: ChannelId) -> usize { + val as usize + } +} + +pub enum DmaDirection { + MemoryToPeripheral, + PeripheralToMemory, +} + +#[derive(Copy, Clone)] +pub enum DmaPeripheral { + Usart1Tx, + Usart1Rx, +} + +impl DmaPeripheral { + fn get_params(&self) -> (u32, u32, DmaDirection) { + match self { + DmaPeripheral::Usart1Tx => ( + USART1_TDR, + GPDMA_REQ_USART1_TX, + DmaDirection::MemoryToPeripheral, + ), + DmaPeripheral::Usart1Rx => ( + USART1_RDR, + GPDMA_REQ_USART1_RX, + DmaDirection::PeripheralToMemory, + ), + } + } +} + +pub struct ChannelDma { + pub channel: ChannelId, + pub in_use: Cell, + pub client: OptionalCell<&'static dyn DmaClient>, +} + +impl ChannelDma { + pub const fn new(id: ChannelId) -> Self { + Self { + channel: id, + in_use: Cell::new(false), + client: OptionalCell::empty(), + } + } +} + +pub struct Dma { + pub registers: StaticRef, + channels: [ChannelDma; 16], +} + +impl Dma { + pub const fn new(base: StaticRef) -> Self { + Self { + registers: base, + channels: [ + ChannelDma::new(ChannelId::Channel00), + ChannelDma::new(ChannelId::Channel01), + ChannelDma::new(ChannelId::Channel02), + ChannelDma::new(ChannelId::Channel03), + ChannelDma::new(ChannelId::Channel04), + ChannelDma::new(ChannelId::Channel05), + ChannelDma::new(ChannelId::Channel06), + ChannelDma::new(ChannelId::Channel07), + ChannelDma::new(ChannelId::Channel08), + ChannelDma::new(ChannelId::Channel09), + ChannelDma::new(ChannelId::Channel10), + ChannelDma::new(ChannelId::Channel11), + ChannelDma::new(ChannelId::Channel12), + ChannelDma::new(ChannelId::Channel13), + ChannelDma::new(ChannelId::Channel14), + ChannelDma::new(ChannelId::Channel15), + ], + } + } + + pub fn setup( + &self, + channel: ChannelId, + peripheral: DmaPeripheral, + buffer_addr: u32, + length: u32, + ) { + let channel_id: usize = channel.into(); + let (periph_addr, reqsel, direction) = peripheral.get_params(); + + // 1. Mark channel as Secure AND Privileged + self.registers.seccfgr.modify(CH_FIELDS[channel_id].val(1)); + self.registers.privcfgr.modify(CH_FIELDS[channel_id].val(1)); + + let ch = &self.registers.channels[channel_id]; + + // 2. Ensure channel is disabled + ch.c_r.write(DmaChannelCR::EN::CLEAR); + + // 3. Clear all flags + ch.f_cr.write( + DmaChannelFCR::SUSPF::SET + + DmaChannelFCR::USEF::SET + + DmaChannelFCR::ULEF::SET + + DmaChannelFCR::DTEF::SET + + DmaChannelFCR::HTF::SET + + DmaChannelFCR::TCF::SET, + ); + + // 4. Configure TR1, TR2 and addresses based on direction + match direction { + DmaDirection::MemoryToPeripheral => { + // Source is memory (incrementing), Destination is peripheral (fixed) + ch.t_r1.write( + DmaChannelTR1::SINC::SET + + DmaChannelTR1::SAP::CLEAR + + DmaChannelTR1::DAP::CLEAR, + ); + // Source request comes from destination peripheral + ch.t_r2 + .write(DmaChannelTR2::REQSEL.val(reqsel) + DmaChannelTR2::DREQ::SET); + ch.s_ar.write(DmaChannelSAR::SAR.val(buffer_addr)); + ch.d_ar.write(DmaChannelDAR::DAR.val(periph_addr)); + } + DmaDirection::PeripheralToMemory => { + // Destination is memory (incrementing), Source is peripheral (fixed) + // Note: Keeping security bits as in previous RX implementation + ch.t_r1.write( + DmaChannelTR1::DINC::SET + DmaChannelTR1::SSEC::SET + DmaChannelTR1::DSEC::SET, + ); + // Source request comes from source peripheral + ch.t_r2.write(DmaChannelTR2::REQSEL.val(reqsel)); + ch.s_ar.write(DmaChannelSAR::SAR.val(periph_addr)); + ch.d_ar.write(DmaChannelDAR::DAR.val(buffer_addr)); + } + } + + // 5. Set Block Register 1 (BR1) + ch.b_r1.write(DmaChannelBR1::BNDT.val(length & 0xFFFF)); + + // 6. Enable Transfer Complete Interrupt and start the channel + ch.c_r + .write(DmaChannelCR::TCIE::SET + DmaChannelCR::EN::SET); + } + + pub fn clear_interrupt(&self, channel: ChannelId) { + // `channel_id` is a `usize` converted from `ChannelId` + // which is an enum that can only take values between 0 and 15. + let channel_id: usize = channel.into(); + let ch = &self.registers.channels[channel_id]; + ch.f_cr.write( + DmaChannelFCR::SUSPF::SET + + DmaChannelFCR::USEF::SET + + DmaChannelFCR::ULEF::SET + + DmaChannelFCR::DTEF::SET + + DmaChannelFCR::HTF::SET + + DmaChannelFCR::TCF::SET, + ); + } + + pub fn request_channel(&self) -> Option { + for channel in &self.channels { + if !channel.in_use.get() { + channel.in_use.set(true); + return Some(channel.channel); + } + } + None + } + + pub fn release_channel(&self, id: ChannelId) { + // `channel_id` is a `usize` converted from `ChannelId` + // which is an enum that can only take values between 0 and 15. + let index: usize = id.into(); + if self.channels[index].in_use.get() { + self.channels[index].in_use.set(false); + self.channels[index].client.clear(); + } + } + + pub fn set_client(&self, id: ChannelId, client: &'static dyn DmaClient) { + // `channel_id` is a `usize` converted from `ChannelId` + // which is an enum that can only take values between 0 and 15. + let index: usize = id.into(); + self.channels[index].client.set(client); + } + + pub fn handle_interrupt(&self, id: ChannelId) { + self.clear_interrupt(id); + // `channel_id` is a `usize` converted from `ChannelId` + // which is an enum that can only take values between 0 and 15. + let index: usize = id.into(); + self.channels[index].client.map(|client| { + client.transfer_done(id); + }); + } +} diff --git a/chips/stm32u5xx/src/exti.rs b/chips/stm32u5xx/src/exti.rs new file mode 100644 index 0000000000..03f7faeec9 --- /dev/null +++ b/chips/stm32u5xx/src/exti.rs @@ -0,0 +1,413 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright OxidOS Automotive 2026. + +use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; +use kernel::utilities::registers::{register_bitfields, register_structs, ReadWrite}; +use kernel::utilities::StaticRef; + +register_structs! { + pub ExtiRegisters { + /// Rising trigger selection register + (0x000 => pub rtsr1: ReadWrite), + /// Falling trigger selection register + (0x004 => pub ftsr1: ReadWrite), + /// Software interrupt event register + (0x008 => pub swier1: ReadWrite), + /// Pending register 1 (Rising) + (0x00C => pub rpr1: ReadWrite), + /// Pending register 1 (Falling) + (0x010 => pub fpr1: ReadWrite), + /// Security configuration register + (0x014 => pub seccfgr1: ReadWrite), + (0x018 => _reserved0: [u32; 18]), + /// External interrupt selection registers + (0x060 => pub exticr: [ReadWrite; 4]), + (0x070 => _reserved1: [u32; 4]), + /// Interrupt mask register + (0x080 => pub imr1: ReadWrite), + (0x084 => @END), + } +} + +register_bitfields![u32, + pub LineReg [ + L0 OFFSET(0) NUMBITS(1) [], + L1 OFFSET(1) NUMBITS(1) [], + L2 OFFSET(2) NUMBITS(1) [], + L3 OFFSET(3) NUMBITS(1) [], + L4 OFFSET(4) NUMBITS(1) [], + L5 OFFSET(5) NUMBITS(1) [], + L6 OFFSET(6) NUMBITS(1) [], + L7 OFFSET(7) NUMBITS(1) [], + L8 OFFSET(8) NUMBITS(1) [], + L9 OFFSET(9) NUMBITS(1) [], + L10 OFFSET(10) NUMBITS(1) [], + L11 OFFSET(11) NUMBITS(1) [], + L12 OFFSET(12) NUMBITS(1) [], + L13 OFFSET(13) NUMBITS(1) [], + L14 OFFSET(14) NUMBITS(1) [], + L15 OFFSET(15) NUMBITS(1) [] + ], + pub EXTICR [ + EXTI0 OFFSET(0) NUMBITS(8) [], + EXTI1 OFFSET(8) NUMBITS(8) [], + EXTI2 OFFSET(16) NUMBITS(8) [], + EXTI3 OFFSET(24) NUMBITS(8) [] + ] +]; + +/// Base address for EXTI in Secure Alias mode. +pub const EXTI_BASE: StaticRef = + unsafe { StaticRef::new(0x56022000 as *const ExtiRegisters) }; + +#[derive(Copy, Clone, PartialEq)] +/// Identifiers for the 16 external interrupt lines (EXTI0 - EXTI15). +pub enum LineId { + Line00 = 0, + Line01 = 1, + Line02 = 2, + Line03 = 3, + Line04 = 4, + Line05 = 5, + Line06 = 6, + Line07 = 7, + Line08 = 8, + Line09 = 9, + Line10 = 10, + Line11 = 11, + Line12 = 12, + Line13 = 13, + Line14 = 14, + Line15 = 15, +} + +/// The EXTI controller manages external interrupt lines and routes them +/// to registered clients (usually GPIO Pins). +pub struct Exti<'a> { + registers: StaticRef, + clients: [kernel::utilities::cells::OptionalCell<&'a dyn kernel::hil::gpio::Client>; 16], +} + +impl<'a> Exti<'a> { + /// Creates a new EXTI driver instance. + pub const fn new(base: StaticRef) -> Self { + Self { + registers: base, + clients: [const { kernel::utilities::cells::OptionalCell::empty() }; 16], + } + } + + /// Processes external interrupts and notifies registered clients. + /// + /// This is called from the chip's main ISR dispatcher. It clears the + /// hardware pending flags and calls `fired()` on the associated client. + pub fn handle_interrupt(&self, line: LineId) { + let line_num = line as usize; + // line_num is of type line with size usize + // line is of type LineId, where LineId is an enumeration that can take only values between 0 and 15 + if line_num < 16 { + // Clear pending flags + self.clear_pending(line); + + // Notify the client + self.clients[line_num].map(|client| { + client.fired(); + }); + } + } + + pub(crate) fn register_client(&self, line: LineId, client: &'a dyn kernel::hil::gpio::Client) { + let index = line as usize; + if index < 16 { + self.clients[index].set(client); + } + } + + pub(crate) fn select_port(&self, line: LineId, port: u32) { + let line_num = line as usize; + + if line_num < 16 { + let register_index = line_num / 4; + let field_index = line_num % 4; + + let reg = &self.registers.exticr[register_index]; + match field_index { + 0 => reg.modify(EXTICR::EXTI0.val(port)), + 1 => reg.modify(EXTICR::EXTI1.val(port)), + 2 => reg.modify(EXTICR::EXTI2.val(port)), + 3 => reg.modify(EXTICR::EXTI3.val(port)), + _ => unreachable!(), + } + } + } + + pub(crate) fn set_secure(&self, line: LineId) { + match line { + LineId::Line00 => self.registers.seccfgr1.modify(LineReg::L0::SET), + LineId::Line01 => self.registers.seccfgr1.modify(LineReg::L1::SET), + LineId::Line02 => self.registers.seccfgr1.modify(LineReg::L2::SET), + LineId::Line03 => self.registers.seccfgr1.modify(LineReg::L3::SET), + LineId::Line04 => self.registers.seccfgr1.modify(LineReg::L4::SET), + LineId::Line05 => self.registers.seccfgr1.modify(LineReg::L5::SET), + LineId::Line06 => self.registers.seccfgr1.modify(LineReg::L6::SET), + LineId::Line07 => self.registers.seccfgr1.modify(LineReg::L7::SET), + LineId::Line08 => self.registers.seccfgr1.modify(LineReg::L8::SET), + LineId::Line09 => self.registers.seccfgr1.modify(LineReg::L9::SET), + LineId::Line10 => self.registers.seccfgr1.modify(LineReg::L10::SET), + LineId::Line11 => self.registers.seccfgr1.modify(LineReg::L11::SET), + LineId::Line12 => self.registers.seccfgr1.modify(LineReg::L12::SET), + LineId::Line13 => self.registers.seccfgr1.modify(LineReg::L13::SET), + LineId::Line14 => self.registers.seccfgr1.modify(LineReg::L14::SET), + LineId::Line15 => self.registers.seccfgr1.modify(LineReg::L15::SET), + } + } + + pub(crate) fn mask_interrupt(&self, line: LineId) { + match line { + LineId::Line00 => self.registers.imr1.modify(LineReg::L0::CLEAR), + LineId::Line01 => self.registers.imr1.modify(LineReg::L1::CLEAR), + LineId::Line02 => self.registers.imr1.modify(LineReg::L2::CLEAR), + LineId::Line03 => self.registers.imr1.modify(LineReg::L3::CLEAR), + LineId::Line04 => self.registers.imr1.modify(LineReg::L4::CLEAR), + LineId::Line05 => self.registers.imr1.modify(LineReg::L5::CLEAR), + LineId::Line06 => self.registers.imr1.modify(LineReg::L6::CLEAR), + LineId::Line07 => self.registers.imr1.modify(LineReg::L7::CLEAR), + LineId::Line08 => self.registers.imr1.modify(LineReg::L8::CLEAR), + LineId::Line09 => self.registers.imr1.modify(LineReg::L9::CLEAR), + LineId::Line10 => self.registers.imr1.modify(LineReg::L10::CLEAR), + LineId::Line11 => self.registers.imr1.modify(LineReg::L11::CLEAR), + LineId::Line12 => self.registers.imr1.modify(LineReg::L12::CLEAR), + LineId::Line13 => self.registers.imr1.modify(LineReg::L13::CLEAR), + LineId::Line14 => self.registers.imr1.modify(LineReg::L14::CLEAR), + LineId::Line15 => self.registers.imr1.modify(LineReg::L15::CLEAR), + } + } + + pub(crate) fn unmask_interrupt(&self, line: LineId) { + match line { + LineId::Line00 => self.registers.imr1.modify(LineReg::L0::SET), + LineId::Line01 => self.registers.imr1.modify(LineReg::L1::SET), + LineId::Line02 => self.registers.imr1.modify(LineReg::L2::SET), + LineId::Line03 => self.registers.imr1.modify(LineReg::L3::SET), + LineId::Line04 => self.registers.imr1.modify(LineReg::L4::SET), + LineId::Line05 => self.registers.imr1.modify(LineReg::L5::SET), + LineId::Line06 => self.registers.imr1.modify(LineReg::L6::SET), + LineId::Line07 => self.registers.imr1.modify(LineReg::L7::SET), + LineId::Line08 => self.registers.imr1.modify(LineReg::L8::SET), + LineId::Line09 => self.registers.imr1.modify(LineReg::L9::SET), + LineId::Line10 => self.registers.imr1.modify(LineReg::L10::SET), + LineId::Line11 => self.registers.imr1.modify(LineReg::L11::SET), + LineId::Line12 => self.registers.imr1.modify(LineReg::L12::SET), + LineId::Line13 => self.registers.imr1.modify(LineReg::L13::SET), + LineId::Line14 => self.registers.imr1.modify(LineReg::L14::SET), + LineId::Line15 => self.registers.imr1.modify(LineReg::L15::SET), + } + } + + pub(crate) fn clear_pending(&self, line: LineId) { + match line { + LineId::Line00 => { + self.registers.rpr1.write(LineReg::L0::SET); + self.registers.fpr1.write(LineReg::L0::SET); + } + LineId::Line01 => { + self.registers.rpr1.write(LineReg::L1::SET); + self.registers.fpr1.write(LineReg::L1::SET); + } + LineId::Line02 => { + self.registers.rpr1.write(LineReg::L2::SET); + self.registers.fpr1.write(LineReg::L2::SET); + } + LineId::Line03 => { + self.registers.rpr1.write(LineReg::L3::SET); + self.registers.fpr1.write(LineReg::L3::SET); + } + LineId::Line04 => { + self.registers.rpr1.write(LineReg::L4::SET); + self.registers.fpr1.write(LineReg::L4::SET); + } + LineId::Line05 => { + self.registers.rpr1.write(LineReg::L5::SET); + self.registers.fpr1.write(LineReg::L5::SET); + } + LineId::Line06 => { + self.registers.rpr1.write(LineReg::L6::SET); + self.registers.fpr1.write(LineReg::L6::SET); + } + LineId::Line07 => { + self.registers.rpr1.write(LineReg::L7::SET); + self.registers.fpr1.write(LineReg::L7::SET); + } + LineId::Line08 => { + self.registers.rpr1.write(LineReg::L8::SET); + self.registers.fpr1.write(LineReg::L8::SET); + } + LineId::Line09 => { + self.registers.rpr1.write(LineReg::L9::SET); + self.registers.fpr1.write(LineReg::L9::SET); + } + LineId::Line10 => { + self.registers.rpr1.write(LineReg::L10::SET); + self.registers.fpr1.write(LineReg::L10::SET); + } + LineId::Line11 => { + self.registers.rpr1.write(LineReg::L11::SET); + self.registers.fpr1.write(LineReg::L11::SET); + } + LineId::Line12 => { + self.registers.rpr1.write(LineReg::L12::SET); + self.registers.fpr1.write(LineReg::L12::SET); + } + LineId::Line13 => { + self.registers.rpr1.write(LineReg::L13::SET); + self.registers.fpr1.write(LineReg::L13::SET); + } + LineId::Line14 => { + self.registers.rpr1.write(LineReg::L14::SET); + self.registers.fpr1.write(LineReg::L14::SET); + } + LineId::Line15 => { + self.registers.rpr1.write(LineReg::L15::SET); + self.registers.fpr1.write(LineReg::L15::SET); + } + } + } + + pub(crate) fn select_rising_trigger(&self, line: LineId) { + match line { + LineId::Line00 => self.registers.rtsr1.modify(LineReg::L0::SET), + LineId::Line01 => self.registers.rtsr1.modify(LineReg::L1::SET), + LineId::Line02 => self.registers.rtsr1.modify(LineReg::L2::SET), + LineId::Line03 => self.registers.rtsr1.modify(LineReg::L3::SET), + LineId::Line04 => self.registers.rtsr1.modify(LineReg::L4::SET), + LineId::Line05 => self.registers.rtsr1.modify(LineReg::L5::SET), + LineId::Line06 => self.registers.rtsr1.modify(LineReg::L6::SET), + LineId::Line07 => self.registers.rtsr1.modify(LineReg::L7::SET), + LineId::Line08 => self.registers.rtsr1.modify(LineReg::L8::SET), + LineId::Line09 => self.registers.rtsr1.modify(LineReg::L9::SET), + LineId::Line10 => self.registers.rtsr1.modify(LineReg::L10::SET), + LineId::Line11 => self.registers.rtsr1.modify(LineReg::L11::SET), + LineId::Line12 => self.registers.rtsr1.modify(LineReg::L12::SET), + LineId::Line13 => self.registers.rtsr1.modify(LineReg::L13::SET), + LineId::Line14 => self.registers.rtsr1.modify(LineReg::L14::SET), + LineId::Line15 => self.registers.rtsr1.modify(LineReg::L15::SET), + } + } + + pub(crate) fn deselect_rising_trigger(&self, line: LineId) { + match line { + LineId::Line00 => self.registers.rtsr1.modify(LineReg::L0::CLEAR), + LineId::Line01 => self.registers.rtsr1.modify(LineReg::L1::CLEAR), + LineId::Line02 => self.registers.rtsr1.modify(LineReg::L2::CLEAR), + LineId::Line03 => self.registers.rtsr1.modify(LineReg::L3::CLEAR), + LineId::Line04 => self.registers.rtsr1.modify(LineReg::L4::CLEAR), + LineId::Line05 => self.registers.rtsr1.modify(LineReg::L5::CLEAR), + LineId::Line06 => self.registers.rtsr1.modify(LineReg::L6::CLEAR), + LineId::Line07 => self.registers.rtsr1.modify(LineReg::L7::CLEAR), + LineId::Line08 => self.registers.rtsr1.modify(LineReg::L8::CLEAR), + LineId::Line09 => self.registers.rtsr1.modify(LineReg::L9::CLEAR), + LineId::Line10 => self.registers.rtsr1.modify(LineReg::L10::CLEAR), + LineId::Line11 => self.registers.rtsr1.modify(LineReg::L11::CLEAR), + LineId::Line12 => self.registers.rtsr1.modify(LineReg::L12::CLEAR), + LineId::Line13 => self.registers.rtsr1.modify(LineReg::L13::CLEAR), + LineId::Line14 => self.registers.rtsr1.modify(LineReg::L14::CLEAR), + LineId::Line15 => self.registers.rtsr1.modify(LineReg::L15::CLEAR), + } + } + + pub(crate) fn select_falling_trigger(&self, line: LineId) { + match line { + LineId::Line00 => self.registers.ftsr1.modify(LineReg::L0::SET), + LineId::Line01 => self.registers.ftsr1.modify(LineReg::L1::SET), + LineId::Line02 => self.registers.ftsr1.modify(LineReg::L2::SET), + LineId::Line03 => self.registers.ftsr1.modify(LineReg::L3::SET), + LineId::Line04 => self.registers.ftsr1.modify(LineReg::L4::SET), + LineId::Line05 => self.registers.ftsr1.modify(LineReg::L5::SET), + LineId::Line06 => self.registers.ftsr1.modify(LineReg::L6::SET), + LineId::Line07 => self.registers.ftsr1.modify(LineReg::L7::SET), + LineId::Line08 => self.registers.ftsr1.modify(LineReg::L8::SET), + LineId::Line09 => self.registers.ftsr1.modify(LineReg::L9::SET), + LineId::Line10 => self.registers.ftsr1.modify(LineReg::L10::SET), + LineId::Line11 => self.registers.ftsr1.modify(LineReg::L11::SET), + LineId::Line12 => self.registers.ftsr1.modify(LineReg::L12::SET), + LineId::Line13 => self.registers.ftsr1.modify(LineReg::L13::SET), + LineId::Line14 => self.registers.ftsr1.modify(LineReg::L14::SET), + LineId::Line15 => self.registers.ftsr1.modify(LineReg::L15::SET), + } + } + + pub(crate) fn deselect_falling_trigger(&self, line: LineId) { + match line { + LineId::Line00 => self.registers.ftsr1.modify(LineReg::L0::CLEAR), + LineId::Line01 => self.registers.ftsr1.modify(LineReg::L1::CLEAR), + LineId::Line02 => self.registers.ftsr1.modify(LineReg::L2::CLEAR), + LineId::Line03 => self.registers.ftsr1.modify(LineReg::L3::CLEAR), + LineId::Line04 => self.registers.ftsr1.modify(LineReg::L4::CLEAR), + LineId::Line05 => self.registers.ftsr1.modify(LineReg::L5::CLEAR), + LineId::Line06 => self.registers.ftsr1.modify(LineReg::L6::CLEAR), + LineId::Line07 => self.registers.ftsr1.modify(LineReg::L7::CLEAR), + LineId::Line08 => self.registers.ftsr1.modify(LineReg::L8::CLEAR), + LineId::Line09 => self.registers.ftsr1.modify(LineReg::L9::CLEAR), + LineId::Line10 => self.registers.ftsr1.modify(LineReg::L10::CLEAR), + LineId::Line11 => self.registers.ftsr1.modify(LineReg::L11::CLEAR), + LineId::Line12 => self.registers.ftsr1.modify(LineReg::L12::CLEAR), + LineId::Line13 => self.registers.ftsr1.modify(LineReg::L13::CLEAR), + LineId::Line14 => self.registers.ftsr1.modify(LineReg::L14::CLEAR), + LineId::Line15 => self.registers.ftsr1.modify(LineReg::L15::CLEAR), + } + } + + pub(crate) fn is_pending(&self, line: LineId) -> bool { + match line { + LineId::Line00 => { + self.registers.rpr1.is_set(LineReg::L0) || self.registers.fpr1.is_set(LineReg::L0) + } + LineId::Line01 => { + self.registers.rpr1.is_set(LineReg::L1) || self.registers.fpr1.is_set(LineReg::L1) + } + LineId::Line02 => { + self.registers.rpr1.is_set(LineReg::L2) || self.registers.fpr1.is_set(LineReg::L2) + } + LineId::Line03 => { + self.registers.rpr1.is_set(LineReg::L3) || self.registers.fpr1.is_set(LineReg::L3) + } + LineId::Line04 => { + self.registers.rpr1.is_set(LineReg::L4) || self.registers.fpr1.is_set(LineReg::L4) + } + LineId::Line05 => { + self.registers.rpr1.is_set(LineReg::L5) || self.registers.fpr1.is_set(LineReg::L5) + } + LineId::Line06 => { + self.registers.rpr1.is_set(LineReg::L6) || self.registers.fpr1.is_set(LineReg::L6) + } + LineId::Line07 => { + self.registers.rpr1.is_set(LineReg::L7) || self.registers.fpr1.is_set(LineReg::L7) + } + LineId::Line08 => { + self.registers.rpr1.is_set(LineReg::L8) || self.registers.fpr1.is_set(LineReg::L8) + } + LineId::Line09 => { + self.registers.rpr1.is_set(LineReg::L9) || self.registers.fpr1.is_set(LineReg::L9) + } + LineId::Line10 => { + self.registers.rpr1.is_set(LineReg::L10) || self.registers.fpr1.is_set(LineReg::L10) + } + LineId::Line11 => { + self.registers.rpr1.is_set(LineReg::L11) || self.registers.fpr1.is_set(LineReg::L11) + } + LineId::Line12 => { + self.registers.rpr1.is_set(LineReg::L12) || self.registers.fpr1.is_set(LineReg::L12) + } + LineId::Line13 => { + self.registers.rpr1.is_set(LineReg::L13) || self.registers.fpr1.is_set(LineReg::L13) + } + LineId::Line14 => { + self.registers.rpr1.is_set(LineReg::L14) || self.registers.fpr1.is_set(LineReg::L14) + } + LineId::Line15 => { + self.registers.rpr1.is_set(LineReg::L15) || self.registers.fpr1.is_set(LineReg::L15) + } + } + } +} diff --git a/chips/stm32u5xx/src/gpio.rs b/chips/stm32u5xx/src/gpio.rs new file mode 100644 index 0000000000..8efb03cfa8 --- /dev/null +++ b/chips/stm32u5xx/src/gpio.rs @@ -0,0 +1,945 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright OxidOS Automotive 2026. + +use kernel::hil::gpio; +use kernel::hil::gpio::Input; +use kernel::utilities::cells::OptionalCell; +use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; +use kernel::utilities::registers::{ + register_bitfields, register_structs, ReadOnly, ReadWrite, WriteOnly, +}; +use kernel::utilities::StaticRef; + +use crate::exti::{Exti, LineId}; + +register_structs! { + pub GpioRegisters { + /// GPIO port mode register + (0x000 => pub moder: ReadWrite), + /// GPIO port output type register + (0x004 => pub otyper: ReadWrite), + /// GPIO port output speed register + (0x008 => pub ospeedr: ReadWrite), + /// GPIO port pull-up/pull-down register + (0x00C => pub pupdr: ReadWrite), + /// GPIO port input data register + (0x010 => pub idr: ReadOnly), + /// GPIO port output data register + (0x014 => pub odr: ReadWrite), + /// GPIO port bit set/reset register + (0x018 => pub bsrr: WriteOnly), + /// GPIO port configuration lock register + (0x01C => pub lckr: ReadWrite), + /// GPIO alternate function low register + (0x020 => pub afrl: ReadWrite), + /// GPIO alternate function high register + (0x024 => pub afrh: ReadWrite), + (0x028 => @END), + } +} + +register_bitfields![u32, + pub MODER [ + MODER0 OFFSET(0) NUMBITS(2) [ + Input = 0, + Output = 1, + AlternateFunction = 2, + Analog = 3 + ], + MODER1 OFFSET(2) NUMBITS(2) [ + Input = 0, + Output = 1, + AlternateFunction = 2, + Analog = 3 + ], + MODER2 OFFSET(4) NUMBITS(2) [ + Input = 0, + Output = 1, + AlternateFunction = 2, + Analog = 3 + ], + MODER3 OFFSET(6) NUMBITS(2) [ + Input = 0, + Output = 1, + AlternateFunction = 2, + Analog = 3 + ], + MODER4 OFFSET(8) NUMBITS(2) [ + Input = 0, + Output = 1, + AlternateFunction = 2, + Analog = 3 + ], + MODER5 OFFSET(10) NUMBITS(2) [ + Input = 0, + Output = 1, + AlternateFunction = 2, + Analog = 3 + ], + MODER6 OFFSET(12) NUMBITS(2) [ + Input = 0, + Output = 1, + AlternateFunction = 2, + Analog = 3 + ], + MODER7 OFFSET(14) NUMBITS(2) [ + Input = 0, + Output = 1, + AlternateFunction = 2, + Analog = 3 + ], + MODER8 OFFSET(16) NUMBITS(2) [ + Input = 0, + Output = 1, + AlternateFunction = 2, + Analog = 3 + ], + MODER9 OFFSET(18) NUMBITS(2) [ + Input = 0, + Output = 1, + AlternateFunction = 2, + Analog = 3 + ], + MODER10 OFFSET(20) NUMBITS(2) [ + Input = 0, + Output = 1, + AlternateFunction = 2, + Analog = 3 + ], + MODER11 OFFSET(22) NUMBITS(2) [ + Input = 0, + Output = 1, + AlternateFunction = 2, + Analog = 3 + ], + MODER12 OFFSET(24) NUMBITS(2) [ + Input = 0, + Output = 1, + AlternateFunction = 2, + Analog = 3 + ], + MODER13 OFFSET(26) NUMBITS(2) [ + Input = 0, + Output = 1, + AlternateFunction = 2, + Analog = 3 + ], + MODER14 OFFSET(28) NUMBITS(2) [ + Input = 0, + Output = 1, + AlternateFunction = 2, + Analog = 3 + ], + MODER15 OFFSET(30) NUMBITS(2) [ + Input = 0, + Output = 1, + AlternateFunction = 2, + Analog = 3 + ] + ], + pub OTYPER [ + OT0 OFFSET(0) NUMBITS(1) [], + OT1 OFFSET(1) NUMBITS(1) [], + OT2 OFFSET(2) NUMBITS(1) [], + OT3 OFFSET(3) NUMBITS(1) [], + OT4 OFFSET(4) NUMBITS(1) [], + OT5 OFFSET(5) NUMBITS(1) [], + OT6 OFFSET(6) NUMBITS(1) [], + OT7 OFFSET(7) NUMBITS(1) [], + OT8 OFFSET(8) NUMBITS(1) [], + OT9 OFFSET(9) NUMBITS(1) [], + OT10 OFFSET(10) NUMBITS(1) [], + OT11 OFFSET(11) NUMBITS(1) [], + OT12 OFFSET(12) NUMBITS(1) [], + OT13 OFFSET(13) NUMBITS(1) [], + OT14 OFFSET(14) NUMBITS(1) [], + OT15 OFFSET(15) NUMBITS(1) [] + ], + pub OSPEEDR [ + OSPEEDR0 OFFSET(0) NUMBITS(2) [], + OSPEEDR1 OFFSET(2) NUMBITS(2) [], + OSPEEDR2 OFFSET(4) NUMBITS(2) [], + OSPEEDR3 OFFSET(6) NUMBITS(2) [], + OSPEEDR4 OFFSET(8) NUMBITS(2) [], + OSPEEDR5 OFFSET(10) NUMBITS(2) [], + OSPEEDR6 OFFSET(12) NUMBITS(2) [], + OSPEEDR7 OFFSET(14) NUMBITS(2) [], + OSPEEDR8 OFFSET(16) NUMBITS(2) [], + OSPEEDR9 OFFSET(18) NUMBITS(2) [], + OSPEEDR10 OFFSET(20) NUMBITS(2) [], + OSPEEDR11 OFFSET(22) NUMBITS(2) [], + OSPEEDR12 OFFSET(24) NUMBITS(2) [], + OSPEEDR13 OFFSET(26) NUMBITS(2) [], + OSPEEDR14 OFFSET(28) NUMBITS(2) [], + OSPEEDR15 OFFSET(30) NUMBITS(2) [] + ], + pub PUPDR [ + PUPDR0 OFFSET(0) NUMBITS(2) [ + None = 0, + PullUp = 1, + PullDown = 2 + ], + PUPDR1 OFFSET(2) NUMBITS(2) [ + None = 0, + PullUp = 1, + PullDown = 2 + ], + PUPDR2 OFFSET(4) NUMBITS(2) [ + None = 0, + PullUp = 1, + PullDown = 2 + ], + PUPDR3 OFFSET(6) NUMBITS(2) [ + None = 0, + PullUp = 1, + PullDown = 2 + ], + PUPDR4 OFFSET(8) NUMBITS(2) [ + None = 0, + PullUp = 1, + PullDown = 2 + ], + PUPDR5 OFFSET(10) NUMBITS(2) [ + None = 0, + PullUp = 1, + PullDown = 2 + ], + PUPDR6 OFFSET(12) NUMBITS(2) [ + None = 0, + PullUp = 1, + PullDown = 2 + ], + PUPDR7 OFFSET(14) NUMBITS(2) [ + None = 0, + PullUp = 1, + PullDown = 2 + ], + PUPDR8 OFFSET(16) NUMBITS(2) [ + None = 0, + PullUp = 1, + PullDown = 2 + ], + PUPDR9 OFFSET(18) NUMBITS(2) [ + None = 0, + PullUp = 1, + PullDown = 2 + ], + PUPDR10 OFFSET(20) NUMBITS(2) [ + None = 0, + PullUp = 1, + PullDown = 2 + ], + PUPDR11 OFFSET(22) NUMBITS(2) [ + None = 0, + PullUp = 1, + PullDown = 2 + ], + PUPDR12 OFFSET(24) NUMBITS(2) [ + None = 0, + PullUp = 1, + PullDown = 2 + ], + PUPDR13 OFFSET(26) NUMBITS(2) [ + None = 0, + PullUp = 1, + PullDown = 2 + ], + PUPDR14 OFFSET(28) NUMBITS(2) [ + None = 0, + PullUp = 1, + PullDown = 2 + ], + PUPDR15 OFFSET(30) NUMBITS(2) [ + None = 0, + PullUp = 1, + PullDown = 2 + ] + ], + pub IDR [ + ID0 OFFSET(0) NUMBITS(1) [], + ID1 OFFSET(1) NUMBITS(1) [], + ID2 OFFSET(2) NUMBITS(1) [], + ID3 OFFSET(3) NUMBITS(1) [], + ID4 OFFSET(4) NUMBITS(1) [], + ID5 OFFSET(5) NUMBITS(1) [], + ID6 OFFSET(6) NUMBITS(1) [], + ID7 OFFSET(7) NUMBITS(1) [], + ID8 OFFSET(8) NUMBITS(1) [], + ID9 OFFSET(9) NUMBITS(1) [], + ID10 OFFSET(10) NUMBITS(1) [], + ID11 OFFSET(11) NUMBITS(1) [], + ID12 OFFSET(12) NUMBITS(1) [], + ID13 OFFSET(13) NUMBITS(1) [], + ID14 OFFSET(14) NUMBITS(1) [], + ID15 OFFSET(15) NUMBITS(1) [] + ], + pub ODR [ + OD0 OFFSET(0) NUMBITS(1) [], + OD1 OFFSET(1) NUMBITS(1) [], + OD2 OFFSET(2) NUMBITS(1) [], + OD3 OFFSET(3) NUMBITS(1) [], + OD4 OFFSET(4) NUMBITS(1) [], + OD5 OFFSET(5) NUMBITS(1) [], + OD6 OFFSET(6) NUMBITS(1) [], + OD7 OFFSET(7) NUMBITS(1) [], + OD8 OFFSET(8) NUMBITS(1) [], + OD9 OFFSET(9) NUMBITS(1) [], + OD10 OFFSET(10) NUMBITS(1) [], + OD11 OFFSET(11) NUMBITS(1) [], + OD12 OFFSET(12) NUMBITS(1) [], + OD13 OFFSET(13) NUMBITS(1) [], + OD14 OFFSET(14) NUMBITS(1) [], + OD15 OFFSET(15) NUMBITS(1) [] + ], + pub BSRR [ + BS0 OFFSET(0) NUMBITS(1) [], + BS1 OFFSET(1) NUMBITS(1) [], + BS2 OFFSET(2) NUMBITS(1) [], + BS3 OFFSET(3) NUMBITS(1) [], + BS4 OFFSET(4) NUMBITS(1) [], + BS5 OFFSET(5) NUMBITS(1) [], + BS6 OFFSET(6) NUMBITS(1) [], + BS7 OFFSET(7) NUMBITS(1) [], + BS8 OFFSET(8) NUMBITS(1) [], + BS9 OFFSET(9) NUMBITS(1) [], + BS10 OFFSET(10) NUMBITS(1) [], + BS11 OFFSET(11) NUMBITS(1) [], + BS12 OFFSET(12) NUMBITS(1) [], + BS13 OFFSET(13) NUMBITS(1) [], + BS14 OFFSET(14) NUMBITS(1) [], + BS15 OFFSET(15) NUMBITS(1) [], + BR0 OFFSET(16) NUMBITS(1) [], + BR1 OFFSET(17) NUMBITS(1) [], + BR2 OFFSET(18) NUMBITS(1) [], + BR3 OFFSET(19) NUMBITS(1) [], + BR4 OFFSET(20) NUMBITS(1) [], + BR5 OFFSET(21) NUMBITS(1) [], + BR6 OFFSET(22) NUMBITS(1) [], + BR7 OFFSET(23) NUMBITS(1) [], + BR8 OFFSET(24) NUMBITS(1) [], + BR9 OFFSET(25) NUMBITS(1) [], + BR10 OFFSET(26) NUMBITS(1) [], + BR11 OFFSET(27) NUMBITS(1) [], + BR12 OFFSET(28) NUMBITS(1) [], + BR13 OFFSET(29) NUMBITS(1) [], + BR14 OFFSET(30) NUMBITS(1) [], + BR15 OFFSET(31) NUMBITS(1) [] + ], + pub AFRL [ + AF0 OFFSET(0) NUMBITS(4) [], + AF1 OFFSET(4) NUMBITS(4) [], + AF2 OFFSET(8) NUMBITS(4) [], + AF3 OFFSET(12) NUMBITS(4) [], + AF4 OFFSET(16) NUMBITS(4) [], + AF5 OFFSET(20) NUMBITS(4) [], + AF6 OFFSET(24) NUMBITS(4) [], + AF7 OFFSET(28) NUMBITS(4) [] + ], + pub AFRH [ + AF8 OFFSET(0) NUMBITS(4) [], + AF9 OFFSET(4) NUMBITS(4) [], + AF10 OFFSET(8) NUMBITS(4) [], + AF11 OFFSET(12) NUMBITS(4) [], + AF12 OFFSET(16) NUMBITS(4) [], + AF13 OFFSET(20) NUMBITS(4) [], + AF14 OFFSET(24) NUMBITS(4) [], + AF15 OFFSET(28) NUMBITS(4) [] + ] +]; + +pub const GPIO_A_BASE: StaticRef = + unsafe { StaticRef::new(0x52020000 as *const GpioRegisters) }; + +pub const GPIO_C_BASE: StaticRef = + unsafe { StaticRef::new(0x52020800 as *const GpioRegisters) }; + +#[derive(Copy, Clone, PartialEq)] +pub enum PinId { + Pin00 = 0, + Pin01 = 1, + Pin02 = 2, + Pin03 = 3, + Pin04 = 4, + Pin05 = 5, + Pin06 = 6, + Pin07 = 7, + Pin08 = 8, + Pin09 = 9, + Pin10 = 10, + Pin11 = 11, + Pin12 = 12, + Pin13 = 13, + Pin14 = 14, + Pin15 = 15, +} + +#[derive(Copy, Clone, PartialEq)] +pub enum Mode { + Input = 0, + Output = 1, + AlternateFunction = 2, + Analog = 3, +} + +pub enum PullUpPullDown { + None = 0, + PullUp = 1, + PullDown = 2, +} + +#[derive(Copy, Clone, PartialEq)] +pub enum GpioPort { + PortA = 0, + PortB = 1, + PortC = 2, + PortD = 3, + PortE = 4, + PortF = 5, + PortG = 6, + PortH = 7, + PortI = 8, + PortJ = 9, +} + +pub struct Pin<'a> { + registers: StaticRef, + pin: PinId, + exti: &'a Exti<'a>, + port_id: GpioPort, + client: OptionalCell<&'a dyn gpio::Client>, + exti_lineid: OptionalCell, +} + +impl<'a> Pin<'a> { + // Only our own crate can create pins + pub(crate) const fn new( + base: StaticRef, + pin: PinId, + exti: &'a Exti<'a>, + port_id: GpioPort, + ) -> Pin<'a> { + Pin { + registers: base, + pin, + exti, + port_id, + client: OptionalCell::empty(), + exti_lineid: OptionalCell::empty(), + } + } + /// Sets the mode of the pin. + /// + /// This is a low-level function intended for board-level muxing. + /// For general GPIO usage, use the `kernel::hil::gpio::Configure` trait. + pub fn set_mode(&self, mode: Mode) { + match self.pin { + PinId::Pin00 => self.registers.moder.modify(MODER::MODER0.val(mode as u32)), + PinId::Pin01 => self.registers.moder.modify(MODER::MODER1.val(mode as u32)), + PinId::Pin02 => self.registers.moder.modify(MODER::MODER2.val(mode as u32)), + PinId::Pin03 => self.registers.moder.modify(MODER::MODER3.val(mode as u32)), + PinId::Pin04 => self.registers.moder.modify(MODER::MODER4.val(mode as u32)), + PinId::Pin05 => self.registers.moder.modify(MODER::MODER5.val(mode as u32)), + PinId::Pin06 => self.registers.moder.modify(MODER::MODER6.val(mode as u32)), + PinId::Pin07 => self.registers.moder.modify(MODER::MODER7.val(mode as u32)), + PinId::Pin08 => self.registers.moder.modify(MODER::MODER8.val(mode as u32)), + PinId::Pin09 => self.registers.moder.modify(MODER::MODER9.val(mode as u32)), + PinId::Pin10 => self.registers.moder.modify(MODER::MODER10.val(mode as u32)), + PinId::Pin11 => self.registers.moder.modify(MODER::MODER11.val(mode as u32)), + PinId::Pin12 => self.registers.moder.modify(MODER::MODER12.val(mode as u32)), + PinId::Pin13 => self.registers.moder.modify(MODER::MODER13.val(mode as u32)), + PinId::Pin14 => self.registers.moder.modify(MODER::MODER14.val(mode as u32)), + PinId::Pin15 => self.registers.moder.modify(MODER::MODER15.val(mode as u32)), + } + } + /// Sets the output speed to 'Very High'. + /// + /// This is a low-level function intended for high-speed peripherals + /// like USART or SPI. + pub fn set_speed_high(&self) { + match self.pin { + PinId::Pin00 => self.registers.ospeedr.modify(OSPEEDR::OSPEEDR0.val(3)), + PinId::Pin01 => self.registers.ospeedr.modify(OSPEEDR::OSPEEDR1.val(3)), + PinId::Pin02 => self.registers.ospeedr.modify(OSPEEDR::OSPEEDR2.val(3)), + PinId::Pin03 => self.registers.ospeedr.modify(OSPEEDR::OSPEEDR3.val(3)), + PinId::Pin04 => self.registers.ospeedr.modify(OSPEEDR::OSPEEDR4.val(3)), + PinId::Pin05 => self.registers.ospeedr.modify(OSPEEDR::OSPEEDR5.val(3)), + PinId::Pin06 => self.registers.ospeedr.modify(OSPEEDR::OSPEEDR6.val(3)), + PinId::Pin07 => self.registers.ospeedr.modify(OSPEEDR::OSPEEDR7.val(3)), + PinId::Pin08 => self.registers.ospeedr.modify(OSPEEDR::OSPEEDR8.val(3)), + PinId::Pin09 => self.registers.ospeedr.modify(OSPEEDR::OSPEEDR9.val(3)), + PinId::Pin10 => self.registers.ospeedr.modify(OSPEEDR::OSPEEDR10.val(3)), + PinId::Pin11 => self.registers.ospeedr.modify(OSPEEDR::OSPEEDR11.val(3)), + PinId::Pin12 => self.registers.ospeedr.modify(OSPEEDR::OSPEEDR12.val(3)), + PinId::Pin13 => self.registers.ospeedr.modify(OSPEEDR::OSPEEDR13.val(3)), + PinId::Pin14 => self.registers.ospeedr.modify(OSPEEDR::OSPEEDR14.val(3)), + PinId::Pin15 => self.registers.ospeedr.modify(OSPEEDR::OSPEEDR15.val(3)), + } + } + + /// Configures the pin for an Alternate Function (AF). + /// + /// Refer to the STM32U5 datasheet for the AF mapping table. + /// This is a low-level function intended for peripheral initialization. + pub fn set_alternate_function(&self, func: u32) { + match self.pin { + PinId::Pin00 => self.registers.afrl.modify(AFRL::AF0.val(func)), + PinId::Pin01 => self.registers.afrl.modify(AFRL::AF1.val(func)), + PinId::Pin02 => self.registers.afrl.modify(AFRL::AF2.val(func)), + PinId::Pin03 => self.registers.afrl.modify(AFRL::AF3.val(func)), + PinId::Pin04 => self.registers.afrl.modify(AFRL::AF4.val(func)), + PinId::Pin05 => self.registers.afrl.modify(AFRL::AF5.val(func)), + PinId::Pin06 => self.registers.afrl.modify(AFRL::AF6.val(func)), + PinId::Pin07 => self.registers.afrl.modify(AFRL::AF7.val(func)), + PinId::Pin08 => self.registers.afrh.modify(AFRH::AF8.val(func)), + PinId::Pin09 => self.registers.afrh.modify(AFRH::AF9.val(func)), + PinId::Pin10 => self.registers.afrh.modify(AFRH::AF10.val(func)), + PinId::Pin11 => self.registers.afrh.modify(AFRH::AF11.val(func)), + PinId::Pin12 => self.registers.afrh.modify(AFRH::AF12.val(func)), + PinId::Pin13 => self.registers.afrh.modify(AFRH::AF13.val(func)), + PinId::Pin14 => self.registers.afrh.modify(AFRH::AF14.val(func)), + PinId::Pin15 => self.registers.afrh.modify(AFRH::AF15.val(func)), + } + } + + fn get_mode(&self) -> Mode { + let val = match self.pin { + PinId::Pin00 => self.registers.moder.read(MODER::MODER0), + PinId::Pin01 => self.registers.moder.read(MODER::MODER1), + PinId::Pin02 => self.registers.moder.read(MODER::MODER2), + PinId::Pin03 => self.registers.moder.read(MODER::MODER3), + PinId::Pin04 => self.registers.moder.read(MODER::MODER4), + PinId::Pin05 => self.registers.moder.read(MODER::MODER5), + PinId::Pin06 => self.registers.moder.read(MODER::MODER6), + PinId::Pin07 => self.registers.moder.read(MODER::MODER7), + PinId::Pin08 => self.registers.moder.read(MODER::MODER8), + PinId::Pin09 => self.registers.moder.read(MODER::MODER9), + PinId::Pin10 => self.registers.moder.read(MODER::MODER10), + PinId::Pin11 => self.registers.moder.read(MODER::MODER11), + PinId::Pin12 => self.registers.moder.read(MODER::MODER12), + PinId::Pin13 => self.registers.moder.read(MODER::MODER13), + PinId::Pin14 => self.registers.moder.read(MODER::MODER14), + PinId::Pin15 => self.registers.moder.read(MODER::MODER15), + }; + match val { + 0 => Mode::Input, + 1 => Mode::Output, + 2 => Mode::AlternateFunction, + _ => Mode::Analog, + } + } + + fn set_pull(&self, pull: PullUpPullDown) { + match self.pin { + PinId::Pin00 => self.registers.pupdr.modify(PUPDR::PUPDR0.val(pull as u32)), + PinId::Pin01 => self.registers.pupdr.modify(PUPDR::PUPDR1.val(pull as u32)), + PinId::Pin02 => self.registers.pupdr.modify(PUPDR::PUPDR2.val(pull as u32)), + PinId::Pin03 => self.registers.pupdr.modify(PUPDR::PUPDR3.val(pull as u32)), + PinId::Pin04 => self.registers.pupdr.modify(PUPDR::PUPDR4.val(pull as u32)), + PinId::Pin05 => self.registers.pupdr.modify(PUPDR::PUPDR5.val(pull as u32)), + PinId::Pin06 => self.registers.pupdr.modify(PUPDR::PUPDR6.val(pull as u32)), + PinId::Pin07 => self.registers.pupdr.modify(PUPDR::PUPDR7.val(pull as u32)), + PinId::Pin08 => self.registers.pupdr.modify(PUPDR::PUPDR8.val(pull as u32)), + PinId::Pin09 => self.registers.pupdr.modify(PUPDR::PUPDR9.val(pull as u32)), + PinId::Pin10 => self.registers.pupdr.modify(PUPDR::PUPDR10.val(pull as u32)), + PinId::Pin11 => self.registers.pupdr.modify(PUPDR::PUPDR11.val(pull as u32)), + PinId::Pin12 => self.registers.pupdr.modify(PUPDR::PUPDR12.val(pull as u32)), + PinId::Pin13 => self.registers.pupdr.modify(PUPDR::PUPDR13.val(pull as u32)), + PinId::Pin14 => self.registers.pupdr.modify(PUPDR::PUPDR14.val(pull as u32)), + PinId::Pin15 => self.registers.pupdr.modify(PUPDR::PUPDR15.val(pull as u32)), + } + } + + fn get_pull(&self) -> PullUpPullDown { + let val = match self.pin { + PinId::Pin00 => self.registers.pupdr.read(PUPDR::PUPDR0), + PinId::Pin01 => self.registers.pupdr.read(PUPDR::PUPDR1), + PinId::Pin02 => self.registers.pupdr.read(PUPDR::PUPDR2), + PinId::Pin03 => self.registers.pupdr.read(PUPDR::PUPDR3), + PinId::Pin04 => self.registers.pupdr.read(PUPDR::PUPDR4), + PinId::Pin05 => self.registers.pupdr.read(PUPDR::PUPDR5), + PinId::Pin06 => self.registers.pupdr.read(PUPDR::PUPDR6), + PinId::Pin07 => self.registers.pupdr.read(PUPDR::PUPDR7), + PinId::Pin08 => self.registers.pupdr.read(PUPDR::PUPDR8), + PinId::Pin09 => self.registers.pupdr.read(PUPDR::PUPDR9), + PinId::Pin10 => self.registers.pupdr.read(PUPDR::PUPDR10), + PinId::Pin11 => self.registers.pupdr.read(PUPDR::PUPDR11), + PinId::Pin12 => self.registers.pupdr.read(PUPDR::PUPDR12), + PinId::Pin13 => self.registers.pupdr.read(PUPDR::PUPDR13), + PinId::Pin14 => self.registers.pupdr.read(PUPDR::PUPDR14), + PinId::Pin15 => self.registers.pupdr.read(PUPDR::PUPDR15), + }; + match val { + 1 => PullUpPullDown::PullUp, + 2 => PullUpPullDown::PullDown, + _ => PullUpPullDown::None, + } + } +} + +impl gpio::Configure for Pin<'_> { + fn configuration(&self) -> gpio::Configuration { + match self.get_mode() { + Mode::Input => gpio::Configuration::Input, + Mode::Output => gpio::Configuration::Output, + Mode::AlternateFunction => gpio::Configuration::Function, + Mode::Analog => gpio::Configuration::LowPower, + } + } + + fn make_output(&self) -> gpio::Configuration { + self.set_mode(Mode::Output); + gpio::Configuration::Output + } + + fn disable_output(&self) -> gpio::Configuration { + self.set_mode(Mode::Input); + gpio::Configuration::Input + } + + fn make_input(&self) -> gpio::Configuration { + self.set_mode(Mode::Input); + gpio::Configuration::Input + } + + fn disable_input(&self) -> gpio::Configuration { + self.set_mode(Mode::Analog); + gpio::Configuration::LowPower + } + + /// Deactivates the pin to its lowest power state. + /// + /// According to RM0456 (STM32U5 Reference Manual), Section 13.3.12 + /// (Analog configuration), setting a pin to Analog mode deactivates + /// the Schmitt trigger input, providing zero consumption for every + /// analog value of the I/O pin. We do not disable the clock to + /// the entire GPIO port here because other pins on the same + /// port may still be in use. + fn deactivate_to_low_power(&self) { + self.set_mode(Mode::Analog); + } + + fn set_floating_state(&self, state: gpio::FloatingState) { + match state { + gpio::FloatingState::PullUp => self.set_pull(PullUpPullDown::PullUp), + gpio::FloatingState::PullDown => self.set_pull(PullUpPullDown::PullDown), + gpio::FloatingState::PullNone => self.set_pull(PullUpPullDown::None), + } + } + + fn floating_state(&self) -> gpio::FloatingState { + match self.get_pull() { + PullUpPullDown::PullUp => gpio::FloatingState::PullUp, + PullUpPullDown::PullDown => gpio::FloatingState::PullDown, + PullUpPullDown::None => gpio::FloatingState::PullNone, + } + } +} + +impl gpio::Input for Pin<'_> { + fn read(&self) -> bool { + match self.pin { + PinId::Pin00 => self.registers.idr.is_set(IDR::ID0), + PinId::Pin01 => self.registers.idr.is_set(IDR::ID1), + PinId::Pin02 => self.registers.idr.is_set(IDR::ID2), + PinId::Pin03 => self.registers.idr.is_set(IDR::ID3), + PinId::Pin04 => self.registers.idr.is_set(IDR::ID4), + PinId::Pin05 => self.registers.idr.is_set(IDR::ID5), + PinId::Pin06 => self.registers.idr.is_set(IDR::ID6), + PinId::Pin07 => self.registers.idr.is_set(IDR::ID7), + PinId::Pin08 => self.registers.idr.is_set(IDR::ID8), + PinId::Pin09 => self.registers.idr.is_set(IDR::ID9), + PinId::Pin10 => self.registers.idr.is_set(IDR::ID10), + PinId::Pin11 => self.registers.idr.is_set(IDR::ID11), + PinId::Pin12 => self.registers.idr.is_set(IDR::ID12), + PinId::Pin13 => self.registers.idr.is_set(IDR::ID13), + PinId::Pin14 => self.registers.idr.is_set(IDR::ID14), + PinId::Pin15 => self.registers.idr.is_set(IDR::ID15), + } + } +} + +impl gpio::Output for Pin<'_> { + fn set(&self) { + match self.pin { + PinId::Pin00 => self.registers.bsrr.write(BSRR::BS0::SET), + PinId::Pin01 => self.registers.bsrr.write(BSRR::BS1::SET), + PinId::Pin02 => self.registers.bsrr.write(BSRR::BS2::SET), + PinId::Pin03 => self.registers.bsrr.write(BSRR::BS3::SET), + PinId::Pin04 => self.registers.bsrr.write(BSRR::BS4::SET), + PinId::Pin05 => self.registers.bsrr.write(BSRR::BS5::SET), + PinId::Pin06 => self.registers.bsrr.write(BSRR::BS6::SET), + PinId::Pin07 => self.registers.bsrr.write(BSRR::BS7::SET), + PinId::Pin08 => self.registers.bsrr.write(BSRR::BS8::SET), + PinId::Pin09 => self.registers.bsrr.write(BSRR::BS9::SET), + PinId::Pin10 => self.registers.bsrr.write(BSRR::BS10::SET), + PinId::Pin11 => self.registers.bsrr.write(BSRR::BS11::SET), + PinId::Pin12 => self.registers.bsrr.write(BSRR::BS12::SET), + PinId::Pin13 => self.registers.bsrr.write(BSRR::BS13::SET), + PinId::Pin14 => self.registers.bsrr.write(BSRR::BS14::SET), + PinId::Pin15 => self.registers.bsrr.write(BSRR::BS15::SET), + } + } + + fn clear(&self) { + match self.pin { + PinId::Pin00 => self.registers.bsrr.write(BSRR::BR0::SET), + PinId::Pin01 => self.registers.bsrr.write(BSRR::BR1::SET), + PinId::Pin02 => self.registers.bsrr.write(BSRR::BR2::SET), + PinId::Pin03 => self.registers.bsrr.write(BSRR::BR3::SET), + PinId::Pin04 => self.registers.bsrr.write(BSRR::BR4::SET), + PinId::Pin05 => self.registers.bsrr.write(BSRR::BR5::SET), + PinId::Pin06 => self.registers.bsrr.write(BSRR::BR6::SET), + PinId::Pin07 => self.registers.bsrr.write(BSRR::BR7::SET), + PinId::Pin08 => self.registers.bsrr.write(BSRR::BR8::SET), + PinId::Pin09 => self.registers.bsrr.write(BSRR::BR9::SET), + PinId::Pin10 => self.registers.bsrr.write(BSRR::BR10::SET), + PinId::Pin11 => self.registers.bsrr.write(BSRR::BR11::SET), + PinId::Pin12 => self.registers.bsrr.write(BSRR::BR12::SET), + PinId::Pin13 => self.registers.bsrr.write(BSRR::BR13::SET), + PinId::Pin14 => self.registers.bsrr.write(BSRR::BR14::SET), + PinId::Pin15 => self.registers.bsrr.write(BSRR::BR15::SET), + } + } + + fn toggle(&self) -> bool { + match self.pin { + PinId::Pin00 => { + let val = self.registers.odr.is_set(ODR::OD0); + if val { + self.clear(); + } else { + self.set(); + } + } + PinId::Pin01 => { + let val = self.registers.odr.is_set(ODR::OD1); + if val { + self.clear(); + } else { + self.set(); + } + } + PinId::Pin02 => { + let val = self.registers.odr.is_set(ODR::OD2); + if val { + self.clear(); + } else { + self.set(); + } + } + PinId::Pin03 => { + let val = self.registers.odr.is_set(ODR::OD3); + if val { + self.clear(); + } else { + self.set(); + } + } + PinId::Pin04 => { + let val = self.registers.odr.is_set(ODR::OD4); + if val { + self.clear(); + } else { + self.set(); + } + } + PinId::Pin05 => { + let val = self.registers.odr.is_set(ODR::OD5); + if val { + self.clear(); + } else { + self.set(); + } + } + PinId::Pin06 => { + let val = self.registers.odr.is_set(ODR::OD6); + if val { + self.clear(); + } else { + self.set(); + } + } + PinId::Pin07 => { + let val = self.registers.odr.is_set(ODR::OD7); + if val { + self.clear(); + } else { + self.set(); + } + } + PinId::Pin08 => { + let val = self.registers.odr.is_set(ODR::OD8); + if val { + self.clear(); + } else { + self.set(); + } + } + PinId::Pin09 => { + let val = self.registers.odr.is_set(ODR::OD9); + if val { + self.clear(); + } else { + self.set(); + } + } + PinId::Pin10 => { + let val = self.registers.odr.is_set(ODR::OD10); + if val { + self.clear(); + } else { + self.set(); + } + } + PinId::Pin11 => { + let val = self.registers.odr.is_set(ODR::OD11); + if val { + self.clear(); + } else { + self.set(); + } + } + PinId::Pin12 => { + let val = self.registers.odr.is_set(ODR::OD12); + if val { + self.clear(); + } else { + self.set(); + } + } + PinId::Pin13 => { + let val = self.registers.odr.is_set(ODR::OD13); + if val { + self.clear(); + } else { + self.set(); + } + } + PinId::Pin14 => { + let val = self.registers.odr.is_set(ODR::OD14); + if val { + self.clear(); + } else { + self.set(); + } + } + PinId::Pin15 => { + let val = self.registers.odr.is_set(ODR::OD15); + if val { + self.clear(); + } else { + self.set(); + } + } + } + self.read() + } +} + +impl<'a> gpio::Interrupt<'a> for Pin<'a> { + fn set_client(&self, client: &'a dyn gpio::Client) { + self.client.set(client); + } + + fn enable_interrupts(&self, mode: gpio::InterruptEdge) { + let line = LineId::from(self.pin); + self.exti_lineid.set(line); + + self.client.map(|client| { + self.exti.register_client(line, client); + }); + + // 1. Route the port to the line + self.exti.select_port(line, self.port_id as u32); + + // 2. Configure the EXTI line as Secure. + // On the STM32U5, the EXTI controller is TrustZone-aware. Since the Tock + // kernel is running in the Secure state, we must explicitly mark the + // interrupt line as Secure in the EXTI_SECCFGR1 register. If we omit this, + // the hardware firewall will block the interrupt signal from reaching + // the Secure CPU context. + self.exti.set_secure(line); + + self.exti.mask_interrupt(line); + self.exti.clear_pending(line); + + match mode { + gpio::InterruptEdge::EitherEdge => { + self.exti.select_rising_trigger(line); + self.exti.select_falling_trigger(line); + } + gpio::InterruptEdge::RisingEdge => { + self.exti.select_rising_trigger(line); + self.exti.deselect_falling_trigger(line); + } + gpio::InterruptEdge::FallingEdge => { + self.exti.deselect_rising_trigger(line); + self.exti.select_falling_trigger(line); + } + } + self.exti.unmask_interrupt(line); + } + + fn disable_interrupts(&self) { + self.exti_lineid.map(|line| { + self.exti.mask_interrupt(line); + self.exti.clear_pending(line); + }); + } + + fn is_pending(&self) -> bool { + self.exti_lineid + .map_or(false, |line| self.exti.is_pending(line)) + } +} + +impl From for LineId { + fn from(pin: PinId) -> Self { + match pin { + PinId::Pin00 => LineId::Line00, + PinId::Pin01 => LineId::Line01, + PinId::Pin02 => LineId::Line02, + PinId::Pin03 => LineId::Line03, + PinId::Pin04 => LineId::Line04, + PinId::Pin05 => LineId::Line05, + PinId::Pin06 => LineId::Line06, + PinId::Pin07 => LineId::Line07, + PinId::Pin08 => LineId::Line08, + PinId::Pin09 => LineId::Line09, + PinId::Pin10 => LineId::Line10, + PinId::Pin11 => LineId::Line11, + PinId::Pin12 => LineId::Line12, + PinId::Pin13 => LineId::Line13, + PinId::Pin14 => LineId::Line14, + PinId::Pin15 => LineId::Line15, + } + } +} + +/// Represents a collection of 16 GPIO pins. +pub struct Port<'a> { + registers: StaticRef, + exti: &'a Exti<'a>, + port_id: GpioPort, +} + +impl<'a> Port<'a> { + /// Creates a new Port instance. + pub const fn new( + base: StaticRef, + exti: &'a Exti<'a>, + port_id: GpioPort, + ) -> Self { + Port { + registers: base, + exti, + port_id, + } + } + + /// Returns a Pin instance for a specific physical pin on this port. + pub fn pin(&self, pin: PinId) -> Pin<'a> { + Pin::new(self.registers, pin, self.exti, self.port_id) + } +} diff --git a/chips/stm32u5xx/src/lib.rs b/chips/stm32u5xx/src/lib.rs new file mode 100644 index 0000000000..f9bc0e4651 --- /dev/null +++ b/chips/stm32u5xx/src/lib.rs @@ -0,0 +1,45 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright OxidOS Automotive 2026. + +#![no_std] + +pub mod chip; +pub mod dma; +pub mod exti; +pub mod gpio; +pub mod nvic; +pub mod rcc; +pub mod tim; +pub mod usart; + +use cortexm33::{initialize_ram_jump_to_main, unhandled_interrupt, CortexM33, CortexMVariant}; + +extern "C" { + // _estack is the initial stack pointer (defined in the linker script). + fn _estack(); +} + +#[cfg_attr( + all(target_arch = "arm", target_os = "none"), + link_section = ".vectors" +)] +#[cfg_attr(all(target_arch = "arm", target_os = "none"), used)] +pub static BASE_VECTORS: [unsafe extern "C" fn(); 16] = [ + _estack, // 0x00: Initial stack pointer + initialize_ram_jump_to_main, // 0x04: Reset + unhandled_interrupt, // 0x08: NMI + CortexM33::HARD_FAULT_HANDLER, // 0x0C: HardFault + unhandled_interrupt, // 0x10: MemManage + unhandled_interrupt, // 0x14: BusFault + unhandled_interrupt, // 0x18: UsageFault + unhandled_interrupt, // 0x1C: SecureFault + unhandled_interrupt, // 0x20: Reserved + unhandled_interrupt, // 0x24: Reserved + unhandled_interrupt, // 0x28: Reserved + CortexM33::SVC_HANDLER, // 0x2C: SVCall + unhandled_interrupt, // 0x30: Debug monitor + unhandled_interrupt, // 0x34: Reserved + unhandled_interrupt, // 0x38: PendSV + CortexM33::SYSTICK_HANDLER, // 0x3C: SysTick +]; diff --git a/chips/stm32u5xx/src/nvic.rs b/chips/stm32u5xx/src/nvic.rs new file mode 100644 index 0000000000..cf73cbe982 --- /dev/null +++ b/chips/stm32u5xx/src/nvic.rs @@ -0,0 +1,148 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. +// Copyright OxidOS Automotive 2026. + +//! Named constants for NVIC ids shared across the stm32u5xx family of chips + +pub const WWDG_IRQ: u32 = 0; +pub const PVD_PVM_IRQ: u32 = 1; +pub const RTC_IRQ: u32 = 2; +pub const RTC_S_IRQ: u32 = 3; +pub const TAMP_IRQ: u32 = 4; +pub const RAMCFG_IRQ: u32 = 5; +pub const FLASH_IRQ: u32 = 6; +pub const FLASH_S_IRQ: u32 = 7; +pub const GTZC_IRQ: u32 = 8; +pub const RCC_IRQ: u32 = 9; +pub const RCC_S_IRQ: u32 = 10; +pub const EXTI0_IRQ: u32 = 11; +pub const EXTI1_IRQ: u32 = 12; +pub const EXTI2_IRQ: u32 = 13; +pub const EXTI3_IRQ: u32 = 14; +pub const EXTI4_IRQ: u32 = 15; +pub const EXTI5_IRQ: u32 = 16; +pub const EXTI6_IRQ: u32 = 17; +pub const EXTI7_IRQ: u32 = 18; +pub const EXTI8_IRQ: u32 = 19; +pub const EXTI9_IRQ: u32 = 20; +pub const EXTI10_IRQ: u32 = 21; +pub const EXTI11_IRQ: u32 = 22; +pub const EXTI12_IRQ: u32 = 23; +pub const EXTI13_IRQ: u32 = 24; +pub const EXTI14_IRQ: u32 = 25; +pub const EXTI15_IRQ: u32 = 26; +pub const IWDG_IRQ: u32 = 27; +pub const SAES_IRQ: u32 = 28; +pub const GPDMA1_CH0_IRQ: u32 = 29; +pub const GPDMA1_CH1_IRQ: u32 = 30; +pub const GPDMA1_CH2_IRQ: u32 = 31; +pub const GPDMA1_CH3_IRQ: u32 = 32; +pub const GPDMA1_CH4_IRQ: u32 = 33; +pub const GPDMA1_CH5_IRQ: u32 = 34; +pub const GPDMA1_CH6_IRQ: u32 = 35; +pub const GPDMA1_CH7_IRQ: u32 = 36; +pub const ADC1_2_IRQ: u32 = 37; +pub const DAC1_IRQ: u32 = 38; +pub const FDCAN1_IT0_IRQ: u32 = 39; +pub const FDCAN1_IT1_IRQ: u32 = 40; +pub const TIM1_BRK_IRQ: u32 = 41; +pub const TIM1_UP_IRQ: u32 = 42; +pub const TIM1_TRG_COM_IRQ: u32 = 43; +pub const TIM1_CC_IRQ: u32 = 44; +pub const TIM2_IRQ: u32 = 45; +pub const TIM3_IRQ: u32 = 46; +pub const TIM4_IRQ: u32 = 47; +pub const TIM5_IRQ: u32 = 48; +pub const TIM6_IRQ: u32 = 49; +pub const TIM7_IRQ: u32 = 50; +pub const TIM8_BRK_IRQ: u32 = 51; +pub const TIM8_UP_IRQ: u32 = 52; +pub const TIM8_TRG_COM_IRQ: u32 = 53; +pub const TIM8_CC_IRQ: u32 = 54; +pub const I2C1_EV_IRQ: u32 = 55; +pub const I2C1_ER_IRQ: u32 = 56; +pub const I2C2_EV_IRQ: u32 = 57; +pub const I2C2_ER_IRQ: u32 = 58; +pub const SPI1_IRQ: u32 = 59; +pub const SPI2_IRQ: u32 = 60; +pub const USART1_IRQ: u32 = 61; +pub const USART2_IRQ: u32 = 62; +pub const USART3_IRQ: u32 = 63; +pub const UART4_IRQ: u32 = 64; +pub const UART5_IRQ: u32 = 65; +pub const LPUART1_IRQ: u32 = 66; +pub const LPTIM1_IRQ: u32 = 67; +pub const LPTIM2_IRQ: u32 = 68; +pub const TIM15_IRQ: u32 = 69; +pub const TIM16_IRQ: u32 = 70; +pub const TIM17_IRQ: u32 = 71; +pub const COMP_IRQ: u32 = 72; +pub const OTG_HS_IRQ: u32 = 73; +pub const CRS_IRQ: u32 = 74; +pub const FMC_IRQ: u32 = 75; +pub const OCTOSPI1_IRQ: u32 = 76; +pub const PWR_S3WU_IRQ: u32 = 77; +pub const SDMMC1_IRQ: u32 = 78; +pub const SDMMC2_IRQ: u32 = 79; +pub const GPDMA1_CH8_IRQ: u32 = 80; +pub const GPDMA1_CH9_IRQ: u32 = 81; +pub const GPDMA1_CH10_IRQ: u32 = 82; +pub const GPDMA1_CH11_IRQ: u32 = 83; +pub const GPDMA1_CH12_IRQ: u32 = 84; +pub const GPDMA1_CH13_IRQ: u32 = 85; +pub const GPDMA1_CH14_IRQ: u32 = 86; +pub const GPDMA1_CH15_IRQ: u32 = 87; +pub const I2C3_EV_IRQ: u32 = 88; +pub const I2C3_ER_IRQ: u32 = 89; +pub const SAI1_IRQ: u32 = 90; +pub const SAI2_IRQ: u32 = 91; +pub const TSC_IRQ: u32 = 92; +pub const AES_IRQ: u32 = 93; +pub const RNG_IRQ: u32 = 94; +pub const FPU_IRQ: u32 = 95; +pub const HASH_IRQ: u32 = 96; +pub const PKA_IRQ: u32 = 97; +pub const LPTIM3_IRQ: u32 = 98; +pub const SPI3_IRQ: u32 = 99; +pub const I2C4_ER_IRQ: u32 = 100; +pub const I2C4_EV_IRQ: u32 = 101; +pub const MDF1_FLT0_IRQ: u32 = 102; +pub const MDF1_FLT1_IRQ: u32 = 103; +pub const MDF1_FLT2_IRQ: u32 = 104; +pub const MDF1_FLT3_IRQ: u32 = 105; +pub const UCPD1_IRQ: u32 = 106; +pub const ICACHE_IRQ: u32 = 107; +pub const OTFDEC1_IRQ: u32 = 108; +pub const OTFDEC2_IRQ: u32 = 109; +pub const LPTIM4_IRQ: u32 = 110; +pub const DCACHE1_IRQ: u32 = 111; +pub const ADF1_IRQ: u32 = 112; +pub const ADC4_IRQ: u32 = 113; +pub const LPDMA1_CH0_IRQ: u32 = 114; +pub const LPDMA1_CH1_IRQ: u32 = 115; +pub const LPDMA1_CH2_IRQ: u32 = 116; +pub const LPDMA1_CH3_IRQ: u32 = 117; +pub const DMA2D_IRQ: u32 = 118; +pub const DCMI_PSSI_IRQ: u32 = 119; +pub const OCTOSPI2_IRQ: u32 = 120; +pub const MDF1_FLT4_IRQ: u32 = 121; +pub const MDF1_FLT5_IRQ: u32 = 122; +pub const CORDIC_IRQ: u32 = 123; +pub const FMAC_IRQ: u32 = 124; +pub const LSECSSD_IRQ: u32 = 125; +pub const USART6_IRQ: u32 = 126; +pub const I2C5_ER_IRQ: u32 = 127; +pub const I2C5_EV_IRQ: u32 = 128; +pub const I2C6_ER_IRQ: u32 = 129; +pub const I2C6_EV_IRQ: u32 = 130; +pub const HSPI1_IRQ: u32 = 131; +pub const GPU2D_IRQ: u32 = 132; +pub const GPU2D_ER_IRQ: u32 = 133; +pub const GFXMMU_IRQ: u32 = 134; +pub const LTDC_IRQ: u32 = 135; +pub const LTDC_ER_IRQ: u32 = 136; +pub const DSI_IRQ: u32 = 137; +pub const DCACHE2_IRQ: u32 = 138; +pub const GFXTIM_IRQ: u32 = 139; +pub const JPEG_IRQ: u32 = 140; diff --git a/chips/stm32u5xx/src/rcc.rs b/chips/stm32u5xx/src/rcc.rs new file mode 100644 index 0000000000..aec1859559 --- /dev/null +++ b/chips/stm32u5xx/src/rcc.rs @@ -0,0 +1,109 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. +// Copyright OxidOS Automotive 2026. + +use kernel::utilities::registers::interfaces::ReadWriteable; +use kernel::utilities::registers::{register_bitfields, register_structs, ReadWrite}; +use kernel::utilities::StaticRef; + +register_structs! { + pub RccRegisters { + /// Control register + (0x000 => cr: ReadWrite), + (0x004 => _reserved0: [u32; 33]), + /// AHB1 peripheral clock enable register + (0x088 => ahb1enr: ReadWrite), + /// AHB2 peripheral clock enable register 1 + (0x08C => ahb2enr1: ReadWrite), + (0x090 => _reserved1: [u32; 3]), + /// APB1 peripheral clock enable register 1 + (0x09C => apb1enr1: ReadWrite), + (0x0A0 => _reserved2: [u32; 1]), + /// APB2 peripheral clock enable register + (0x0A4 => apb2enr: ReadWrite), + /// APB3 peripheral clock enable register + (0x0A8 => apb3enr: ReadWrite), + (0x0AC => _reserved3: [u32; 13]), + /// Peripherals independent clock configuration register 1 + (0x0E0 => ccipr1: ReadWrite), + (0x0E4 => @END), + } +} + +register_bitfields![u32, + pub AHB1ENR [ + GPDMA1EN OFFSET(0) NUMBITS(1) [] + ], + pub AHB2ENR1 [ + GPIOAEN OFFSET(0) NUMBITS(1) [], + GPIOBEN OFFSET(1) NUMBITS(1) [], + GPIOCEN OFFSET(2) NUMBITS(1) [], + GPIODEN OFFSET(3) NUMBITS(1) [], + GPIOEEN OFFSET(4) NUMBITS(1) [], + GPIOFEN OFFSET(5) NUMBITS(1) [], + GPIOGEN OFFSET(6) NUMBITS(1) [], + GPIOHEN OFFSET(7) NUMBITS(1) [], + GPIOIEN OFFSET(8) NUMBITS(1) [], + GPIOJEN OFFSET(9) NUMBITS(1) [] + ], + pub APB1ENR1 [ + TIM2EN OFFSET(0) NUMBITS(1) [] + ], + pub APB2ENR [ + USART1EN OFFSET(14) NUMBITS(1) [] + ], + pub APB3ENR [ + SYSCFGEN OFFSET(1) NUMBITS(1) [] + ], + pub CCIPR1 [ + USART1SEL OFFSET(0) NUMBITS(2) [ + PCLK = 0, + SYSCLK = 1, + HSI16 = 2, + LSE = 3 + ] + ] +]; + +/// Base address for RCC in Secure mode. +pub const RCC_BASE: StaticRef = + unsafe { StaticRef::new(0x46020C00 as *const RccRegisters) }; + +pub struct Rcc { + registers: StaticRef, +} + +impl Rcc { + pub const fn new(base: StaticRef) -> Rcc { + Rcc { registers: base } + } + + pub fn enable_dma1(&self) { + self.registers.ahb1enr.modify(AHB1ENR::GPDMA1EN::SET); + } + + pub fn enable_gpioa(&self) { + self.registers.ahb2enr1.modify(AHB2ENR1::GPIOAEN::SET); + } + + pub fn enable_gpioc(&self) { + self.registers.ahb2enr1.modify(AHB2ENR1::GPIOCEN::SET); + } + + pub fn enable_usart1(&self) { + self.registers.apb2enr.modify(APB2ENR::USART1EN::SET); + } + + pub fn enable_tim2(&self) { + self.registers.apb1enr1.modify(APB1ENR1::TIM2EN::SET); + } + + pub fn enable_syscfg(&self) { + self.registers.apb3enr.modify(APB3ENR::SYSCFGEN::SET); + } + + pub fn set_usart1_source_pclk(&self) { + self.registers.ccipr1.modify(CCIPR1::USART1SEL::PCLK); + } +} diff --git a/chips/stm32u5xx/src/tim.rs b/chips/stm32u5xx/src/tim.rs new file mode 100644 index 0000000000..89b7486aa4 --- /dev/null +++ b/chips/stm32u5xx/src/tim.rs @@ -0,0 +1,200 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright OxidOS Automotive 2026. + +use kernel::hil::time::Time; +use kernel::hil::time::{self, Ticks, Ticks32}; +use kernel::utilities::cells::OptionalCell; +use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; +use kernel::utilities::registers::{register_bitfields, register_structs, ReadWrite, WriteOnly}; +use kernel::utilities::StaticRef; + +register_structs! { + pub TimRegisters { + /// Control register 1 + (0x000 => cr1: ReadWrite), + (0x004 => _reserved0), + /// DMA/Interrupt enable register + (0x00C => dier: ReadWrite), + /// Status register + (0x010 => sr: ReadWrite), + /// Event generation register + (0x014 => egr: WriteOnly), + (0x018 => _reserved1), + /// Counter + (0x024 => cnt: ReadWrite), + /// Prescaler + (0x028 => psc: ReadWrite), + /// Auto-reload register + (0x02C => arr: ReadWrite), + (0x030 => _reserved2), + /// Capture/compare register 1 + (0x034 => ccr1: ReadWrite), + (0x038 => @END), + } +} + +pub const TIM2_BASE: StaticRef = + unsafe { StaticRef::new(0x50000000 as *const TimRegisters) }; + +register_bitfields![u32, + pub CR1 [ + /// Counter enable + CEN OFFSET(0) NUMBITS(1) [] + ], + pub DIER [ + /// Update interrupt enable + UIE OFFSET(0) NUMBITS(1) [], + /// CC1 interrupt enable + CC1IE OFFSET(1) NUMBITS(1) [] + ], + pub SR [ + /// Update interrupt flag + UIF OFFSET(0) NUMBITS(1) [], + /// CC1 interrupt flag + CC1IF OFFSET(1) NUMBITS(1) [] + ], + pub EGR [ + /// Update generation + UG OFFSET(0) NUMBITS(1) [] + ], + pub CNT [ + /// Counter value + CNT OFFSET(0) NUMBITS(32) [] + ], + pub PSC [ + /// Prescaler value + PSC OFFSET(0) NUMBITS(16) [] + ], + pub ARR [ + /// Auto-reload value + ARR OFFSET(0) NUMBITS(32) [] + ], + pub CCR1 [ + /// Capture/compare value + CCR1 OFFSET(0) NUMBITS(32) [] + ] +]; + +/// TIM2 hardware driver for the STM32U5. +/// +/// This driver implements the Tock Alarm HIL using the 32-bit general-purpose +/// TIM2 timer. It is configured to run at 32kHz to provide high-resolution +/// timing while remaining power-efficient. +pub struct Tim2<'a> { + registers: StaticRef, + enable_clock: fn(), + client: OptionalCell<&'a dyn time::AlarmClient>, +} + +impl<'a> Tim2<'a> { + /// Creates a new instance of the driver. + /// + /// - `base`: The StaticRef pointing to the MMIO base address of the peripheral. + /// - `enable_clock`: (For Timers) A callback function to power on the peripheral via RCC. + pub const fn new(base: StaticRef, enable_clock: fn()) -> Tim2<'a> { + Tim2 { + registers: base, + enable_clock, + client: OptionalCell::empty(), + } + } + + fn enable_clock(&self) { + (self.enable_clock)(); + } + + /// Core interrupt handler for the peripheral. + /// + /// This function must be called from the chip's main interrupt service routine + /// (located in `chip.rs`) whenever the corresponding IRQ fires. It + /// identifies the cause of the interrupt, clears the relevant hardware + /// pending flags, and notifies any registered clients. + pub fn handle_interrupt(&self) { + // Clear interrupt flag + self.registers.sr.modify(SR::CC1IF::CLEAR); + + self.client.map(|client| { + client.alarm(); + }); + } + + /// Initializes and starts the timer hardware. + /// + /// This sets the prescaler to 124 (converting the 4MHz clock to 32kHz) + /// and enables the 32-bit free-running counter. + pub fn start(&self) { + self.enable_clock(); + + // 1. Set the value + self.registers.psc.write(PSC::PSC.val(124)); + + // 2. Force the hardware to load the value NOW + // On STM32, the PSC is buffered. By setting the UG bit in EGR, + self.registers.egr.write(EGR::UG::SET); + + // 3. Clear the status flag caused by the manual update + self.registers.sr.modify(SR::UIF::CLEAR); + + self.registers.arr.write(ARR::ARR.val(0xFFFFFFFF)); + self.registers.cr1.modify(CR1::CEN::SET); + } +} + +impl time::Time for Tim2<'_> { + type Frequency = time::Freq32KHz; + type Ticks = Ticks32; + + fn now(&self) -> Ticks32 { + Ticks32::from(self.registers.cnt.read(CNT::CNT)) + } +} + +impl<'a> time::Alarm<'a> for Tim2<'a> { + fn set_alarm_client(&self, client: &'a dyn time::AlarmClient) { + self.client.set(client); + } + + fn set_alarm(&self, reference: Ticks32, dt: Ticks32) { + // 1. Calculate the raw target time + let mut expire = reference.wrapping_add(dt); + let now = self.now(); + + // 2. The "Past Check": If the target is behind us, clamp it to 'now' + if !now.within_range(reference, expire) { + expire = now; + } + + // 3. The "Minimum Delay": If the alarm is too close to now, + // push it forward slightly to give the CPU time to finish this function. + if expire.wrapping_sub(now) < self.minimum_dt() { + expire = now.wrapping_add(self.minimum_dt()); + } + + // 4. DISARM and CLEAR FIRST + // This stops old alarms from firing while we are setting the new one. + let _ = self.disarm(); + self.registers.sr.modify(SR::CC1IF::CLEAR); + + // 5. Program the hardware + self.registers.ccr1.write(CCR1::CCR1.val(expire.into_u32())); + self.registers.dier.modify(DIER::CC1IE::SET); + } + + fn get_alarm(&self) -> Ticks32 { + Ticks32::from(self.registers.ccr1.read(CCR1::CCR1)) + } + + fn is_armed(&self) -> bool { + self.registers.dier.is_set(DIER::CC1IE) + } + + fn disarm(&self) -> Result<(), kernel::ErrorCode> { + self.registers.dier.modify(DIER::CC1IE::CLEAR); + Ok(()) + } + + fn minimum_dt(&self) -> Ticks32 { + Ticks32::from(2) + } +} diff --git a/chips/stm32u5xx/src/usart.rs b/chips/stm32u5xx/src/usart.rs new file mode 100644 index 0000000000..f93ef4fb1c --- /dev/null +++ b/chips/stm32u5xx/src/usart.rs @@ -0,0 +1,535 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright OxidOS Automotive 2026. + +use crate::dma::{ChannelId, Dma, DmaPeripheral}; +use core::cell::Cell; +use cortexm33::dma_fence::CortexMDmaFence; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; +use kernel::hil::uart::{self}; +use kernel::platform::chip::PanicWriter; +use kernel::utilities::cells::MapCell; +use kernel::utilities::cells::OptionalCell; +use kernel::utilities::dma_slice::DmaSubSliceMut; +use kernel::utilities::io_write::IoWrite; +use kernel::utilities::leasable_buffer::SubSliceMut; +use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; +use kernel::utilities::registers::{ + register_bitfields, register_structs, ReadOnly, ReadWrite, WriteOnly, +}; +use kernel::utilities::StaticRef; + +register_structs! { + pub UsartRegisters { + /// Control register 1 + (0x000 => pub cr1: ReadWrite), + /// Control register 2 + (0x004 => pub cr2: ReadWrite), + /// Control register 3 + (0x008 => pub cr3: ReadWrite), + /// Baud rate register + (0x00C => pub brr: ReadWrite), + /// Guard time and prescaler register + (0x010 => pub gtpr: ReadWrite), + /// Receiver timeout register + (0x014 => pub rtor: ReadWrite), + /// Request register + (0x018 => pub rqr: WriteOnly), + /// Interrupt and status register + (0x01C => pub isr: ReadOnly), + /// Interrupt flag clear register + (0x020 => pub icr: WriteOnly), + /// Receive data register + (0x024 => pub rdr: ReadOnly), + /// Transmit data register + (0x028 => pub tdr: ReadWrite), + /// Prescaler register + (0x02C => pub presc: ReadWrite), + (0x030 => @END), + } +} + +/// Base address for USART1 in Secure Alias mode. +pub const USART1_BASE: StaticRef = + unsafe { StaticRef::new(0x50013800 as *const UsartRegisters) }; + +register_bitfields![u32, + pub CR1 [ + /// USART enable + UE OFFSET(0) NUMBITS(1) [], + /// Receiver enable + RE OFFSET(2) NUMBITS(1) [], + /// Transmitter enable + TE OFFSET(3) NUMBITS(1) [], + /// RXNE interrupt enable + RXNEIE OFFSET(5) NUMBITS(1) [], + /// Transmission complete interrupt enable + TCIE OFFSET(6) NUMBITS(1) [], + /// TXE interrupt enable + TXEIE OFFSET(7) NUMBITS(1) [] + ], + pub CR2 [ + /// STOP bits + STOP OFFSET(12) NUMBITS(2) [] + ], + pub CR3 [ + /// Error interrupt enable + EIE OFFSET(0) NUMBITS(1) [], + /// DMA enable transmitter + DMAT OFFSET(7) NUMBITS(1) [], + /// DMA enable receiver + DMAR OFFSET(6) NUMBITS(1) [] + ], + pub BRR [ + /// Baud rate divider + BRR OFFSET(0) NUMBITS(16) [] + ], + pub GTPR [ + /// Guard time value + GT OFFSET(8) NUMBITS(8) [], + /// Prescaler value + PSC OFFSET(0) NUMBITS(8) [] + ], + pub RTOR [ + /// Receiver timeout value + RTO OFFSET(0) NUMBITS(24) [], + /// Block length + BLEN OFFSET(24) NUMBITS(8) [] + ], + pub RQR [ + /// Transmit data flush request + TXFRQ OFFSET(4) NUMBITS(1) [], + /// Receive data flush request + RXFRQ OFFSET(3) NUMBITS(1) [], + /// Mute mode request + MMRQ OFFSET(2) NUMBITS(1) [], + /// Send break request + SBKRQ OFFSET(1) NUMBITS(1) [], + /// Auto baud rate request + ABRRQ OFFSET(0) NUMBITS(1) [] + ], + pub ISR [ + /// Transmission complete + TC OFFSET(6) NUMBITS(1) [], + /// Read data register not empty + RXNE OFFSET(5) NUMBITS(1) [], + /// Transmit data register empty + TXE OFFSET(7) NUMBITS(1) [], + /// Overrun error + ORE OFFSET(3) NUMBITS(1) [], + /// Noise detected flag + NE OFFSET(2) NUMBITS(1) [], + /// Framing error + FE OFFSET(1) NUMBITS(1) [], + /// Parity error + PE OFFSET(0) NUMBITS(1) [] + ], + pub ICR [ + /// Transmission complete clear flag + TCCF OFFSET(6) NUMBITS(1) [], + /// Overrun error clear flag + ORECF OFFSET(3) NUMBITS(1) [], + /// Noise detected clear flag + NECF OFFSET(2) NUMBITS(1) [], + /// Framing error clear flag + FECF OFFSET(1) NUMBITS(1) [], + /// Parity error clear flag + PECF OFFSET(0) NUMBITS(1) [] + ], + pub RDR [ + /// Receive data value + RDR OFFSET(0) NUMBITS(9) [] + ], + pub TDR [ + /// Transmit data value + TDR OFFSET(0) NUMBITS(9) [] + ], + pub PRESC [ + /// Clock prescaler + PRESCALER OFFSET(0) NUMBITS(4) [] + ] +]; + +/// USART driver implementation for the STM32U5 series. +/// +/// This driver uses the GPDMA (General Purpose Direct Memory Access) controller +/// for both transmitting and receiving data, which provides high efficiency and +/// ensures that fast data bursts (such as arrow key escape sequences) are +/// captured correctly. +pub struct Usart<'a> { + pub registers: StaticRef, + dma: OptionalCell<&'a Dma>, + dma_channel_tx: Cell>, + dma_channel_rx: Cell>, + tx_client: OptionalCell<&'a dyn uart::TransmitClient>, + rx_client: OptionalCell<&'a dyn uart::ReceiveClient>, + tx_dma_buf: MapCell>, + rx_dma_buf: MapCell>, + tx_len: Cell, + rx_len: Cell, + deferred_call: DeferredCall, + tx_deferred: Cell, + rx_deferred: Cell, +} + +impl<'a> Usart<'a> { + pub fn new(base: StaticRef) -> Self { + Self { + registers: base, + dma: OptionalCell::empty(), + dma_channel_tx: Cell::new(None), + dma_channel_rx: Cell::new(None), + tx_client: OptionalCell::empty(), + rx_client: OptionalCell::empty(), + tx_dma_buf: MapCell::empty(), + rx_dma_buf: MapCell::empty(), + tx_len: Cell::new(0), + rx_len: Cell::new(0), + deferred_call: DeferredCall::new(), + tx_deferred: Cell::new(false), + rx_deferred: Cell::new(false), + } + } + + /// Associates a DMA controller and channels with the USART driver. + pub fn set_dma( + usart: &'static Self, + dma: &'a Dma, + tx_channel: ChannelId, + rx_channel: ChannelId, + ) { + usart.dma.set(dma); + usart.dma_channel_tx.set(Some(tx_channel)); + usart.dma_channel_rx.set(Some(rx_channel)); + dma.set_client(tx_channel, usart); + dma.set_client(rx_channel, usart); + } + + /// Hardware interrupt handler for the USART. + /// + /// This handles non-DMA events like errors. DMA-specific completions + /// are handled in `handle_dma_interrupt`. + pub fn handle_interrupt(&self) { + let regs = &*self.registers; + let isr = regs.isr.extract(); + + // Clear any error flags (Parity, Framing, Noise, Overrun). + if isr.any_matching_bits_set(ISR::PE::SET + ISR::FE::SET + ISR::NE::SET + ISR::ORE::SET) { + regs.icr + .write(ICR::PECF::SET + ICR::FECF::SET + ICR::NECF::SET + ICR::ORECF::SET); + } + } + + /// Handles completion interrupts from the GPDMA controller. + /// + /// This is called when a DMA transfer for either Transmit or Receive + /// has finished. + pub fn handle_dma_interrupt(&self, is_tx: bool) { + if is_tx { + self.dma.map(|dma| { + if let Some(ch) = self.dma_channel_tx.get() { + dma.clear_interrupt(ch); + } + }); + self.registers.cr3.modify(CR3::DMAT::CLEAR); + self.tx_deferred.set(false); + if let Some(dma_slice) = self.tx_dma_buf.take() { + let fence = unsafe { CortexMDmaFence::new() }; + let mut subslice = unsafe { dma_slice.take(fence) }; + subslice.reset(); + let buf = subslice.take(); + let len = self.tx_len.get(); + self.tx_client.map(move |client| { + client.transmitted_buffer(buf, len, Ok(())); + }); + } + } else { + self.dma.map(|dma| { + if let Some(ch) = self.dma_channel_rx.get() { + dma.clear_interrupt(ch); + } + }); + self.registers.cr3.modify(CR3::DMAR::CLEAR); + self.rx_deferred.set(false); + if let Some(dma_slice) = self.rx_dma_buf.take() { + let fence = unsafe { CortexMDmaFence::new() }; + let mut subslice = unsafe { dma_slice.take(fence) }; + subslice.reset(); + let buf = subslice.take(); + let len = self.rx_len.get(); + self.rx_client.map(move |client| { + client.received_buffer(buf, len, Ok(()), uart::Error::None); + }); + } + } + } + + /// Synchronous (Blocking) send. + /// ONLY for use in the Panic handler when DMA is unavailable. + pub fn transmit_byte(&self, byte: u8) { + let regs = &*self.registers; + + // 1. Disable DMA transmitter temporarily if it was enabled + // so that manual writes to TDR are processed correctly. + let dmat_enabled = regs.cr3.is_set(CR3::DMAT); + if dmat_enabled { + regs.cr3.modify(CR3::DMAT::CLEAR); + } + + // 2. Wait until TXE (Transmit Data Register Empty) is set + while !regs.isr.is_set(ISR::TXE) {} + regs.tdr.write(TDR::TDR.val(byte as u32)); + + // 3. Wait for Transmission Complete before potentially re-enabling DMA + while !regs.isr.is_set(ISR::TC) {} + + // 4. Restore DMA state + if dmat_enabled { + regs.cr3.modify(CR3::DMAT::SET); + } + } +} + +impl DeferredCallClient for Usart<'_> { + fn register(&'static self) { + self.deferred_call.register(self); + } + + fn handle_deferred_call(&self) { + if self.tx_deferred.get() { + self.tx_deferred.set(false); + self.tx_client.map(move |client| { + let dma_slice = self.tx_dma_buf.take().unwrap(); + let fence = unsafe { CortexMDmaFence::new() }; + let mut subslice = unsafe { dma_slice.take(fence) }; + subslice.reset(); + let buf = subslice.take(); + client.transmitted_buffer(buf, 0, Err(kernel::ErrorCode::CANCEL)); + }); + } + if self.rx_deferred.get() { + self.rx_deferred.set(false); + self.rx_client.map(move |client| { + let dma_slice = self.rx_dma_buf.take().unwrap(); + let fence = unsafe { CortexMDmaFence::new() }; + let mut subslice = unsafe { dma_slice.take(fence) }; + subslice.reset(); + let buf = subslice.take(); + client.received_buffer( + buf, + 0, + Err(kernel::ErrorCode::CANCEL), + uart::Error::Aborted, + ); + }); + } + } +} + +impl crate::dma::DmaClient for Usart<'_> { + fn transfer_done(&self, channel: ChannelId) { + if let Some(tx_ch) = self.dma_channel_tx.get() { + if channel == tx_ch { + self.handle_dma_interrupt(true); + return; + } + } + if let Some(rx_ch) = self.dma_channel_rx.get() { + if channel == rx_ch { + self.handle_dma_interrupt(false); + } + } + } +} + +impl<'a> uart::Transmit<'a> for Usart<'a> { + fn set_transmit_client(&self, client: &'a dyn uart::TransmitClient) { + self.tx_client.set(client); + } + + fn transmit_buffer( + &self, + tx_buffer: &'static mut [u8], + tx_len: usize, + ) -> Result<(), (kernel::ErrorCode, &'static mut [u8])> { + if self.tx_dma_buf.is_some() { + return Err((kernel::ErrorCode::BUSY, tx_buffer)); + } + + let Some(dma) = self.dma.get() else { + return Err((kernel::ErrorCode::OFF, tx_buffer)); + }; + + // Move buffer into SubSlice + let mut subslice = SubSliceMut::new(tx_buffer); + subslice.slice(0..tx_len); + + // Hardware fence + let fence = unsafe { CortexMDmaFence::new() }; + // Convert subslice into DmaSlice + let dma_slice = DmaSubSliceMut::new_static(subslice, fence); + + // Extract the physical pointer and length for MMIO + let ptr = dma_slice.as_mut_ptr() as u32; + let len = dma_slice.len() as u32; + + // Save DmaSlice in the struct + self.tx_dma_buf.replace(dma_slice); + self.tx_len.set(tx_len); + + // Trigger USART + if let Some(ch) = self.dma_channel_tx.get() { + dma.setup(ch, DmaPeripheral::Usart1Tx, ptr, len); + self.registers.cr3.modify(CR3::DMAT::SET); + Ok(()) + } else { + self.tx_dma_buf + .take() + .map(|s| { + let f = unsafe { CortexMDmaFence::new() }; + let mut buf = unsafe { s.take(f) }; + buf.reset(); + Err((kernel::ErrorCode::RESERVE, buf.take())) + }) + .unwrap() + } + } + + fn transmit_abort(&self) -> Result<(), kernel::ErrorCode> { + self.registers.cr3.modify(CR3::DMAT::CLEAR); + if self.tx_dma_buf.is_some() { + self.tx_deferred.set(true); + self.deferred_call.set(); + Err(kernel::ErrorCode::BUSY) + } else { + Ok(()) + } + } + + fn transmit_word(&self, _word: u32) -> Result<(), kernel::ErrorCode> { + Err(kernel::ErrorCode::NOSUPPORT) + } +} + +impl uart::Configure for Usart<'_> { + fn configure(&self, _params: uart::Parameters) -> Result<(), kernel::ErrorCode> { + let regs = &*self.registers; + regs.cr1.modify(CR1::UE::CLEAR); + regs.presc.write(PRESC::PRESCALER.val(0)); + regs.brr.write(BRR::BRR.val(35)); + regs.icr.write( + ICR::TCCF::SET + ICR::ORECF::SET + ICR::NECF::SET + ICR::FECF::SET + ICR::PECF::SET, + ); + + // Enable transmitter, receiver, and USART + regs.cr1.write(CR1::TE::SET + CR1::RE::SET + CR1::UE::SET); + + Ok(()) + } +} + +impl<'a> uart::Receive<'a> for Usart<'a> { + fn set_receive_client(&self, client: &'a dyn uart::ReceiveClient) { + self.rx_client.set(client); + } + + fn receive_buffer( + &self, + rx_buffer: &'static mut [u8], + rx_len: usize, + ) -> Result<(), (kernel::ErrorCode, &'static mut [u8])> { + if self.rx_dma_buf.is_some() { + return Err((kernel::ErrorCode::BUSY, rx_buffer)); + } + + let Some(dma) = self.dma.get() else { + return Err((kernel::ErrorCode::OFF, rx_buffer)); + }; + + let mut subslice = SubSliceMut::new(rx_buffer); + subslice.slice(0..rx_len); + let fence = unsafe { CortexMDmaFence::new() }; + let dma_slice = DmaSubSliceMut::new_static(subslice, fence); + + let ptr = dma_slice.as_mut_ptr() as u32; + let len = dma_slice.len() as u32; + + self.rx_dma_buf.replace(dma_slice); + self.rx_len.set(rx_len); + + if let Some(ch) = self.dma_channel_rx.get() { + dma.setup(ch, DmaPeripheral::Usart1Rx, ptr, len); + self.registers.cr3.modify(CR3::DMAR::SET); + Ok(()) + } else { + self.rx_dma_buf + .take() + .map(|s| { + let f = unsafe { CortexMDmaFence::new() }; + let mut buf = unsafe { s.take(f) }; + buf.reset(); + Err((kernel::ErrorCode::RESERVE, buf.take())) + }) + .unwrap() + } + } + + fn receive_abort(&self) -> Result<(), kernel::ErrorCode> { + self.registers.cr3.modify(CR3::DMAR::CLEAR); + if self.rx_dma_buf.is_some() { + self.rx_deferred.set(true); + self.deferred_call.set(); + Err(kernel::ErrorCode::BUSY) + } else { + Ok(()) + } + } + + fn receive_word(&self) -> Result<(), kernel::ErrorCode> { + Err(kernel::ErrorCode::NOSUPPORT) + } +} + +struct UsartPanicWriter { + registers: StaticRef, +} + +impl IoWrite for UsartPanicWriter { + fn write(&mut self, buf: &[u8]) -> usize { + for &byte in buf { + let regs = &*self.registers; + while !regs.isr.is_set(ISR::TXE) {} + regs.tdr.write(TDR::TDR.val(byte as u32)); + while !regs.isr.is_set(ISR::TC) {} + } + buf.len() + } +} + +impl core::fmt::Write for UsartPanicWriter { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + self.write(s.as_bytes()); + Ok(()) + } +} + +pub struct UsartPanicWriterConfig { + pub base: StaticRef, +} + +impl PanicWriter for Usart<'_> { + type Config = UsartPanicWriterConfig; + unsafe fn create_panic_writer(config: Self::Config) -> impl IoWrite + core::fmt::Write { + let writer = UsartPanicWriter { + registers: config.base, + }; + + let regs = &*writer.registers; + regs.cr1.modify(CR1::UE::CLEAR); + // Baud Rate + regs.brr.write(BRR::BRR.val(35)); + regs.cr1.write(CR1::TE::SET + CR1::RE::SET + CR1::UE::SET); + + writer + } +} diff --git a/chips/stm32wle5jc/src/lib.rs b/chips/stm32wle5jc/src/lib.rs index a43f7f9e2f..bf3d32c3c1 100644 --- a/chips/stm32wle5jc/src/lib.rs +++ b/chips/stm32wle5jc/src/lib.rs @@ -84,7 +84,3 @@ pub static IRQS: [unsafe extern "C" fn(); 62] = [ CortexM4::GENERIC_ISR, // DMA2_CH7 (60) CortexM4::GENERIC_ISR, // DMAMUX1_OVR (61) ]; - -pub unsafe fn init() { - stm32wle5xx::init(); -} diff --git a/chips/stm32wle5xx/src/chip.rs b/chips/stm32wle5xx/src/chip.rs index 861898a24b..1effe023dc 100644 --- a/chips/stm32wle5xx/src/chip.rs +++ b/chips/stm32wle5xx/src/chip.rs @@ -113,6 +113,14 @@ impl<'a, I: InterruptService + 'a> Chip for Stm32wle5xx<'a, I> { type UserspaceKernelBoundary = cortexm4::syscall::SysCall; type ThreadIdProvider = cortexm4::thread_id::CortexMThreadIdProvider; + fn init() { + unsafe { + cortexm4::nvic::disable_all(); + cortexm4::nvic::clear_all_pending(); + cortexm4::nvic::enable_all(); + } + } + fn service_pending_interrupts(&self) { unsafe { // We have a bit of a hacky solution here to deal with a peculiarity of the diff --git a/chips/stm32wle5xx/src/lib.rs b/chips/stm32wle5xx/src/lib.rs index af90fc7cac..707fb52e25 100644 --- a/chips/stm32wle5xx/src/lib.rs +++ b/chips/stm32wle5xx/src/lib.rs @@ -62,9 +62,3 @@ pub static BASE_VECTORS: [unsafe extern "C" fn(); 16] = [ unhandled_interrupt, // PendSV CortexM4::SYSTICK_HANDLER, // SysTick ]; - -pub unsafe fn init() { - cortexm4::nvic::disable_all(); - cortexm4::nvic::clear_all_pending(); - cortexm4::nvic::enable_all(); -} diff --git a/chips/stm32wle5xx/src/tim2.rs b/chips/stm32wle5xx/src/tim2.rs index e3cc64d69d..b92997e6e2 100644 --- a/chips/stm32wle5xx/src/tim2.rs +++ b/chips/stm32wle5xx/src/tim2.rs @@ -3,9 +3,7 @@ // Copyright Tock Contributors 2025. use cortexm4::support::with_interrupts_disabled; -use kernel::hil::time::{ - Alarm, AlarmClient, Counter, Freq16KHz, OverflowClient, Ticks, Ticks32, Time, -}; +use kernel::hil::time::{Alarm, AlarmClient, Counter, Freq16KHz, Ticks, Ticks32, Time}; use kernel::platform::chip::ClockInterface; use kernel::utilities::cells::OptionalCell; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; @@ -378,8 +376,6 @@ impl Time for Tim2<'_> { } impl<'a> Counter<'a> for Tim2<'a> { - fn set_overflow_client(&self, _client: &'a dyn OverflowClient) {} - // starts the timer fn start(&self) -> Result<(), ErrorCode> { self.start_counter(); diff --git a/chips/veer_el2/src/chip.rs b/chips/veer_el2/src/chip.rs index 74afbb8ff8..09d9f4fa6e 100644 --- a/chips/veer_el2/src/chip.rs +++ b/chips/veer_el2/src/chip.rs @@ -94,6 +94,8 @@ impl<'a, I: InterruptService + 'a> kernel::platform::chip::Chip for VeeR<'a, I> type UserspaceKernelBoundary = SysCall; type ThreadIdProvider = rv32i::thread_id::RiscvThreadIdProvider; + fn init() {} + fn mpu(&self) -> &Self::MPU { &self.pmp } diff --git a/chips/x86_q35/src/chip.rs b/chips/x86_q35/src/chip.rs index cf13809dec..192e8264a5 100644 --- a/chips/x86_q35/src/chip.rs +++ b/chips/x86_q35/src/chip.rs @@ -124,6 +124,18 @@ impl Pc<'static, PcDefaultPeripherals, impl<'a, I1: InterruptService, I2: InterruptService, const PR: u16> Chip for Pc<'a, I1, I2, PR> { type ThreadIdProvider = x86::thread_id::X86ThreadIdProvider; + fn init() { + // TODO: Correctly implement the x86 init API. + // + // As of June 2026 we are not currently using this init() because the + // safety requirements cannot be properly handled. The current safety + // requirements require assurance init is only called once, which we + // can't enforce. + // + // As the safety of the x86 crate is better understood, we expect + // the requirement to be easier to satisfy. + } + type MPU = PagingMPU<'a>; fn mpu(&self) -> &Self::MPU { &self.paging diff --git a/chips/x86_q35/src/pic.rs b/chips/x86_q35/src/pic.rs index 05391a1e7b..7a099e7173 100644 --- a/chips/x86_q35/src/pic.rs +++ b/chips/x86_q35/src/pic.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // Copyright Tock Contributors 2024. -//! Module for interacting with legacy 8259 PIC +//! Module for interacting with legacy 8259 Programmable Interrupt Controller (PIC) //! //! This implementation is based on guidance from the following sources: //! diff --git a/doc/reference/trd105-time.md b/doc/reference/trd105-time.md index e26e0feb3c..5d37eab839 100644 --- a/doc/reference/trd105-time.md +++ b/doc/reference/trd105-time.md @@ -4,13 +4,9 @@ Kernel Time HIL **TRD:** 105
**Working Group:** Kernel
**Type:** Documentary
-**Status:** Draft
-**Obsoletes:** 101
+**Status:** Obsolete
+**Obsoleted By:** xxx
**Author:** Guillaume Endignoux, Amit Levy, Philip Levis, and Jett Rink
-**Draft-Created:** 2021/07/23
-**Draft-Modified:** 2021/07/23
-**Draft-Version:** 1.0
-**Draft-Discuss:** Github PR
Abstract ------------------------------- diff --git a/doc/reference/trdxxx-time.md b/doc/reference/trdxxx-time.md new file mode 100644 index 0000000000..b13862e224 --- /dev/null +++ b/doc/reference/trdxxx-time.md @@ -0,0 +1,470 @@ +Kernel Time HIL +======================================== + +**TRD:** xxx
+**Working Group:** Kernel
+**Type:** Documentary
+**Status:** Draft
+**Obsoletes:** 105
+**Author:** Guillaume Endignoux, Amit Levy, Philip Levis, Jett Rink and Gabriel Stanciu
+**Draft-Created:** 2026/06/09
+**Draft-Version:** 1.0
+**Draft-Discuss:** Github PR
+ +Abstract +------------------------------- + +This document describes the hardware independent layer interface (HIL) for time +in the Tock operating system kernel. It describes the Rust traits and other +definitions for this service as well as the reasoning behind them. This +document is in full compliance with [TRD1](./trd1-trds.md). + + +1 Introduction +=============================== + +Microcontrollers provide a variety of hardware controllers that keep track of +time. The Tock kernel organizes these various types of controllers into two +broad categories: alarms and timers. Alarms continuously increment a clock and +can fire an event when the clock reaches a specific value. Timers can fire an +event after a certain number of clock ticks have elapsed. + +The time HIL is in the kernel crate, in module `hil::time`. It provides several +main traits: + + + * `kernel::hil::time::Time`: provides an abstraction of a moment in time. It has two associated types. One describes the width and maximum value of a time value. The other specifies the frequency of the ticks of the time value. + * `kernel::hil::time::Counter`: derives from `Time` and provides an abstraction of a free-running counter that can be started or stopped. A `Counter`'s moment in time is the current value of the counter. + * `kernel::hil::time::CounterOverflow`: derives from `Counter`, and provides an abstraction for hardware counters that can generate overflow notifications. + * `kernel::hil::time::Alarm`: derives from `Time`, and provides an abstraction of being able to receive a callback at a future moment in time. + * `kernel::hil::time::Timer`: derives from `Time`, and provides an abstraction of being able to receive a callback at some amount of time in the future, or a series of callbacks at a given period. + * `kernel::hil::time::OverflowClient`: handles an overflow callback from a `CounterOverflow`. + * `kernel::hil::time::AlarmClient`: handles the callback from an `Alarm`. + * `kernel::hil::time::TimerClient`: handles the callback from a `Timer`. + +In addition, to provide a level of minimal platform independence, a +port of Tock to a given microcontoller is expected to implement +certain instances of these traits. This allows, for example, system +call capsules for alarm callbacks to work across boards and chips. + +This document describes these traits, their semantics, and the +instances that a Tock chip is expected to implement. + +2 `Time`, `Frequency`, `Ticks`, and `ConvertTicks` traits +=============================== + +The `Time` trait represents a moment in time, which is obtained by +calling `now`. + +The trait has two associated types. The first, `Frequency`, is an +implementation of the `Frequency` trait which describes how many ticks +there are in a second. The inverse of the frequency defines the time +interval between two ticks of time. + +The second associated type, `Ticks`, defines the width of the time +value. This is an associated type because different microcontrollers +represent time with different bit widths: most Cortex-M +microcontrollers, for example, use 32 bits, while RISC-V uses 64 bits +and the Nordic nRF51822 provides only a 24-bit counter. The `Ticks` +associated type defines this, such that users of the `Time` trait can +know when wraparound will occur. + +The `Ticks` trait requires several other traits from `core::cmp`: `Ord`, +`PartialOrd`, and `Eq`. This is so that methods such as `min_by_key` can +be used with Iterators for when examining a set of `Ticks` values. +The `MuxAlarm` structure in `capsules::virtual_alarm` does this, for example, +to find the next alarm that should fire. + +```rust +pub trait Ticks: Clone + Copy + From + fmt::Debug + Ord + PartialOrd + Eq { + fn into_usize(self) -> usize; + fn into_u32(self) -> u32; + + fn wrapping_add(self, other: Self) -> Self; + fn wrapping_sub(self, other: Self) -> Self; + + // Returns whether `self` is in the range of [`start`, `end`), using + // unsigned arithmetic and considering wraparound. It returns true + // if, incrementing from `start`, `self` will be reached before `end`. + // Put another way, it returns `self - start < end - start` in + // unsigned arithmetic. + fn within_range(self, start: Self, end: Self); + + fn max_value() -> Self; + + /// Converts the specified val into this type if it fits, otherwise the + /// `max_value()` is returned + fn from_or_max(val: u64) -> Self; + + /// Scales the ticks by the specified numerator and denominator. If the + /// resulting value would be greater than u32,`u32::MAX` is returned instead + fn saturating_scale(self, numerator: u32, denominator: u32) -> u32; +} + +pub trait Frequency { + fn frequency() -> u32; // Represented in Hz +} + +pub trait Time { + type Frequency: Frequency; + type Ticks: Ticks; + + fn now(&self) -> Self::Ticks; +} + +pub trait ConvertTicks { + /// Returns the number of ticks in the provided number of seconds, + /// rounding down any fractions. If the value overflows Ticks, it + /// returns `Ticks::max_value()`. + fn ticks_from_seconds(&self, s: u32) -> T; + /// Returns the number of ticks in the provided number of milliseconds, + /// rounding down any fractions. If the value overflows Ticks, it + /// returns `Ticks::max_value()`. + fn ticks_from_ms(&self, ms: u32) -> T; + /// Returns the number of ticks in the provided number of microseconds, + /// rounding down any fractions. If the value overflows Ticks, it + /// returns `Ticks::max_value()`. + fn ticks_from_us(&self, us: u32) -> T; + /// Returns the number of seconds in the provided number of ticks, + /// rounding down any fractions. If the value overflows u32, `u32::MAX` + /// is returned, + fn ticks_to_seconds(&self, tick: T) -> u32; + /// Returns the number of milliseconds in the provided number of ticks, + /// rounding down any fractions. If the value overflows u32, `u32::MAX` + /// is returned, + fn ticks_to_ms(&self, tick: T) -> u32; + /// Returns the number of microseconds in the provided number of ticks, + /// rounding down any fractions. If the value overflows u32, `u32::MAX` + /// is returned, + fn ticks_to_us(&self, tick: T) -> u32; +} +``` + +Frequency is defined with an [associated type][associated_type] +of the `Time` trait (`Time::Frequencey`). It MUST implement the +`Frequency` trait, which has a single method, `frequency`. `frequency` +returns the frequency in Hz, e.g. 1 MHz is 1000000. Clients can use this +to write code that is independent of the underlying frequency. + +An instance of `Time` or derived trait MUST NOT have a `Frequency` +which is greater than its underlying frequency precision. It must be +able to accurately return every possible value in the range of `Ticks` +without further quantization. It is therefore not allowed to take a +32 kHz clock and present it as an instance of `Time` with a frequency +of `Freq16MHz`. + +`Frequency` allows a user of `Time` to know the granularity of ticks +and so avoid quantization error when two different times map to the +same time tick. For example, if a user of `Time` needs microsecond +precision, then the associated type can be used to statically check +that it is not put on top of an implementation with 32 kHz precision. + +The `ConvertTicks` trait is auto-implemented on any object that implements +the `Time` trait. This auto-implemented trait is provided for convenience to +help convert seconds, milliseconds, or microsecond to/from ticks. These +helper methods all round down the result. This means, for example, that +if the `Time` instance has a frequency of 32 kHz, calling +`ticks_from_us(20)` returns 0, because a single tick of a 32 kHz clock +is 30.5 microseconds. + +[associated_type]: https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#specifying-placeholder-types-in-trait-definitions-with-associated-types + + +3 `Counter`, `CounterOverflow`, and `OverflowClient` traits +=============================== + +The `Counter` trait is the abstraction of a free-running counter that +can be started and stopped. This trait derives from the `Time` trait, so +it has associated `Frequency` and `Tick` types. + +To support hardware that can explicitly notify the system when the timer +wraps around, the `CounterOverflow` trait extends `Counter`. This is provided +as a separate extension trait so that support is explicit at compile-time. + +```rust +pub trait OverflowClient { + fn overflow(&self); +} + +pub trait Counter<'a>: Time { + fn start(&self) -> Result<(), ErrorCode>; + fn stop(&self) -> Result<(), ErrorCode>; + fn reset(&self) -> Result<(), ErrorCode>; + fn is_running(&self) -> bool; +} + +pub trait CounterOverflow<'a>: Counter<'a> { + fn set_overflow_client(&self, client: &'a dyn OverflowClient); +} +``` + +The `OverflowClient` trait is separated from the `AlarmClient` trait +because there are cases when software simply wants a free-running +counter to keep track of time, but does not need triggers at a +particular time. For hardware that has a limited number of +compare registers, allocating one of them when the compare itself +isn't needed would be wasteful. + +Note that Tock's concurrency model means interrupt bottom halves +can be delayed until the current bottom half (or syscall +invocation) completes. This means that an overflow callback can +seem to occur *after* an overflow. For example, suppose there is +an 8-bit counter. The following execution is possible: + + 1. Client code calls Time::now, which returns 250. + 1. An overflow happens, marking an interrupt as pending but the bottom half doesn't execute yet. + 1. Client code calls Time::now, which returns 12. + 1. The main event loop runs, invoking the bottom half. + 1. The Counter calls OverflowClient::overflow, notifying the client of the overflow. + +A `Counter` implementation MUST NOT provide a `Frequency` of a higher +resolution than an underlying hardware counter. For example, if the +underlying hardware counter has a frequency of 32 kHz, then a `Counter` +cannot say it has a frequency of 1MHz by multiplying the underlying +counter by 32. A `Counter` implementation MAY provide a `Frequency` of +a lower resolution (e.g., by stripping bits). + +The `reset` method of `Counter` resets the counter to 0. + +4 `Alarm` and `AlarmClient` traits +=============================== + +Instances of the `Alarm` trait track an incrementing clock and can +trigger callbacks when the clock reaches a specific value as well as +when it overflows. The trait is derived from `Time` trait and +therefore has associated `Time::Frequency` and `Ticks` types. + +The `AlarmClient` trait handles callbacks from an instance of `Alarm`. +The trait derives from `OverflowClient` and adds an additional callback +denoting that the time specified to the `Alarm` has been reached. + +`Alarm` and `Timer` (presented below) differ in their level of +abstraction. An `Alarm` presents the abstraction of receiving a +callback when a point in time is reached or on an overflow. In +contrast, `Timer` allows one to request callbacks at some interval in +the future, either once or periodically. `Alarm` requests a callback +at an absolute moment while `Timer` requests a callback at a point +relative to now. + +```rust +pub trait AlarmClient { + fn alarm(&self); +} + +pub trait Alarm: Time { + fn set_alarm(&self, reference: Self::Ticks, dt: Self::Ticks); + fn get_alarm(&self) -> Self::Ticks; + fn disarm(&self) -> Result<(), ErrorCode>; + fn set_alarm_client(&self, client: &'a dyn AlarmClient); +} +``` + +`Alarm` has a `disable` in order to cancel an existing alarm. Calling +`set_alarm` enables an alarm. If there is currently no alarm set, this +sets a new alarm. If there is an alarm set, calling `set_alarm` cancels +the previous alarm and replaces the it with the new one. It cancels the +previous alarm so a client does not have to disambiguate which alarm it +is handling, the previous or current one. + +The `reference` parameter of `set_alarm` is typically a sample of +`Time::now` just before `set_alarm` is called, but it can also be a +stored value from a previous call. The `reference` parameter follows +the invariant that it is in the past: its value is by definition equal +to or less than a call to `Time::now`. + +The `set_alarm` method takes a `reference` and a `dt` parameter to +handle edge cases in which it can be impossible distinguish between +alarms for the very near past and alarms for the very far future. The +edge case occurs when the underlying counter increments past the +compare value between when the call was made and the compare register +is actually set. Because the counter has moved past the intended +compare value, it will have to wrap around before the alarm will +fire. However, one cannot assume that the counter has moved past the +intended compare and issue a callback: the software may have requested +an alarm very far in the future, close to the width of the counter. + +Having a `reference` and `dt` parameters disambiguates these two +cases. Suppose the current counter value is `current`. If `current` +is not within the range [`reference`, `reference + dt`) (considering +unsigned wraparound), then this means the requested firing time has +passed and the callback should be issued immediately (e.g., with a +deferred procedure call, or setting the alarm very short in the +future). + + +5 `Timer` and `TimerClient` traits +=============================== + +The `Timer` trait presents the abstraction of a timer. The +timer can either be one-shot or periodic with a fixed +interval. `Timer` derives from `Time`, therefore has associated +`Time::Frequency` and `Ticks` types. + +The `TimerClient` trait handles callbacks from an instance of `Timer`. +The trait has a single callback, denoting that the timer has fired. + +```rust +pub trait TimerClient { + fn timer(&self); +} + +pub trait Timer<'a>: Time { + fn set_timer_client(&self, &'a dyn TimerClient); + fn oneshot(&self, interval: Self::Ticks) -> Self::Ticks; + fn repeating(&self, interval: Self::Ticks) -> Self::Ticks; + + fn interval(&self) -> Option; + fn is_oneshot(&self) -> bool; + fn is_repeating(&self) -> bool; + + fn time_remaining(&self) -> Option; + fn is_enabled(&self) -> bool; + + fn cancel(&self) -> Result<(), ErrorCode>; +} +``` + +The `oneshot` method causes the timer to issue the `TimerClient`'s +`fired` method exactly once when `interval` clock ticks have elapsed. +Calling `oneshot` MUST invalidate and replace any previous calls to +`oneshot` or `repeating`. The method returns the actual number of +ticks in the future that the callback will execute. This value MAY be +greater than `interval` to prevent certain timer race conditions +(e.g., that require a compare be set at least N ticks in the future) +but MUST NOT be less than `interval`. + +The `repeating` method causes the timer to call the `Client`'s `fired` +method periodically, every `interval` clock ticks. Calling `oneshot` +MUST invalidate and replace any previous calls to `oneshot` or +`repeat`. The method returns the actual number of ticks in the future +that the first callback will execute. This value MAY be greater than +`interval` to prevent certain timer race conditions (e.g., that +require a compare be set at least N ticks in the future) but MUST NOT +be less than `interval`. + + +6 `Frequency` and `Ticks` Implementations +================================= + +The time HIL provides four standard implementations of `Frequency`: + +```rust +pub struct Freq16MHz; +pub struct Freq1MHz; +pub struct Freq32KHz; +pub struct Freq16KHz; +pub struct Freq1KHz; +``` + +The time HIL provides three standard implementaitons of `Ticks`: + +```rust +pub struct Ticks24Bits(u32); +pub struct Ticks32Bits(u32); +pub struct Ticks64Bits(u64); +``` + +The 24 bits implementation is to support some Nordic Semiconductor +nRF platforms (e.g. nRF52840) that only support a 24-bit counter. + + +7 Capsules +=============================== + +The Tock kernel provides three standard capsules: + + * `capsules::alarm::AlarmDriver` provides a system call driver for + an `Alarm`. + * `capsules::virtual_alarm` provides a set of + abstractions for virtualizing a single `Alarm` into many. + * `capsules::virtual_timer` provides a set of abstractions for + virtualizing a single `Alarm` into many `Timer` instances. + +8 Required Modules +=============================== + +A chip MUST provide an instance of `Alarm` with a `Frequency` of `Freq32KHz` +and a `Ticks` of `Ticks32Bits`. + +A chip MUST provide an instance of `Time` with a `Frequency` of `Freq32KHz` and +a `Ticks` of `Ticks64Bits`. + +A chip SHOULD provide an Alarm with a `Frequency` of `Freq1MHz` and a `Ticks` +of `Ticks32Bits`. + + +9 Implementation Considerations +=============================== + +This section describes implementation considerations for hardware +implementations. + +The trickiest aspects of implementing the traits in this document relate +to the `Alarm` trait and the semantics of how and when callbacks +are triggered. In particular, if `set_alarm` indicates a time that has +already passed, then the implementation should adjust it so that it +will trigger very soon (rather than wait for a wrap-around). + +This is complicated by the fact that as the code is executing, the +underlying counter continues to tick. Therefore an implementation must +also be careful that this "very soon" time does not fall into the +past. Furthermore, many instances of timer hardware requires that a +compare value be some minimum number of ticks in the future. In +practice, this means setting "very soon" to be a safe number of ticks +in the future is a better implementation approach than trying to be +extremely precise and inadvertently choosing too soon and then waiting +for a wraparound. + +Pseudocode to handle these cases is as follows: + +``` +set_alarm(self, reference, dt): + now = now() + expires = reference.wrapping_add(dt) + if !now.within_range(reference, expired): + expires = now + + if expires.wrapping_sub(now) < MIN_DELAY: + expires = now.wrapping_add(MIN_DELAY) + + clear_alarm() + set_compare(expires) + enable_alarm() +``` + +10 Acknowledgements +=============================== + +The traits and abstractions in this document draw from contributions +and ideas from Patrick Mooney and Guillaume Endignoux as well as +others. + +11 Modification After TRD 105 +=============================== + +This TRD obsoletes TRD 105, and the changes include: + + * Extracted overflow management from `Counter` into a separate `CounterOverflow` trait to provide compile-time guarantees for hardware support. + +12 Authors' Address +================================= +``` +Amit Levy +amit@amitlevy.com + +Philip Levis +409 Gates Hall +Stanford University +Stanford, CA 94305 +USA +pal@cs.stanford.edu + +Guillaume Endignoux +guillaumee@google.com + +Jett Rink +jettrink@google.com + +Gabriel Stanciu +github.com/stanciugabriel +``` diff --git a/doc/wg/core/notes/core-notes-2026-05-20.md b/doc/wg/core/notes/core-notes-2026-05-20.md new file mode 100644 index 0000000000..8666668574 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2026-05-20.md @@ -0,0 +1,90 @@ +# Tock Meeting Notes 2026-05-20 + +## Attendees + - Branden Ghena + - Johnathan Van Why + - Amit Levy + + +## Tock Registers Update + * Johnathan: Work on Tock registers PR has reached point where I think this is the correct design moving forward. Thought about typestates and DMA deeply for a bit, and found that they shouldn't require any changes to the design. Can be developed outside of tock-registers and then later move into it if we want. + * Johnathan: I'm going to reach out to the dev email list next for thoughts. Then start making PRs to merge + * Johnathan: I'm also going to reach out to the Rust embedded community about it asking for feedback. I'm gonna make a matrix chat room so people can live-chat there about it. + * Amit: Awesome!! + * Branden: Yeah, super big deal. It'll be great to have this merged into Tock + * Johnathan: Lots of small nit-picky things we should decide on. Some today. + +### Version Numbering + * Johnathan: Should this be 1.0, or 2.0, or a new minor version? Or what? + * Branden: My thoughts are that we should go straight to 2.0. This is a new thing, separate from the prior thing. So 1.0 might feel like just building on top of the prior thing. + * Johnathan: We should release a version with both the old and new stuff. Otherwise you'd have to update everything if you jumped to the new version. + * Amit: Couldn't you have two dependencies and rename one of them + * Branden: My concern is that having new stuff and old stuff together in a release is a bit of a mess + * Johnathan: Okay, so we'd have no transitional release. Just go straight to 2.0.0 with no 1.0.0 release ever existing. Or we could do a transitional release with both the old stuff and new stuff together as a 0.something release. + * Amit: Does crates.io support beta releases? We could have 2.0-beta, then 2.0 which supersedes it? Is there a way for us to non-committally try the new thing. That way we can see how hard it is to do the transition. That would help us decide whether to briefly have both available. + * Johnathan: We have about 7000 registers in Tock. Not sure how painful that will be. Cargo has some reference to alpha releases, so that might be an option. + * Amit: Might be good for soliciting feedback + * Johnathan: So far all of my changes have been backwards-compatible. Deleting the old thing would let us make other breaking changes though. + +### Merge Strategy + * Johnathan: Planning to merge into master directly, get it all in, then do a release. But should we instead make a feature branch, merge into feature branch, then merge that into master + * Johnathan: Is there any reason to use a master branch? + * Branden: Will master end up broken if you merge some PRs but not others? + * Johnathan: No. + * Branden: Then I don't mind merging directly into master + +### Punctuation Bike-shedding + * Johnathan: https://github.com/tock/tock-registers/pull/11#discussion_r3274627305 + * Branden: I vote Idea A: commas everywhere. Everything else has rules about when and where to do things which I'm going to forget, and commas everywhere is straightforward + * Amit: No opinion here, seems fine + +### Renaming Macro + * Johnathan: Should it be `register_layouts!` or something else? Want this before sharing PR broadly + * Branden: Brad thought this should be `register_layout!` singular. I suspect no one actually cares + * Amit: Why not `registers!`? + * Johnathan: Overloaded with other traits and structs. Registers plural appears 900 times in the PR. Worried about word losing meaning. + * Amit: But doesn't it mean the same thing? Or we could use `regs!` maybe? + * Amit: Why this matters: this is the main thing that people are typing and reading. 99% of the time people will use the public interface in their own code. The thing in their own code being useful seems important. + * Branden: I thought it was nice to have register_layouts compared to register_bitfields. Both are part of registers + * Amit: It does take in a single register map. One or more maps actually + * Johnathan: Yes. Could be as small as a single register. + * Branden: So that lands on `register_layout!` to me. It's a set of registers that exists as a single layout + * Johnathan: Hmm. Inside of the macro, it provides zero or more things. Those things are single registers or register blocks. Each of those things internally is a "layout". But also, a chip has a register layout. So the collection of things you accumulate is a layout. Means two different things. + * Amit: To me, a layout is a set of registers with positions relative to each other. + * Johnathan: That is a register block. + * Amit: So what is a register_layout? + * Johnathan: It's an AST + * Amit: So registers from `registers!` means something. But the "layout" portion of `register_layouts!` doesn't mean anything useful. We wouldn't want to name it `register_ast_node!` + * Branden: So I think a register block is what Brad and I thought a register_layout was. Maybe we were just confused + * Amit: I think a block sounds like a meaningful term inside of tock registers. And a register map is a term of the same thing + * Amit: A UART has a register map, registers that are near each other. A chip has a register map which is many register blocks that aren't near each other. + * Branden: I don't understand what a `register_layout!` that isn't "a set of registers with positions relative to each other" + * Johnathan: They're definitely different. You can make a layout that has a single register, or multiple which aren't positioned relative to each other. + * Branden: I don't understand what that would even mean + * Amit: So the things inside a `layouts!` is a register, a block, a whatever. Each of those single elements, has one or more registers within it with positions relative to each other. Each block is also named independently. Blocks do not have relative relationships to each other. For example, if I wanted to define the entire set of registers for a chip, I would not do that with a single register_layouts! because that wouldn't let me specify positions relative to each other. + * Amit: When I define more than one thing in a single `register_layouts!`, I have a name for each of these things, which is semantically identical to defining them in separate macro invocations. + * Johnathan: You have to copy the inner attributes, but yes + * Branden: So then, why does `register_layouts!` allow you to put multiple things inside of it? + * Johnathan: It allows you to have inner attributes that apply to all of the things inside that register invocation. It's not a huge deal at the moment, because the only attributes are bus, buses, and doc comments + * Branden: If we only held a single thing inside the macro, then it would make sense to me. Right now it doesn't make sense to me because it's an abstract syntax tree + * Branden: We could make it so it only holds one thing + * Johnathan: That would probably be fine + * Amit: So we could have it hold one thing, and have it called `register_map!` or `register_layout!`. Or hold multiple things and it should be plural and something different from those two. The name has to be explainable. + * Johnathan: I'm also fine with `regs!` + * Amit: `regs!` makes sense as a connotation of "I'm entering the DSL". But the length of the name doesn't matter so much + * Amit: How often do we expect reusing inner attributes to be useful? + * Johnathan: Probably not often. I'm just worried that I'm wrong and would have to re-add stuff later. + * Amit: Yeah. Or is there another way to do it. + * Johnathan: We could allow multiple things, but sort-of hide it and make that not the default usage + * Amit: Is it possible to share inner attributes via some external thing? + * Johnathan: That would be hard. We'd have to invoke one macro from another macro. Gets messy fast + * Amit: For what it's worth, I feel fine with something like `register_layouts!` be an experimental or not highly surfaced option that's still "there". And having `register_map!` be only a single register block and be the main thing that's advertised and documented. And if use cases come up, then we can do stuff. + * Branden: I like that + * Johnathan: So you're proposing two macros, one with multiple things internally and one with only a single. + * Amit: Yes. Just call register_layouts! from register_map! + * Johnathan: Okay. That would be several macros, but doable. Feels a little duplicative. I think we just have it accept one thing by default, and we have it possibly accept two things but not be obviously presented + * Amit: Could have them be aliased and identical, but documented differently + * Johnathan: I think there shouldn't be more names. Confusing. + * Amit: We just wouldn't want it to be part of the stable API, that way we could remove it later if we wanted + * Johnathan: Then remove it and make it only accept one. It'll be a pain to add back in later + diff --git a/doc/wg/core/notes/core-notes-2026-05-27.md b/doc/wg/core/notes/core-notes-2026-05-27.md new file mode 100644 index 0000000000..7e0d9d6766 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2026-05-27.md @@ -0,0 +1,67 @@ +# Tock Meeting Notes 2026-05-27 + +## Attendees + - Johnathan Van Why + - Brad Campbell + - Amit Levy + - Hudson Ayers + + +## Updates +### Tock-Registers + * Johnathan: we have settled on register\_map as a name and on keeping multi-layout support, but not emphasizing it in the documentation + + +## Tock Registers Merge Plan + * Johnathan: On this call I am looking for consensus that we want to move forward with the design currently in the giant PR. Once we have that, I will look into splitting up into multiple PRs + * Brad: What does splitting the PR get us? Does that help you? + * Johnathan: I think there will be different reviewers for different parts of the changes, and I think giant PRs rarely get properly reviewed + * Johnathan: I am planning a tree of PRs about 6 nodes deep, with some of those layers being multiple PRs wide. + * Hudson: I recommend not splitting as many of the width PRs if you think they will have a single reviewer — it can become a lot of overhead to track that many separate PRs + * Johnathan: Sure, I will let reviewers decide if they want to request additional splitting. + * Brad: The main thing I am concerned about is versioning numbers (0/1/2) — we are currently on v0, need to indicate a split. Also, we need a concrete plan for update tock to the new registers format + * Johnathan: Some people proposed jumping from v0 straight to 2, I am considering that. + * Johnathan: I was planning to work out the remaining things on matrix (e.g. dev branch vs. master, etc.) + +## PR 4479 + * https://github.com/tock/tock/pull/4479 + * Brad: This changes some debug functions in the kernel to use a new capability rather than unsafe + * Amit: This is tricky — this sort of tests what is reasonably in the domain of capabilities vs. unsafe. I think the question to ask is whether this is only protecting privileged kernel functionality or is this also protecting Rust soundness? Can misuse of this capability to call panic\_print() from a non-panicking context break Rust? + * Hudson: It seems relevant that we call disable\_app\_mpu in panic\_print — could that be misused to create unsoundness? + * Brad: Leon would say that if you called this and then ran an app then you could touch arbitrary memory owned by the kernel from that app, which would be unsound, and you could do that without calling unsafe + * Amit: I think that is fixable by re-enabling the app\_mpu later in that function + * Brad: Lets take the hypothetical that we could fix all potential soundness issues, would we still prefer to have this be unsafe? Is it worth continuing with the capability idea? + * Amit: I would definitely prefer capability if there is no soundness implication + * Brad: I think we need to fix our safety docs for everything called in this so we can reason about this better, and then decide + +## PR 4682 + * https://github.com/tock/tock/pull/4682 + * Amit: I approved this awhile ago + * Johnathan: Do we need something to prevent init from being called multiple times? + * Amit: I don’t think so, for the same reason we did not prevent the things init is now calling from being called multiple times before + +## PR 4793 + * https://github.com/tock/tock/pull/4793 + * Brad: This is an alternative to #4716 to fix a soundness issue in process_create + * Brad: Basically, use MaybeUninit in the RingBuffer rather than pre-initializing everything in the RingBuffer + * Amit: Didn’t this leave off with you trying to verify this using Flux? + * Brad: Yeah there were some issues with the indexing brackets, I resolved that by using .get(), so the Flux issue is handled + * Brad: I think I fixed Leon’s comment, + * Amit: Is that not what was going to be proven using Flux? + * Brad: I don’t know. I am not sure if Flux is going to help us with the correct assume_init API. Lines 106 and 128 are the lines in question here. + * Amit: The important variant is that if is_valid returns true, line 159 executed with self.tail being equivalent to …. + * Amit: Ok, so are we waiting on anything? Did something change in your latest force push such that Leon’s comment is now outdated? I guess the other comment on this is that this is hard to reason about + * Amit: Since changes are requested on this, I am approving the design as seeming sound, and that way you can merge once you work through those changes + +## PR 4794 + * https://github.com/tock/tock/pull/4794 + * Brad: This has my name on it, but was pulled from a new board contribution + * Brad: Pat had a few concerns with this, I think maybe we should just close it + * Amit: Yeah, lets close it + +## PR 4817 + * https://github.com/tock/tock/pull/4817 + * Brad: If you have a public key you trust for services, which is separate from other keys you have, we have capsules for key switching etc. and that works in our tutorial. However, when adding that functionality for mobisys we do not have a way to propagate information about which key worked, other than its order. We want to be able to use the kernel attributes (at the end of the kernel binary) and put keys there and tag them as trusted for different things. This PR adds an API to propagate that metadata. The keys could actually be anywhere, this PR just adds a way for services to verify that whatever thing was verified with whatever key. + * Amit: I approved + * Hudson: This seems reasonable + * Brad: Hopefully there will be followon PRs, and I think those will just be capsules diff --git a/kernel/src/collections/ring_buffer.rs b/kernel/src/collections/ring_buffer.rs index ea2598742f..f41440ecf1 100644 --- a/kernel/src/collections/ring_buffer.rs +++ b/kernel/src/collections/ring_buffer.rs @@ -5,15 +5,27 @@ //! Implementation of a ring buffer. use crate::collections::queue; - -pub struct RingBuffer<'a, T: 'a> { - ring: &'a mut [T], +use core::mem::MaybeUninit; + +/// Queue implementation based on a contiguous buffer. +/// +/// The buffer is assumed to be uninitialized when the [`RingBuffer`] is +/// created. This can be safely created using uninitialized memory for the ring. +pub struct RingBuffer<'a, T: Copy + 'a> { + /// Array of elements `T`. The buffer does not need to have initialized + /// elements `T`. The `RingBuffer` assumes that any element not tracked in + /// the ring based on head and tail are not initialized. + ring: &'a mut [MaybeUninit], head: usize, tail: usize, } impl<'a, T: Copy> RingBuffer<'a, T> { - pub fn new(ring: &'a mut [T]) -> RingBuffer<'a, T> { + /// Create a [`RingBuffer`]. + /// + /// The provided `ring` is assumed to be uninitialized and the ring starts + /// empty. + pub fn new(ring: &'a mut [MaybeUninit]) -> RingBuffer<'a, T> { RingBuffer { head: 0, tail: 0, @@ -38,22 +50,85 @@ impl<'a, T: Copy> RingBuffer<'a, T> { /// contents of the buffer is `[left, right].concat()` (although physically the "left" slice is /// stored after the "right" slice). pub fn as_slices(&'a self) -> (Option<&'a [T]>, Option<&'a [T]>) { + // SAFETY: Reinterprets &[MaybeUninit] as &[T]. MaybeUninit has the same layout as + // T, and every element in the returned slice falls within [head, tail) which has been + // written by enqueue/push, so reading those elements as T is valid. + let assume_init = |s: &[MaybeUninit]| -> &[T] { + unsafe { core::slice::from_raw_parts(s.as_ptr().cast::(), s.len()) } + }; + if self.head < self.tail { - (Some(&self.ring[self.head..self.tail]), None) + (Some(assume_init(&self.ring[self.head..self.tail])), None) } else if self.head > self.tail { let (left, right) = self.ring.split_at(self.head); ( - Some(right), + Some(assume_init(right)), if self.tail == 0 { None } else { - Some(&left[..self.tail]) + Some(assume_init(&left[..self.tail])) }, ) } else { (None, None) } } + + /// Check if `index` into the internal `ring` contains a valid element + /// stored in the [`RingBuffer`]. + fn is_valid(&self, index: usize) -> bool { + let capacity = self.ring.len(); + let position_in_ring = (index + capacity - self.head) % capacity; + + // Check if the position in the ring of `index` is not actually within + // the populated ring holding valid elements. If so, return true. + // Otherwise, return false. + position_in_ring < queue::Queue::len(self) + } + + /// Get the element stored at `index` in the `ring` if there is a valid + /// element at that index. + fn get_internal(&self, index: usize) -> Option { + // Check if the position in the ring of `index` is not actually within + // the populated ring holding valid elements. If so, this `get()` fails + // and we return `None`. + if !self.is_valid(index) { + None + } else { + let element = self.ring.get(index); + element.map(|e| { + // # Safety + // + // It is safe to read the element from this location in the ring and + // assume it is initialized because we verified that the index is + // within the populated elements of the ring. Our invariant is that + // any index with head and tail _must_ be initialized. + unsafe { e.assume_init() } + }) + } + } + + /// Get a reference to the element stored at `index` in the `ring` if there + /// is a valid element at that index. + fn get_internal_ref(&self, index: usize) -> Option<&T> { + // Check if the position in the ring of `index` is not actually within + // the populated ring holding valid elements. If so, this `get()` fails + // and we return `None`. + if !self.is_valid(index) { + None + } else { + let element = self.ring.get(index); + element.map(|e| { + // # Safety + // + // It is safe to read the element from this location in the ring and + // assume it is initialized because we verified that the index is + // within the populated elements of the ring. Our invariant is that + // any index with head and tail _must_ be initialized. + unsafe { e.assume_init_ref() } + }) + } + } } impl queue::Queue for RingBuffer<'_, T> { @@ -81,7 +156,7 @@ impl queue::Queue for RingBuffer<'_, T> { // Incrementing tail will overwrite head false } else { - self.ring[self.tail] = val; + self.ring[self.tail].write(val); self.tail = (self.tail + 1) % self.ring.len(); true } @@ -89,23 +164,23 @@ impl queue::Queue for RingBuffer<'_, T> { fn push(&mut self, val: T) -> Option { let result = if self.is_full() { - let val = self.ring[self.head]; + let old = self.get_internal(self.head); self.head = (self.head + 1) % self.ring.len(); - Some(val) + old } else { None }; - self.ring[self.tail] = val; + self.ring[self.tail].write(val); self.tail = (self.tail + 1) % self.ring.len(); result } fn dequeue(&mut self) -> Option { if self.has_elements() { - let val = self.ring[self.head]; + let val = self.get_internal(self.head); self.head = (self.head + 1) % self.ring.len(); - Some(val) + val } else { None } @@ -122,11 +197,12 @@ impl queue::Queue for RingBuffer<'_, T> { let len = self.ring.len(); let mut slot = self.head; while slot != self.tail { - if f(&self.ring[slot]) { - // This is the desired element, return a reference to it - return Some(&self.ring[slot]); + if let Some(e) = self.get_internal_ref(slot) { + if f(e) { + return Some(e); + } + slot = (slot + 1) % len; } - slot = (slot + 1) % len; } None } @@ -145,21 +221,25 @@ impl queue::Queue for RingBuffer<'_, T> { let len = self.ring.len(); let mut slot = self.head; while slot != self.tail { - if f(&self.ring[slot]) { - // This is the desired element, remove it and return it - let val = self.ring[slot]; - - let mut next_slot = (slot + 1) % len; - // Move everything past this element forward in the ring - while next_slot != self.tail { - self.ring[slot] = self.ring[next_slot]; - slot = next_slot; - next_slot = (next_slot + 1) % len; + if let Some(e) = self.get_internal_ref(slot) { + if f(e) { + // This is the desired element, remove it and return it. + let val = self.get_internal(slot); + + let mut next_slot = (slot + 1) % len; + // Move everything past this element forward in the ring. + while next_slot != self.tail { + if let Some(to_move) = self.get_internal(next_slot) { + self.ring[slot].write(to_move); + slot = next_slot; + next_slot = (next_slot + 1) % len; + } + } + self.tail = slot; + return val; } - self.tail = slot; - return Some(val); + slot = (slot + 1) % len; } - slot = (slot + 1) % len; } None } @@ -180,15 +260,19 @@ impl queue::Queue for RingBuffer<'_, T> { let mut dst = self.head; while src != self.tail { - if f(&self.ring[src]) { - // When the predicate is true, move the current element to the - // destination if needed, and increment the destination index. - if src != dst { - self.ring[dst] = self.ring[src]; + if let Some(e) = self.get_internal_ref(src) { + if f(e) { + // When the predicate is true, move the current element to the + // destination if needed, and increment the destination index. + if src != dst { + if let Some(to_move) = self.get_internal(src) { + self.ring[dst].write(to_move); + } + } + dst = (dst + 1) % len; } - dst = (dst + 1) % len; + src = (src + 1) % len; } - src = (src + 1) % len; } self.tail = dst; @@ -199,11 +283,12 @@ impl queue::Queue for RingBuffer<'_, T> { mod test { use super::super::queue::Queue; use super::RingBuffer; + use core::mem::MaybeUninit; #[test] fn test_enqueue_dequeue() { const LEN: usize = 10; - let mut ring = [0; LEN]; + let mut ring: [MaybeUninit; LEN] = [MaybeUninit::uninit(); LEN]; let mut buf = RingBuffer::new(&mut ring); for _ in 0..2 * LEN { @@ -221,7 +306,7 @@ mod test { fn test_push() { const LEN: usize = 10; const MAX: usize = 100; - let mut ring = [0; LEN + 1]; + let mut ring: [MaybeUninit; LEN + 1] = [MaybeUninit::uninit(); LEN + 1]; let mut buf = RingBuffer::new(&mut ring); for i in 0..LEN { @@ -296,7 +381,7 @@ mod test { #[test] fn test_fill_once() { const LEN: usize = 10; - let mut ring = [0; LEN]; + let mut ring: [MaybeUninit; LEN] = [MaybeUninit::uninit(); LEN]; let mut buf = RingBuffer::new(&mut ring); assert!(!buf.has_elements()); @@ -309,7 +394,7 @@ mod test { #[test] fn test_refill() { const LEN: usize = 10; - let mut ring = [0; LEN]; + let mut ring: [MaybeUninit; LEN] = [MaybeUninit::uninit(); LEN]; let mut buf = RingBuffer::new(&mut ring); for _ in 0..10 { @@ -321,7 +406,7 @@ mod test { #[test] fn test_retain() { const LEN: usize = 10; - let mut ring = [0; LEN]; + let mut ring: [MaybeUninit; LEN] = [MaybeUninit::uninit(); LEN]; let mut buf = RingBuffer::new(&mut ring); move_head(&mut buf, LEN - 2); @@ -383,6 +468,7 @@ mod flux_specs { use crate::collections::list::ListIterator; use crate::collections::queue::Queue; use crate::collections::ring_buffer::RingBuffer; + use core::mem::MaybeUninit; #[flux::specs { // Specify well-formedness for RingBuffer. @@ -391,7 +477,7 @@ mod flux_specs { // This ensures that all indices `ring[head]` and `ring[tail]` are valid. #[refined_by(ring_len: int, hd: int, tl: int)] struct RingBuffer { - ring: {&mut [T][ring_len] | ring_len > 0}, + ring: {&mut [MaybeUninit][ring_len] | ring_len > 0}, head: {usize[hd] | hd < ring_len}, tail: {usize[tl] | tl < ring_len}, } @@ -411,7 +497,7 @@ mod flux_specs { // Weaker vs stronger contracts is a design decision: it is easier to prove that a weak contract // holds in the implementation, but it lets you prove less in the rest of Tock (e.g., if there was code // that was only safe if head/tail was 0 after it called new, we could prove its safety only with the stronger contract). - fn new({&mut [T][@ring_len] | ring_len > 0}) -> RingBuffer[ring_len, 0, 0]; + fn new({&mut [MaybeUninit][@ring_len] | ring_len > 0}) -> RingBuffer[ring_len, 0, 0]; } impl Queue for RingBuffer { @@ -456,7 +542,7 @@ mod flux_specs { #[allow(dead_code)] #[flux_rs::should_fail] fn bad_split_into_ringbuffers<'a, T: Copy>( - buf: &'a mut [T], + buf: &'a mut [MaybeUninit], output_len: usize, ) -> (RingBuffer<'a, T>, RingBuffer<'a, T>) { // If `output_len` is `0` or `buf.len()`, then `output_ringbuf` @@ -484,7 +570,7 @@ mod flux_specs { if rb.is_full() { false } else { - rb.ring[rb.tail] = val; + rb.ring[rb.tail].write(val); // CORRECT: rb.tail = (rb.tail + 1) % rb.ring.len(); rb.tail = rb.tail + 1; true diff --git a/kernel/src/hil/time.rs b/kernel/src/hil/time.rs index 8e2d7fc87c..c026f207da 100644 --- a/kernel/src/hil/time.rs +++ b/kernel/src/hil/time.rs @@ -234,11 +234,6 @@ pub trait OverflowClient { /// Represents a free-running hardware counter that can be started and stopped. pub trait Counter<'a>: Time { - /// Specify the callback for when the counter overflows its maximum - /// value (defined by `Ticks`). If there was a previously registered - /// callback this call replaces it. - fn set_overflow_client(&self, client: &'a dyn OverflowClient); - /// Starts the free-running hardware counter. Valid `Result<(), ErrorCode>` values are: /// - `Ok(())`: the counter is now running /// - `Err(ErrorCode::OFF)`: underlying clocks or other hardware resources @@ -269,6 +264,14 @@ pub trait Counter<'a>: Time { fn is_running(&self) -> bool; } +/// Extension trait for counters that support hardware overflow notifications. +#[deprecated( + note = "set_overflow_client never used so moved to an independent trait. Set to be removed in future releases unless a concrete use case is raised." +)] +pub trait CounterOverflow<'a>: Counter<'a> { + fn set_overflow_client(&self, client: &'a dyn OverflowClient); +} + /// Callback handler for when an Alarm fires (a `Counter` reaches a specific /// value). pub trait AlarmClient { diff --git a/kernel/src/platform/chip.rs b/kernel/src/platform/chip.rs index a477b9af84..e2366ecc52 100644 --- a/kernel/src/platform/chip.rs +++ b/kernel/src/platform/chip.rs @@ -29,6 +29,27 @@ pub trait Chip { /// chips may have various custom requirements. type UserspaceKernelBoundary: syscall::UserspaceKernelBoundary; + /// Run any necessary initialization for this chip. + /// + /// This should be called first by the board in its `main()` function. + /// + /// This should contain any necessary initialization steps, including: + /// + /// - Any architecture-specific setup + /// - Any errata fixes + /// - Any configuration needed for the chip to continue booting the kernel + /// + /// `init()` does not use `&self` so it can be called before the actual + /// `Chip` is instantiated. These initializations may be needed very soon + /// after the chip boots, and potentially much before the actual `Chip` can + /// be created. + /// + /// `init()` is marked safe as the implementation must handle any safety + /// requirements of the underlying initialization steps. As this will be + /// called very early when the chip boots, it is not useful or reasonable + /// for the kernel's main function to address any safety requirements. + fn init(); + /// The kernel calls this function to tell the chip to check for all pending /// interrupts and to correctly dispatch them to the peripheral drivers for /// the chip. diff --git a/kernel/src/process.rs b/kernel/src/process.rs index 60f4064637..c7febc8e6c 100644 --- a/kernel/src/process.rs +++ b/kernel/src/process.rs @@ -917,8 +917,8 @@ pub enum Error { AlreadyInUse, } -impl From for Result<(), ErrorCode> { - fn from(err: Error) -> Result<(), ErrorCode> { +impl From for Result { + fn from(err: Error) -> Result { match err { Error::OutOfMemory => Err(ErrorCode::NOMEM), Error::AddressOutOfBounds => Err(ErrorCode::INVAL), diff --git a/kernel/src/process_standard.rs b/kernel/src/process_standard.rs index 0ed6e6c3e7..acb4e726f9 100644 --- a/kernel/src/process_standard.rs +++ b/kernel/src/process_standard.rs @@ -2119,6 +2119,10 @@ impl ProcessStandard<'_, C kernel_memory_break = unsafe { kernel_memory_break.offset(-(Self::CALLBACKS_OFFSET as isize)) }; + // Set up ring buffer for upcalls to the process. The memory is uninitialized here, + // so we cast to MaybeUninit which accurately represents that state. + let upcall_buf: *mut core::mem::MaybeUninit = kernel_memory_break.cast(); + // # Safety // // This is safe today, as MPU constraints ensure that `memory_start` @@ -2130,8 +2134,6 @@ impl ProcessStandard<'_, C // // TODO: https://github.com/tock/tock/issues/1739 #[allow(clippy::cast_ptr_alignment)] - // Set up ring buffer for upcalls to the process. - let upcall_buf: *mut Task = kernel_memory_break.cast(); let upcall_buf = unsafe { slice::from_raw_parts_mut(upcall_buf, Self::CALLBACK_LEN) }; let tasks = RingBuffer::new(upcall_buf); diff --git a/tools/ci/tockbot/requirements.txt b/tools/ci/tockbot/requirements.txt index 6efa03d228..553ea2dda3 100644 --- a/tools/ci/tockbot/requirements.txt +++ b/tools/ci/tockbot/requirements.txt @@ -13,7 +13,7 @@ idna==3.15 platformdirs==4.2.0 pycparser==2.21 PyGithub==2.1.1 -PyJWT==2.12.0 +PyJWT==2.13.0 PyNaCl==1.6.2 python-dateutil==2.8.2 PyYAML==6.0.1