From 2a27d7b3f1593a8dc33ef9b7c10158e6703a36d2 Mon Sep 17 00:00:00 2001 From: tact1m4n3 Date: Wed, 29 Jan 2025 14:56:29 +0200 Subject: [PATCH] rebase --- core/src/cpus/cortex_m.zig | 112 ++-- core/src/cpus/cortex_m/m0.zig | 8 + core/src/cpus/cortex_m/m33.zig | 12 + core/src/interrupt.zig | 50 +- core/src/mmio.zig | 2 +- core/src/start.zig | 53 -- core/src/utilities.zig | 55 ++ examples/raspberrypi/rp2xxx/build.zig | 1 + .../raspberrypi/rp2xxx/src/interrupts.zig | 67 +++ port/raspberrypi/rp2xxx/src/cpus/hazard3.zig | 500 ++++++++++++++++-- tools/regz/src/arch/arm.zig | 1 + tools/regz/src/arch/avr.zig | 1 + tools/regz/src/arch/riscv.zig | 1 + tools/regz/src/gen.zig | 50 ++ 14 files changed, 733 insertions(+), 180 deletions(-) create mode 100644 examples/raspberrypi/rp2xxx/src/interrupts.zig diff --git a/core/src/cpus/cortex_m.zig b/core/src/cpus/cortex_m.zig index 14dae0296..5e1527c44 100644 --- a/core/src/cpus/cortex_m.zig +++ b/core/src/cpus/cortex_m.zig @@ -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; } @@ -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)) { + 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"); } @@ -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; diff --git a/core/src/cpus/cortex_m/m0.zig b/core/src/cpus/cortex_m/m0.zig index e4dc1c2c6..5d77bfa92 100644 --- a/core/src/cpus/cortex_m/m0.zig +++ b/core/src/cpus/cortex_m/m0.zig @@ -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); + } }; diff --git a/core/src/cpus/cortex_m/m33.zig b/core/src/cpus/cortex_m/m33.zig index f1955b85d..75a1aa716 100644 --- a/core/src/cpus/cortex_m/m33.zig +++ b/core/src/cpus/cortex_m/m33.zig @@ -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 { diff --git a/core/src/interrupt.zig b/core/src/interrupt.zig index a675d105e..aff76ab1d 100644 --- a/core/src/interrupt.zig +++ b/core/src/interrupt.zig @@ -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. @@ -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 +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"); +} diff --git a/core/src/mmio.zig b/core/src/mmio.zig index c27df1f59..068b32432 100644 --- a/core/src/mmio.zig +++ b/core/src/mmio.zig @@ -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; } diff --git a/core/src/start.zig b/core/src/start.zig index 988f65b06..3f6dd9176 100644 --- a/core/src/start.zig +++ b/core/src/start.zig @@ -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 ( @@ -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. diff --git a/core/src/utilities.zig b/core/src/utilities.zig index f3ba89908..03312540e 100644 --- a/core/src/utilities.zig +++ b/core/src/utilities.zig @@ -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. @@ -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,", diff --git a/examples/raspberrypi/rp2xxx/build.zig b/examples/raspberrypi/rp2xxx/build.zig index b35685669..12d1702f2 100644 --- a/examples/raspberrypi/rp2xxx/build.zig +++ b/examples/raspberrypi/rp2xxx/build.zig @@ -47,6 +47,7 @@ pub fn build(b: *std.Build) void { .{ .name = "changing-system-clocks", .file = "src/changing_system_clocks.zig" }, .{ .name = "custom-clock-config", .file = "src/custom_clock_config.zig" }, .{ .name = "watchdog-timer", .file = "src/watchdog_timer.zig" }, + .{ .name = "interrupts", .file = "src/interrupts.zig" }, .{ .name = "pico_stepper", .file = "src/stepper.zig" }, .{ .name = "pico_usb-cdc", .file = "src/usb_cdc.zig" }, }; diff --git a/examples/raspberrypi/rp2xxx/src/interrupts.zig b/examples/raspberrypi/rp2xxx/src/interrupts.zig new file mode 100644 index 000000000..e8487f024 --- /dev/null +++ b/examples/raspberrypi/rp2xxx/src/interrupts.zig @@ -0,0 +1,67 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const rp2xxx = microzig.hal; +const time = rp2xxx.time; + +const led = rp2xxx.gpio.num(0); +const uart = rp2xxx.uart.instance.num(0); +const baud_rate = 115200; +const uart_tx_pin = rp2xxx.gpio.num(12); +const uart_rx_pin = rp2xxx.gpio.num(13); + +pub const microzig_options = .{ + .log_level = .debug, + .logFn = rp2xxx.uart.logFn, +}; + +pub const interrupts = .{ + .TIMER0_IRQ_0 = TIMER0_IRQ_0, +}; + +fn TIMER0_IRQ_0() callconv(.C) void { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + std.log.info("toggle led!", .{}); + led.toggle(); + + microzig.chip.peripherals.TIMER0.INTR.toggle(.{ .ALARM_0 = 0 }); + set_alarm(1_000_000); +} + +pub fn set_alarm(us: u32) void { + const Duration = microzig.drivers.time.Duration; + const current = time.get_time_since_boot(); + const target = current.add_duration(Duration.from_us(us)); + microzig.chip.peripherals.TIMER0.ALARM0.write_raw(@intCast(@intFromEnum(target) & 0xffffffff)); +} + +pub fn main() !void { + // init uart logging + inline for (&.{ uart_tx_pin, uart_rx_pin }) |pin| { + pin.set_function(.uart_first); + } + + uart.apply(.{ + .baud_rate = baud_rate, + .clock_config = rp2xxx.clock_config, + }); + + rp2xxx.uart.init_logger(uart); + + led.set_function(.sio); + led.set_direction(.out); + + set_alarm(1_000_000); + + microzig.chip.peripherals.TIMER0.INTE.toggle(.{ .ALARM_0 = 1 }); + if (rp2xxx.compatibility.arch == .riscv) { + microzig.cpu.enable(.MachineExternal); + } + microzig.cpu.enable(.TIMER0_IRQ_0); + microzig.cpu.enable_interrupts(); + + while (true) { + asm volatile ("wfi"); + } +} diff --git a/port/raspberrypi/rp2xxx/src/cpus/hazard3.zig b/port/raspberrypi/rp2xxx/src/cpus/hazard3.zig index 92c198d27..4443565aa 100644 --- a/port/raspberrypi/rp2xxx/src/cpus/hazard3.zig +++ b/port/raspberrypi/rp2xxx/src/cpus/hazard3.zig @@ -1,13 +1,78 @@ const std = @import("std"); const root = @import("root"); const microzig = @import("microzig"); +const app = microzig.app; + +pub const Exception = enum(u8) { + InstructionMisaligned = 0x0, + InstructionFault = 0x1, + IllegalInstruction = 0x2, + Breakpoint = 0x3, + LoadMisaligned = 0x4, + LoadFault = 0x5, + StoreMisaligned = 0x6, + StoreFault = 0x7, + UserEnvCall = 0x8, + MachineEnvCall = 0xb, +}; + +pub const CoreInterrupt = enum(u8) { + MachineSoftware = 0x3, + MachineTimer = 0x7, + MachineExternal = 0xb, +}; + +pub const ExternalInterrupt = microzig.utilities.GenerateInterruptEnum(); + +pub const InterruptOptions = microzig.utilities.GenerateInterruptOptions( + fn () callconv(.C) void, + .{ + enum { Exception }, + CoreInterrupt, + ExternalInterrupt, + }, +); + +const interrupts: InterruptOptions = if (@hasDecl(app, "interrupts")) app.interrupts else .{}; + +pub fn globally_enabled() bool { + return csr.mstatus.read().mie == 1; +} pub fn enable_interrupts() void { - @panic("TODO"); + csr.mstatus.set(.{ .mie = 1 }); } pub fn disable_interrupts() void { - @panic("TODO"); + csr.mstatus.clear(.{ .mie = 1 }); +} + +pub fn enable(comptime interrupt: anytype) void { + const interrupt_name = @tagName(interrupt); + if (@hasField(CoreInterrupt, interrupt_name)) { + csr.mie.set_raw(1 << @intFromEnum(@field(CoreInterrupt, interrupt_name))); + } else if (@hasField(ExternalInterrupt, interrupt_name)) { + const num: u32 = @intCast(@intFromEnum(@field(ExternalInterrupt, interrupt_name))); + const index: u32 = num / 16; + const mask: u32 = 1 << (num % 16); + csr.meiea.set_raw(index | (mask << 16)); + } else { + @compileError("can't enable interrupt: " ++ interrupt_name); + } +} + +pub fn disable(comptime interrupt: anytype) void { + const interrupt_name = @tagName(interrupt); + if (@hasField(CoreInterrupt, interrupt_name)) { + csr.mie.clear(1 << @intFromEnum(@field(CoreInterrupt, interrupt_name))); + } else if (@hasField(ExternalInterrupt, interrupt_name)) { + const num: u32 = @intCast(@intFromEnum(@field(ExternalInterrupt, interrupt_name))); + const index: u32 = num / 16; + const mask: u32 = 1 << (num % 16); + csr.meiea.clear(index | (mask << 16)); + } else { + @compileError("can't disable interrupt: " ++ @tagName(interrupt)); + } } pub fn wfi() void { @@ -37,7 +102,8 @@ pub const startup_logic = struct { ); asm volatile ( - \\la a0, vector_table + \\la a0, _vector_table + \\or a0, a0, 1 \\csrw mtvec, a0 \\ \\csrr a0, mhartid // if core 1 gets here (through a miracle), send it back to bootrom @@ -57,69 +123,411 @@ pub const startup_logic = struct { microzig_main(); } - pub fn trap_handler() callconv(.C) void { - // TODO: something useful - @panic("trap occured"); + fn max_enum_value(Enum: type) @typeInfo(Enum).Enum.tag_type { + var max_value: @typeInfo(Enum).Enum.tag_type = 0; + for (@typeInfo(Enum).Enum.fields) |field| { + max_value = @max(max_value, field.value); + } + return max_value; } -}; -pub const vector_table = wrap_trap_handler(startup_logic.trap_handler); + pub fn _vector_table() align(64) callconv(.Naked) noreturn { + asm volatile ( + \\j _exception_handler + \\.word 0 + \\.word 0 + \\j _machine_software_handler + \\.word 0 + \\.word 0 + \\.word 0 + \\j _machine_timer_handler + \\.word 0 + \\.word 0 + \\.word 0 + \\j _machine_external_handler + ); + } + + pub fn unhandled() callconv(.C) noreturn { + @panic("unhandled interrupt"); + } + + pub fn _exception_handler() callconv(.Naked) noreturn { + const handler = interrupts.Exception orelse unhandled; + @export(handler, .{ .name = "_exception_handler_c" }); + + push_interrupt_state(); + + asm volatile ("jal _exception_handler_c"); + + pop_interrupt_state(); + asm volatile ("mret"); + } + + pub fn _machine_software_handler() callconv(.Naked) noreturn { + const handler = interrupts.MachineSoftware orelse unhandled; + @export(handler, .{ .name = "_machine_software_handler_c" }); + + push_interrupt_state(); + + asm volatile ("jal _machine_software_handler_c"); + + pop_interrupt_state(); + asm volatile ("mret"); + } + + pub fn _machine_timer_handler() callconv(.Naked) noreturn { + const handler = interrupts.MachineTimer orelse unhandled; + @export(handler, .{ .name = "_machine_timer_handler_c" }); + + push_interrupt_state(); + + asm volatile ("jal _machine_timer_handler_c"); + + pop_interrupt_state(); + asm volatile ("mret"); + } + + pub fn _machine_external_handler() callconv(.Naked) noreturn { + push_interrupt_state(); + + if (interrupts.MachineExternal) |handler| { + @export(handler, .{ .name = "_machine_external_handler_c" }); + + asm volatile ("jal _machine_external_handler_c"); + } else { + _ = struct { + export const _external_interrupt_table = blk: { + const Handler = *const fn () callconv(.C) void; + const count = microzig.utilities.max_enum_tag(ExternalInterrupt); + var external_interrupt_table: [count]Handler = [1]Handler{unhandled} ** count; + + for (@typeInfo(ExternalInterrupt).Enum.fields) |field| { + if (@field(interrupts, field.name)) |handler| { + external_interrupt_table[field.value] = handler; + } + } + + break :blk external_interrupt_table; + }; + }; + + asm volatile ( + \\csrrsi a0, 0xbe4, 1 + \\bltz a0, no_more_irqs + \\ + \\dispatch_irq: + \\lui a1, %hi(_external_interrupt_table) + \\add a1, a1, a0 + \\lw a1, %lo(_external_interrupt_table)(a1) + \\jalr ra, a1 + \\ + \\get_next_irq: + \\csrr a0, 0xbe4 + \\bgez a0, dispatch_irq + \\ + \\no_more_irqs: + ); + } + + pop_interrupt_state(); + asm volatile ("mret"); + } +}; pub fn export_startup_logic() void { @export(startup_logic._start, .{ .name = "_start", + .section = "microzig_flash_start", }); - @export(startup_logic._start_c, .{ - .name = "_start_c", - }); + @export(startup_logic._start_c, .{ .name = "_start_c" }); + @export(startup_logic._vector_table, .{ .name = "_vector_table" }); + @export(startup_logic._exception_handler, .{ .name = "_exception_handler" }); + @export(startup_logic._machine_software_handler, .{ .name = "_machine_software_handler" }); + @export(startup_logic._machine_timer_handler, .{ .name = "_machine_timer_handler" }); + @export(startup_logic._machine_external_handler, .{ .name = "_machine_external_handler" }); } -pub inline fn wrap_trap_handler(inner: fn () callconv(.C) void) fn () callconv(.Naked) noreturn { - return struct { - const unique_call_inner_export_name = @typeName(@This()) ++ "_call_inner"; +const registers = [_][]const u8{ + "ra", "t0", "t1", "t2", "t3", "t4", "t5", "t6", + "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", +}; - comptime { - @export(call_inner, .{ - .name = unique_call_inner_export_name, - }); - } +// TODO: maybe this should be relocated somewhere else +inline fn push_interrupt_state() void { + asm volatile (std.fmt.comptimePrint("addi sp, sp, -{}", .{registers.len * @sizeOf(u32)})); - pub fn wrapper() callconv(.Naked) noreturn { - push_interrupt_state(); + inline for (registers, 0..) |reg, i| { + asm volatile (std.fmt.comptimePrint("sw {s}, 4*{}(sp)", .{ reg, i })); + } +} - asm volatile (std.fmt.comptimePrint("jal ra, {s}", .{unique_call_inner_export_name})); +// TODO: maybe this should be relocated somewhere else +inline fn pop_interrupt_state() void { + inline for (registers, 0..) |reg, i| { + asm volatile (std.fmt.comptimePrint("lw {s}, 4*{}(sp)", .{ reg, i })); + } - pop_interrupt_state(); + asm volatile (std.fmt.comptimePrint("addi sp, sp, {}", .{registers.len * @sizeOf(u32)})); +} - asm volatile ( - \\ mret - ); - } +pub const csr = struct { + // Machine Information Registers + pub const mvendorid = CSR(0xF11, u32); + pub const marchid = CSR(0xF12, u32); + pub const mimpid = CSR(0xF13, u32); + pub const mhartid = CSR(0xF14, u32); - const registers = [_][]const u8{ - "ra", "t0", "t1", "t2", "t3", "t4", "t5", "t6", - "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", - }; + pub const mstatus = CSR(0x300, packed struct { + reserved0: u3, + mie: u1, + reserved1: u28, + }); + pub const misa = CSR(0x301, u32); + pub const medeleg = CSR(0x302, u32); + pub const mideleg = CSR(0x303, u32); + pub const mie = CSR(0x304, u32); + pub const mtvec = CSR(0x305, u32); + pub const mcounteren = CSR(0x306, u32); + + pub const mscratch = CSR(0x340, u32); + pub const mepc = CSR(0x341, u32); + pub const mcause = CSR(0x342, u32); + pub const mtval = CSR(0x343, u32); + pub const mip = CSR(0x344, u32); + + pub const pmpcfg0 = CSR(0x3A0, u32); + pub const pmpcfg1 = CSR(0x3A1, u32); + pub const pmpcfg2 = CSR(0x3A2, u32); + pub const pmpcfg3 = CSR(0x3A3, u32); - inline fn push_interrupt_state() void { - asm volatile (std.fmt.comptimePrint("addi sp, sp, -{}", .{registers.len * @sizeOf(u32)})); + pub const pmpaddr0 = CSR(0x3B0, u32); + pub const pmpaddr1 = CSR(0x3B1, u32); + pub const pmpaddr2 = CSR(0x3B2, u32); + pub const pmpaddr3 = CSR(0x3B3, u32); + pub const pmpaddr4 = CSR(0x3B4, u32); + pub const pmpaddr5 = CSR(0x3B5, u32); + pub const pmpaddr6 = CSR(0x3B6, u32); + pub const pmpaddr7 = CSR(0x3B7, u32); + pub const pmpaddr8 = CSR(0x3B8, u32); + pub const pmpaddr9 = CSR(0x3B9, u32); + pub const pmpaddr10 = CSR(0x3BA, u32); + pub const pmpaddr11 = CSR(0x3BB, u32); + pub const pmpaddr12 = CSR(0x3BC, u32); + pub const pmpaddr13 = CSR(0x3BD, u32); + pub const pmpaddr14 = CSR(0x3BE, u32); + pub const pmpaddr15 = CSR(0x3BF, u32); - inline for (registers, 0..) |reg, i| { - asm volatile (std.fmt.comptimePrint("sw {s}, 4*{}(sp)", .{ reg, i })); + pub const mcycle = CSR(0xB00, u32); + pub const minstret = CSR(0xB02, u32); + pub const mhpmcounter3 = CSR(0xB03, u32); + pub const mhpmcounter4 = CSR(0xB04, u32); + pub const mhpmcounter5 = CSR(0xB05, u32); + pub const mhpmcounter6 = CSR(0xB06, u32); + pub const mhpmcounter7 = CSR(0xB07, u32); + pub const mhpmcounter8 = CSR(0xB08, u32); + pub const mhpmcounter9 = CSR(0xB09, u32); + pub const mhpmcounter10 = CSR(0xB0A, u32); + pub const mhpmcounter11 = CSR(0xB0B, u32); + pub const mhpmcounter12 = CSR(0xB0C, u32); + pub const mhpmcounter13 = CSR(0xB0D, u32); + pub const mhpmcounter14 = CSR(0xB0E, u32); + pub const mhpmcounter15 = CSR(0xB0F, u32); + pub const mhpmcounter16 = CSR(0xB10, u32); + pub const mhpmcounter17 = CSR(0xB11, u32); + pub const mhpmcounter18 = CSR(0xB12, u32); + pub const mhpmcounter19 = CSR(0xB13, u32); + pub const mhpmcounter20 = CSR(0xB14, u32); + pub const mhpmcounter21 = CSR(0xB15, u32); + pub const mhpmcounter22 = CSR(0xB16, u32); + pub const mhpmcounter23 = CSR(0xB17, u32); + pub const mhpmcounter24 = CSR(0xB18, u32); + pub const mhpmcounter25 = CSR(0xB19, u32); + pub const mhpmcounter26 = CSR(0xB1A, u32); + pub const mhpmcounter27 = CSR(0xB1B, u32); + pub const mhpmcounter28 = CSR(0xB1C, u32); + pub const mhpmcounter29 = CSR(0xB1D, u32); + pub const mhpmcounter30 = CSR(0xB1E, u32); + pub const mhpmcounter31 = CSR(0xB1F, u32); + pub const mcycleh = CSR(0xB80, u32); + pub const minstreth = CSR(0xB82, u32); + pub const mhpmcounter3h = CSR(0xB83, u32); + pub const mhpmcounter4h = CSR(0xB84, u32); + pub const mhpmcounter5h = CSR(0xB85, u32); + pub const mhpmcounter6h = CSR(0xB86, u32); + pub const mhpmcounter7h = CSR(0xB87, u32); + pub const mhpmcounter8h = CSR(0xB88, u32); + pub const mhpmcounter9h = CSR(0xB89, u32); + pub const mhpmcounter10h = CSR(0xB8A, u32); + pub const mhpmcounter11h = CSR(0xB8B, u32); + pub const mhpmcounter12h = CSR(0xB8C, u32); + pub const mhpmcounter13h = CSR(0xB8D, u32); + pub const mhpmcounter14h = CSR(0xB8E, u32); + pub const mhpmcounter15h = CSR(0xB8F, u32); + pub const mhpmcounter16h = CSR(0xB90, u32); + pub const mhpmcounter17h = CSR(0xB91, u32); + pub const mhpmcounter18h = CSR(0xB92, u32); + pub const mhpmcounter19h = CSR(0xB93, u32); + pub const mhpmcounter20h = CSR(0xB94, u32); + pub const mhpmcounter21h = CSR(0xB95, u32); + pub const mhpmcounter22h = CSR(0xB96, u32); + pub const mhpmcounter23h = CSR(0xB97, u32); + pub const mhpmcounter24h = CSR(0xB98, u32); + pub const mhpmcounter25h = CSR(0xB99, u32); + pub const mhpmcounter26h = CSR(0xB9A, u32); + pub const mhpmcounter27h = CSR(0xB9B, u32); + pub const mhpmcounter28h = CSR(0xB9C, u32); + pub const mhpmcounter29h = CSR(0xB9D, u32); + pub const mhpmcounter30h = CSR(0xB9E, u32); + pub const mhpmcounter31h = CSR(0xB9F, u32); + + pub const mhpmevent3 = CSR(0x323, u32); + pub const mhpmevent4 = CSR(0x324, u32); + pub const mhpmevent5 = CSR(0x325, u32); + pub const mhpmevent6 = CSR(0x326, u32); + pub const mhpmevent7 = CSR(0x327, u32); + pub const mhpmevent8 = CSR(0x328, u32); + pub const mhpmevent9 = CSR(0x329, u32); + pub const mhpmevent10 = CSR(0x32A, u32); + pub const mhpmevent11 = CSR(0x32B, u32); + pub const mhpmevent12 = CSR(0x32C, u32); + pub const mhpmevent13 = CSR(0x32D, u32); + pub const mhpmevent14 = CSR(0x32E, u32); + pub const mhpmevent15 = CSR(0x32F, u32); + pub const mhpmevent16 = CSR(0x330, u32); + pub const mhpmevent17 = CSR(0x331, u32); + pub const mhpmevent18 = CSR(0x332, u32); + pub const mhpmevent19 = CSR(0x333, u32); + pub const mhpmevent20 = CSR(0x334, u32); + pub const mhpmevent21 = CSR(0x335, u32); + pub const mhpmevent22 = CSR(0x336, u32); + pub const mhpmevent23 = CSR(0x337, u32); + pub const mhpmevent24 = CSR(0x338, u32); + pub const mhpmevent25 = CSR(0x339, u32); + pub const mhpmevent26 = CSR(0x33A, u32); + pub const mhpmevent27 = CSR(0x33B, u32); + pub const mhpmevent28 = CSR(0x33C, u32); + pub const mhpmevent29 = CSR(0x33D, u32); + pub const mhpmevent30 = CSR(0x33E, u32); + pub const mhpmevent31 = CSR(0x33F, u32); + + pub const tselect = CSR(0x7A0, u32); + pub const tdata1 = CSR(0x7A1, u32); + pub const tdata2 = CSR(0x7A2, u32); + pub const tdata3 = CSR(0x7A3, u32); + + pub const dcsr = CSR(0x7B0, u32); + pub const dpc = CSR(0x7B1, u32); + pub const dscratch = CSR(0x7B2, u32); + + // hazard3 specific + pub const meiea = CSR(0xbe0, u32); + + // TODO: maybe this should be relocated somewhere else + pub fn CSR(addr: u24, T: type) type { + const size = @bitSizeOf(T); + if (size != 32) + @compileError("size must be 32!"); + const ident = std.fmt.comptimePrint("{}", .{addr}); + + return struct { + const Self = @This(); + + pub inline fn read_raw() u32 { + var value: u32 = undefined; + asm volatile ("csrr %[value], " ++ ident + : [value] "+r" (value), + ); + return value; } - } - inline fn pop_interrupt_state() void { - inline for (registers, 0..) |reg, i| { - asm volatile (std.fmt.comptimePrint("lw {s}, 4*{}(sp)", .{ reg, i })); + pub inline fn read() T { + return @bitCast(read_raw()); } - asm volatile (std.fmt.comptimePrint("addi sp, sp, {}", .{registers.len * @sizeOf(u32)})); - } + pub inline fn write_raw(value: u32) u32 { + asm volatile ("csrw %[value], " ++ ident + : + : [value] "r" (value), + ); + } - fn call_inner() callconv(.C) void { - inner(); - } - }.wrapper; -} + pub inline fn write(value: T) void { + write_raw(@bitCast(value)); + } + + pub inline fn modify(modifier: anytype) void { + switch (@typeInfo(T)) { + .Struct => { + var value = read(); + inline for (@typeInfo(@TypeOf(modifier)).Struct.fields) |field| { + @field(value, field.name) = @field(modifier, field.name); + } + write(value); + }, + .Int => write(modifier), + else => @compileError("unsupported type"), + } + } + + pub inline fn set_raw(bits: u32) void { + asm volatile ("csrs " ++ ident ++ ", %[bits]" + : + : [bits] "r" (bits), + ); + } + + pub inline fn set(fields: anytype) void { + set_raw(get_bits(fields)); + } + + pub inline fn clear_raw(bits: u32) void { + asm volatile ("csrc " ++ ident ++ ", %[bits]" + : + : [bits] "r" (bits), + ); + } + + pub inline fn clear(fields: anytype) void { + clear_raw(get_bits(fields)); + } + + pub inline fn read_set_raw(bits: u32) u32 { + return asm volatile ("csrrs %[value], " ++ ident ++ ", %[bits]" + : [value] "r" (-> u32), + : [bits] "r" (bits), + ); + } + + pub inline fn read_set(fields: anytype) T { + return @bitCast(read_set_raw(get_bits(fields))); + } + + pub inline fn read_clear_raw(bits: u32) u32 { + return asm volatile ("csrrc %[value], " ++ ident ++ ", %[bits]" + : [value] "r" (-> u32), + : [bits] "r" (bits), + ); + } + + pub inline fn read_clear(fields: anytype) T { + return @bitCast(read_clear_raw(get_bits(fields))); + } + + inline fn get_bits(fields: anytype) u32 { + return switch (@typeInfo(T)) { + .Struct => blk: { + var bits: T = @bitCast(@as(u32, 0)); + inline for (@typeInfo(@TypeOf(fields)).Struct.fields) |field| { + @field(bits, field.name) = @field(fields, field.name); + } + break :blk @bitCast(bits); + }, + .Int => fields, + else => @compileError("unsupported type"), + }; + } + }; + } +}; diff --git a/tools/regz/src/arch/arm.zig b/tools/regz/src/arch/arm.zig index 51e9412b1..f9da40e95 100644 --- a/tools/regz/src/arch/arm.zig +++ b/tools/regz/src/arch/arm.zig @@ -101,6 +101,7 @@ pub fn write_interrupt_vector( if (interrupts.len > 0) { try writer.writeAll( + \\ \\pub const VectorTable = extern struct { \\ const Handler = micro.interrupt.Handler; \\ const unhandled = micro.interrupt.unhandled; diff --git a/tools/regz/src/arch/avr.zig b/tools/regz/src/arch/avr.zig index b936c8922..fc347c65d 100644 --- a/tools/regz/src/arch/avr.zig +++ b/tools/regz/src/arch/avr.zig @@ -22,6 +22,7 @@ pub fn write_interrupt_vector( const interrupts = try db.get_interrupts(arena, device.id); if (interrupts.len > 0) { try writer.writeAll( + \\ \\pub const VectorTable = extern struct { \\ const Handler = micro.interrupt.Handler; \\ const unhandled = micro.interrupt.unhandled; diff --git a/tools/regz/src/arch/riscv.zig b/tools/regz/src/arch/riscv.zig index df7711e8f..e76dd07de 100644 --- a/tools/regz/src/arch/riscv.zig +++ b/tools/regz/src/arch/riscv.zig @@ -19,6 +19,7 @@ pub fn write_interrupt_vector( assert(device.arch.is_riscv()); try writer.writeAll( + \\ \\pub const VectorTable = extern struct { \\ const Handler = micro.interrupt.Handler; \\ const unhandled = micro.interrupt.unhandled; diff --git a/tools/regz/src/gen.zig b/tools/regz/src/gen.zig index 360a554a2..b21c7a02e 100644 --- a/tools/regz/src/gen.zig +++ b/tools/regz/src/gen.zig @@ -45,6 +45,17 @@ pub fn to_zig(db: *Database, out_writer: anytype, opts: ToZigOptions) !void { \\ ); } + + try writer.writeAll( + \\ + \\pub const Interrupt = struct { + \\ name: [:0]const u8, + \\ index: i16, + \\ description: ?[:0]const u8, + \\}; + \\ + ); + try write_devices(db, allocator, writer); try write_types(db, allocator, writer); try writer.writeByte(0); @@ -165,6 +176,9 @@ fn write_device(db: *Database, arena: Allocator, device: *const Database.Device, try writer.writeAll("};\n"); } + write_interrupt_list(db, arena, device, writer) catch |err| + log.warn("failed to write interrupt list: {}", .{err}); + write_vector_table(db, arena, device, writer) catch |err| log.warn("failed to write vector table: {}", .{err}); @@ -259,6 +273,42 @@ fn types_reference(db: *Database, allocator: Allocator, type_id: TypeID) ![]cons return full_name.toOwnedSlice(); } +fn write_interrupt_list( + db: *Database, + arena: Allocator, + device: *const Device, + out_writer: anytype, +) !void { + var buffer = std.ArrayList(u8).init(arena); + defer buffer.deinit(); + + const writer = buffer.writer(); + + const interrupts = try db.get_interrupts(arena, device.id); + + try writer.print( + \\ + \\pub const INTERRUPTS: [{}]Interrupt = .{{ + \\ + , .{interrupts.len}); + + for (interrupts) |interrupt| { + try writer.writeAll(".{ .name = "); + try write_string(interrupt.name, writer); + try writer.print(", .index = {}, .description = ", .{interrupt.idx}); + if (interrupt.description) |description| { + try write_string(description, writer); + } else { + try writer.writeAll("null"); + } + try writer.writeAll(" },\n"); + } + + try writer.writeAll("};\n"); + + try out_writer.writeAll(buffer.items); +} + fn write_vector_table( db: *Database, arena: Allocator,