Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interrupt refactor #369

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
112 changes: 62 additions & 50 deletions core/src/cpus/cortex_m.zig
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
const std = @import("std");
const microzig = @import("microzig");
const mmio = microzig.mmio;
const app = microzig.app;
const root = @import("root");

pub const Interrupt = microzig.utilities.GenerateInterruptEnum();
pub const InterruptOptions = microzig.utilities.GenerateInterruptOptions(fn () callconv(.C) void, .{Interrupt});
const interrupts: InterruptOptions = if (@hasDecl(app, "interrupts")) app.interrupts else .{};

pub fn executing_isr() bool {
return peripherals.scb.ICSR.read().VECTACTIVE != 0;
}
Expand All @@ -15,6 +20,42 @@ pub fn disable_interrupts() void {
asm volatile ("cpsid i");
}

pub fn enable(comptime interrupt: anytype) void {
const interrupt_name = @tagName(interrupt);
if (@hasField(Interrupt, interrupt_name)) {
const num = @intFromEnum(@field(Interrupt, interrupt_name));
if (num >= 0) {
peripherals.nvic.unmask(num);
} else {
@compileError("can't enable exception: " ++ interrupt_name);
}
} else {
@compileError("interrupt not found: " ++ interrupt_name);
}
}

pub fn disable(comptime interrupt: anytype) void {
const interrupt_name = @tagName(interrupt);
if (@hasField(Interrupt, interrupt_name)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

could probably abstract these checks out to some sort of 'interrupt is valid' comptime function to reduce duplicated logic.

const num = @intFromEnum(@field(Interrupt, interrupt_name));
if (num >= 0) {
peripherals.nvic.mask(num);
} else {
@compileError("can't disable exception: " ++ interrupt_name);
}
} else {
@compileError("interrupt not found: " ++ interrupt_name);
}
}

pub fn globally_enabled() bool {
var mrs: u32 = undefined;
asm volatile ("mrs %[mrs], 16"
: [mrs] "+r" (mrs),
);
return mrs & 0x1 == 0;
}

pub fn enable_fault_irq() void {
asm volatile ("cpsie f");
}
Expand Down Expand Up @@ -82,63 +123,34 @@ pub const startup_logic = struct {

microzig_main();
}
};

pub fn export_startup_logic() void {
@export(startup_logic._start, .{
.name = "_start",
});
}
const VectorTable = microzig.chip.VectorTable;

fn is_valid_field(field_name: []const u8) bool {
return !std.mem.startsWith(u8, field_name, "reserved") and
!std.mem.eql(u8, field_name, "initial_stack_pointer") and
!std.mem.eql(u8, field_name, "reset");
}
// will be imported by microzig.zig to allow system startup.
pub const _vector_table: VectorTable = blk: {
var tmp: VectorTable = .{
.initial_stack_pointer = microzig.config.end_of_stack,
.Reset = microzig.cpu.startup_logic._start,
};

const VectorTable = microzig.chip.VectorTable;
for (@typeInfo(@TypeOf(app.interrupts)).Struct.fields) |field| {
@field(tmp, field.name) = @field(app.interrupts, field.name);
}

// will be imported by microzig.zig to allow system startup.
pub const vector_table: VectorTable = blk: {
var tmp: VectorTable = .{
.initial_stack_pointer = microzig.config.end_of_stack,
.Reset = .{ .C = microzig.cpu.startup_logic._start },
break :blk tmp;
};

if (@hasDecl(root, "microzig_options")) {
for (@typeInfo(root.VectorTableOptions).Struct.fields) |field|
@field(tmp, field.name) = @field(root.microzig_options.interrupts, field.name);
}

break :blk tmp;
};

fn create_interrupt_vector(
comptime function: anytype,
) microzig.interrupt.Handler {
const calling_convention = @typeInfo(@TypeOf(function)).Fn.calling_convention;
return switch (calling_convention) {
.C => .{ .C = function },
.Naked => .{ .Naked = function },
// for unspecified calling convention we are going to generate small wrapper
.Unspecified => .{
.C = struct {
fn wrapper() callconv(.C) void {
if (calling_convention == .Unspecified) // TODO: workaround for some weird stage1 bug
@call(.always_inline, function, .{});
}
}.wrapper,
},

else => |val| {
const conv_name = inline for (std.meta.fields(std.builtin.CallingConvention)) |field| {
if (val == @field(std.builtin.CallingConvention, field.name))
break field.name;
} else unreachable;

@compileError("unsupported calling convention for interrupt vector: " ++ conv_name);
},
};
pub fn export_startup_logic() void {
@export(startup_logic._start, .{
.name = "_start",
});

@export(startup_logic._vector_table, .{
.name = "_vector_table",
.section = "microzig_flash_start",
.linkage = .strong,
});
}

const scs_base = 0xE000E000;
Expand Down
8 changes: 8 additions & 0 deletions core/src/cpus/cortex_m/m0.zig
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,12 @@ pub const NestedVectorInterruptController = extern struct {
/// field, bits [5:0] read as zero and ignore writes. This means writing 255 to a priority
/// register saves value 192 to the register.
IPR: [8]u32,

pub fn unmask(nvic: *volatile NestedVectorInterruptController, num: comptime_int) void {
nvic.ISER |= 1 << num;
}

pub fn mask(nvic: *volatile NestedVectorInterruptController, num: comptime_int) void {
nvic.ISER &= !(1 << num);
}
};
12 changes: 12 additions & 0 deletions core/src/cpus/cortex_m/m33.zig
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,18 @@ pub const NestedVectorInterruptController = extern struct {
reserved6: [584]u32,
/// Software Trigger Interrupt Register.
STIR: u32,

pub fn unmask(nvic: *volatile NestedVectorInterruptController, num: comptime_int) void {
const bank = num / 32;
const index = num % 32;
nvic.ISER[bank] |= 1 << index;
}

pub fn mask(nvic: *volatile NestedVectorInterruptController, num: comptime_int) void {
const bank = num / 32;
const index = num % 32;
nvic.ISER[bank] &= !(1 << index);
}
};

pub const SecurityAttributionUnit = extern struct {
Expand Down
50 changes: 20 additions & 30 deletions core/src/interrupt.zig
Original file line number Diff line number Diff line change
@@ -1,40 +1,38 @@
const std = @import("std");
const micro = @import("microzig.zig");
const microzig = @import("microzig.zig");

/// Unmasks the given interrupt and enables its execution.
/// Note that interrupts must be globally enabled with `sei()` as well.
pub fn enable(comptime interrupt: anytype) void {
_ = interrupt;
@compileError("not implemented yet!");
/// Note that interrupts must be globally enabled with `enable_interrupts()` as well.
pub inline fn enable(comptime interrupt: anytype) void {
microzig.cpu.enable(interrupt);
}

/// Masks the given interrupt and disables its execution.
pub fn disable(comptime interrupt: anytype) void {
_ = interrupt;
@compileError("not implemented yet!");
pub inline fn disable(comptime interrupt: anytype) void {
microzig.cpu.disable(interrupt);
}

/// Returns true when the given interrupt is unmasked.
pub fn is_enabled(comptime interrupt: anytype) bool {
pub inline fn is_enabled(comptime interrupt: anytype) bool {
_ = interrupt;
@compileError("not implemented yet!");
}

/// *Set Enable Interrupt*, will enable IRQs globally, but keep the masking done via
/// *Set Enable Interrupt*, will enable interrupts globally, but keep the masking done via
/// `enable` and `disable` intact.
pub fn enable_interrupts() void {
micro.cpu.enable_interrupts();
pub inline fn enable_interrupts() void {
microzig.cpu.enable_interrupts();
}

/// *Clear Enable Interrupt*, will disable IRQs globally, but keep the masking done via
/// *Clear Enable Interrupt*, will disable interrupts globally, but keep the masking done via
/// `enable` and `disable` intact.
pub fn disable_interrupts() void {
micro.cpu.disable_interrupts();
pub inline fn disable_interrupts() void {
microzig.cpu.disable_interrupts();
}

/// Returns true, when interrupts are globally enabled via `sei()`.
pub fn globally_enabled() bool {
@compileError("not implemented yet!");
pub inline fn globally_enabled() bool {
return microzig.cpu.globally_enabled();
}

/// Enters a critical section and disables interrupts globally.
Expand All @@ -60,17 +58,9 @@ const CriticalSection = struct {
}
};

// TODO: update with arch specifics
pub const Handler = extern union {
C: *const fn () callconv(.C) void,
Naked: *const fn () callconv(.Naked) void,
// Interrupt is not supported on arm
};
// TODO: remove this once the vector table uses it's own implementation
Copy link
Contributor

Choose a reason for hiding this comment

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

typo it's -> its

pub const Handler = *const fn () callconv(.C) void;

pub const unhandled = Handler{
.C = struct {
fn tmp() callconv(.C) noreturn {
@panic("unhandled interrupt");
}
}.tmp,
};
pub fn unhandled() callconv(.C) void {
@panic("unhandled interrupt");
}
2 changes: 1 addition & 1 deletion core/src/mmio.zig
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub fn Mmio(comptime PackedT: type) type {
addr.write_raw(@bitCast(val));
}

pub fn write_raw(addr: *volatile Self, val: IntT) void {
pub inline fn write_raw(addr: *volatile Self, val: IntT) void {
addr.raw = val;
}

Expand Down
53 changes: 0 additions & 53 deletions core/src/start.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,7 @@ const app = @import("app");
// Use microzig panic handler if not defined by an application
pub const panic = if (!@hasDecl(app, "panic")) microzig.panic else app.panic;

pub const VectorTableOptions = if (@hasDecl(microzig.chip, "VectorTable")) blk: {
const VectorTable = microzig.chip.VectorTable;
const fields_with_default = fields_with_default: {
var count = 0;
for (@typeInfo(VectorTable).Struct.fields) |field| {
if (field.default_value != null)
count += 1;
}

break :fields_with_default count;
};

var fields: [fields_with_default]std.builtin.Type.StructField = undefined;
var idx = 0;
for (@typeInfo(VectorTable).Struct.fields) |field| {
if (field.default_value == null)
continue;

fields[idx] = field;
idx += 1;
}

break :blk @Type(.{
.Struct = .{
.fields = &fields,
.layout = .auto,
.decls = &.{},
.is_tuple = false,
},
});
} else struct {};

pub const Options = struct {
interrupts: VectorTableOptions = .{},
log_level: std.log.Level = std.log.default_level,
log_scope_levels: []const std.log.ScopeLevel = &.{},
logFn: fn (
Expand Down Expand Up @@ -82,26 +49,6 @@ comptime {
// can just index flash, while harvard or flash-less architectures need
// to copy .rodata into RAM).
microzig.cpu.export_startup_logic();

// Export the vector table to flash start if we have any.
// For a lot of systems, the vector table provides a reset vector
// that is either called (Cortex-M) or executed (AVR) when initalized.

// Allow board and chip to override CPU vector table.
const export_opts = .{
.name = "vector_table",
.section = "microzig_flash_start",
.linkage = .strong,
};

if ((microzig.board != void and @hasDecl(microzig.board, "vector_table")))
@export(microzig.board.vector_table, export_opts)
else if (@hasDecl(microzig.chip, "vector_table"))
@export(microzig.chip.vector_table, export_opts)
else if (@hasDecl(microzig.cpu, "vector_table"))
@export(microzig.cpu.vector_table, export_opts)
else if (@hasDecl(app, "interrupts"))
@compileError("interrupts not configured");
}

/// This is the logical entry point for microzig.
Expand Down
55 changes: 55 additions & 0 deletions core/src/utilities.zig
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const std = @import("std");
const microzig = @import("microzig.zig");

/// A helper class that allows operating on a slice of slices
/// with similar operations to those of a slice.
Expand Down Expand Up @@ -188,6 +189,60 @@ pub fn Slice_Vector(comptime Slice: type) type {
};
}

pub fn max_enum_tag(T: type) @typeInfo(T).Enum.tag_type {
var max_tag: @typeInfo(T).Enum.tag_type = 0;
for (@typeInfo(T).Enum.fields) |field| {
if (field.value > max_tag) {
max_tag = field.value;
}
}
return max_tag;
}

pub fn GenerateInterruptEnum() type {
var fields: [microzig.chip.INTERRUPTS.len]std.builtin.Type.EnumField = undefined;

for (&fields, microzig.chip.INTERRUPTS) |*field, interrupt| {
field.* = .{
.name = interrupt.name,
.value = interrupt.index,
};
}

return @Type(.{ .Enum = .{
.tag_type = i16,
.fields = &fields,
.decls = &.{},
.is_exhaustive = true,
} });
}

pub fn GenerateInterruptOptions(HandlerType: type, sources: anytype) type {
const sources_type_info = @typeInfo(@TypeOf(sources)).Struct;
var ret_fields: []const std.builtin.Type.StructField = &.{};

for (sources_type_info.fields) |sources_field| {
for (@typeInfo(@field(sources, sources_field.name)).Enum.fields) |enum_field| {
ret_fields = ret_fields ++ .{.{
.name = enum_field.name,
.type = ?HandlerType,
.default_value = @as(*const anyopaque, @ptrCast(&@as(?HandlerType, null))),
.is_comptime = false,
.alignment = @alignOf(?HandlerType),
}};
}
}

return @Type(.{
.Struct = .{
.layout = .auto,
.fields = ret_fields,
.decls = &.{},
.is_tuple = false,
},
});
}

test Slice_Vector {
const vec = Slice_Vector([]const u8).init(&.{
"Hello,",
Expand Down
Loading
Loading