Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cortex-m/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ rust-version = "1.85"
[dependencies]
bare-metal = { version = "0.2.4", features = ["const-fn"] }
critical-section = "1.0.0"
derive-mmio = "0.6"
volatile-register = "0.2.2"
bitfield = "0.13.2"
eh0 = { package = "embedded-hal", version = "0.2.4" }
Expand Down
36 changes: 20 additions & 16 deletions cortex-m/src/peripheral/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,7 @@ impl Peripherals {
MPU: MPU {
_marker: PhantomData,
},
NVIC: NVIC {
_marker: PhantomData,
},
NVIC: NVIC::steal(),
SAU: SAU {
_marker: PhantomData,
},
Expand Down Expand Up @@ -536,30 +534,36 @@ impl ops::Deref for MPU {
}

/// Nested Vector Interrupt Controller
pub struct NVIC {
_marker: PhantomData<*const ()>,
}
pub struct NVIC(nvic::MmioRegisterBlock<'static>);

unsafe impl Send for NVIC {}

impl NVIC {
/// Pointer to the register block
pub const PTR: *const nvic::RegisterBlock = 0xE000_E100 as *const _;

/// Returns a pointer to the register block
#[inline(always)]
#[deprecated(since = "0.7.5", note = "Use the associated constant `PTR` instead")]
pub const fn ptr() -> *const nvic::RegisterBlock {
Self::PTR
/// Unsafely steal an instance of the NVIC.
///
/// # Safety
///
/// This potentially allows to create multiple instances of the NVIC register block, which
/// might only be valid in certain multi-core environments.
#[inline]
pub unsafe fn steal() -> Self {
NVIC(unsafe { nvic::RegisterBlock::new_mmio_fixed() })
}
Comment on lines 541 to 551
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change removes the long-standing NVIC::PTR / NVIC::ptr() raw-pointer API and changes NVIC from a ZST marker to an owning wrapper. That’s a breaking public API change (and is inconsistent with the other peripherals in this module that still expose PTR). Consider keeping PTR (as a raw pointer/usize base address) and the deprecated ptr() for compatibility, even if you steer users to steal()/derive-mmio accessors.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a breaking change anyway. Not sure how useful it would be to keep the old PTR API for the new type.

}

impl ops::Deref for NVIC {
type Target = self::nvic::RegisterBlock;
type Target = nvic::MmioRegisterBlock<'static>;

Comment thread
robamu marked this conversation as resolved.
#[inline(always)]
fn deref(&self) -> &Self::Target {
unsafe { &*Self::PTR }
&self.0
}
}

impl ops::DerefMut for NVIC {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

Expand Down
160 changes: 98 additions & 62 deletions cortex-m/src/peripheral/nvic.rs
Original file line number Diff line number Diff line change
@@ -1,46 +1,46 @@
//! Nested Vector Interrupt Controller

use volatile_register::RW;
#[cfg(not(armv6m))]
use volatile_register::{RO, WO};

use crate::interrupt::InterruptNumber;
use crate::peripheral::NVIC;

/// Register block
/// NVIC base address.
pub const BASE_ADDRESS: usize = 0xE000_E100;

/// NVIC register block.
#[derive(derive_mmio::Mmio)]
#[repr(C)]
pub struct RegisterBlock {
/// Interrupt Set-Enable
pub iser: [RW<u32>; 16],
iser: [u32; 16],

_reserved0: [u32; 16],

/// Interrupt Clear-Enable
pub icer: [RW<u32>; 16],
icer: [u32; 16],

_reserved1: [u32; 16],

/// Interrupt Set-Pending
pub ispr: [RW<u32>; 16],
ispr: [u32; 16],

_reserved2: [u32; 16],

/// Interrupt Clear-Pending
pub icpr: [RW<u32>; 16],
icpr: [u32; 16],

_reserved3: [u32; 16],

/// Interrupt Active Bit (not present on Cortex-M0 variants)
#[cfg(not(armv6m))]
pub iabr: [RO<u32>; 16],
#[mmio(PureRead)]
iabr: [u32; 16],
#[cfg(armv6m)]
_reserved4: [u32; 16],

_reserved5: [u32; 16],

#[cfg(armv8m)]
/// Interrupt Target Non-secure (only present on Arm v8-M)
pub itns: [RW<u32>; 16],
itns: [u32; 16],
#[cfg(not(armv8m))]
_reserved6: [u32; 16],

Expand All @@ -50,36 +50,46 @@ pub struct RegisterBlock {
///
/// On ARMv7-M, 124 word-sized registers are available. Each of those
/// contains of 4 interrupt priorities of 8 byte each.The architecture
/// specifically allows accessing those along byte boundaries, so they are
/// represented as 496 byte-sized registers, for convenience, and to allow
/// atomic priority updates.
/// specifically allows accessing those along byte boundaries.
///
/// On ARMv6-M, the registers must only be accessed along word boundaries,
/// so convenient byte-sized representation wouldn't work on that
/// architecture.
#[cfg(not(armv6m))]
pub ipr: [RW<u8>; 496],
ipr: [u32; 124],

/// Interrupt Priority
///
/// On ARMv7-M, 124 word-sized registers are available. Each of those
/// contains of 4 interrupt priorities of 8 byte each.The architecture
/// specifically allows accessing those along byte boundaries, so they are
/// represented as 496 byte-sized registers, for convenience, and to allow
/// atomic priority updates.
/// specifically allows accessing those along byte boundaries.
///
/// On ARMv6-M, the registers must only be accessed along word boundaries,
/// so convenient byte-sized representation wouldn't work on that
/// architecture.
#[cfg(armv6m)]
pub ipr: [RW<u32>; 8],
ipr: [u32; 8],

#[cfg(not(armv6m))]
_reserved8: [u32; 580],

/// Software Trigger Interrupt
#[cfg(not(armv6m))]
pub stir: WO<u32>,
#[mmio(Write)]
stir: u32,
}

impl RegisterBlock {
/// Creates a new instance of the NVIC register block.
///
/// # Safety
///
/// This potentially allows to create multiple instances of the NVIC register block, which
/// might only be valid in certain multi-core environments.
#[inline]
pub const unsafe fn new_mmio_fixed() -> MmioRegisterBlock<'static> {
unsafe { RegisterBlock::new_mmio_at(BASE_ADDRESS) }
}
}

impl NVIC {
Expand All @@ -100,9 +110,7 @@ impl NVIC {
{
let nr = interrupt.number();

unsafe {
self.stir.write(u32::from(nr));
}
self.write_stir(u32::from(nr));
}

/// Disables `interrupt`
Expand All @@ -112,8 +120,10 @@ impl NVIC {
I: InterruptNumber,
{
let nr = interrupt.number();
// NOTE(unsafe) this is a write to a stateless register
unsafe { (*Self::PTR).icer[usize::from(nr / 32)].write(1 << (nr % 32)) }
// SAFETY: this is a write to a stateless register.
let mut nvic = unsafe { Self::steal() };
// SAFETY: InterruptNumber is an unsafe trait, we can assume correct implementation.
unsafe { nvic.write_icer_unchecked(usize::from(nr / 32), 1 << (nr % 32)) }
}

/// Enables `interrupt`
Expand All @@ -124,18 +134,20 @@ impl NVIC {
where
I: InterruptNumber,
{
unsafe {
let nr = interrupt.number();
// NOTE(ptr) this is a write to a stateless register
(*Self::PTR).iser[usize::from(nr / 32)].write(1 << (nr % 32))
}
let nr = interrupt.number();
// SAFETY: this is a write to a stateless register,
let mut nvic = unsafe { Self::steal() };
// SAFETY: InterruptNumber is an unsafe trait, we can assume correct implementation.
unsafe { nvic.write_iser_unchecked(usize::from(nr / 32), 1 << (nr % 32)) }
}

/// Returns the NVIC priority of `interrupt`
///
/// *NOTE* NVIC encodes priority in the highest bits of a byte so values like `1` and `2` map
/// to the same priority. Also for NVIC priorities, a lower value (e.g. `16`) has higher
/// priority (urgency) than a larger value (e.g. `32`).
/// *NOTE* The NVIC encodes priorities in the *most-significant* bits of the 8-bit block for
/// each interrupt. This means that the priority value retrieved by this function MUST be
/// right-shifted by (8 - NUMBER_OF_PRIORITY_BITS), where NUMBER_OF_PRIORITY_BITS can be
/// different between cores. Also for NVIC priorities, a lower value (e.g. `0b0000_0000`) has
/// higher priority (urgency) than a larger value (e.g. `0b0100_0000`).
#[inline]
pub fn get_priority<I>(interrupt: I) -> u8
where
Expand All @@ -144,16 +156,22 @@ impl NVIC {
#[cfg(not(armv6m))]
{
let nr = interrupt.number();
// NOTE(unsafe) atomic read with no side effects
unsafe { (*Self::PTR).ipr[usize::from(nr)].read() }
// SAFETY: atomic read with no side effects
let nvic = unsafe { Self::steal() };
let ipr_ptr = nvic.pointer_to_ipr_start() as *const u8;
// SAFETY:
// - atomic read with no side effects
// - InterruptNumber is an unsafe trait, we can assume correct implementation.
unsafe { core::ptr::read_volatile(ipr_ptr.offset(nr as isize)) }
Comment thread
robamu marked this conversation as resolved.
}

#[cfg(armv6m)]
{
// NOTE(unsafe) atomic read with no side effects
let ipr_n = unsafe { (*Self::PTR).ipr[Self::ipr_index(interrupt)].read() };
let prio = (ipr_n >> Self::ipr_shift(interrupt)) & 0x0000_00ff;
prio as u8
// SAFETY: atomic read with no side effects
let nvic = unsafe { Self::steal() };
// SAFETY: InterruptNumber is an unsafe trait, we can assume correct implementation.
let ipr_n = unsafe { nvic.read_ipr_unchecked(Self::ipr_index(interrupt)) };
((ipr_n >> Self::ipr_shift(interrupt)) & 0x0000_00ff) as u8
}
}

Expand All @@ -167,8 +185,10 @@ impl NVIC {
let nr = interrupt.number();
let mask = 1 << (nr % 32);

// NOTE(unsafe) atomic read with no side effects
unsafe { ((*Self::PTR).iabr[usize::from(nr / 32)].read() & mask) == mask }
// SAFETY: atomic read with no side effects
let nvic = unsafe { Self::steal() };
// SAFETY: InterruptNumber is an unsafe trait, we can assume correct implementation.
unsafe { nvic.read_iabr_unchecked(usize::from(nr / 32)) & mask == mask }
}

/// Checks if `interrupt` is enabled
Expand All @@ -180,8 +200,10 @@ impl NVIC {
let nr = interrupt.number();
let mask = 1 << (nr % 32);

// NOTE(unsafe) atomic read with no side effects
unsafe { ((*Self::PTR).iser[usize::from(nr / 32)].read() & mask) == mask }
// SAFETY: atomic read with no side effects
let nvic = unsafe { Self::steal() };
// SAFETY: InterruptNumber is an unsafe trait, we can assume correct implementation.
unsafe { nvic.read_iser_unchecked(usize::from(nr / 32)) & mask == mask }
}

/// Checks if `interrupt` is pending
Expand All @@ -193,8 +215,10 @@ impl NVIC {
let nr = interrupt.number();
let mask = 1 << (nr % 32);

// NOTE(unsafe) atomic read with no side effects
unsafe { ((*Self::PTR).ispr[usize::from(nr / 32)].read() & mask) == mask }
// SAFETY: atomic read with no side effects
let nvic = unsafe { Self::steal() };
// SAFETY: InterruptNumber is an unsafe trait, we can assume correct implementation.
unsafe { nvic.read_ispr_unchecked(usize::from(nr / 32)) & mask == mask }
}

/// Forces `interrupt` into pending state
Expand All @@ -205,14 +229,19 @@ impl NVIC {
{
let nr = interrupt.number();

// NOTE(unsafe) atomic stateless write; ICPR doesn't store any state
unsafe { (*Self::PTR).ispr[usize::from(nr / 32)].write(1 << (nr % 32)) }
// SAFETY: atomic stateless write; Register doesn't store any state
let mut nvic = unsafe { Self::steal() };
// SAFETY: InterruptNumber is an unsafe trait, we can assume correct implementation.
unsafe { nvic.write_ispr_unchecked(usize::from(nr / 32), 1 << (nr % 32)) }
}

/// Sets the "priority" of `interrupt` to `prio`
///
/// *NOTE* See [`get_priority`](struct.NVIC.html#method.get_priority) method for an explanation
/// of how NVIC priorities work.
/// *NOTE* The NVIC encodes priorities in the *most-significant* bits of the 8-bit block for
/// each interrupt. This means that the priority value passed to this function MUST be shifted
/// by (8 - NUMBER_OF_PRIORITY_BITS), where NUMBER_OF_PRIORITY_BITS can be different between
/// cores. Also for NVIC priorities, a lower value (e.g. `0b0000_0000`) has higher
/// priority (urgency) than a larger value (e.g. `0b0010_0000`).
///
/// On ARMv6-M, updating an interrupt priority requires a read-modify-write operation. On
/// ARMv7-M, the operation is performed in a single atomic write operation.
Expand All @@ -226,21 +255,26 @@ impl NVIC {
where
I: InterruptNumber,
{
unsafe {
#[cfg(not(armv6m))]
{
let nr = interrupt.number();
self.ipr[usize::from(nr)].write(prio)
}
#[cfg(not(armv6m))]
{
let nr = interrupt.number();
let ipr_ptr = self.pointer_to_ipr_start() as *mut u8;
// SAFETY:
// - atomic stateless write; IPR doesn't store any state
// - InterruptNumber is an unsafe trait, we can assume correct implementation.
unsafe { core::ptr::write_volatile(ipr_ptr.offset(nr as isize), prio) }
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NVIC::set_priority (non-armv6m path) uses ptr.offset(...) on an MMIO pointer. As with get_priority, this can be UB under strict provenance because offset requires staying within a single allocated object. Prefer wrapping_add/wrapping_offset (or address arithmetic from BASE_ADDRESS) before calling write_volatile.

Suggested change
unsafe { core::ptr::write_volatile(ipr_ptr.offset(nr as isize), prio) }
unsafe { core::ptr::write_volatile(ipr_ptr.wrapping_add(usize::from(nr)), prio) }

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

@robamu robamu Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be fine I think? The pointer is related to the IPR allocation and we assume the interrupt number is valid.

}

#[cfg(armv6m)]
{
self.ipr[Self::ipr_index(interrupt)].modify(|value| {
#[cfg(armv6m)]
{
// SAFETY: InterruptNumber is an unsafe trait, we can assume correct implementation.
unsafe {
self.modify_ipr_unchecked(Self::ipr_index(interrupt), |value| {
let mask = 0x0000_00ff << Self::ipr_shift(interrupt);
let prio = u32::from(prio) << Self::ipr_shift(interrupt);

(value & !mask) | prio
})
});
}
}
}
Expand All @@ -253,8 +287,10 @@ impl NVIC {
{
let nr = interrupt.number();

// NOTE(unsafe) atomic stateless write; ICPR doesn't store any state
unsafe { (*Self::PTR).icpr[usize::from(nr / 32)].write(1 << (nr % 32)) }
// SAFETY: atomic stateless write; ICPR doesn't store any state
let mut nvic = unsafe { Self::steal() };
// SAFETY: InterruptNumber is an unsafe trait, we can assume correct implementation.
unsafe { nvic.write_icpr_unchecked(usize::from(nr / 32), 1 << (nr % 32)) }
}

#[cfg(armv6m)]
Expand Down
Loading
Loading