diff --git a/adapter/cmsis/BUILD.gn b/adapter/cmsis/BUILD.gn index 8352b507..9ab23bf1 100644 --- a/adapter/cmsis/BUILD.gn +++ b/adapter/cmsis/BUILD.gn @@ -111,6 +111,7 @@ build_rust("adapter_validation") { ":cmsis_adapter", ":validation_cases", "//external/vendor/semihosting-0.1.20:semihosting", + "//kernel/arch:bluekernel_arch", "//kernel/kconfig:generate_rustflags_file", # in fact, validation_cases need an c library diff --git a/adapter/cmsis/src/validation.rs b/adapter/cmsis/src/validation.rs index ffdfcf96..b10e8dfc 100644 --- a/adapter/cmsis/src/validation.rs +++ b/adapter/cmsis/src/validation.rs @@ -105,51 +105,34 @@ mod tests { 0 } - use cortex_m::interrupt::InterruptNumber; - - // copy from kernel/src/arch/arm/irq.rs - // used for rv2 ISR context api tests - #[derive(Debug, Copy, Clone)] - #[repr(transparent)] - pub struct IrqNumber(u16); - impl IrqNumber { - #[inline] - pub const fn new(number: u16) -> Self { - Self(number) - } - } - // SAFETY: get the number of the interrupt is safe - unsafe impl InterruptNumber for IrqNumber { - #[inline] - fn number(self) -> u16 { - self.0 - } - } + use bluekernel_arch::cortex_m::nvic; #[no_mangle] pub unsafe extern "C" fn NVIC_SetPriority(irq: u16, priority: u8) { - cortex_m::Peripherals::steal() - .NVIC - .set_priority(IrqNumber::new(irq), priority); + // Keep the CMSIS validation shim on the same in-tree architecture + // boundary as the kernel. `bluekernel_arch` owns the NVIC MMIO access + // now, so the adapter test should not pull in a second architecture + // abstraction just to drive validation interrupts. + unsafe { nvic::set_priority(irq, priority) }; } #[no_mangle] pub unsafe extern "C" fn NVIC_EnableIRQ(irq: u16) { - cortex_m::peripheral::NVIC::unmask(IrqNumber::new(irq)); + unsafe { nvic::enable(irq) }; } #[no_mangle] pub unsafe extern "C" fn NVIC_DisableIRQ(irq: u16) { - cortex_m::peripheral::NVIC::mask(IrqNumber::new(irq)); + unsafe { nvic::disable(irq) }; } #[no_mangle] pub unsafe extern "C" fn NVIC_GetPendingIRQ(irq: u16) -> bool { - cortex_m::peripheral::NVIC::is_pending(IrqNumber::new(irq)) + unsafe { nvic::is_pending(irq) } } #[no_mangle] pub unsafe extern "C" fn NVIC_SetPendingIRQ(irq: u16) { - cortex_m::peripheral::NVIC::pend(IrqNumber::new(irq)); + unsafe { nvic::pend(irq) }; } #[no_mangle] diff --git a/arch/BUILD.gn b/arch/BUILD.gn new file mode 100644 index 00000000..4b79356a --- /dev/null +++ b/arch/BUILD.gn @@ -0,0 +1,29 @@ +# Copyright (c) 2025 vivo Mobile Communication Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/templates/build_template.gni") +import("//build/toolchain/blueos.gni") + +build_rust("bluekernel_arch") { + crate_type = "rlib" + sources = [ "src/lib.rs" ] + edition = "2021" + deps = [ + "//external/vendor/tock-registers-0.9.0:tock_registers", + "//kernel/hal:blueos_hal", + "//kernel/kconfig:blueos_kconfig", + "//kernel/kconfig:generate_rustflags_file", + ] + configs += [ "//kernel/kconfig:kconfigs" ] +} diff --git a/arch/arm/cortex_m/asm.rs b/arch/arm/cortex_m/asm.rs new file mode 100644 index 00000000..0e3b6033 --- /dev/null +++ b/arch/arm/cortex_m/asm.rs @@ -0,0 +1,324 @@ +// Copyright (c) 2026 vivo Mobile Communication Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Small Cortex-M assembly instruction wrappers. +//! +//! This module follows the shape of `cortex_m::asm` for simple architectural +//! instructions, while keeping Bluekernel-specific safety contracts explicit: +//! +//! +//! It intentionally contains only reusable CPU instructions and special-register +//! accessors. Larger naked assembly routines that depend on the kernel's +//! `Context` layout, exception-frame layout, or scheduler calling convention +//! remain in `kernel/src/arch/arm`. +//! +//! See also: +//! - CMSIS-Core register access helpers for MSP, PSP, and BASEPRI: +//! +//! - Arm Cortex-M memory barrier guidance: +//! + +use core::arch::asm; + +/// A no-operation instruction. +/// +/// # Safety +/// +/// The caller must ensure that executing a Cortex-M `nop` instruction is valid +/// in the current target and execution context. +#[inline(always)] +pub unsafe fn nop() { + unsafe { + asm!("nop", options(nomem, nostack, preserves_flags)); + } +} + +/// Waits for an interrupt. +/// +/// # Safety +/// +/// The caller must ensure that entering sleep until an interrupt is compatible +/// with the current interrupt state and platform power-management rules. If +/// preceding memory-mapped writes must complete before sleep, issue `dsb()` +/// first as required by the platform. +#[inline(always)] +pub unsafe fn wfi() { + unsafe { + asm!("wfi", options(nomem, nostack, preserves_flags)); + } +} + +/// Waits for an event. +/// +/// # Safety +/// +/// The caller must ensure that entering event-wait state is valid for the +/// current kernel and platform state. +#[inline(always)] +pub unsafe fn wfe() { + unsafe { + asm!("wfe", options(nomem, nostack, preserves_flags)); + } +} + +/// Sends an event to wake processors waiting in `wfe`. +/// +/// # Safety +/// +/// The caller must ensure that signaling an event is valid for the platform's +/// synchronization protocol. +#[inline(always)] +pub unsafe fn sev() { + unsafe { + asm!("sev", options(nomem, nostack, preserves_flags)); + } +} + +/// Instruction Synchronization Barrier. +/// +/// ISB flushes the pipeline so instructions after the barrier are fetched and +/// executed using the effects of context-changing operations before it. +/// +/// # Safety +/// +/// The caller must ensure that executing an ISB is valid in the current +/// Cortex-M context. +#[inline(always)] +pub unsafe fn isb() { + unsafe { + asm!("isb", options(nomem, nostack, preserves_flags)); + } +} + +/// Data Synchronization Barrier. +/// +/// DSB waits until memory accesses before the barrier have completed before +/// subsequent instructions continue. +/// +/// # Safety +/// +/// The caller must ensure that executing a DSB is valid in the current +/// Cortex-M context. +#[inline(always)] +pub unsafe fn dsb() { + unsafe { + asm!("dsb", options(nomem, nostack, preserves_flags)); + } +} + +/// Data Memory Barrier. +/// +/// DMB orders memory accesses before and after the barrier without requiring +/// all preceding accesses to complete. +/// +/// # Safety +/// +/// The caller must ensure that executing a DMB is valid in the current +/// Cortex-M context. +#[inline(always)] +pub unsafe fn dmb() { + unsafe { + asm!("dmb", options(nomem, nostack, preserves_flags)); + } +} + +/// Disables maskable interrupts by setting PRIMASK.I. +/// +/// # Safety +/// +/// The caller must preserve kernel interrupt-state invariants. Bluekernel +/// normally uses BASEPRI for local critical sections, so this should be +/// reserved for early boot or architecture code that really needs PRIMASK. +#[inline(always)] +pub unsafe fn cpsid_i() { + unsafe { + asm!("cpsid i", options(nomem, nostack, preserves_flags)); + } +} + +/// Enables maskable interrupts by clearing PRIMASK.I. +/// +/// # Safety +/// +/// The caller must ensure all required exception handlers and interrupt +/// priorities are initialized before interrupts can run. +#[inline(always)] +pub unsafe fn cpsie_i() { + unsafe { + asm!("cpsie i", options(nomem, nostack, preserves_flags)); + } +} + +/// Reads the currently selected stack pointer. +/// +/// In Thread mode this is selected by CONTROL.SPSEL. In Handler mode it is the +/// main stack pointer. +/// +/// # Safety +/// +/// The caller must ensure that observing the current stack pointer does not +/// violate any calling-convention or diagnostic assumptions. +#[inline(always)] +pub unsafe fn read_sp() -> usize { + let value: usize; + unsafe { + asm!("mov {}, sp", out(reg) value, options(nomem, nostack, preserves_flags)); + } + value +} + +/// Reads the Main Stack Pointer (MSP). +/// +/// # Safety +/// +/// The caller must ensure the instruction is executed on Cortex-M and that the +/// returned pointer is interpreted only according to the current CPU mode. +#[inline(always)] +pub unsafe fn read_msp() -> usize { + let value: usize; + unsafe { + asm!("mrs {}, msp", out(reg) value, options(nomem, nostack, preserves_flags)); + } + value +} + +/// Writes the Main Stack Pointer (MSP). +/// +/// # Safety +/// +/// The caller must provide a valid, correctly aligned stack pointer for the +/// current exception and boot state. An invalid MSP can make exception entry, +/// return, or fault handling immediately undefined. +#[inline(always)] +pub unsafe fn write_msp(value: usize) { + unsafe { + asm!("msr msp, {}", in(reg) value, options(nomem, nostack, preserves_flags)); + } +} + +/// Reads the Process Stack Pointer (PSP). +/// +/// # Safety +/// +/// The caller must ensure the PSP is meaningful for the current execution +/// state. PSP is typically used by OS thread mode code. +#[inline(always)] +pub unsafe fn read_psp() -> usize { + let value: usize; + unsafe { + asm!("mrs {}, psp", out(reg) value, options(nomem, nostack, preserves_flags)); + } + value +} + +/// Writes the Process Stack Pointer (PSP). +/// +/// # Safety +/// +/// The caller must provide a valid, correctly aligned process stack pointer for +/// the thread or exception-return path that will consume it. +#[inline(always)] +pub unsafe fn write_psp(value: usize) { + unsafe { + asm!("msr psp, {}", in(reg) value, options(nomem, nostack, preserves_flags)); + } +} + +/// Reads BASEPRI, the Cortex-M priority threshold mask. +/// +/// # Safety +/// +/// The caller must ensure the returned raw priority threshold is interpreted +/// using the target's implemented priority bits. +#[inline(always)] +pub unsafe fn read_basepri() -> usize { + let value: usize; + unsafe { + asm!("mrs {}, basepri", out(reg) value, options(nomem, nostack, preserves_flags)); + } + value +} + +/// Writes BASEPRI, the Cortex-M priority threshold mask. +/// +/// Writing `0` disables the BASEPRI mask. Non-zero values mask interrupts with +/// equal or lower urgency according to the target's implemented priority bits. +/// +/// # Safety +/// +/// The caller must preserve Bluekernel's priority invariants. Raising or +/// clearing BASEPRI at the wrong point can break critical sections or allow an +/// interrupt to observe partially updated scheduler state. +#[inline(always)] +pub unsafe fn write_basepri(value: usize) { + unsafe { + asm!("msr basepri, {}", in(reg) value, options(nomem, nostack, preserves_flags)); + } +} + +/// Resets MSP/PSP and enters the first scheduled thread-mode continuation. +/// +/// This is the non-returning bootstrap sequence used after the kernel has +/// chosen the first process stack. It updates both stack pointers, restores the +/// minimal architectural state expected by the continuation, masks lower +/// priority interrupts with BASEPRI, enables PRIMASK-controlled interrupts, +/// and branches to `cont`. +/// +/// The operation stays in one inline-assembly block so the compiler cannot use +/// the old stack after MSP has been replaced. This mirrors the previous kernel +/// inline assembly while moving the reusable Cortex-M register writes into the +/// architecture crate. +/// +/// # Safety +/// +/// The caller must ensure: +/// +/// - `msp` points to a valid main stack for exception handling. +/// - `psp` points to a valid process stack for the first scheduled thread. +/// - `xpsr`, `control`, and `basepri` are valid architectural values for the +/// target and kernel interrupt policy. +/// - `cont` is a valid executable continuation and must not return. +#[inline(always)] +pub unsafe fn reset_stack_pointers_and_start( + msp: *mut u8, + psp: usize, + xpsr: usize, + control: usize, + basepri: usize, + cont: extern "C" fn() -> !, +) -> ! { + unsafe { + asm!( + " + msr psp, {psp} + msr msp, {msp} + ", + " + msr xpsr, {xpsr} + msr control, {control} + ldr lr, =0 + msr basepri, {basepri} + cpsie i + bx {cont} + ", + options(nostack, noreturn), + msp = in(reg) msp, + psp = in(reg) psp, + xpsr = in(reg) xpsr, + control = in(reg) control, + basepri = in(reg) basepri, + cont = in(reg) cont, + ) + } +} diff --git a/arch/arm/cortex_m/exception.rs b/arch/arm/cortex_m/exception.rs new file mode 100644 index 00000000..d2918b16 --- /dev/null +++ b/arch/arm/cortex_m/exception.rs @@ -0,0 +1,262 @@ +// Copyright (c) 2026 vivo Mobile Communication Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Cortex-M exception stack frame helpers. +//! +//! On exception entry, Cortex-M hardware pushes a basic stack frame containing +//! R0-R3, R12, LR, PC, and xPSR. On hard-float targets, the same type also +//! includes the low floating-point extension frame fields used by the current +//! kernel ABI. +//! +//! See Arm's exception stack frame overview: +//! + +/// xPSR value required for returning to Thumb state on Cortex-M. +pub const THUMB_MODE_XPSR: usize = 1 << 24; + +/// xPSR bit that records hardware-inserted stack alignment padding. +pub const STACK_ALIGNMENT_PADDING: usize = 1 << 9; + +/// Initial FPSCR value used when preparing a fresh hard-float thread frame. +#[cfg(target_abi = "eabihf")] +pub const INITIAL_FPSCR: usize = 1 << 25; + +/// Sentinel value kept in the reserved word following FPSCR in the current +/// hard-float frame layout. +#[cfg(target_abi = "eabihf")] +pub const INITIAL_FPU_RESERVED: usize = 0xc0dec0de; + +/// Registers automatically stacked by Cortex-M exception entry. +/// +/// The field order starts with the architectural basic exception frame. On +/// `target_abi = "eabihf"` builds, it also carries the floating-point extension +/// fields after the basic frame to match the kernel's existing hard-float stack +/// layout. Keep this type `repr(C)` because kernel context structures embed it +/// directly and assembly code relies on the resulting memory layout. +#[repr(C, align(8))] +#[derive(Default, Copy, Clone)] +pub struct ExceptionStackFrame { + /// Argument/result register R0. + pub r0: usize, + /// Argument register R1. + pub r1: usize, + /// Argument register R2. + pub r2: usize, + /// Argument register R3. + pub r3: usize, + /// Intra-procedure-call scratch register R12. + pub r12: usize, + /// Link register saved by exception entry. + pub lr: usize, + /// Program counter restored by exception return. + pub pc: usize, + /// Program status register restored by exception return. + pub xpsr: usize, + /// Floating-point register S0. + #[cfg(target_abi = "eabihf")] + pub s0: usize, + /// Floating-point register S1. + #[cfg(target_abi = "eabihf")] + pub s1: usize, + /// Floating-point register S2. + #[cfg(target_abi = "eabihf")] + pub s2: usize, + /// Floating-point register S3. + #[cfg(target_abi = "eabihf")] + pub s3: usize, + /// Floating-point register S4. + #[cfg(target_abi = "eabihf")] + pub s4: usize, + /// Floating-point register S5. + #[cfg(target_abi = "eabihf")] + pub s5: usize, + /// Floating-point register S6. + #[cfg(target_abi = "eabihf")] + pub s6: usize, + /// Floating-point register S7. + #[cfg(target_abi = "eabihf")] + pub s7: usize, + /// Floating-point register S8. + #[cfg(target_abi = "eabihf")] + pub s8: usize, + /// Floating-point register S9. + #[cfg(target_abi = "eabihf")] + pub s9: usize, + /// Floating-point register S10. + #[cfg(target_abi = "eabihf")] + pub s10: usize, + /// Floating-point register S11. + #[cfg(target_abi = "eabihf")] + pub s11: usize, + /// Floating-point register S12. + #[cfg(target_abi = "eabihf")] + pub s12: usize, + /// Floating-point register S13. + #[cfg(target_abi = "eabihf")] + pub s13: usize, + /// Floating-point register S14. + #[cfg(target_abi = "eabihf")] + pub s14: usize, + /// Floating-point register S15. + #[cfg(target_abi = "eabihf")] + pub s15: usize, + /// Floating-Point Status and Control Register. + #[cfg(target_abi = "eabihf")] + pub fpscr: usize, + /// Reserved word in the floating-point extension frame. + #[cfg(target_abi = "eabihf")] + pub fpu_reserved: usize, +} + +impl core::fmt::Debug for ExceptionStackFrame { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "ExceptionStackFrame {{")?; + write!(f, "r0: 0x{:x} ", self.r0)?; + write!(f, "r1: 0x{:x} ", self.r1)?; + write!(f, "r2: 0x{:x} ", self.r2)?; + write!(f, "r3: 0x{:x} ", self.r3)?; + write!(f, "r12: 0x{:x} ", self.r12)?; + write!(f, "lr: 0x{:x} ", self.lr)?; + write!(f, "pc: 0x{:x} ", self.pc)?; + write!(f, "xpsr: 0x{:x}", self.xpsr)?; + #[cfg(target_abi = "eabihf")] + { + write!(f, " fpscr: 0x{:x}", self.fpscr)?; + write!(f, " fpu_reserved: 0x{:x}", self.fpu_reserved)?; + } + write!(f, "}}") + } +} + +impl ExceptionStackFrame { + /// Borrows a Cortex-M exception frame from a stack pointer. + /// + /// # Safety + /// + /// The caller must ensure `sp` is non-null, correctly aligned, and points + /// to a complete Cortex-M exception frame for the returned lifetime. + /// The caller must also ensure no mutable reference aliases the same frame + /// while the returned shared reference is alive. + #[inline(always)] + pub unsafe fn from_stack_pointer<'a>(sp: *const usize) -> &'a Self { + unsafe { &*(sp as *const Self) } + } + + /// Mutably borrows a Cortex-M exception frame from a stack pointer. + /// + /// # Safety + /// + /// The caller must ensure `sp` is non-null, correctly aligned, and points + /// to a complete Cortex-M exception frame for the returned lifetime. + /// The caller must also have exclusive access to that frame while the + /// returned mutable reference is alive. + #[inline(always)] + pub unsafe fn from_stack_pointer_mut<'a>(sp: *mut usize) -> &'a mut Self { + unsafe { &mut *(sp as *mut Self) } + } + + /// Sets the return PC restored by exception return. + /// + /// # Safety + /// + /// The caller must ensure this frame will be consumed as a valid Cortex-M + /// exception-return frame and that `pc` points to executable Thumb code. + #[inline(always)] + pub unsafe fn set_return_address(&mut self, pc: usize) -> &mut Self { + self.pc = pc; + self + } + + /// Returns the PC restored by exception return. + /// + /// # Safety + /// + /// The caller must ensure this value is interpreted in the context of a + /// valid Cortex-M exception stack frame. + #[inline(always)] + pub unsafe fn return_address(&self) -> usize { + self.pc + } + + /// Sets one of the four register arguments in R0-R3. + /// + /// # Safety + /// + /// The caller must ensure mutating the stacked argument register preserves + /// the function ABI expected when the frame is restored. + /// + /// # Panics + /// + /// Panics if `i` is not in `0..4`. Additional arguments must be passed by + /// stack according to the AAPCS calling convention. + #[inline(always)] + pub unsafe fn set_arg(&mut self, i: usize, val: usize) -> &mut Self { + match i { + 0 => self.r0 = val, + 1 => self.r1 = val, + 2 => self.r2 = val, + 3 => self.r3 = val, + _ => panic!("Should be passed by stack"), + } + self + } + + /// Initializes xPSR for a fresh Thread-mode exception return frame. + /// + /// # Safety + /// + /// The caller must ensure this frame is being prepared for a fresh + /// Cortex-M Thread-mode start and that other frame fields are initialized + /// consistently before exception return consumes it. + #[inline(always)] + pub unsafe fn init_thread_mode(&mut self) -> &mut Self { + self.xpsr = THUMB_MODE_XPSR; + #[cfg(target_abi = "eabihf")] + { + self.fpscr = INITIAL_FPSCR; + self.fpu_reserved = INITIAL_FPU_RESERVED; + } + self + } + + /// Clears the stacked xPSR alignment-padding marker. + /// + /// When duplicating or rewriting an exception frame, clearing this bit keeps + /// the copied frame from describing padding that is not present in the new + /// stack location. + /// + /// # Safety + /// + /// The caller must ensure the copied frame really is being placed at a + /// stack location without the hardware alignment padding described by the + /// original xPSR bit. + #[inline(always)] + pub unsafe fn clear_stack_alignment_padding(&mut self) -> &mut Self { + self.xpsr &= !STACK_ALIGNMENT_PADDING; + self + } + + /// Returns a copy of the stacked xPSR with the alignment-padding marker + /// cleared. + /// + /// # Safety + /// + /// The caller must ensure the returned value is used only when duplicating + /// or rewriting a frame at a stack location that does not include the + /// original hardware-inserted alignment padding. + #[inline(always)] + pub unsafe fn xpsr_without_stack_alignment_padding(&self) -> usize { + self.xpsr & !STACK_ALIGNMENT_PADDING + } +} diff --git a/arch/arm/cortex_m/hardfault.rs b/arch/arm/cortex_m/hardfault.rs new file mode 100644 index 00000000..993feea7 --- /dev/null +++ b/arch/arm/cortex_m/hardfault.rs @@ -0,0 +1,214 @@ +// Copyright (c) 2026 vivo Mobile Communication Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Cortex-M HardFault entry and diagnostic helpers. +//! +//! HardFault handling is architecture-owned: Cortex-M records the active stack +//! frame through the EXC_RETURN value in LR, and the fault reason lives in SCB +//! status registers. This module keeps those mechanics and the default panic +//! diagnostic path in `bluekernel_arch`, so the kernel can re-export the entry +//! points without carrying Cortex-M register decoding logic. +//! +//! Arm references: +//! - HardFault status and configurable fault status registers: +//! +//! - Cortex-M exception stack frame selection through EXC_RETURN: +//! + +use crate::cortex_m::{disable_local_irq, exception::ExceptionStackFrame, scb, xpsr}; +use core::fmt; + +/// Snapshot of fault-status registers used when diagnosing a HardFault. +#[derive(Debug, Default, Clone, Copy)] +pub struct HardFaultRegs { + cfsr: u32, + hfsr: u32, + mmfar: u32, + bfar: u32, + afsr: u32, +} + +impl HardFaultRegs { + /// Reads the SCB fault-status registers. + /// + /// # Safety + /// + /// The caller must run on a Cortex-M profile that implements the SCB fault + /// status registers at the architectural addresses. Reading these + /// registers is intended for fault handling or low-level diagnostics. + #[inline(always)] + pub unsafe fn read() -> Self { + let fault_status = unsafe { scb::read_fault_status() }; + + Self { + cfsr: fault_status.cfsr, + hfsr: fault_status.hfsr, + mmfar: fault_status.mmfar, + bfar: fault_status.bfar, + afsr: fault_status.afsr, + } + } +} + +impl fmt::Display for HardFaultRegs { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f, "\nHFSR: 0x{:08x}", self.hfsr)?; + if self.hfsr & (1 << 30) != 0 { + writeln!(f, " - Forced Hard Fault")?; + } + if self.hfsr & (1 << 31) != 0 { + writeln!(f, " - Debug Event")?; + } + if self.hfsr & (1 << 1) != 0 { + writeln!(f, " - Vector Table Read Fault")?; + } + + writeln!(f, "CFSR: 0x{:08x}", self.cfsr)?; + writeln!(f, "Fault Status:")?; + + if self.cfsr & 0xFF != 0 { + writeln!(f, " Memory Management Fault:")?; + if self.cfsr & (1 << 0) != 0 { + writeln!(f, " - Instruction access violation")?; + } + if self.cfsr & (1 << 1) != 0 { + writeln!(f, " - Data access violation")?; + } + if self.cfsr & (1 << 3) != 0 { + writeln!(f, " - Unstacking error")?; + } + if self.cfsr & (1 << 4) != 0 { + writeln!(f, " - Stacking error")?; + } + if self.cfsr & (1 << 5) != 0 { + writeln!(f, " - Lazy floating-point state preservation error")?; + } + if self.cfsr & (1 << 7) != 0 { + writeln!(f, " - MMFAR valid")?; + writeln!(f, " Fault Address: 0x{:08x}", self.mmfar)?; + } + } + + if self.cfsr & 0xFF00 != 0 { + writeln!(f, " Bus Fault:")?; + if self.cfsr & (1 << 8) != 0 { + writeln!(f, " - Instruction bus error")?; + } + if self.cfsr & (1 << 9) != 0 { + writeln!(f, " - Precise error")?; + } + if self.cfsr & (1 << 10) != 0 { + writeln!(f, " - Imprecise error")?; + } + if self.cfsr & (1 << 11) != 0 { + writeln!(f, " - Unstack error")?; + } + if self.cfsr & (1 << 12) != 0 { + writeln!(f, " - Stacking error")?; + } + if self.cfsr & (1 << 13) != 0 { + writeln!(f, " - Lazy state preservation error")?; + } + if self.cfsr & (1 << 15) != 0 { + writeln!(f, " - BFAR valid")?; + writeln!(f, " Fault Address: 0x{:08x}", self.bfar)?; + } + } + + if self.cfsr & 0xFFFF0000 != 0 { + writeln!(f, " Usage Fault:")?; + if self.cfsr & (1 << 16) != 0 { + writeln!(f, " - Undefined instruction")?; + } + if self.cfsr & (1 << 17) != 0 { + writeln!(f, " - Invalid state")?; + } + if self.cfsr & (1 << 18) != 0 { + writeln!(f, " - Invalid PC load")?; + } + if self.cfsr & (1 << 19) != 0 { + writeln!(f, " - No coprocessor")?; + } + #[cfg(armv8m)] + if self.cfsr & (1 << 20) != 0 { + writeln!(f, " - Stack overflow")?; + } + if self.cfsr & (1 << 24) != 0 { + writeln!(f, " - Unaligned access")?; + } + if self.cfsr & (1 << 25) != 0 { + writeln!(f, " - Divide by zero")?; + } + } + + writeln!(f, "AFSR: 0x{:08x}", self.afsr)?; + if self.afsr != 0 { + writeln!(f, " - Auxiliary Faults detected")?; + } + + Ok(()) + } +} + +/// Default HardFault panic callback. +/// +/// This disables local interrupts, snapshots the Cortex-M fault registers and +/// xPSR, then panics with the stacked exception frame. It lives next to +/// [`handle_hardfault`] so the naked entry and the diagnostic register decoding +/// remain in the same architecture crate. +/// +/// # Safety +/// +/// `ctx` must point to the active Cortex-M exception stack frame selected by +/// the HardFault entry code. The function is intended to be called only from +/// [`handle_hardfault`] or equivalent low-level exception glue. +#[no_mangle] +pub unsafe extern "C" fn panic_on_hardfault(ctx: &ExceptionStackFrame) -> ! { + disable_local_irq(); + let fault_regs = unsafe { HardFaultRegs::read() }; + let xpsr = unsafe { xpsr::read() }; + panic!( + " + ==== HARD FAULT ==== + FRAME: {:?} + FAULT REGS: {} + XPSR: {} + ", + ctx, fault_regs, xpsr, + ); +} + +/// HardFault exception entry. +/// +/// The entry code chooses MSP or PSP from EXC_RETURN bit 2 in LR and passes +/// the active hardware exception frame to [`panic_on_hardfault`]. +/// +/// # Safety +/// +/// This function must be installed only as the Cortex-M HardFault handler. +#[naked] +#[no_mangle] +pub unsafe extern "C" fn handle_hardfault() { + core::arch::naked_asm!( + " + mrs r0, msp + tst lr, #0x04 + beq 1f + mrs r0, psp + 1: + bl {panic} + ", + panic = sym panic_on_hardfault + ) +} diff --git a/kernel/src/arch/arm/irq.rs b/arch/arm/cortex_m/irq.rs similarity index 66% rename from kernel/src/arch/arm/irq.rs rename to arch/arm/cortex_m/irq.rs index f3b0936b..3c186070 100644 --- a/kernel/src/arch/arm/irq.rs +++ b/arch/arm/cortex_m/irq.rs @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::cortex_m::{nvic, scb, scb::SystemHandler, xpsr}; use blueos_hal::isr::{IsrDesc, IsrReg}; -use cortex_m::{interrupt::InterruptNumber, peripheral::scb::SystemHandler, Peripherals}; #[cfg(irq_priority_bits_2)] pub const IRQ_PRIORITY_STEP: u8 = 0x40; @@ -34,7 +34,7 @@ pub const SVC_PRIORITY: u8 = IRQ_PRIORITY_FOR_SCHEDULER - IRQ_PRIORITY_STEP; #[derive(Debug, Copy, Clone)] #[repr(u8)] pub enum Priority { - // can't use ipc in high priority irq + // Can't use IPC in high priority IRQ. High = IRQ_PRIORITY_FOR_SCHEDULER - IRQ_PRIORITY_STEP * 2, Normal = IRQ_PRIORITY_FOR_SCHEDULER, Low = IRQ_PRIORITY_FOR_SCHEDULER + IRQ_PRIORITY_STEP, @@ -57,55 +57,42 @@ impl From for usize { } } -// SAFETY: get the number of the interrupt is safe -unsafe impl InterruptNumber for IrqNumber { - #[inline] - fn number(self) -> u16 { - self.0 - } -} - pub fn init() { - // SAFETY: steal and set the peripherals in init is safe + // Safety: system handler priorities are set during early IRQ init before + // normal scheduling and priority-based critical sections are active. unsafe { - let mut scb = Peripherals::steal(); - scb.SCB.set_priority(SystemHandler::SVCall, SVC_PRIORITY); - scb.SCB - .set_priority(SystemHandler::PendSV, IRQ_PRIORITY_FOR_SCHEDULER); + scb::set_system_handler_priority(SystemHandler::SVCall, SVC_PRIORITY); + scb::set_system_handler_priority(SystemHandler::PendSV, IRQ_PRIORITY_FOR_SCHEDULER); } } pub fn enable_irq_with_priority(irq: IrqNumber, priority: Priority) { set_irq_priority(irq, priority as u8); - unsafe { cortex_m::peripheral::NVIC::unmask(irq) }; + unsafe { nvic::enable(irq.0) }; } pub fn enable_irq(irq: IrqNumber) { - unsafe { cortex_m::peripheral::NVIC::unmask(irq) }; + unsafe { nvic::enable(irq.0) }; } pub fn disable_irq(irq: IrqNumber) { - unsafe { cortex_m::peripheral::NVIC::mask(irq) }; + unsafe { nvic::disable(irq.0) }; } pub fn is_irq_enabled(irq: IrqNumber) -> bool { - unsafe { cortex_m::peripheral::NVIC::is_enabled(irq) } + unsafe { nvic::is_enabled(irq.0) } } pub fn is_irq_active(irq: IrqNumber) -> bool { - unsafe { cortex_m::peripheral::NVIC::is_active(irq) } + unsafe { nvic::is_active(irq.0) } } pub fn get_irq_priority(irq: IrqNumber) -> u8 { - unsafe { cortex_m::peripheral::NVIC::get_priority(irq) } + unsafe { nvic::get_priority(irq.0) } } pub fn set_irq_priority(irq: IrqNumber, priority: u8) { - unsafe { - cortex_m::Peripherals::steal() - .NVIC - .set_priority(irq, priority) - }; + unsafe { nvic::set_priority(irq.0, priority) }; } #[derive(Clone, Copy)] @@ -128,8 +115,9 @@ pub const INTERRUPT_TABLE_LEN: usize = blueos_kconfig::CONFIG_NUM_IRQS as usize; /// /// # Safety /// -/// The interrupt vector table must be properly aligned and contain valid function pointers -/// for all used interrupt vectors. Incorrect configuration may lead to undefined behavior. +/// The interrupt vector table must be properly aligned and contain valid +/// function pointers for all used interrupt vectors. Incorrect configuration +/// may lead to undefined behavior. #[used] #[link_section = ".interrupt.handlers"] static mut __INTERRUPT_HANDLERS__: [Vector; blueos_kconfig::CONFIG_NUM_IRQS as usize] = [Vector { @@ -138,15 +126,12 @@ static mut __INTERRUPT_HANDLERS__: [Vector; blueos_kconfig::CONFIG_NUM_IRQS as u INTERRUPT_TABLE_LEN]; extern "C" fn _generic_isr_handler() { - use cortex_m::peripheral::NVIC; - // Get the current ISR index from the IPSR register - let ipsr: u32; - unsafe { - core::arch::asm!("mrs {}, ipsr", out(reg) ipsr, options(nomem, nostack, preserves_flags)); - } - let isr_index = (ipsr & 0x1FF) - .checked_sub(16) - .expect("Invalid ISR index, IPSR value: {ipsr:#X}"); + // Safety: this handler runs on Cortex-M exception entry; xPSR carries the + // same active exception number previously read from IPSR directly. + let xpsr = unsafe { xpsr::read() }; + let isr_index = xpsr + .external_interrupt_number() + .expect("Invalid ISR index, xPSR does not identify an external interrupt"); if let Some(isr_desc) = unsafe { ISR_DESC[isr_index as usize].as_ref() } { isr_desc.service_isr(); @@ -156,15 +141,13 @@ extern "C" fn _generic_isr_handler() { #[cfg(round_robin)] { - use core::intrinsics::likely; - - use crate::scheduler::is_schedule_ready; - - if likely(is_schedule_ready()) { - // If the scheduler is preemptive, trigger PendSV to perform - // a context switch after handling the current interrupt. - cortex_m::peripheral::SCB::set_pendsv(); - } + // If the scheduler is preemptive, trigger PendSV to perform a context + // switch after handling the current interrupt. External interrupts + // firing before the scheduler is ready are unsupported and would be + // undefined for BlueOS; that boot-time ordering violation should not + // happen. PendSV still keeps the final readiness guard before touching + // PSP or scheduler task queues. + unsafe { scb::set_pendsv() }; } } @@ -195,14 +178,16 @@ pub fn init_interrupt_registry() { } } -/// This function is used to register the raw interrupt handler for the given irq number. -/// The handler should be defined in the assembly file, and the caller should ensure that -/// the handler is properly defined and linked. This function is unsafe because it allows -/// registering a raw interrupt handler, which may lead to undefined behavior if not used -/// correctly. -/// Safety: race condition may occur if this function is called while the corresponding -/// interrupt is enabled and can be triggered, so the caller should ensure that the interrupt -/// is disabled before calling this function, and enable it after the handler is registered. +/// This function is used to register the raw interrupt handler for the given +/// IRQ number. The handler should be defined in the assembly file, and the +/// caller should ensure that the handler is properly defined and linked. +/// +/// # Safety +/// +/// Race condition may occur if this function is called while the corresponding +/// interrupt is enabled and can be triggered, so the caller should ensure that +/// the interrupt is disabled before calling this function, and enable it after +/// the handler is registered. pub unsafe fn register_raw_isr(irq: IrqNumber, handler: unsafe extern "C" fn()) { __INTERRUPT_HANDLERS__[irq.0 as usize] = Vector { handler }; } diff --git a/arch/arm/cortex_m/link.x b/arch/arm/cortex_m/link.x index 8b1d73b9..ca0e4d5a 100644 --- a/arch/arm/cortex_m/link.x +++ b/arch/arm/cortex_m/link.x @@ -176,6 +176,21 @@ SECTIONS __stop___llvm_prf_data = .; KEEP(*(.jcr*)) + + /* + * Keep GOT sections inside the copied data range. The linker may emit + * PC-relative loads through .got for optimized Rust code even in this + * bare-metal image; if .got becomes an orphan section after __data_end, + * the startup copy table leaves it zeroed in RAM and indirect calls can + * branch through a null entry. + */ + . = ALIGN(4); + *(.got) + *(.got.*) + *(.igot.*) + *(.got.plt) + *(.igot.plt) + . = ALIGN(4); __data_end = .; @@ -233,5 +248,7 @@ SECTIONS } EXTERN(handle_hardfault); +PROVIDE(handle_nmi = handle_hardfault); PROVIDE(handle_memfault = handle_hardfault); - +PROVIDE(handle_busfault = handle_hardfault); +PROVIDE(handle_usagefault = handle_hardfault); diff --git a/arch/arm/cortex_m/mod.rs b/arch/arm/cortex_m/mod.rs new file mode 100644 index 00000000..2ec837fb --- /dev/null +++ b/arch/arm/cortex_m/mod.rs @@ -0,0 +1,278 @@ +// Copyright (c) 2025 vivo Mobile Communication Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Safe Cortex-M policy layer for BlueOS. +//! +//! This module is the public boundary consumed by the kernel. Functions exposed +//! here keep the old kernel-facing contract as safe C-ABI helpers, while the +//! raw assembly and register accessors stay in submodules such as [`asm`], +//! [`scb`], [`nvic`], [`systick`], and [`mpu`]. That split lets kernel code +//! re-export these helpers directly without spreading `unsafe` through normal +//! scheduler, interrupt, and debug paths. + +pub mod asm; +pub mod exception; +pub mod hardfault; +pub mod irq; +pub mod mpu; +pub mod nvic; +pub mod scb; +pub mod systick; +pub mod xpsr; + +pub use exception::{ExceptionStackFrame, THUMB_MODE_XPSR}; + +use core::sync::atomic::{compiler_fence, Ordering}; + +// Keep the Cortex-M architectural exception slots in bluekernel_arch rather +// than rebuilding the same fixed layout in the kernel. The kernel still +// provides the handlers with OS semantics (`_start`, SVC, PendSV, SysTick), +// while this table preserves the previous vector order and zero-filled +// reserved entries. +core::arch::global_asm!( + r#" + .section .exception.handlers, "a", %progbits + .balign 4 + .global __EXCEPTION_HANDLERS__ + .type __EXCEPTION_HANDLERS__, %object +__EXCEPTION_HANDLERS__: + .word _start + .word handle_nmi + .word bk_handle_hardfault + .word handle_memfault + .word handle_busfault + .word handle_usagefault + .word 0 + .word 0 + .word 0 + .word 0 + .word handle_svc + .word 0 + .word 0 + .word handle_pendsv + .word handle_systick + .size __EXCEPTION_HANDLERS__, . - __EXCEPTION_HANDLERS__ + "# +); + +/// BASEPRI threshold used to mask local scheduler-level interrupts. +#[cfg(irq_priority_bits_2)] +pub const LOCAL_IRQ_BASEPRI: usize = 0x80; +/// BASEPRI threshold used to mask local scheduler-level interrupts. +#[cfg(irq_priority_bits_3)] +pub const LOCAL_IRQ_BASEPRI: usize = 0x40; +/// BASEPRI threshold used to mask local scheduler-level interrupts. +#[cfg(irq_priority_bits_8)] +pub const LOCAL_IRQ_BASEPRI: usize = 0x20; + +/// Complete saved thread context for Cortex-M exception return. +/// +/// The context begins with software-saved callee registers and ends with the +/// hardware exception stack frame. On hard-float builds, the same type expands +/// with the high floating-point callee-saved registers S16-S31 before the +/// exception frame. Keeping this as one `repr(C)` type with ABI-gated fields +/// matches [`ExceptionStackFrame`] and makes the non-float and hard-float ABI +/// variants share one API surface. +#[repr(C, align(8))] +#[derive(Default, Debug, Copy, Clone)] +pub struct Context { + /// Callee-saved register R4. + pub r4: usize, + /// Callee-saved register R5. + pub r5: usize, + /// Callee-saved register R6. + pub r6: usize, + /// Callee-saved register R7. + pub r7: usize, + /// Callee-saved register R8. + pub r8: usize, + /// Callee-saved register R9. + pub r9: usize, + /// Callee-saved register R10. + pub r10: usize, + /// Callee-saved register R11. + pub r11: usize, + /// Floating-point callee-saved register S16. + #[cfg(target_abi = "eabihf")] + pub s16: usize, + /// Floating-point callee-saved register S17. + #[cfg(target_abi = "eabihf")] + pub s17: usize, + /// Floating-point callee-saved register S18. + #[cfg(target_abi = "eabihf")] + pub s18: usize, + /// Floating-point callee-saved register S19. + #[cfg(target_abi = "eabihf")] + pub s19: usize, + /// Floating-point callee-saved register S20. + #[cfg(target_abi = "eabihf")] + pub s20: usize, + /// Floating-point callee-saved register S21. + #[cfg(target_abi = "eabihf")] + pub s21: usize, + /// Floating-point callee-saved register S22. + #[cfg(target_abi = "eabihf")] + pub s22: usize, + /// Floating-point callee-saved register S23. + #[cfg(target_abi = "eabihf")] + pub s23: usize, + /// Floating-point callee-saved register S24. + #[cfg(target_abi = "eabihf")] + pub s24: usize, + /// Floating-point callee-saved register S25. + #[cfg(target_abi = "eabihf")] + pub s25: usize, + /// Floating-point callee-saved register S26. + #[cfg(target_abi = "eabihf")] + pub s26: usize, + /// Floating-point callee-saved register S27. + #[cfg(target_abi = "eabihf")] + pub s27: usize, + /// Floating-point callee-saved register S28. + #[cfg(target_abi = "eabihf")] + pub s28: usize, + /// Floating-point callee-saved register S29. + #[cfg(target_abi = "eabihf")] + pub s29: usize, + /// Floating-point callee-saved register S30. + #[cfg(target_abi = "eabihf")] + pub s30: usize, + /// Floating-point callee-saved register S31. + #[cfg(target_abi = "eabihf")] + pub s31: usize, + /// Hardware-stacked exception frame restored by exception return. + pub exception_frame: ExceptionStackFrame, +} + +impl core::ops::Deref for Context { + type Target = ExceptionStackFrame; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.exception_frame + } +} + +impl core::ops::DerefMut for Context { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.exception_frame + } +} + +impl Context { + /// Sets the return PC restored when this context exits the exception. + #[inline(always)] + pub fn set_return_address(&mut self, pc: usize) -> &mut Self { + unsafe { + self.exception_frame.set_return_address(pc); + } + self + } + + /// Returns the PC restored when this context exits the exception. + #[inline(always)] + pub fn get_return_address(&self) -> usize { + unsafe { self.exception_frame.return_address() } + } + + /// Sets one of the first four argument registers in the embedded frame. + #[inline(always)] + pub fn set_arg(&mut self, i: usize, val: usize) -> &mut Self { + unsafe { + self.exception_frame.set_arg(i, val); + } + self + } + + /// Initializes the embedded exception frame for a fresh Thread-mode start. + #[inline(always)] + pub fn init(&mut self) -> &mut Self { + unsafe { + self.exception_frame.init_thread_mode(); + } + self + } +} + +/// Clears BASEPRI so locally masked interrupts can run again. +#[inline(always)] +pub extern "C" fn enable_local_irq() { + unsafe { asm::write_basepri(0) } +} + +/// Raises BASEPRI to the local interrupt mask and synchronizes the instruction +/// stream. +#[inline(always)] +pub extern "C" fn disable_local_irq() { + unsafe { + asm::write_basepri(LOCAL_IRQ_BASEPRI); + asm::isb(); + } +} + +/// Raises BASEPRI and returns the previous local interrupt mask. +#[inline(always)] +pub extern "C" fn disable_local_irq_save() -> usize { + let old = unsafe { asm::read_basepri() }; + unsafe { + asm::write_basepri(LOCAL_IRQ_BASEPRI); + asm::isb(); + } + compiler_fence(Ordering::SeqCst); + old +} + +/// Restores a BASEPRI value returned by `disable_local_irq_save`. +#[inline(always)] +pub extern "C" fn enable_local_irq_restore(old: usize) { + compiler_fence(Ordering::SeqCst); + unsafe { asm::write_basepri(old) } +} + +/// Returns whether the BASEPRI local interrupt mask is disabled. +#[inline(always)] +pub extern "C" fn local_irq_enabled() -> bool { + unsafe { asm::read_basepri() == 0 } +} + +/// Enters idle by waiting for the next interrupt. +#[inline(always)] +pub extern "C" fn idle() { + unsafe { asm::wfi() } +} + +/// Returns the currently selected stack pointer. +#[inline(always)] +pub extern "C" fn current_sp() -> usize { + unsafe { asm::read_sp() } +} + +/// Returns the Main Stack Pointer. +#[inline(always)] +pub extern "C" fn current_msp() -> usize { + unsafe { asm::read_msp() } +} + +/// Returns the Process Stack Pointer. +#[inline(always)] +pub extern "C" fn current_psp() -> usize { + unsafe { asm::read_psp() } +} + +/// Returns whether the CPU is currently handling an exception. +#[inline(always)] +pub extern "C" fn is_in_interrupt() -> bool { + unsafe { scb::is_in_exception() } +} diff --git a/arch/arm/cortex_m/mpu.rs b/arch/arm/cortex_m/mpu.rs new file mode 100644 index 00000000..6f2414ec --- /dev/null +++ b/arch/arm/cortex_m/mpu.rs @@ -0,0 +1,89 @@ +// Copyright (c) 2026 vivo Mobile Communication Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Cortex-M Memory Protection Unit (MPU) registers. +//! +//! This register block is based on Arm CMSIS-Core's `MPU_Type` layout: +//! +//! +//! CMSIS documents the common MPU type/control/region registers. This module +//! exposes the ARMv8-M RBAR/RLAR path used by the kernel stack-guard setup. + +use tock_registers::{ + interfaces::{Readable, Writeable}, + register_structs, + registers::{ReadOnly, ReadWrite}, +}; + +const MPU_BASE: usize = 0xE000_ED90; + +#[inline(always)] +fn registers() -> &'static MpuRegisters { + // Safety: MPU is a core peripheral at a fixed architectural MMIO address. + unsafe { &*(MPU_BASE as *const MpuRegisters) } +} + +/// Reads the MPU Control Register. +/// +/// # Safety +/// +/// The caller must run on a Cortex-M profile with an MPU at the architectural +/// address. +#[inline(always)] +pub unsafe fn control() -> u32 { + registers().CTRL.get() +} + +/// Writes the MPU Control Register. +/// +/// # Safety +/// +/// The caller must preserve MPU invariants required by the current memory map. +#[inline(always)] +pub unsafe fn set_control(value: u32) { + registers().CTRL.set(value); +} + +/// Programs an ARMv8-M MPU region through RNR/RBAR/RLAR. +/// +/// # Safety +/// +/// The caller must pass an architecturally valid region number and encoded +/// RBAR/RLAR values for the target MPU. +#[inline(always)] +pub unsafe fn set_region_v8m(region: u32, rbar: u32, rlar: u32) { + let regs = registers(); + regs.RNR.set(region); + regs.RBAR.set(rbar); + regs.RLAR.set(rlar); +} + +register_structs! { + /// ARMv8-M MPU register block. + #[allow(non_snake_case)] + MpuRegisters { + /// MPU Type Register. + (0x000 => TYPE: ReadOnly), + /// MPU Control Register. + (0x004 => CTRL: ReadWrite), + /// MPU Region Number Register. + (0x008 => RNR: ReadWrite), + /// MPU Region Base Address Register. + (0x00C => RBAR: ReadWrite), + /// MPU Region Limit Address Register. + (0x010 => RLAR: ReadWrite), + /// Alias registers and MAIR are not needed by the current kernel MPU setup. + (0x014 => @END), + } +} diff --git a/arch/arm/cortex_m/nvic.rs b/arch/arm/cortex_m/nvic.rs new file mode 100644 index 00000000..a7712bd8 --- /dev/null +++ b/arch/arm/cortex_m/nvic.rs @@ -0,0 +1,255 @@ +// Copyright (c) 2025 vivo Mobile Communication Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Cortex-M Nested Vectored Interrupt Controller (NVIC) registers. +//! +//! This register block follows Arm CMSIS-Core's `NVIC_Type` layout: +//! +//! +//! The CMSIS layout defines eight 32-bit bitmap registers for interrupt +//! enable/pending/active state, 240 byte-wide priority registers, and the +//! software-trigger register at offset `0xE00`. + +use tock_registers::{ + interfaces::{Readable, Writeable}, + register_structs, + registers::{ReadWrite, WriteOnly}, +}; + +/// Memory-mapped base address of the Nested Vectored Interrupt Controller. +const NVIC_BASE: usize = 0xE000_E100; + +/// Maximum number of external interrupts described by the CMSIS NVIC register map. +pub const NUM_EXTERNAL_INTERRUPTS: usize = 240; + +/// Number of 32-bit interrupt bitmap registers in the CMSIS NVIC register map. +const NUM_INTERRUPT_WORDS: usize = 8; + +#[inline(always)] +const fn word_index(interrupt: u16) -> usize { + interrupt as usize / u32::BITS as usize +} + +#[inline(always)] +const fn bit_mask(interrupt: u16) -> u32 { + 1 << (interrupt % u32::BITS as u16) +} + +#[inline(always)] +fn registers() -> &'static NvicRegisters { + // Safety: NVIC is a core peripheral at a fixed architectural MMIO address. + unsafe { &*(NVIC_BASE as *const NvicRegisters) } +} + +/// Enables an external interrupt. +/// +/// # Safety +/// +/// Enabling an interrupt can break interrupt- or priority-based critical +/// sections if the caller has not installed a valid handler first. +#[inline(always)] +pub unsafe fn enable(interrupt: u16) { + debug_assert!((interrupt as usize) < NUM_EXTERNAL_INTERRUPTS); + + // Safety: the debug assertion documents the expected architectural range; + // out-of-range interrupt numbers are caller bugs just like raw NVIC access. + unsafe { + registers() + .ISER + .get_unchecked(word_index(interrupt)) + .set(bit_mask(interrupt)); + } +} + +/// Disables an external interrupt. +/// +/// # Safety +/// +/// The caller must pass a valid external interrupt number for the target NVIC. +#[inline(always)] +pub unsafe fn disable(interrupt: u16) { + debug_assert!((interrupt as usize) < NUM_EXTERNAL_INTERRUPTS); + + // Safety: writing ICER is a stateless one-bit command to the NVIC. + unsafe { + registers() + .ICER + .get_unchecked(word_index(interrupt)) + .set(bit_mask(interrupt)); + } +} + +/// Returns whether an external interrupt is enabled. +/// +/// # Safety +/// +/// The caller must pass a valid external interrupt number for the target NVIC. +#[inline(always)] +pub unsafe fn is_enabled(interrupt: u16) -> bool { + debug_assert!((interrupt as usize) < NUM_EXTERNAL_INTERRUPTS); + + // Safety: reading ISER has no side effects and uses the validated word index. + unsafe { + (registers().ISER.get_unchecked(word_index(interrupt)).get() & bit_mask(interrupt)) != 0 + } +} + +/// Returns whether an external interrupt is active or preempted. +/// +/// # Safety +/// +/// The caller must pass a valid external interrupt number for the target NVIC. +#[inline(always)] +pub unsafe fn is_active(interrupt: u16) -> bool { + debug_assert!((interrupt as usize) < NUM_EXTERNAL_INTERRUPTS); + + // Safety: reading IABR has no side effects and uses the validated word index. + unsafe { + (registers().IABR.get_unchecked(word_index(interrupt)).get() & bit_mask(interrupt)) != 0 + } +} + +/// Returns whether an external interrupt is pending. +/// +/// # Safety +/// +/// The caller must pass a valid external interrupt number for the target NVIC. +#[inline(always)] +pub unsafe fn is_pending(interrupt: u16) -> bool { + debug_assert!((interrupt as usize) < NUM_EXTERNAL_INTERRUPTS); + + // Safety: reading ISPR has no side effects and uses the validated word index. + unsafe { + (registers().ISPR.get_unchecked(word_index(interrupt)).get() & bit_mask(interrupt)) != 0 + } +} + +/// Marks an external interrupt as pending. +/// +/// # Safety +/// +/// The caller must pass a valid external interrupt number for the target NVIC. +#[inline(always)] +pub unsafe fn pend(interrupt: u16) { + debug_assert!((interrupt as usize) < NUM_EXTERNAL_INTERRUPTS); + + // Safety: writing ISPR is a stateless one-bit command to the NVIC. + unsafe { + registers() + .ISPR + .get_unchecked(word_index(interrupt)) + .set(bit_mask(interrupt)); + } +} + +/// Clears an external interrupt's pending state. +/// +/// # Safety +/// +/// The caller must pass a valid external interrupt number for the target NVIC. +#[inline(always)] +pub unsafe fn unpend(interrupt: u16) { + debug_assert!((interrupt as usize) < NUM_EXTERNAL_INTERRUPTS); + + // Safety: writing ICPR is a stateless one-bit command to the NVIC. + unsafe { + registers() + .ICPR + .get_unchecked(word_index(interrupt)) + .set(bit_mask(interrupt)); + } +} + +/// Returns the encoded priority byte for an external interrupt. +/// +/// # Safety +/// +/// The caller must pass a valid external interrupt number for the target NVIC. +#[inline(always)] +pub unsafe fn get_priority(interrupt: u16) -> u8 { + debug_assert!((interrupt as usize) < NUM_EXTERNAL_INTERRUPTS); + + // Safety: NVIC priorities are byte-addressable on the supported Cortex-M + // profiles here, and the interrupt number indexes one priority byte. + unsafe { registers().IPR.get_unchecked(interrupt as usize).get() } +} + +/// Sets the encoded priority byte for an external interrupt. +/// +/// Lower numerical values represent higher urgency. The value is written +/// directly because the NVIC stores only the implemented priority bits. +/// +/// # Safety +/// +/// Changing priorities can invalidate priority-based critical sections if the +/// caller does not preserve the kernel's priority invariants. +#[inline(always)] +pub unsafe fn set_priority(interrupt: u16, priority: u8) { + debug_assert!((interrupt as usize) < NUM_EXTERNAL_INTERRUPTS); + + // Safety: see the function contract; the unchecked index avoids adding a + // bounds check on the hot path while preserving the previous raw access. + unsafe { + registers() + .IPR + .get_unchecked(interrupt as usize) + .set(priority); + } +} + +/// Requests an external interrupt through the software trigger register. +/// +/// # Safety +/// +/// The caller must pass a valid external interrupt number for the target NVIC. +#[inline(always)] +pub unsafe fn request(interrupt: u16) { + debug_assert!((interrupt as usize) < NUM_EXTERNAL_INTERRUPTS); + + registers().STIR.set(u32::from(interrupt)); +} + +register_structs! { + /// Cortex-M Nested Vectored Interrupt Controller register block. + #[allow(non_snake_case)] + NvicRegisters { + /// Interrupt Set Enable Registers. Writing 1 enables the corresponding external interrupt. + (0x000 => ISER: [ReadWrite; NUM_INTERRUPT_WORDS]), + /// Reserved gap between the set-enable and clear-enable register banks. + (0x020 => _reserved0), + /// Interrupt Clear Enable Registers. Writing 1 disables the corresponding external interrupt. + (0x080 => ICER: [ReadWrite; NUM_INTERRUPT_WORDS]), + /// Reserved gap between the clear-enable and set-pending register banks. + (0x0A0 => _reserved1), + /// Interrupt Set Pending Registers. Writing 1 marks the corresponding external interrupt pending. + (0x100 => ISPR: [ReadWrite; NUM_INTERRUPT_WORDS]), + /// Reserved gap between the set-pending and clear-pending register banks. + (0x120 => _reserved2), + /// Interrupt Clear Pending Registers. Writing 1 clears the corresponding pending state. + (0x180 => ICPR: [ReadWrite; NUM_INTERRUPT_WORDS]), + /// Reserved gap between the clear-pending and active-bit register banks. + (0x1A0 => _reserved3), + /// Interrupt Active Bit Registers. A set bit means the corresponding interrupt is active. + (0x200 => IABR: [ReadWrite; NUM_INTERRUPT_WORDS]), + /// Reserved gap between the active-bit registers and priority byte registers. + (0x220 => _reserved4), + /// Interrupt Priority Registers. Each byte stores the priority for one external interrupt. + (0x300 => IPR: [ReadWrite; NUM_EXTERNAL_INTERRUPTS]), + /// Reserved gap between priority registers and the software-trigger register. + (0x3F0 => _reserved5), + /// Software Trigger Interrupt Register. Writing an interrupt ID pends that interrupt in software. + (0xE00 => STIR: WriteOnly), + (0xE04 => @END), + } +} diff --git a/arch/arm/cortex_m/scb.rs b/arch/arm/cortex_m/scb.rs new file mode 100644 index 00000000..43b728eb --- /dev/null +++ b/arch/arm/cortex_m/scb.rs @@ -0,0 +1,217 @@ +// Copyright (c) 2026 vivo Mobile Communication Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Cortex-M System Control Block (SCB) registers. +//! +//! This register block follows Arm CMSIS-Core's `SCB_Type` layout: +//! +//! +//! The CMSIS layout describes the interrupt-control register, system handler +//! priority bytes, system fault status registers, and coprocessor control +//! register used by Cortex-M system control code. + +use tock_registers::{ + interfaces::{Readable, Writeable}, + register_structs, + registers::ReadWrite, +}; + +const SCB_BASE: usize = 0xE000_ED04; +const ICSR_VECTACTIVE_MASK: u32 = 0x1ff; +const ICSR_PENDSVSET: u32 = 1 << 28; +const ICSR_PENDSTSET: u32 = 1 << 26; +const SHCSR_MEMFAULTENA: u32 = 1 << 16; + +/// System handlers with configurable priority. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(u8)] +pub enum SystemHandler { + /// SVCall exception. + SVCall = 11, + /// PendSV exception. + PendSV = 14, + /// SysTick exception. + SysTick = 15, +} + +/// Snapshot of SCB fault status registers. +#[derive(Debug, Default, Clone, Copy)] +pub struct FaultStatus { + /// Configurable Fault Status Register. + pub cfsr: u32, + /// HardFault Status Register. + pub hfsr: u32, + /// Memory Management Fault Address Register. + pub mmfar: u32, + /// BusFault Address Register. + pub bfar: u32, + /// Auxiliary Fault Status Register. + pub afsr: u32, +} + +#[inline(always)] +fn registers() -> &'static ScbRegisters { + // Safety: SCB is a core peripheral at a fixed architectural MMIO address. + unsafe { &*(SCB_BASE as *const ScbRegisters) } +} + +/// Sets the PendSV pending bit. +/// +/// # Safety +/// +/// The caller must ensure that pending PendSV is valid in the current kernel +/// state and will not violate priority- or interrupt-based invariants. +#[inline(always)] +pub unsafe fn set_pendsv() { + registers().ICSR.set(ICSR_PENDSVSET); +} + +/// Sets the SysTick pending bit. +/// +/// # Safety +/// +/// The caller must ensure that pending SysTick is valid in the current kernel +/// state and that the SysTick handler is installed. +#[inline(always)] +pub unsafe fn set_pendst() { + registers().ICSR.set(ICSR_PENDSTSET); +} + +/// Returns the raw active exception number from ICSR.VECTACTIVE. +/// +/// # Safety +/// +/// The caller must run on a Cortex-M profile where this SCB register block is +/// present at the architectural address. +#[inline(always)] +pub unsafe fn active_exception_number() -> u16 { + (registers().ICSR.get() & ICSR_VECTACTIVE_MASK) as u16 +} + +/// Returns whether the CPU is currently handling an exception. +/// +/// # Safety +/// +/// The caller must run on a Cortex-M profile where this SCB register block is +/// present at the architectural address. +#[inline(always)] +pub unsafe fn is_in_exception() -> bool { + unsafe { active_exception_number() != 0 } +} + +/// Sets the encoded priority byte for a configurable system handler. +/// +/// # Safety +/// +/// Changing priorities can invalidate priority-based critical sections if the +/// caller does not preserve the kernel's priority invariants. +#[inline(always)] +pub unsafe fn set_system_handler_priority(handler: SystemHandler, priority: u8) { + // Cortex-M SHPR byte 0 maps to exception 4. This mirrors the previous + // cortex-m crate call for the supported ARMv7-M/ARMv8-M targets. + let index = handler as usize - 4; + + // Safety: SystemHandler values are all in the configurable [4, 15] range. + unsafe { + registers().SHPR.get_unchecked(index).set(priority); + } +} + +/// Reads the SCB fault status registers. +/// +/// # Safety +/// +/// The caller must run on a Cortex-M profile that implements these fault +/// status registers at the architectural SCB offsets. +#[inline(always)] +pub unsafe fn read_fault_status() -> FaultStatus { + let regs = registers(); + FaultStatus { + cfsr: regs.CFSR.get(), + hfsr: regs.HFSR.get(), + mmfar: regs.MMFAR.get(), + bfar: regs.BFAR.get(), + afsr: regs.AFSR.get(), + } +} + +/// Reads the Configurable Fault Status Register. +/// +/// # Safety +/// +/// The caller must run on a Cortex-M profile that implements CFSR. +#[inline(always)] +pub unsafe fn cfsr() -> u32 { + registers().CFSR.get() +} + +/// Writes the Configurable Fault Status Register. +/// +/// # Safety +/// +/// The caller must write only architecturally valid write-one-to-clear fault +/// status bits for the current handler state. +#[inline(always)] +pub unsafe fn write_cfsr(value: u32) { + registers().CFSR.set(value); +} + +/// Enables MemManage faults in SHCSR. +/// +/// # Safety +/// +/// The caller must ensure that the MemManage handler is installed before +/// enabling MemManage faults. +#[inline(always)] +pub unsafe fn enable_memfault() { + let shcsr = registers().SHCSR.get() | SHCSR_MEMFAULTENA; + registers().SHCSR.set(shcsr); +} + +register_structs! { + /// System Control Block register block. + #[allow(non_snake_case)] + ScbRegisters { + /// Interrupt Control and State Register. + (0x000 => ICSR: ReadWrite), + /// Vector Table Offset Register. + (0x004 => VTOR: ReadWrite), + /// Application Interrupt and Reset Control Register. + (0x008 => AIRCR: ReadWrite), + /// System Control Register. + (0x00C => SCR: ReadWrite), + /// Configuration and Control Register. + (0x010 => CCR: ReadWrite), + /// System Handler Priority Registers, byte-addressable on ARMv7-M/ARMv8-M. + (0x014 => SHPR: [ReadWrite; 12]), + /// System Handler Control and State Register. + (0x020 => SHCSR: ReadWrite), + /// Configurable Fault Status Register. + (0x024 => CFSR: ReadWrite), + /// HardFault Status Register. + (0x028 => HFSR: ReadWrite), + /// Debug Fault Status Register. + (0x02C => DFSR: ReadWrite), + /// Memory Management Fault Address Register. + (0x030 => MMFAR: ReadWrite), + /// BusFault Address Register. + (0x034 => BFAR: ReadWrite), + /// Auxiliary Fault Status Register. + (0x038 => AFSR: ReadWrite), + (0x03C => _reserved0), + /// Coprocessor Access Control Register. + (0x084 => CPACR: ReadWrite), + (0x088 => @END), + } +} diff --git a/arch/arm/cortex_m/systick.rs b/arch/arm/cortex_m/systick.rs new file mode 100644 index 00000000..35e1c713 --- /dev/null +++ b/arch/arm/cortex_m/systick.rs @@ -0,0 +1,131 @@ +// Copyright (c) 2026 vivo Mobile Communication Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Cortex-M SysTick timer registers. +//! +//! This register block follows Arm CMSIS-Core's `SysTick_Type` layout: +//! +//! +//! The CMSIS layout defines the control/status, reload, current value, and +//! calibration registers for the Cortex-M system timer. + +use tock_registers::{ + interfaces::{Readable, Writeable}, + register_structs, + registers::{ReadOnly, ReadWrite}, +}; + +const SYSTICK_BASE: usize = 0xE000_E010; +const CSR_ENABLE: u32 = 1 << 0; +const CSR_TICKINT: u32 = 1 << 1; +const CSR_CLKSOURCE: u32 = 1 << 2; +const CSR_COUNTFLAG: u32 = 1 << 16; + +#[inline(always)] +fn registers() -> &'static SystickRegisters { + // Safety: SysTick is a core peripheral at a fixed architectural MMIO address. + unsafe { &*(SYSTICK_BASE as *const SystickRegisters) } +} + +/// Disables the SysTick counter. +/// +/// # Safety +/// +/// The caller must ensure this does not violate timer or scheduler invariants. +#[inline(always)] +pub unsafe fn disable_counter() { + let csr = registers().CSR.get() & !CSR_ENABLE; + registers().CSR.set(csr); +} + +/// Selects the core clock as the SysTick clock source. +/// +/// # Safety +/// +/// The caller must ensure the selected clock source is valid for the board. +#[inline(always)] +pub unsafe fn use_core_clock() { + let csr = registers().CSR.get() | CSR_CLKSOURCE; + registers().CSR.set(csr); +} + +/// Sets the SysTick reload value. +/// +/// # Safety +/// +/// The caller must pass a valid 24-bit SysTick reload value. +#[inline(always)] +pub unsafe fn set_reload(value: u32) { + registers().RVR.set(value); +} + +/// Clears the SysTick current value and COUNTFLAG. +/// +/// # Safety +/// +/// The caller must account for the side effect of clearing COUNTFLAG. +#[inline(always)] +pub unsafe fn clear_current() { + registers().CVR.set(0); +} + +/// Enables the SysTick counter. +/// +/// # Safety +/// +/// The caller must ensure reload/current/control have been initialized. +#[inline(always)] +pub unsafe fn enable_counter() { + let csr = registers().CSR.get() | CSR_ENABLE; + registers().CSR.set(csr); +} + +/// Enables SysTick interrupts. +/// +/// # Safety +/// +/// The caller must ensure the SysTick handler is installed and has a valid +/// priority before enabling the interrupt. +#[inline(always)] +pub unsafe fn enable_interrupt() { + let csr = registers().CSR.get() | CSR_TICKINT; + registers().CSR.set(csr); +} + +/// Returns whether SysTick has wrapped since the last CSR read. +/// +/// # Safety +/// +/// Reading CSR clears COUNTFLAG, so the caller must account for that side +/// effect. +#[inline(always)] +pub unsafe fn has_wrapped() -> bool { + (registers().CSR.get() & CSR_COUNTFLAG) != 0 +} + +register_structs! { + /// SysTick register block. + #[allow(non_snake_case)] + SystickRegisters { + /// Control and Status Register. + (0x000 => CSR: ReadWrite), + /// Reload Value Register. + (0x004 => RVR: ReadWrite), + /// Current Value Register. + (0x008 => CVR: ReadWrite), + /// Calibration Value Register. + (0x00C => CALIB: ReadOnly), + (0x010 => @END), + } +} diff --git a/arch/arm/cortex_m/xpsr.rs b/arch/arm/cortex_m/xpsr.rs new file mode 100644 index 00000000..53c0ac50 --- /dev/null +++ b/arch/arm/cortex_m/xpsr.rs @@ -0,0 +1,269 @@ +// Copyright (c) 2026 vivo Mobile Communication Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Cortex-M Program Status Register view (xPSR). +//! +//! xPSR is the architectural combined Program Status Register view. It is made +//! from three mutually exclusive field groups: +//! +//! - APSR: condition and saturation flags produced by instruction execution. +//! - IPSR: the currently active exception number. +//! - EPSR: execution state such as Thumb state and IT/ICI bits. +//! +//! This is a CPU special register read with `MRS`, not a memory-mapped +//! controller register like NVIC, SCB, SysTick, or MPU. The wrapper therefore +//! uses `register_bitfields!` plus `LocalRegisterCopy` to describe a snapshot +//! of the register value while keeping the same typed bitfield style as the +//! MMIO register blocks. +//! +//! See Arm's Cortex-M33 program status register documentation: +//! + +use core::{arch::asm, fmt}; + +use tock_registers::{register_bitfields, LocalRegisterCopy}; + +/// xPSR exception number used while executing in Thread mode. +pub const THREAD_MODE_EXCEPTION_NUMBER: u16 = 0; + +/// First xPSR exception number assigned to an external interrupt. +pub const EXTERNAL_INTERRUPT_EXCEPTION_BASE: u16 = 16; + +register_bitfields! {u32, + /// Combined Cortex-M Program Status Register view. + XPSR [ + /// Negative condition flag. + N OFFSET(31) NUMBITS(1) [], + + /// Zero condition flag. + Z OFFSET(30) NUMBITS(1) [], + + /// Carry or borrow condition flag. + C OFFSET(29) NUMBITS(1) [], + + /// Overflow condition flag. + V OFFSET(28) NUMBITS(1) [], + + /// DSP overflow and saturation flag. + Q OFFSET(27) NUMBITS(1) [], + + /// Upper IT/ICI/ECI execution state bits. + IT_ICI_HIGH OFFSET(25) NUMBITS(2) [], + + /// T32 execution state bit. + T OFFSET(24) NUMBITS(1) [], + + /// Branch target identification state bit. + B OFFSET(21) NUMBITS(1) [], + + /// Greater-than-or-equal SIMD comparison flags. + GE OFFSET(16) NUMBITS(4) [], + + /// Lower IT/ICI/ECI execution state bits. + IT_ICI_LOW OFFSET(10) NUMBITS(6) [], + + /// Current exception number from the IPSR portion of xPSR. + EXCEPTION_NUMBER OFFSET(0) NUMBITS(9) [] + ] +} + +/// Snapshot of the Cortex-M xPSR special register. +/// +/// The snapshot is intentionally immutable: reading xPSR observes the current +/// CPU state at one point in time, and field helpers decode that saved value +/// without issuing additional `MRS` instructions. +#[derive(Clone, Copy, Debug)] +pub struct Xpsr { + register: LocalRegisterCopy, +} + +impl Xpsr { + /// Creates an xPSR snapshot from raw register bits. + /// + /// This is useful for decoding the xPSR word stacked by Cortex-M exception + /// entry, or for tests that want to exercise field decoding without running + /// on ARM hardware. + #[inline(always)] + pub const fn from_bits(bits: u32) -> Self { + Self { + register: LocalRegisterCopy::new(bits), + } + } + + /// Returns the raw xPSR bits. + #[inline(always)] + pub fn bits(self) -> u32 { + self.register.get() + } + + /// Returns the current exception number from the IPSR field. + /// + /// `0` means Thread mode. Values `1..=15` are core exceptions, and values + /// starting at `16` identify external interrupts. + #[inline(always)] + pub fn exception_number(self) -> u16 { + self.register.read(XPSR::EXCEPTION_NUMBER) as u16 + } + + /// Returns the external interrupt number if xPSR identifies an IRQ. + /// + /// Cortex-M exception numbers include the 16 core exception slots before + /// external interrupts, so IRQ number `0` is encoded as exception number + /// `16`. + #[inline(always)] + pub fn external_interrupt_number(self) -> Option { + self.exception_number() + .checked_sub(EXTERNAL_INTERRUPT_EXCEPTION_BASE) + } + + /// Returns the combined IT/ICI/ECI execution-state value. + /// + /// Arm splits this execution-state field across two bit ranges. This helper + /// joins `IT_ICI_HIGH[1:0]` and `IT_ICI_LOW[5:0]` into the architecturally + /// described 8-bit value. + #[inline(always)] + pub fn it_ici(self) -> u8 { + let high = self.register.read(XPSR::IT_ICI_HIGH) as u8; + let low = self.register.read(XPSR::IT_ICI_LOW) as u8; + (high << 6) | low + } + + /// Returns whether any IT/ICI/ECI execution-state bit is set. + #[inline(always)] + pub fn has_it_ici(self) -> bool { + self.it_ici() != 0 + } + + /// Returns the GE[3:0] comparison flags from APSR. + /// + /// These flags are used by SIMD-style comparison instructions on profiles + /// that implement them. Callers that only care whether any flag is present + /// can use [`Xpsr::has_ge_flag`]. + #[inline(always)] + pub fn ge_flags(self) -> u8 { + self.register.read(XPSR::GE) as u8 + } + + /// Returns whether any GE comparison flag is set. + #[inline(always)] + pub fn has_ge_flag(self) -> bool { + self.ge_flags() != 0 + } + + /// Returns whether the B state bit is set. + /// + /// The bit is architecturally defined only on profiles that implement the + /// corresponding branch-target state. On cores where the bit is reserved, + /// this simply decodes the snapshot as read. + #[inline(always)] + pub fn b(self) -> bool { + self.register.is_set(XPSR::B) + } + + /// Returns whether the T32 execution state bit is set. + /// + /// Cortex-M executes Thumb instructions. A cleared T bit in an exception + /// frame is therefore useful diagnostic evidence for an invalid-state fault. + #[inline(always)] + pub fn t(self) -> bool { + self.register.is_set(XPSR::T) + } + + /// Returns whether the Q saturation flag is set. + #[inline(always)] + pub fn q(self) -> bool { + self.register.is_set(XPSR::Q) + } + + /// Returns whether the V overflow flag is set. + #[inline(always)] + pub fn v(self) -> bool { + self.register.is_set(XPSR::V) + } + + /// Returns whether the C carry or borrow flag is set. + #[inline(always)] + pub fn c(self) -> bool { + self.register.is_set(XPSR::C) + } + + /// Returns whether the Z zero flag is set. + #[inline(always)] + pub fn z(self) -> bool { + self.register.is_set(XPSR::Z) + } + + /// Returns whether the N negative flag is set. + #[inline(always)] + pub fn n(self) -> bool { + self.register.is_set(XPSR::N) + } +} + +impl Default for Xpsr { + #[inline(always)] + fn default() -> Self { + Self::from_bits(0) + } +} + +/// Reads the Cortex-M xPSR special register. +/// +/// # Safety +/// +/// The caller must ensure this code is executing on a Cortex-M profile where +/// `mrs xpsr` is a valid instruction and that reading current CPU execution +/// state is appropriate for the surrounding exception or thread context. +#[inline(always)] +pub unsafe fn read() -> Xpsr { + let bits; + unsafe { + asm!( + "mrs {}, xpsr", + out(reg) bits, + options(nomem, nostack, preserves_flags) + ); + } + Xpsr::from_bits(bits) +} + +impl fmt::Display for Xpsr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f, "XPSR: 0x{:08x}", self.bits())?; + writeln!(f, " Exception Number: {}", self.exception_number())?; + + if self.t() { + writeln!(f, " - T32 instruction set")?; + } else { + writeln!(f, " - Invalid state")?; + } + + writeln!(f, " B state bit: {}", self.b() as u8)?; + writeln!(f, " IT/ICI/ECI flag: {}", self.has_it_ici())?; + writeln!(f, " IT/ICI/ECI value: {}", self.it_ici())?; + writeln!(f, " GE flags: 0x{:x}", self.ge_flags())?; + writeln!(f, " Condition flags:")?; + writeln!( + f, + " N={} Z={} C={} V={} Q={}", + self.n() as u8, + self.z() as u8, + self.c() as u8, + self.v() as u8, + self.q() as u8 + )?; + + Ok(()) + } +} diff --git a/arch/src/lib.rs b/arch/src/lib.rs new file mode 100644 index 00000000..3749aa1c --- /dev/null +++ b/arch/src/lib.rs @@ -0,0 +1,20 @@ +// Copyright (c) 2025 vivo Mobile Communication Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![feature(naked_functions)] + +#[cfg(target_arch = "arm")] +#[path = "../arm/cortex_m/mod.rs"] +pub mod cortex_m; diff --git a/kernel/BUILD.gn b/kernel/BUILD.gn index 06ef5d30..a738eebd 100644 --- a/kernel/BUILD.gn +++ b/kernel/BUILD.gn @@ -58,6 +58,7 @@ _shared_deps = [ "//external/vendor/thiserror-2.0.17:thiserror", "//external/vendor/tock-registers-0.9.0:tock_registers", "//external/vendor/zerocopy-0.8.27:zerocopy", + "//kernel/arch:bluekernel_arch", "//kernel/driver:blueos_driver", "//kernel/hal:blueos_hal", "//kernel/header:blueos_header", diff --git a/kernel/src/arch/arm/hardfault.rs b/kernel/src/arch/arm/hardfault.rs deleted file mode 100644 index d3f668a1..00000000 --- a/kernel/src/arch/arm/hardfault.rs +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright (c) 2025 vivo Mobile Communication Co., Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::{xpsr, IsrContext}; -use core::fmt; -use cortex_m::peripheral::SCB; - -#[derive(Debug, Default)] -struct HardFaultRegs { - cfsr: u32, // Configurable Fault Status Register - hfsr: u32, // Hard Fault Status Register - mmfar: u32, // Memory Management Fault Address Register - bfar: u32, // Bus Fault Address Register - afsr: u32, // Auxiliary Fault Status Register (ARMv8-M) -} - -impl HardFaultRegs { - pub fn from_scb() -> Self { - // Get the value of the SCB registers - // SAFETY: SCB::PTR comes from cortex_m crate and is a valid pointer - let scb = unsafe { &*SCB::PTR }; - - Self { - cfsr: scb.cfsr.read(), - hfsr: scb.hfsr.read(), - mmfar: scb.mmfar.read(), - bfar: scb.bfar.read(), - afsr: scb.afsr.read(), - } - } -} - -impl fmt::Display for HardFaultRegs { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!(f, "\nHFSR: 0x{:08x}", self.hfsr)?; - if self.hfsr & (1 << 30) != 0 { - writeln!(f, " - Forced Hard Fault")?; - } - if self.hfsr & (1 << 31) != 0 { - writeln!(f, " - Debug Event")?; - } - if self.hfsr & (1 << 1) != 0 { - writeln!(f, " - Vector Table Read Fault")?; - } - writeln!(f, "CFSR: 0x{:08x}", self.cfsr)?; - writeln!(f, "Fault Status:")?; - if self.cfsr & 0xFF != 0 { - // https://developer.arm.com/documentation/ddi0553/latest/ - // MMFARVALID, bit [7] - MMFAR valid flag. Indicates validity of the MMFAR register. - // 0: MMFAR content not valid. - // 1: MMFAR content valid - // MLSPERR, bit [5] - MemManage lazy Floating-point state preservation error flag. - // Records whether a MemManage fault occurred during lazy Floating-point state preservation. - // 0: No MemManage occurred. - // 1: MemManage occurred. - // MSTKERR, bit [4] - MemManage stacking error flag. Records whether a derived MemManage fault occurred during exception entry stacking. - // 0: No derived MemManage occurred. - // 1: Derived MemManage occurred during exception entry. - // MUNSTKERR, bit [3] - MemManage unstacking error flag. Records whether a derived MemManage fault occurred during exception return unstacking. - // 0: No derived MemManage fault occurred. - // 1: Derived MemManage fault occurred during exception return - // DACCVIOL, bit [1] - Data access violation flag. Records whether a data access violation has occurred. - // 0: No MemManage fault on data access has occurred. - // 1: MemManage fault on data access has occurred. - // A DACCVIOL will be accompanied by an MMFAR update. - // IACCVIOL, bit [0] - Instruction access violation. Records whether an instruction related memory access violation has occurred. - // 0: No MemManage fault on instruction access has occurred. - // 1: MemManage fault on instruction access has occurred. - writeln!(f, " Memory Management Fault:")?; - if self.cfsr & (1 << 0) != 0 { - writeln!(f, " - Instruction access violation")?; - } - if self.cfsr & (1 << 1) != 0 { - writeln!(f, " - Data access violation")?; - } - if self.cfsr & (1 << 3) != 0 { - writeln!(f, " - Unstacking error")?; - } - if self.cfsr & (1 << 4) != 0 { - writeln!(f, " - Stacking error")?; - } - if self.cfsr & (1 << 5) != 0 { - writeln!(f, " - lazy Floating-point state preservation error")?; - } - if self.cfsr & (1 << 7) != 0 { - writeln!(f, " - MMFAR valid")?; - writeln!(f, " Fault Address: 0x{:08x}", self.mmfar)?; - } - } - if self.cfsr & 0xFF00 != 0 { - // BFARVALID, bit [7] - BFAR valid. Indicates validity of the contents of the BFAR register. - // 0: BFAR content not valid. - // 1: BFAR content valid. - // LSPERR, bit [5] - Lazy state preservation error. Records whether a precise BusFault occurred during floating-point lazy - // Floating-point state preservation. - // 0: No BusFault occurred. - // 1: BusFault occurred. - // If AIRCR.BFHFNMINS is zero this bit is RAZ/WI from Non-secure state. - // STKERR, bit [4] - Stack error. Records whether a precise derived BusFault occurred during exception entry stacking. - // 0: No derived BusFault occurred. - // 1: Derived BusFault occurred during exception entry. - // Derived BusFault occurred during exception entry. - // If AIRCR.BFHFNMINS is zero this bit is RAZ/WI from Non-secure state. - // UNSTKERR, bit [3] - Unstack error. Records whether a precise derived BusFault occurred during exception return unstacking. - // 0 :No derived BusFault occurred. - // 1: Derived BusFault occurred during exception return. - // If AIRCR.BFHFNMINS is zero this bit is RAZ/WI from Non-secure state. - // IMPRECISERR, bit [2] - Imprecise error. Records whether an imprecise data access error has occurred. - // 0: No imprecise data access error has occurred. - // 1: Imprecise data access error has occurred. - // If AIRCR.BFHFNMINS is zero this bit is RAZ/WI from Non-secure state. - // PRECISERR, bit [1] - Precise error. Records whether a precise data access error has occurred. - // 0: No precise data access error has occurred. - // 1: Precise data access error has occurred. - // When a precise error is recorded, the associated address is written to the BFAR and BFSR.BFARVALID bit - // is set. - // If AIRCR.BFHFNMINS is zero this bit is RAZ/WI from Non-secure state. - // IBUSERR, bit [0] - Instruction bus error. Records whether a precise BusFault on an instruction prefetch has occurred. - // 0: No BusFault on instruction prefetch has occurred. - // 1: A BusFault on an instruction prefetch has occurred. - // An IBUSERR is only recorded if the instruction is issued for execution. - // If AIRCR.BFHFNMINS is zero this bit is RAZ/WI from Non-secure state. - writeln!(f, " Bus Fault:")?; - if self.cfsr & (1 << 8) != 0 { - writeln!(f, " - Instruction bus error")?; - } - if self.cfsr & (1 << 9) != 0 { - writeln!(f, " - Precise error")?; - } - if self.cfsr & (1 << 10) != 0 { - writeln!(f, " - Imprecise error")?; - } - if self.cfsr & (1 << 11) != 0 { - writeln!(f, " - Unstack error")?; - } - if self.cfsr & (1 << 12) != 0 { - writeln!(f, " - Stacking error")?; - } - if self.cfsr & (1 << 13) != 0 { - writeln!(f, " - Lazy state preservation error")?; - } - if self.cfsr & (1 << 15) != 0 { - writeln!(f, " - BFAR valid")?; - writeln!(f, " Fault Address: 0x{:08x}", self.bfar)?; - } - } - // DIVBYZERO, bit [9] - Divide by zero flag. Sticky flag indicating whether an integer division by zero error has occurred. - // 0: Error has not occurred. - // 1: Error has occurred. - // UNALIGNED, bit [8] - Unaligned access flag. Sticky flag indicating whether an unaligned access error has occurred. - // 0: Error has not occurred. - // 1: Error has occurred. - // Bits [7:5] - Reserved, RES0. - // STKOF, bit [4] - Stack overflow flag. Sticky flag indicating whether a stack overflow error has occurred. - // 0: Error has not occurred. - // 1: Error has occurred. - // NOCP, bit [3] - No coprocessor flag. Sticky flag indicating whether a coprocessor disabled or not present error has occurred. - // 0: Error has not occurred. - // 1: Error has occurred. - // INVPC, bit [2] - Invalid PC flag. Sticky flag indicating whether an integrity check error has occurred. - // 0: Error has not occurred. - // 1: Error has occurred. - // INVSTATE, bit [1] - Invalid state flag. Sticky flag indicating whether an EPSR.B, EPSR.T, EPSR.IT, or FPSCR.LTPSIZE validity - // 0: Error has not occurred. - // 1: Error has occurred. - // UNDEFINSTR, bit [0] - UNDEFINED instruction flag. Sticky flag indicating whether an UNDEFINED instruction error has occurred. - // 0: Error has not occurred. - // 1: Error has occurred. - // This includes attempting to execute an UNDEFINED instruction associated with an enable coprocessor. - if self.cfsr & 0xFFFF0000 != 0 { - writeln!(f, " Usage Fault:")?; - if self.cfsr & (1 << 16) != 0 { - writeln!(f, " - Undefined instruction")?; - } - if self.cfsr & (1 << 17) != 0 { - writeln!(f, " - Invalid state")?; - } - if self.cfsr & (1 << 18) != 0 { - writeln!(f, " - Invalid PC load")?; - } - if self.cfsr & (1 << 19) != 0 { - writeln!(f, " - No coprocessor")?; - } - #[cfg(armv8m)] - if self.cfsr & (1 << 20) != 0 { - writeln!(f, " - Stack overflow")?; - } - if self.cfsr & (1 << 24) != 0 { - writeln!(f, " - Unaligned access")?; - } - if self.cfsr & (1 << 25) != 0 { - writeln!(f, " - Divide by zero")?; - } - } - - writeln!(f, "AFSR: 0x{:08x}", self.afsr)?; - if self.afsr != 0 { - writeln!(f, " - Auxiliary Faults detected")?; - } - - Ok(()) - } -} - -pub extern "C" fn panic_on_hardfault(ctx: &IsrContext) { - super::disable_local_irq(); - let fault_regs: HardFaultRegs = HardFaultRegs::from_scb(); - let xpsr = xpsr::read(); - panic!( - " - ==== HARD FAULT ==== - FRAME: {:?} - FAULT REGS: {} - XPSR: {} - ", - ctx, fault_regs, xpsr, - ); -} - -#[naked] -#[no_mangle] -pub(crate) unsafe extern "C" fn handle_hardfault() { - core::arch::naked_asm!( - " - mrs r0, msp - tst lr, #0x04 - beq 1f - mrs r0, psp - 1: - bl {panic} - ", - panic = sym panic_on_hardfault - ) -} diff --git a/kernel/src/arch/arm/mod.rs b/kernel/src/arch/arm/mod.rs index 4df3e114..36b02454 100644 --- a/kernel/src/arch/arm/mod.rs +++ b/kernel/src/arch/arm/mod.rs @@ -12,40 +12,39 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub(crate) mod hardfault; -pub mod irq; -pub(crate) mod xpsr; use crate::{ - arch::irq::Vector, - boot::_start, scheduler, support::{sideeffect, Region, RegionalObjectBuilder}, syscalls::{dispatch_syscall, Context as ScContext}, }; -pub(crate) use hardfault::handle_hardfault; -pub use hardfault::panic_on_hardfault; +pub(crate) use bluekernel_arch::cortex_m::hardfault::handle_hardfault; +use bluekernel_arch::cortex_m::{asm as cortex_asm, scb}; +pub use bluekernel_arch::cortex_m::{ + current_sp, disable_local_irq, disable_local_irq_save, enable_local_irq, + enable_local_irq_restore, idle, irq, local_irq_enabled, Context, ExceptionStackFrame, +}; #[cfg(use_mpu)] pub mod mpu; -use core::{ - fmt, - mem::offset_of, - sync::{atomic, atomic::Ordering}, -}; -use cortex_m::peripheral::SCB; +use core::mem::offset_of; use scheduler::ContextSwitchHookHolder; -pub const EXCEPTION_LR: usize = 0xFFFFFFFD; // See https://developer.arm.com/documentation/100235/0100/The-Cortex-M33-Processor/Programmer-s-model/Core-registers/CONTROL-register. #[cfg(not(target_abi = "eabihf"))] pub const CONTROL: usize = 0b10; #[cfg(target_abi = "eabihf")] pub const CONTROL: usize = 0b110; -pub const THUMB_MODE: usize = 0x01000000; +pub const THUMB_MODE: usize = bluekernel_arch::cortex_m::THUMB_MODE_XPSR; pub const NR_SWITCH: usize = !0; pub const NR_RET_FROM_SYSCALL: usize = NR_SWITCH - 1; pub const NR_DEBUG_SYSCALL: usize = NR_SWITCH - 2; -pub const DISABLE_LOCAL_IRQ_BASEPRI: u8 = irq::IRQ_PRIORITY_FOR_SCHEDULER; +pub const DISABLE_LOCAL_IRQ_BASEPRI: u8 = bluekernel_arch::cortex_m::LOCAL_IRQ_BASEPRI as u8; +// Keep one callee-saved register macro: the assembler elides S16-S31 handling +// for non-hard-float builds, matching the ABI-gated fields in `Context`. +#[cfg(target_abi = "eabihf")] +const CALLEE_SAVED_FPU_REGS: usize = 1; +#[cfg(not(target_abi = "eabihf"))] +const CALLEE_SAVED_FPU_REGS: usize = 0; #[no_mangle] #[linkage = "weak"] @@ -61,46 +60,6 @@ pub unsafe extern "C" fn handle_systick() { crate::time::handle_clock_interrupt(); } -#[used] -#[link_section = ".exception.handlers"] -#[no_mangle] -pub static __EXCEPTION_HANDLERS__: [Vector; 15] = build_exception_handlers(); - -unsafe extern "C" { - unsafe fn handle_memfault(); -} - -// See https://documentation-service.arm.com/static/5ea823e69931941038df1b02?token=. -const fn build_exception_handlers() -> [Vector; 15] { - let mut tbl = [Vector { reserved: 0 }; 15]; - tbl[0] = Vector { handler: _start }; - tbl[1] = Vector { - handler: handle_hardfault, - }; // NMI - tbl[2] = Vector { - handler: bk_handle_hardfault, - }; // HardFault - tbl[3] = Vector { - handler: handle_memfault, - }; // MemManage - tbl[4] = Vector { - handler: handle_hardfault, - }; // BusFault - tbl[5] = Vector { - handler: handle_hardfault, - }; // UsageFault - tbl[10] = Vector { - handler: handle_svc, - }; - tbl[13] = Vector { - handler: handle_pendsv, - }; - tbl[14] = Vector { - handler: handle_systick, - }; - tbl -} - #[macro_export] macro_rules! arch_bootstrap { ($stack_start:expr, $stack_end:expr, $cont:path) => { @@ -122,54 +81,22 @@ extern "C" fn prepare_schedule() -> usize { } extern "C" { - pub static mut __sys_stack_start: u8; pub static mut __sys_stack_end: u8; } -macro_rules! disable_interrupt { - () => { - " - cpsid i - " - }; -} - -macro_rules! enable_interrupt { - () => { - " - cpsie i - " - }; -} - pub extern "C" fn reset_msp_and_start_schedule(msp: *mut u8, cont: extern "C" fn() -> !) { let sp = prepare_schedule(); unsafe { - core::arch::asm!( - " - msr psp, {sp} - msr msp, {msp} - ", - // Reset handler is special, see - // https://stackoverflow.com/questions/59008284/if-the-main-function-is-called-inside-the-reset-handler-how-other-interrupts-ar - " - ldr {tmp}, ={thumb} - msr xpsr, {tmp} - ldr {tmp}, ={ctrl} - msr control, {tmp} - ldr lr, =0 - msr basepri, {basepri} - cpsie i - bx {cont} - ", - options(nostack, noreturn), - thumb = const THUMB_MODE, - ctrl = const CONTROL, - msp = in(reg) msp, - sp = in(reg) sp, - tmp = in(reg) 0, - cont = in(reg) cont, - basepri = in(reg) DISABLE_LOCAL_IRQ_BASEPRI, + // Reset handler entry is special: after switching MSP/PSP, the code + // must not return or let the compiler use the old stack again. See: + // https://stackoverflow.com/questions/59008284/if-the-main-function-is-called-inside-the-reset-handler-how-other-interrupts-ar + cortex_asm::reset_stack_pointers_and_start( + msp, + sp, + THUMB_MODE, + CONTROL, + DISABLE_LOCAL_IRQ_BASEPRI as usize, + cont, ) } } @@ -181,197 +108,27 @@ pub extern "C" fn start_schedule(cont: extern "C" fn() -> !) { unsafe { reset_msp_and_start_schedule(&mut __sys_stack_end as *mut u8, cont) } } -#[cfg(not(target_abi = "eabihf"))] -#[repr(C, align(8))] -#[derive(Default, Debug, Copy, Clone)] -pub struct Context { - pub r4: usize, - pub r5: usize, - pub r6: usize, - pub r7: usize, - pub r8: usize, - pub r9: usize, - pub r10: usize, - pub r11: usize, - // Cortex-m saves R0, R1, R2, R3, R12, LR, PC, xPSR automatically - // on psp, so they don't appear in the Context. Additionally, sp - // == R13, lr == R14, pc == R15. - pub r0: usize, - pub r1: usize, - pub r2: usize, - pub r3: usize, - pub r12: usize, - pub lr: usize, - pub pc: usize, - pub xpsr: usize, -} - -#[cfg(target_abi = "eabihf")] -#[repr(C, align(8))] -#[derive(Default, Debug, Copy, Clone)] -pub struct Context { - pub r4: usize, - pub r5: usize, - pub r6: usize, - pub r7: usize, - pub r8: usize, - pub r9: usize, - pub r10: usize, - pub r11: usize, - pub s16: usize, - pub s17: usize, - pub s18: usize, - pub s19: usize, - pub s20: usize, - pub s21: usize, - pub s22: usize, - pub s23: usize, - pub s24: usize, - pub s25: usize, - pub s26: usize, - pub s27: usize, - pub s28: usize, - pub s29: usize, - pub s30: usize, - pub s31: usize, - // Cortex-m saves R0, R1, R2, R3, R12, LR, PC, xPSR automatically - // on psp, so they don't appear in the Context. Additionally, sp - // == R13, lr == R14, pc == R15. - pub r0: usize, - pub r1: usize, - pub r2: usize, - pub r3: usize, - pub r12: usize, - pub lr: usize, - pub pc: usize, - pub xpsr: usize, - pub s0: usize, - pub s1: usize, - pub s2: usize, - pub s3: usize, - pub s4: usize, - pub s5: usize, - pub s6: usize, - pub s7: usize, - pub s8: usize, - pub s9: usize, - pub s10: usize, - pub s11: usize, - pub s12: usize, - pub s13: usize, - pub s14: usize, - pub s15: usize, - pub fpscr: usize, - pub vpr: usize, -} - -#[cfg(not(target_abi = "eabihf"))] -#[repr(C, align(8))] -#[derive(Default)] -pub struct IsrContext { - pub r0: usize, - pub r1: usize, - pub r2: usize, - pub r3: usize, - pub r12: usize, - pub lr: usize, - pub pc: usize, - pub xpsr: usize, -} - -// See https://developer.arm.com/documentation/107706/0100/Exceptions-and-interrupts-overview/Stack-frames. -#[cfg(target_abi = "eabihf")] -#[repr(C, align(8))] -#[derive(Default)] -pub struct IsrContext { - pub r0: usize, - pub r1: usize, - pub r2: usize, - pub r3: usize, - pub r12: usize, - pub lr: usize, - pub pc: usize, - pub xpsr: usize, - pub s0: usize, - pub s1: usize, - pub s2: usize, - pub s3: usize, - pub s4: usize, - pub s5: usize, - pub s6: usize, - pub s7: usize, - pub s8: usize, - pub s9: usize, - pub s10: usize, - pub s11: usize, - pub s12: usize, - pub s13: usize, - pub s14: usize, - pub s15: usize, - pub fpscr: usize, - pub vpr: usize, -} - -impl fmt::Debug for IsrContext { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "IsrContext {{")?; - write!(f, "r0: 0x{:x} ", self.r0)?; - write!(f, "r1: 0x{:x} ", self.r1)?; - write!(f, "r2: 0x{:x} ", self.r2)?; - write!(f, "r3: 0x{:x} ", self.r3)?; - write!(f, "r12: 0x{:x} ", self.r12)?; - write!(f, "lr: 0x{:x} ", self.lr)?; - write!(f, "pc: 0x{:x} ", self.pc)?; - write!(f, "xpsr: 0x{:x} ", self.xpsr)?; - #[cfg(target_abi = "eabihf")] - { - write!(f, "fpscr: 0x{:x} ", self.fpscr)?; - write!(f, "vpr: 0x{:x} ", self.vpr)?; - } - write!(f, "}}")?; - Ok(()) - } -} - // FIXME: We need to pass a scratch register to perform saving. // Use r12 as scratch register now. -#[cfg(not(target_abi = "eabihf"))] -macro_rules! store_callee_saved_regs { - () => { - " - mrs r12, psp - stmdb r12!, {{r4-r11}} - " - }; -} - -#[cfg(not(target_abi = "eabihf"))] -macro_rules! load_callee_saved_regs { - () => { - " - ldmia r12!, {{r4-r11}} - msr psp, r12 - " - }; -} - -#[cfg(target_abi = "eabihf")] macro_rules! store_callee_saved_regs { () => { " mrs r12, psp + .if {callee_saved_fpu_regs} vstmdb r12!, {{s16-s31}} + .endif stmdb r12!, {{r4-r11}} " }; } -#[cfg(target_abi = "eabihf")] macro_rules! load_callee_saved_regs { () => { " ldmia r12!, {{r4-r11}} + .if {callee_saved_fpu_regs} vldmia r12!, {{s16-s31}} + .endif msr psp, r12 " }; @@ -379,11 +136,12 @@ macro_rules! load_callee_saved_regs { #[inline(always)] pub(crate) extern "C" fn post_pendsv() { - SCB::set_pendsv(); - unsafe { core::arch::asm!("isb", options(nostack),) } + unsafe { scb::set_pendsv() }; + unsafe { cortex_asm::isb() } } #[naked] +#[no_mangle] pub unsafe extern "C" fn handle_svc() { core::arch::naked_asm!( concat!( @@ -409,6 +167,7 @@ pub unsafe extern "C" fn handle_svc() { ), syscall_handler = sym handle_syscall, basepri = const DISABLE_LOCAL_IRQ_BASEPRI, + callee_saved_fpu_regs = const CALLEE_SAVED_FPU_REGS, ) } @@ -488,19 +247,29 @@ extern "C" fn handle_syscall(ctx: &Context) -> usize { unsafe { sideeffect(); dup_ctx - .byte_offset(offset_of!(Context, pc) as isize) + .byte_offset( + (offset_of!(Context, exception_frame) + offset_of!(ExceptionStackFrame, pc)) + as isize, + ) .write_volatile(syscall_stub as usize); dup_ctx - .byte_offset(offset_of!(Context, r0) as isize) + .byte_offset( + (offset_of!(Context, exception_frame) + offset_of!(ExceptionStackFrame, r0)) + as isize, + ) .write_volatile(ctx as *const _ as usize); dup_ctx - .byte_offset(offset_of!(Context, xpsr) as isize) - .write_volatile(ctx.xpsr & !(1 << 9)) + .byte_offset( + (offset_of!(Context, exception_frame) + offset_of!(ExceptionStackFrame, xpsr)) + as isize, + ) + .write_volatile(ctx.xpsr_without_stack_alignment_padding()) } base } #[naked] +#[no_mangle] pub unsafe extern "C" fn handle_pendsv() { core::arch::naked_asm!( concat!( @@ -526,134 +295,10 @@ pub unsafe extern "C" fn handle_pendsv() { ), next_thread_sp = sym scheduler::relinquish_me_and_return_next_sp, basepri = const DISABLE_LOCAL_IRQ_BASEPRI, + callee_saved_fpu_regs = const CALLEE_SAVED_FPU_REGS, ) } -impl Context { - #[inline(never)] - pub fn set_return_address(&mut self, pc: usize) -> &mut Self { - self.pc = pc; - self - } - - #[inline] - pub fn get_return_address(&self) -> usize { - self.pc - } - - #[inline] - pub fn set_arg(&mut self, i: usize, val: usize) -> &mut Self { - match i { - 0 => self.r0 = val, - 1 => self.r1 = val, - 2 => self.r2 = val, - 3 => self.r3 = val, - _ => panic!("Should be passed by stack"), - } - self - } - - #[cfg(not(target_abi = "eabihf"))] - #[inline] - pub fn init(&mut self) -> &mut Self { - self.xpsr = THUMB_MODE; - self - } - - // See https://developer.arm.com/documentation/100235/0004/the-cortex-m33-peripherals/floating-point-unit/floating-point-status-control-register. - #[cfg(target_abi = "eabihf")] - #[inline] - pub fn init(&mut self) -> &mut Self { - self.xpsr = THUMB_MODE; - self.fpscr = 1 << 25; - self.vpr = 0xc0dec0de; - self - } -} - -#[inline] -pub extern "C" fn enable_local_irq() { - unsafe { - core::arch::asm!( - "msr basepri, {}", - in(reg) 0, - options(nostack) - ) - } -} - -#[inline] -pub extern "C" fn disable_local_irq() { - unsafe { - core::arch::asm!( - "msr basepri, {}", - "isb", - in(reg) DISABLE_LOCAL_IRQ_BASEPRI, - options(nostack), - ) - } -} - -#[coverage(off)] -#[cfg_attr(debug, inline(never))] -pub extern "C" fn disable_local_irq_save() -> usize { - let old: usize; - unsafe { - core::arch::asm!( - concat!( - " - mrs {old}, basepri - msr basepri, {val} - isb - ", - ), - old = out(reg) old, - val = in(reg) DISABLE_LOCAL_IRQ_BASEPRI, - options(nostack) - ) - } - atomic::compiler_fence(Ordering::SeqCst); - old -} - -#[coverage(off)] -#[cfg_attr(debug, inline(never))] -pub extern "C" fn enable_local_irq_restore(old: usize) { - atomic::compiler_fence(Ordering::SeqCst); - unsafe { - core::arch::asm!( - "msr basepri, {}", - in(reg) old, - options(nostack)) - } -} - -#[inline] -pub extern "C" fn idle() { - unsafe { core::arch::asm!("wfi") } -} - -#[inline] -pub extern "C" fn current_sp() -> usize { - let x: usize; - unsafe { core::arch::asm!("mov {}, sp", out(reg) x, options(nostack, nomem)) }; - x -} - -#[inline] -pub extern "C" fn current_msp() -> usize { - let x: usize; - unsafe { core::arch::asm!("mrs {}, msp", out(reg) x, options(nostack, nomem)) }; - x -} - -#[inline] -pub extern "C" fn current_psp() -> usize { - let x: usize; - unsafe { core::arch::asm!("mrs {}, psp", out(reg) x, options(nostack, nomem)) }; - x -} - #[inline(never)] pub(crate) extern "C" fn switch_context_with_hook(hook: *mut ContextSwitchHookHolder) { unsafe { @@ -675,34 +320,11 @@ pub extern "C" fn pend_switch_context() { post_pendsv(); } -#[inline(always)] -pub(crate) extern "C" fn restore_context_with_hook(hook: *mut ContextSwitchHookHolder) -> ! { - switch_context_with_hook(hook); - unreachable!("Should have switched to another thread"); -} - #[inline] pub extern "C" fn current_cpu_id() -> usize { 0 } -#[inline] -pub extern "C" fn local_irq_enabled() -> bool { - let x: usize; - unsafe { - core::arch::asm!( - "mrs {}, basepri", - out(reg) x, options(nostack) - ); - }; - x == 0 -} - -#[inline] -pub extern "C" fn is_in_interrupt() -> bool { - cortex_m::peripheral::SCB::vect_active() != cortex_m::peripheral::scb::VectActive::ThreadMode -} - #[naked] pub(crate) extern "C" fn switch_stack( to_sp: usize, @@ -733,23 +355,23 @@ mod tests { #[cfg(target_abi = "eabihf")] { assert_eq!( - core::mem::size_of::(), + core::mem::size_of::(), core::mem::size_of::() * 26 ); assert_eq!( core::mem::size_of::(), - core::mem::size_of::() + 8 * 4 + 16 * 4 + core::mem::size_of::() + 8 * 4 + 16 * 4 ); } #[cfg(not(target_abi = "eabihf"))] { assert_eq!( - core::mem::size_of::(), + core::mem::size_of::(), core::mem::size_of::() * 8 ); assert_eq!( core::mem::size_of::(), - core::mem::size_of::() + 8 * 4 + core::mem::size_of::() + 8 * 4 ); } } diff --git a/kernel/src/arch/arm/mpu/mpu_v8m.rs b/kernel/src/arch/arm/mpu/mpu_v8m.rs index 5a5c1f96..59f6d13d 100644 --- a/kernel/src/arch/arm/mpu/mpu_v8m.rs +++ b/kernel/src/arch/arm/mpu/mpu_v8m.rs @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use bluekernel_arch::cortex_m::{asm as cortex_asm, mpu, scb}; use core::ptr::addr_of; -use cortex_m::peripheral::{MPU, SCB}; const MPU_CTRL_ENABLE: u32 = 1 << 0; const MPU_CTRL_PRIVDEFENA: u32 = 1 << 2; @@ -24,7 +24,6 @@ const MPU_RBAR_XN: u32 = 1 << 0; // AP=0b10: privileged read-only, unprivileged no access. const MPU_RBAR_AP_PRIV_RO: u32 = 0b10 << 1; const MPU_RLAR_REGION_ENABLE: u32 = 1 << 0; -const SCB_SHCSR_MEMFAULTENA: u32 = 1 << 16; extern "C" { static __sys_stack_guard_start: u8; @@ -34,7 +33,8 @@ extern "C" { #[inline] fn barrier() { unsafe { - core::arch::asm!("dsb", "isb", options(nostack, preserves_flags)); + cortex_asm::dsb(); + cortex_asm::isb(); } } @@ -61,25 +61,22 @@ pub fn init_sys_stack_guard() { debug_assert_eq!(guard_start & 0x1f, 0); debug_assert_eq!(guard_end & 0x1f, 0); - let mpu = unsafe { &*MPU::PTR }; - let scb = unsafe { &*SCB::PTR }; - - let mut ctrl = mpu.ctrl.read(); + let mut ctrl = unsafe { mpu::control() }; ctrl &= !MPU_CTRL_ENABLE; - unsafe { mpu.ctrl.write(ctrl) }; + unsafe { mpu::set_control(ctrl) }; barrier(); unsafe { - mpu.rnr.write(MPU_REGION_GUARD); - mpu.rbar.write(encode_rbar(guard_start)); - mpu.rlar.write(encode_rlar(guard_end)); - } + mpu::set_region_v8m( + MPU_REGION_GUARD, + encode_rbar(guard_start), + encode_rlar(guard_end), + ) + }; // Keep background memory map for privileged code and enable MPU faulting. - unsafe { mpu.ctrl.write(MPU_CTRL_ENABLE | MPU_CTRL_PRIVDEFENA) }; - let mut shcsr = scb.shcsr.read(); - shcsr |= SCB_SHCSR_MEMFAULTENA; - unsafe { scb.shcsr.write(shcsr) }; + unsafe { mpu::set_control(MPU_CTRL_ENABLE | MPU_CTRL_PRIVDEFENA) }; + unsafe { scb::enable_memfault() }; barrier(); } @@ -107,11 +104,12 @@ pub fn update_thread_stack_guard(next: &crate::thread::Thread) { debug_assert_eq!(guard_start & (MPU_REGION_ALIGN - 1), 0); debug_assert_eq!(guard_end & (MPU_REGION_ALIGN - 1), 0); - let mpu = unsafe { &*MPU::PTR }; unsafe { - mpu.rnr.write(MPU_REGION_THREAD_GUARD); - mpu.rbar.write(encode_rbar(guard_start)); - mpu.rlar.write(encode_rlar(guard_end)); + mpu::set_region_v8m( + MPU_REGION_THREAD_GUARD, + encode_rbar(guard_start), + encode_rlar(guard_end), + ); } barrier(); } diff --git a/kernel/src/arch/arm/xpsr.rs b/kernel/src/arch/arm/xpsr.rs deleted file mode 100644 index f3987a71..00000000 --- a/kernel/src/arch/arm/xpsr.rs +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright (c) 2025 vivo Mobile Communication Co., Ltd. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Program Status Register - -use core::arch::asm; - -/// Program Status Register -#[derive(Clone, Copy, Debug, Default)] -pub struct Xpsr { - bits: u32, -} - -impl Xpsr { - #[inline] - pub fn from_bits(bits: u32) -> Self { - Self { bits } - } - - /// Returns the contents of the register as raw bits - #[inline] - pub fn bits(self) -> u32 { - self.bits - } - - /// Exception number bits [8:0] - #[inline] - pub fn e(self) -> u32 { - self.bits & 0xFF - } - - /// IT/ICI/ECI Value - #[inline] - pub fn it_val(self) -> u32 { - (self.bits >> 10) & 0x3F - } - - /// Greater-than or equal flag, bits [19:16] - #[inline] - pub fn ge(self) -> bool { - (self.bits >> 16) & 0xF != 0 - } - - /// Branch target identification active - #[inline] - pub fn b(self) -> bool { - self.bits & (1 << 21) == (1 << 21) - } - - /// T32 state - #[inline] - pub fn t(self) -> bool { - self.bits & (1 << 24) == (1 << 24) - } - - /// IT/ICI/ECI flag - #[inline] - pub fn it(self) -> bool { - // [{EPSR[26:25], EPSR[11:10]} != 0] - ((self.bits >> 25) & 0x3) << 6 | ((self.bits >> 10) & 0x3) != 0 - } - - /// DSP overflow and saturation flag - #[inline] - pub fn q(self) -> bool { - self.bits & (1 << 27) == (1 << 27) - } - - /// Overflow flag - #[inline] - pub fn v(self) -> bool { - self.bits & (1 << 28) == (1 << 28) - } - - /// Carry or borrow flag - #[inline] - pub fn c(self) -> bool { - self.bits & (1 << 29) == (1 << 29) - } - - /// Zero flag - #[inline] - pub fn z(self) -> bool { - self.bits & (1 << 30) == (1 << 30) - } - - /// Negative flag - #[inline] - pub fn n(self) -> bool { - self.bits & (1 << 31) == (1 << 31) - } -} - -/// Reads the CPU register -#[inline] -pub fn read() -> Xpsr { - let bits; - // SAFETY: Safe register read operation - unsafe { asm!("mrs {}, XPSR", out(reg) bits, options(nomem, nostack, preserves_flags)) }; - Xpsr { bits } -} - -impl core::fmt::Display for Xpsr { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - writeln!(f, "XPSR: 0x{:08x}", self.bits)?; - - // Display Exception Number - writeln!(f, " Exception Number: {}", self.e())?; - - // Display T32 state - if self.t() { - writeln!(f, " - T32 instruction set")?; - } else { - writeln!(f, " - Invalid state")?; - } - - // Display branch target identification state - if self.b() { - writeln!(f, " - Branch target identification inactive")?; - } else { - writeln!(f, " - Branch target identification active")?; - } - - // Display IT/ICI/ECI state - writeln!(f, " IT/ICI/ECI flag: {}", self.it())?; - writeln!(f, " IT/ICI/ECI value: {}", self.it_val())?; - - // Display condition flags - writeln!(f, " Condition flags:")?; - writeln!( - f, - " N={} Z={} C={} V={} Q={}", - self.n() as u8, - self.z() as u8, - self.c() as u8, - self.v() as u8, - self.q() as u8 - )?; - - Ok(()) - } -} - -#[cfg(all(test, not(target_os = "none")))] -mod tests { - use super::*; - use blueos_test_macro::test; - - #[test] - fn test_xpsr_flags() { - let mut xpsr = Xpsr { bits: 0 }; - - xpsr.bits = 1 << 31; - assert!(xpsr.n()); - xpsr.bits = 0; - assert!(!xpsr.n()); - - xpsr.bits = 1 << 30; - assert!(xpsr.z()); - xpsr.bits = 0; - assert!(!xpsr.z()); - - xpsr.bits = 1 << 29; - assert!(xpsr.c()); - xpsr.bits = 0; - assert!(!xpsr.c()); - - xpsr.bits = 1 << 28; - assert!(xpsr.v()); - xpsr.bits = 0; - assert!(!xpsr.v()); - - xpsr.bits = 1 << 27; - assert!(xpsr.q()); - xpsr.bits = 0; - assert!(!xpsr.q()); - - xpsr.bits = 1 << 24; - assert!(xpsr.t()); - xpsr.bits = 0; - assert!(!xpsr.t()); - - xpsr.bits = 0x1F; - assert_eq!(xpsr.e(), 0x1F); - xpsr.bits = 0; - assert_eq!(xpsr.e(), 0); - } -} diff --git a/kernel/src/devices/clock/systick.rs b/kernel/src/devices/clock/systick.rs index f653b0c1..f0100266 100644 --- a/kernel/src/devices/clock/systick.rs +++ b/kernel/src/devices/clock/systick.rs @@ -13,9 +13,13 @@ // limitations under the License. use crate::{arch, arch::irq::IRQ_PRIORITY_FOR_SCHEDULER}; +use bluekernel_arch::cortex_m::{ + asm as cortex_asm, + scb::{self, SystemHandler}, + systick, +}; use blueos_hal::clock::Clock; use core::sync::atomic::{AtomicUsize, Ordering}; -use cortex_m::peripheral::{scb::SystemHandler, syst::SystClkSource, SCB, SYST}; // A SysTick device is a built-in, 24-bit system timer in ARM Cortex-M // processors, acting as a simple, precise hardware timer for generating @@ -44,8 +48,8 @@ impl Clock for SysTickClock Clock for SysTickClock SysTickClock { - fn systick() -> SYST { - unsafe { core::mem::transmute::<(), SYST>(()) } - } - - fn scb() -> SCB { - unsafe { core::mem::transmute::<(), SCB>(()) } - } - pub fn init() { debug_assert_eq!(HZ % TICKS_PS, 0); let reload = (HZ / TICKS_PS) as u32; debug_assert!(reload <= 0x00ff_ffff); debug_assert!(reload > 0); - let mut scb = Self::scb(); - unsafe { scb.set_priority(SystemHandler::SysTick, IRQ_PRIORITY_FOR_SCHEDULER) }; - let mut systick = Self::systick(); - systick.disable_counter(); - systick.set_clock_source(SystClkSource::Core); - systick.set_reload(reload); - systick.clear_current(); - systick.enable_counter(); - systick.enable_interrupt(); + unsafe { + scb::set_system_handler_priority(SystemHandler::SysTick, IRQ_PRIORITY_FOR_SCHEDULER) + }; + unsafe { + systick::disable_counter(); + systick::use_core_clock(); + systick::set_reload(reload); + systick::clear_current(); + systick::enable_counter(); + systick::enable_interrupt(); + } } pub fn claim_interrupt() -> bool { - let now = if Self::systick().has_wrapped() { + let now = if unsafe { systick::has_wrapped() } { TICKS.fetch_add(1, Ordering::Relaxed) + 1 } else { // In case pend_st is manually called. diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index c53a46bd..3508a5c3 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -335,9 +335,8 @@ mod tests { } #[cfg(mpu_stack_guard)] - extern "C" fn handle_memfault_impl(ctx: &mut crate::arch::IsrContext) { - let scb = unsafe { &*cortex_m::peripheral::SCB::PTR }; - let cfsr = scb.cfsr.read(); + extern "C" fn handle_memfault_impl(ctx: &mut crate::arch::ExceptionStackFrame) { + let cfsr = unsafe { bluekernel_arch::cortex_m::scb::cfsr() }; // MMFSR is in CFSR[7:0]. assert_ne!( cfsr & 0xff, @@ -351,7 +350,7 @@ mod tests { ); MEMFAULT_TRIGGERED.store(true, Ordering::Release); // Clear MMFSR bits and skip the faulting instruction. - unsafe { scb.cfsr.write(cfsr & 0xff) }; + unsafe { bluekernel_arch::cortex_m::scb::write_cfsr(cfsr & 0xff) }; ctx.pc = ctx.pc.wrapping_add(thumb_instruction_len(ctx.pc)); }