Interrupts & MMIO (std::interrupt)
std/interrupt/ covers the software-managed side of interrupt handling —
vector tables, ISR registration/dispatch, MMIO register access, bitfield
manipulation, and clock configuration — complementing the peripheral
drivers in std::hal. All freestanding-safe.
#include <std/interrupt/mmio.h>
#include <std/interrupt/bitfield.h>
void configure_uart(&volatile unsigned int uart_base) {
struct MmioReg ctrl = std::MmioReg::at(uart_base);
unsigned int baud_field = ctrl.read_field(0, 15);
ctrl.write_field(0, 15, baud_field | 1u);
unsigned int combined = std::bf_insert32(0, 4, 7, 0xA);
}
MMIO (interrupt/mmio.h)
Register addresses are &volatile references (&volatile unsigned int for
32-bit registers), created from a fixed address inside unsafe once — see
HAL for the pattern.
Two equivalent APIs over the same operations: a MmioReg struct
(MmioReg::at(addr) to construct, then methods) for grouping several
accesses to one register, and free functions for one-off access.
MmioReg method |
Equivalent free function |
|---|---|
read32() / write32(val) |
mmio_read32(addr) / mmio_write32(addr, val) |
| — | mmio_read16/mmio_write16, mmio_read8/mmio_write8 |
set_bits(mask) / clear_bits(mask) |
mmio_set_bits32(addr, mask) / mmio_clear_bits32(addr, mask) |
read_field(lo, hi) / write_field(lo, hi, val) |
mmio_read_field32(addr, lo, hi) / mmio_write_field32(addr, lo, hi, val) |
The 8- and 16-bit accessors make 8- and 16-bit bus accesses, so they never touch neighbouring registers. Field helpers accept the full width, (0, 31).
Bitfield Helpers (interrupt/bitfield.h)
Pure functions for register bit manipulation — bf_extract32(val, lo, hi),
bf_insert32(val, lo, hi, field), bf_set32/bf_clear32/bf_toggle32
(bits [hi:lo]), bf_test32(val, n) (single-bit test), bf_mask32(lo, hi)
(build a mask with no register access), bf_popcount32(val).
ISR Vector Table (interrupt/isr.h)
A software dispatch table distinct from the hardware vector table below —
IsrTable holds up to ISR_MAX (256) fn void() handlers:
static void uart_rx_isr() { /* ... */ }
struct IsrTable table = std::IsrTable::new();
table.register_(3, uart_rx_isr); // trailing underscore: 'register' is a C keyword
// re-registering an IRQ replaces its handler
...
if (table.dispatch(3)) { /* handler ran */ }
Plus irq_disable()/irq_enable(), which mask and unmask IRQs at the CPU
on bare-metal targets: cpsid i/cpsie i (ARM), msr daifset/daifclr, #2
(AArch64), clearing/setting mstatus.MIE (RISC-V) and cli/sti (x86).
They need privileged mode; in a hosted process they are only compiler
barriers.
Hardware Vector Table (interrupt/vector_table.h)
VectorTable (global instance vtable) is the actual CPU-recognized
vector table — install(index, handler)/remove(index)/get(index) to
populate it, dispatch(index) to invoke a slot's handler (or the default
handler, set via set_default()), and vtable_activate() to point the
CPU at it:
| Target | What vtable_activate() does |
|---|---|
| Cortex-M | Writes SCB->VTOR (v7-M, v7E-M, v8-M, v8.1-M; not v6-M, where VTOR is optional) |
| RISC-V | Writes mtvec (vectored mode) |
| AArch64 | Writes VBAR_EL1 |
vtable_init() sets every slot to vtable_default_handler (a weak,
infinite-loop fault handler meant to be overridden per project) before use.
set_default(handler) also retargets every slot still holding the
previous default, and dispatch() of an empty or out-of-range index
calls the current default.
VTABLE_MAX_VECTORS is 256 entries (Cortex-M: 16 core exceptions plus up to 240 external IRQs).
Clock Configuration (interrupt/clock.h)
ClockConfig (via ClockConfig::new(base)) — set_source() (CLK_SRC_HSI/
CLK_SRC_HSE/CLK_SRC_PLL), configure_pll(mul, div), set_ahb_div/
set_apb1_div/set_apb2_div (bus prescalers), get_freq().