sylveos

Toy Operating System
Log | Files | Refs

interrupts.zig (10599B)


      1 const uart = @import("devices/mini-uart.zig");
      2 const switching = @import("./switching.zig");
      3 const system = @import("./system.zig");
      4 const PSR = @import("./psr.zig").PSR;
      5 const mem = @import("./mem.zig");
      6 const pi = @import("./root.zig");
      7 
      8 const BASE_ADDRESS: usize = mem.BASE_ADDRESS + 0xB200;
      9 
     10 const IRQ_BASIC_PENDING: usize = BASE_ADDRESS + 0x00;
     11 const IRQ_PENDING_1: usize = BASE_ADDRESS + 0x04;
     12 const IRQ_PENDING_2: usize = BASE_ADDRESS + 0x08;
     13 
     14 const IRQ_FIQ_CONTROL: usize = BASE_ADDRESS + 0x0C;
     15 
     16 const IRQ_ENABLE_1: usize = BASE_ADDRESS + 0x10;
     17 const IRQ_ENABLE_2: usize = BASE_ADDRESS + 0x14;
     18 const IRQ_ENABLE_BASIC: usize = BASE_ADDRESS + 0x18;
     19 
     20 const IRQ_DISABLE_1: usize = BASE_ADDRESS + 0x1C;
     21 const IRQ_DISABLE_2: usize = BASE_ADDRESS + 0x20;
     22 const IRQ_DISABLE_BASIC: usize = BASE_ADDRESS + 0x24;
     23 
     24 pub inline fn enable_interrupts() void {
     25     asm volatile ("cpsie i\n");
     26 }
     27 
     28 pub inline fn disable_interrupts() void {
     29     asm volatile ("cpsid i\n");
     30 }
     31 
     32 pub inline fn clear_interrupt_flags() void {
     33     mem.put_u32(@ptrFromInt(IRQ_DISABLE_1), ~@as(u32, 0));
     34     mem.put_u32(@ptrFromInt(IRQ_DISABLE_2), ~@as(u32, 0));
     35     mem.barrier(.Write);
     36 }
     37 
     38 const BasicInterrupt = enum(u8) {
     39     Timer = 1 << 0,
     40     Mailbox = 1 << 1,
     41     Doorbell0 = 1 << 2,
     42     Doorbell1 = 1 << 3,
     43     GPU0Halted = 1 << 4,
     44     GPU1Halted = 1 << 5,
     45     AccessError1 = 1 << 6,
     46     AccessError0 = 1 << 7,
     47 };
     48 
     49 pub inline fn enable_basic_interrupt(i: BasicInterrupt) void {
     50     mem.put_u32_barrier(@ptrFromInt(IRQ_ENABLE_BASIC), @intFromEnum(i));
     51 }
     52 pub inline fn disable_basic_interrupt(i: BasicInterrupt) void {
     53     mem.put_u32_barrier(@ptrFromInt(IRQ_DISABLE_BASIC), @intFromEnum(i));
     54 }
     55 pub inline fn pending_basic(i: BasicInterrupt) bool {
     56     return (mem.get_u32_barrier(@ptrFromInt(IRQ_BASIC_PENDING)) & @intFromEnum(i)) != 0;
     57 }
     58 
     59 const PeripheralsInterrupt = enum(u6) {
     60     AuxInterrupt = 29,
     61     I2CSPISLVInterrupt = 43,
     62     PWA0 = 45,
     63     PWA1 = 46,
     64     SMI = 48,
     65     GPIO0 = 49,
     66     GPIO1 = 50,
     67     GPIO2 = 51,
     68     GPIO3 = 52,
     69     I2CInt = 53,
     70     SPIInt = 54,
     71     PCMInt = 55,
     72     UARTInt = 57,
     73 };
     74 
     75 // TODO; abstract out GPIO's get_address
     76 pub inline fn enable_peripheral_interrupt(i: PeripheralsInterrupt) void {
     77     const v = @intFromEnum(i);
     78     if (v < 32) {
     79         mem.put_u32_barrier(@ptrFromInt(IRQ_ENABLE_1), 1 << v);
     80     } else {
     81         mem.put_u32_barrier(@ptrFromInt(IRQ_ENABLE_2), 1 << (v - 32));
     82     }
     83 }
     84 pub inline fn disable_peripheral_interrupt(i: PeripheralsInterrupt) void {
     85     const v = @intFromEnum(i);
     86     if (v < 32) {
     87         mem.put_u32_barrier(@ptrFromInt(IRQ_DISABLE_1), 1 << v);
     88     } else {
     89         mem.put_u32_barrier(@ptrFromInt(IRQ_DISABLE_2), 1 << (v - 32));
     90     }
     91 }
     92 pub inline fn pending_peripheral_interrupt(i: PeripheralsInterrupt) bool {
     93     const v = @intFromEnum(i);
     94     if (v < 32) {
     95         return mem.get_u32_barrier(@ptrFromInt(IRQ_PENDING_1)) & (1 << v) != 0;
     96     } else {
     97         return mem.get_u32_barrier(@ptrFromInt(IRQ_PENDING_2)) & (1 << (v - 32)) != 0;
     98     }
     99 }
    100 
    101 fn empty(regs: Registers, vector: ExceptionVector) void {
    102     uart.print("unexpected {s}\n", .{@tagName(vector)});
    103     dump_registers(&regs);
    104 }
    105 
    106 var reset_handler: *const fn (Registers, ExceptionVector) void = empty;
    107 var undefined_instruction_handler: *const fn (Registers, ExceptionVector) void = empty;
    108 var software_interrupt_handler: *const fn (Registers, ExceptionVector) void = empty;
    109 var prefetch_abort_handler: *const fn (Registers, ExceptionVector) void = empty;
    110 var data_abort_handler: *const fn (Registers, ExceptionVector) void = empty;
    111 var interrupt_handler: *const fn (Registers, ExceptionVector) void = empty;
    112 var fast_interrupt_handler: *const fn (Registers, ExceptionVector) void = empty;
    113 
    114 pub const Registers = struct {
    115     gp: [13]usize,
    116     sp: usize,
    117     lr: usize,
    118     pc: usize,
    119     psr: PSR,
    120 };
    121 
    122 // TODO; remove
    123 pub fn dump_registers(regs: *const Registers) void {
    124     uart.print("Registers\n", .{});
    125     for (regs.gp, 0..) |r, i| {
    126         uart.print("  r{d} = 0x{X}\n", .{ i, r });
    127     }
    128     uart.print("  sp = 0x{X}\n  lr = 0x{X}\n  pc = 0x{X}\n", .{ regs.sp, regs.lr, regs.pc });
    129     uart.print("  psr = {s}\n", .{@tagName(regs.psr.mode)});
    130     uart.flush();
    131 }
    132 
    133 // For whatever godforsaken reason, inlining this function causes bad things to happen
    134 noinline fn user_get_sp_lr(sp: *u32, lr: *u32) void {
    135     asm volatile (
    136         \\ stm %[sp], {sp}^
    137         \\ stm %[lr], {lr}^
    138         :
    139         : [sp] "r" (sp),
    140           [lr] "r" (lr),
    141         : .{ .memory = true });
    142 }
    143 
    144 noinline fn privileged_get_sp_lr(sp: *u32, lr: *u32) void {
    145     const cpsr = PSR.switch_current_mode(PSR.get_s().mode);
    146     mem.barrier(.Instruction);
    147 
    148     asm volatile (
    149         \\ str sp, [%[sp]]
    150         \\ str lr, [%[lr]]
    151         :
    152         : [sp] "r" (sp),
    153           [lr] "r" (lr),
    154         : .{ .memory = true });
    155 
    156     cpsr.set_c();
    157     mem.barrier(.Instruction);
    158 }
    159 
    160 inline fn setup_reg(pc: u32, gp: *const [13]u32) Registers {
    161     // We use a copy as if editing the registers was
    162     // desired, using a restore_state would be better
    163     var view: Registers = .{ .gp = undefined, .sp = undefined, .lr = undefined, .pc = pc, .psr = PSR.get_s() };
    164 
    165     @memcpy(&view.gp, gp);
    166 
    167     if (view.psr.mode == .User or view.psr.mode == .System) {
    168         user_get_sp_lr(&view.sp, &view.lr);
    169     } else {
    170         privileged_get_sp_lr(&view.sp, &view.lr);
    171     }
    172 
    173     return view;
    174 }
    175 
    176 // I still hate this
    177 export fn reset_stub(pc: u32, gp: *const [13]u32) void {
    178     reset_handler(setup_reg(pc, gp), .Reset);
    179 }
    180 
    181 export fn undefined_instruction_stub(pc: u32, gp: *const [13]u32) void {
    182     undefined_instruction_handler(setup_reg(pc, gp), .UndefinedInstruction);
    183 }
    184 
    185 export fn software_interrupt_stub(pc: u32, gp: *const [13]u32) void {
    186     software_interrupt_handler(setup_reg(pc, gp), .SoftwareInterrupt);
    187 }
    188 
    189 export fn prefetch_abort_stub(pc: u32, gp: *const [13]u32) void {
    190     prefetch_abort_handler(setup_reg(pc, gp), .PrefetchAbort);
    191 }
    192 
    193 export fn data_abort_stub(pc: u32, gp: *const [13]u32) void {
    194     data_abort_handler(setup_reg(pc, gp), .DataAbort);
    195 }
    196 
    197 export fn interrupt_stub(pc: u32, gp: *const [13]u32) void {
    198     interrupt_handler(setup_reg(pc, gp), .IRQ);
    199 }
    200 
    201 // TODO;
    202 export fn fast_interrupt_stub(pc: u32, reg: *Registers) void {
    203     _ = pc;
    204     _ = reg;
    205 }
    206 
    207 fn create_trampoline(name: []const u8, offset: []const u8, ret: []const u8) []const u8 {
    208     return "" ++
    209         ".global " ++ name ++ "\n" ++
    210         ".type " ++ name ++ ", %function\n" ++
    211         name ++ ":\n" ++
    212         "  ldr sp, " ++ name ++ "_data\n" ++
    213         "  b " ++ name ++ "_skip\n" ++
    214         "  " ++ name ++ "_data: .word 0\n" ++
    215         "  " ++ name ++ "_skip:\n" ++
    216         "  push {r0-r12, lr}\n" ++
    217         "  sub lr, lr, #" ++ offset ++ "\n" ++
    218         "  mov r0, lr\n" ++
    219         "  mov r1, sp\n" ++
    220         "  blx " ++ name ++ "_stub\n" ++
    221         "  pop {r0-r12, lr}\n" ++ ret ++ "\n";
    222 }
    223 
    224 comptime {
    225     asm (create_trampoline("reset", "4", "")); // doesn't matter
    226     asm (create_trampoline("undefined_instruction", "4", "movs pc, lr"));
    227     asm (create_trampoline("software_interrupt", "4", "movs pc, lr"));
    228     asm (create_trampoline("prefetch_abort", "4", "subs pc, lr, #4"));
    229     asm (create_trampoline("data_abort", "8", "subs pc, lr, #8"));
    230     asm (create_trampoline("interrupt", "4", "subs pc, lr, #4"));
    231     asm (create_trampoline("fast_interrupt", "4", "subs pc, lr, #4"));
    232 }
    233 extern fn reset() void;
    234 extern fn undefined_instruction() void;
    235 extern fn software_interrupt() void;
    236 extern fn prefetch_abort() void;
    237 extern fn data_abort() void;
    238 extern fn interrupt() void;
    239 extern fn fast_interrupt() void;
    240 
    241 // https://leiradel.github.io/2019/02/09/Initialization.html
    242 pub fn setup_exception_vector(base_address: u32) void {
    243     var exception_vector = base_address;
    244 
    245     for (0..8) |_| {
    246         mem.put_u32(@ptrFromInt(exception_vector), 0xE59FF018); // ldr pc, [pc, #24]
    247         exception_vector += @sizeOf(u32);
    248     }
    249 
    250     // These need to be absolute addresses
    251     mem.put_u32(@ptrFromInt(exception_vector), @intFromPtr(&reset));
    252     exception_vector += @sizeOf(u32);
    253 
    254     mem.put_u32(@ptrFromInt(exception_vector), @intFromPtr(&undefined_instruction));
    255     exception_vector += @sizeOf(u32);
    256 
    257     mem.put_u32(@ptrFromInt(exception_vector), @intFromPtr(&software_interrupt));
    258     exception_vector += @sizeOf(u32);
    259 
    260     mem.put_u32(@ptrFromInt(exception_vector), @intFromPtr(&prefetch_abort));
    261     exception_vector += @sizeOf(u32);
    262 
    263     mem.put_u32(@ptrFromInt(exception_vector), @intFromPtr(&data_abort));
    264     exception_vector += @sizeOf(u32);
    265 
    266     mem.put_u32(@ptrFromInt(exception_vector), 0);
    267     exception_vector += @sizeOf(u32);
    268 
    269     mem.put_u32(@ptrFromInt(exception_vector), @intFromPtr(&interrupt));
    270     exception_vector += @sizeOf(u32);
    271 
    272     mem.put_u32(@ptrFromInt(exception_vector), @intFromPtr(&fast_interrupt));
    273 
    274     rewrite_stacks(pi.get_interrupt_stack_address());
    275 
    276     // We've just inserted _new_ code
    277     system.flush_self_modifying_code();
    278 }
    279 
    280 pub inline fn relocate_exception_vector(location: u32) void {
    281     asm volatile ("MCR p15, 0, %[value], c12, c0, 0"
    282         :
    283         : [value] "r" (location),
    284     );
    285 }
    286 
    287 pub inline fn rewrite_stacks(stack: u32) void {
    288     // <exception>_data is at an 8 byte offset
    289     const offset = 8;
    290 
    291     // The first instruction of each function is the mov
    292     mem.put_u32(@ptrFromInt(offset + @intFromPtr(&reset)), stack);
    293     mem.put_u32(@ptrFromInt(offset + @intFromPtr(&undefined_instruction)), stack);
    294     mem.put_u32(@ptrFromInt(offset + @intFromPtr(&software_interrupt)), stack);
    295     mem.put_u32(@ptrFromInt(offset + @intFromPtr(&prefetch_abort)), stack);
    296     mem.put_u32(@ptrFromInt(offset + @intFromPtr(&data_abort)), stack);
    297     mem.put_u32(@ptrFromInt(offset + @intFromPtr(&interrupt)), stack);
    298     mem.put_u32(@ptrFromInt(offset + @intFromPtr(&fast_interrupt)), stack);
    299 
    300     // We've just _modified_ existing code
    301     system.flush_self_modifying_code();
    302 }
    303 
    304 pub const ExceptionVector = enum {
    305     Reset,
    306     UndefinedInstruction,
    307     SoftwareInterrupt,
    308     PrefetchAbort,
    309     DataAbort,
    310     IRQ,
    311     FIQ,
    312 };
    313 
    314 pub fn set_exception_handler(vector: ExceptionVector, handler: *const fn (Registers, ExceptionVector) void) void {
    315     switch (vector) {
    316         .Reset => reset_handler = handler,
    317         .UndefinedInstruction => undefined_instruction_handler = handler,
    318         .SoftwareInterrupt => software_interrupt_handler = handler,
    319         .PrefetchAbort => prefetch_abort_handler = handler,
    320         .DataAbort => data_abort_handler = handler,
    321         .IRQ => interrupt_handler = handler,
    322         .FIQ => fast_interrupt_handler = handler,
    323     }
    324 }