Skip to content
195 changes: 192 additions & 3 deletions core/src/cpus/cortex_m.zig
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const std = @import("std");
const builtin = @import("builtin");
const microzig = @import("microzig");
const mmio = microzig.mmio;
const app = microzig.app;
const shared = @import("cortex_m/shared_types.zig");

const Core = enum {
cortex_m0,
Expand Down Expand Up @@ -658,6 +660,17 @@ pub const startup_logic = struct {
}
}

// if (@hasField(types.peripherals.SystemControlBlock, "SHCSR")) {
{
// Enable distinction between MemFault, BusFault and UsageFault:
peripherals.scb.SHCSR.modify(.{
.MEMFAULTENA = 1,
.BUSFAULTENA = 1,
.USGFAULTENA = 1,
});
enable_fault_irq();
}

microzig_main();
}

Expand All @@ -666,20 +679,194 @@ pub const startup_logic = struct {
// will be imported by microzig.zig to allow system startup.
// must be aligned to 256 as VTOR ignores the lower 8 bits of the address.
pub const _vector_table: VectorTable align(256) = blk: {
if (cpu_flags.has_hard_fault and !@hasField(VectorTable, HardFault_name)) {
@compileError("The CPU configures a vector, but none present in the VectorTable type!");
}
if (cpu_flags.has_bus_fault and !@hasField(VectorTable, BusFault_name)) {
@compileError("The CPU configures a vector, but none present in the VectorTable type!");
}
if (cpu_flags.has_mem_manage_fault and !@hasField(VectorTable, MemManageFault_name)) {
@compileError("The CPU configures a vector, but none present in the VectorTable type!");
}
if (cpu_flags.has_usage_fault and !@hasField(VectorTable, UsageFault_name)) {
@compileError("The CPU configures a vector, but none present in the VectorTable type!");
}

var tmp: VectorTable = .{
.initial_stack_pointer = microzig.config.end_of_stack,
.Reset = .{ .c = microzig.cpu.startup_logic._start },
};

for (@typeInfo(@TypeOf(microzig.options.interrupts)).@"struct".fields) |field| {
const maybe_handler = @field(microzig.options.interrupts, field.name);
if (maybe_handler) |handler| {
@field(tmp, field.name) = handler;
}
@field(tmp, field.name) = if (maybe_handler) |handler|
handler
else
default_exception_handler(field.name);
}

break :blk tmp;
};

fn default_exception_handler(comptime name: []const u8) microzig.interrupt.Handler {
if (microzig.options.cpu.verbose_unhandled_irq) {
return .{ .c = debugExceptionHandler(name) };
} else {
return .{ .c = ReleaseExceptionHandler.handle };
}
}

const IrqHandlerFn = *const fn () callconv(.c) void;

fn debugExceptionHandler(comptime name: []const u8) IrqHandlerFn {
Copy link

Choose a reason for hiding this comment

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

Please change to debug_exception_handler, in MicroZig we use snake case for function names.

if (comptime std.mem.eql(u8, name, HardFault_name))
return debug.hard_fault_handler;
if (comptime std.mem.eql(u8, name, BusFault_name))
return debug.bus_fault_handler;
if (comptime std.mem.eql(u8, name, MemManageFault_name))
return debug.mem_manage_fault_handler;
if (comptime std.mem.eql(u8, name, UsageFault_name))
return debug.usage_fault_handler;

return struct {
fn handle() callconv(.c) void {
@panic("Unhandled exception: " ++ name);
}
}.handle;
}

const ReleaseExceptionHandler = struct {
Copy link
Collaborator

Choose a reason for hiding this comment

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

is this just to save on code + data size on release builds?

fn handle() callconv(.c) void {
@panic("Unhandled exception");
}
};

const HardFault_name = "HardFault";
const BusFault_name = "BusFault";
const MemManageFault_name = "MemManageFault";
const UsageFault_name = "UsageFault";
};

/// Implements several mechanisms to easy debugging with Cortex-M cpus.
///
/// Read more here:
/// https://interrupt.memfault.com/blog/cortex-m-hardfault-debug
pub const debug = struct {
const logger = std.log.scoped(.cortex_m3_debug);

/// This frame is pushed mostly by the CPU itself, and we move it into
/// the parameter register, so we can inspect it.
pub const ContextStateFrame = extern struct {
r0: u32,
r1: u32,
r2: u32,
r3: u32,
r12: u32,
lr: u32,
return_address: u32,
xpsr: u32,
};

/// Wraps `handler` in a small asm block that ensures that it is a regular interrupt handler
/// function, but also provides us with a ContextStateFrame fetched from the system status:
pub fn make_fault_handler(comptime handler: *const fn (context: *ContextStateFrame) callconv(.C) void) *const fn () callconv(.C) void {
return struct {
fn invoke() callconv(.C) void {
// See this article on how we use that:
// https://interrupt.memfault.com/blog/cortex-m-hardfault-debug
asm volatile (
\\
// Check 2th bit of LR.
\\tst lr, #4
// Do "if then else" equal
\\ite eq
// if equals, we use the MSP
\\mrseq r0, msp
// otherwise, we use the PSP
\\mrsne r0, psp
// Then we branch to our handler:
\\b %[handler]
:
: [handler] "s" (handler),
);
}
}.invoke;
}

pub fn hard_fault_handler() callconv(.c) void {
const hfsr = peripherals.scb.HFSR;

logger.err("Hard Fault:", .{});
logger.err(" VECTTBL: {}", .{hfsr.VECTTBL});
logger.err(" FORCED: {}", .{hfsr.FORCED});
logger.err(" DEBUGEVT: {}", .{hfsr.DEBUGEVT});

@panic("Hard fault");
}

pub fn mem_manage_fault_handler() callconv(.c) void {
@panic("Memory fault");
}

pub const bus_fault_handler = make_fault_handler(handle_bus_fault_wrapped);

fn handle_bus_fault_wrapped(context: *const ContextStateFrame) callconv(.c) void {
const bfsr = peripherals.scb.CFSR.read().BFSR;

logger.err("Bus Fault:", .{});
logger.err(" context = r0:0x{X:0>8} r1:0x{X:0>8} r2:0x{X:0>8} r3:0x{X:0>8}", .{
context.r0,
context.r1,
context.r2,
context.r3,
});
logger.err(" r12:0x{X:0>8} lr:0x{X:0>8} ra:0x{X:0>8} xpsr:0x{X:0>8}", .{
context.r12,
context.lr,
context.return_address,
context.xpsr,
});
logger.err(" instruction bus error = {}", .{bfsr.instruction_bus_error});
logger.err(" precice data bus error = {}", .{bfsr.precice_data_bus_error});
logger.err(" imprecice data bus error = {}", .{bfsr.imprecice_data_bus_error});
logger.err(" unstacking exception error = {}", .{bfsr.unstacking_exception_error});
logger.err(" exception stacking error = {}", .{bfsr.exception_stacking_error});
logger.err(" busfault address register valid = {}", .{bfsr.busfault_address_register_valid});
if (bfsr.busfault_address_register_valid) {
const address = peripherals.scb.BFAR;
logger.err(" busfault address register = 0x{X:0>8}", .{address});
}

@panic("Bus fault");
}

pub const usage_fault_handler = make_fault_handler(handle_usage_fault_wrapped);

fn handle_usage_fault_wrapped(context: *const ContextStateFrame) callconv(.c) void {
const ufsr = peripherals.scb.CFSR.read().UFSR;

logger.err("Usage Fault:", .{});
logger.err(" context = r0:0x{X:0>8} r1:0x{X:0>8} r2:0x{X:0>8} r3:0x{X:0>8}", .{
context.r0,
context.r1,
context.r2,
context.r3,
});
logger.err(" r12:0x{X:0>8} lr:0x{X:0>8} ra:0x{X:0>8} xpsr:0x{X:0>8}", .{
context.r12,
context.lr,
context.return_address,
context.xpsr,
});
logger.err(" undefined instruction = {}", .{ufsr.undefined_instruction});
logger.err(" invalid state = {}", .{ufsr.invalid_state});
logger.err(" invalid pc load = {}", .{ufsr.invalid_pc_load});
logger.err(" missing coprocessor usage = {}", .{ufsr.missing_coprocessor_usage});
logger.err(" unaligned memory access = {}", .{ufsr.unaligned_memory_access});
logger.err(" divide by zero = {}", .{ufsr.divide_by_zero});

@panic("usage fault");
}
};

fn is_ramimage() bool {
Expand Down Expand Up @@ -731,6 +918,8 @@ const core = blk: {
};
};

const cpu_flags: shared.CpuFlags = core.cpu_flags;

pub const utils = switch (cortex_m) {
.cortex_m7 => @import("cortex_m/m7_utils.zig"),
else => void{},
Expand Down
3 changes: 2 additions & 1 deletion core/src/cpus/cortex_m/m0.zig
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
const microzig = @import("microzig");
const mmio = microzig.mmio;
const shared = @import("shared_types.zig");

pub const CPU_Options = struct {};
pub const CPU_Options = shared.Basic_Options;

pub const scb_base_offset = 0x0d00;

Expand Down
9 changes: 3 additions & 6 deletions core/src/cpus/cortex_m/m0plus.zig
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
const microzig = @import("microzig");
const mmio = microzig.mmio;

pub const CPU_Options = struct {
/// When true, interrupt vectors are moved to RAM so handlers can be set at runtime.
ram_vectors: bool = false,
/// When true, the RAM vectors are placed in section `ram_vectors`.
has_ram_vectors_section: bool = false,
};
const shared = @import("shared_types.zig");

pub const CPU_Options = shared.options.Ram_Vector_Options;

pub const scb_base_offset = 0x0d00;

Expand Down
9 changes: 3 additions & 6 deletions core/src/cpus/cortex_m/m3.zig
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
const microzig = @import("microzig");
const mmio = microzig.mmio;

pub const CPU_Options = struct {
/// When true, interrupt vectors are moved to RAM so handlers can be set at runtime.
ram_vectors: bool = false,
/// When true, the RAM vectors are placed in section `ram_vectors`.
has_ram_vectors_section: bool = false,
};
const shared = @import("shared_types.zig");

pub const CPU_Options = shared.options.Ram_Vector_Options;

pub const scb_base_offset = 0x0d00;

Expand Down
22 changes: 12 additions & 10 deletions core/src/cpus/cortex_m/m33.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
//! - ARM Cortex-M33 Reference: https://developer.arm.com/documentation/100230/latest/
const microzig = @import("microzig");
const mmio = microzig.mmio;
const shared = @import("shared_types.zig");

pub const CPU_Options = struct {
/// When true, interrupt vectors are moved to RAM so handlers can be set at runtime.
ram_vectors: bool = false,
/// When true, the RAM vectors are placed in section `ram_vectors`.
has_ram_vectors_section: bool = false,
pub const cpu_flags: shared.CpuFlags = .{
.has_bus_fault = true,
.has_mem_manage_fault = true,
.has_usage_fault = true,
};

pub const CPU_Options = shared.options.Ram_Vector_Options;

pub const scb_base_offset = 0x0cfc;

pub const SystemControlBlock = extern struct {
Expand Down Expand Up @@ -219,18 +221,18 @@ pub const SystemControlBlock = extern struct {
/// System Handler Priority Registers.
SHPR: [12]u8,
/// System Handler Control and State Register.
SHCSR: u32,
SHCSR: mmio.Mmio(shared.scb.SHCSR),
/// Configurable Fault Status Register.
CFSR: mmio.Mmio(packed struct(u32) {
/// MemManage Fault Register.
MMFSR: u8,
MMFSR: shared.scb.MMFSR,
/// BusFault Status Register.
BFSR: u8,
BFSR: shared.scb.BFSR,
/// Usage Fault Status Register.
UFSR: u16,
UFSR: shared.scb.UFSR,
}),
/// HardFault Status Register.
HFSR: u32,
HFSR: shared.scb.HFSR,
reserved0: u32 = 0,
/// MemManage Fault Address Register.
MMFAR: u32,
Expand Down
9 changes: 3 additions & 6 deletions core/src/cpus/cortex_m/m4.zig
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
const microzig = @import("microzig");
const mmio = microzig.mmio;

pub const CPU_Options = struct {
/// When true, interrupt vectors are moved to RAM so handlers can be set at runtime.
ram_vectors: bool = false,
/// When true, the RAM vectors are placed in section `ram_vectors`.
has_ram_vectors_section: bool = false,
};
const shared = @import("shared_types.zig");

pub const CPU_Options = shared.options.Ram_Vector_Options;

pub const scb_base_offset = 0x0d00;

Expand Down
9 changes: 3 additions & 6 deletions core/src/cpus/cortex_m/m55.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,9 @@
const microzig = @import("microzig");
const mmio = microzig.mmio;

pub const CPU_Options = struct {
/// When true, interrupt vectors are moved to RAM so handlers can be set at runtime.
ram_vectors: bool = false,
/// When true, the RAM vectors are placed in section `ram_vectors`.
has_ram_vectors_section: bool = false,
};
const shared = @import("shared_types.zig");

pub const CPU_Options = shared.options.Ram_Vector_Options;

pub const scb_base_offset = 0x0cfc;

Expand Down
9 changes: 3 additions & 6 deletions core/src/cpus/cortex_m/m7.zig
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
const microzig = @import("microzig");
const mmio = microzig.mmio;

pub const CPU_Options = struct {
/// When true, interrupt vectors are moved to RAM so handlers can be set at runtime.
ram_vectors: bool = false,
/// When true, the RAM vectors are placed in section `ram_vectors`.
has_ram_vectors_section: bool = false,
};
const shared = @import("shared_types.zig");

pub const CPU_Options = shared.options.Ram_Vector_Options;

pub const scb_base_offset = 0x0d00;

Expand Down
Loading
Loading