| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "mos/interrupt/interrupt.hpp" |
| 4 | #include "mos/ksyscall_entry.hpp" |
| 5 | #include "mos/misc/panic.hpp" |
| 6 | #include "mos/misc/profiling.hpp" |
| 7 | #include "mos/tasks/signal.hpp" |
| 8 | |
| 9 | #include <mos/interrupt/ipi.hpp> |
| 10 | #include <mos/lib/structures/list.hpp> |
| 11 | #include <mos/mm/cow.hpp> |
| 12 | #include <mos/platform/platform.hpp> |
| 13 | #include <mos/syscall/dispatcher.h> |
| 14 | #include <mos/syslog/printk.hpp> |
| 15 | #include <mos/x86/cpu/cpu.hpp> |
| 16 | #include <mos/x86/devices/port.hpp> |
| 17 | #include <mos/x86/interrupt/apic.hpp> |
| 18 | #include <mos/x86/tasks/context.hpp> |
| 19 | #include <mos/x86/x86_interrupt.hpp> |
| 20 | #include <mos/x86/x86_platform.hpp> |
| 21 | #include <mos_stdio.hpp> |
| 22 | #include <mos_stdlib.hpp> |
| 23 | |
| 24 | static const char *const x86_exception_names[EXCEPTION_COUNT] = { |
| 25 | "Divide-By-Zero Error" , |
| 26 | "Debug" , |
| 27 | "Non-Maskable Interrupt" , |
| 28 | "Breakpoint" , |
| 29 | "Overflow" , |
| 30 | "Bound Range Exceeded" , |
| 31 | "Invalid Opcode" , |
| 32 | "Device Not Available" , |
| 33 | "Double Fault" , |
| 34 | "Coprocessor Segment Overrun" , |
| 35 | "Invalid TSS" , |
| 36 | "Segment Not Present" , |
| 37 | "Stack Segment Fault" , |
| 38 | "General Protection Fault" , |
| 39 | "Page Fault" , |
| 40 | "Reserved" , |
| 41 | "x87 Floating-Point Error" , |
| 42 | "Alignment Check" , |
| 43 | "Machine Check" , |
| 44 | "SIMD Floating-Point Exception" , |
| 45 | "Virtualization Exception" , |
| 46 | "Control Protection Exception" , |
| 47 | "Reserved" , |
| 48 | "Reserved" , |
| 49 | "Reserved" , |
| 50 | "Reserved" , |
| 51 | "Reserved" , |
| 52 | "Reserved" , |
| 53 | "Hypervisor Injection Exception" , |
| 54 | "VMM Communication Exception" , |
| 55 | "Security Exception" , |
| 56 | "Reserved" , |
| 57 | }; |
| 58 | |
| 59 | static void x86_handle_nmi(const platform_regs_t *regs) |
| 60 | { |
| 61 | pr_emph("cpu %d: NMI received" , lapic_get_id()); |
| 62 | |
| 63 | u8 scp1 = port_inb(port: 0x92); |
| 64 | u8 scp2 = port_inb(port: 0x61); |
| 65 | |
| 66 | static const char *const scp1_names[] = { "Alternate Hot Reset" , "Alternate A20 Gate" , "[RESERVED]" , "Security Lock" , |
| 67 | "Watchdog Timer" , "[RESERVED]" , "HDD 2 Activity" , "HDD 1 Activity" }; |
| 68 | |
| 69 | static const char *const scp2_names[] = { "Timer 2 Tied to Speaker" , "Speaker Data Enable" , "Parity Check Enable" , "Channel Check Enable" , |
| 70 | "Refresh Request" , "Timer 2 Output" , "Channel Check" , "Parity Check" }; |
| 71 | |
| 72 | for (int bit = 0; bit < 8; bit++) |
| 73 | if (scp1 & (1 << bit)) |
| 74 | pr_emph(" %s" , scp1_names[bit]); |
| 75 | |
| 76 | for (int bit = 0; bit < 8; bit++) |
| 77 | if (scp2 & (1 << bit)) |
| 78 | pr_emph(" %s" , scp2_names[bit]); |
| 79 | |
| 80 | platform_dump_regs(regs); |
| 81 | mos_panic("NMI received" ); |
| 82 | } |
| 83 | |
| 84 | static void x86_handle_exception(const platform_regs_t *regs) |
| 85 | { |
| 86 | MOS_ASSERT(regs->interrupt_number < EXCEPTION_COUNT); |
| 87 | |
| 88 | const char *name = x86_exception_names[regs->interrupt_number]; |
| 89 | const char *intr_type = "" ; |
| 90 | |
| 91 | // Faults: These can be corrected and the program may continue as if nothing happened. |
| 92 | // Traps: Traps are reported immediately after the execution of the trapping instruction. |
| 93 | // Aborts: Some severe unrecoverable error. |
| 94 | switch ((x86_exception_enum_t) regs->interrupt_number) |
| 95 | { |
| 96 | case EXCEPTION_NMI: |
| 97 | { |
| 98 | x86_handle_nmi(regs); |
| 99 | break; |
| 100 | } |
| 101 | case EXCEPTION_DEBUG: |
| 102 | { |
| 103 | ptr_t drx[6]; // DR0, DR1, DR2, DR3 and DR6, DR7 |
| 104 | __asm__ volatile("mov %%dr0, %0\n" |
| 105 | "mov %%dr1, %1\n" |
| 106 | "mov %%dr2, %2\n" |
| 107 | "mov %%dr3, %3\n" |
| 108 | "mov %%dr6, %4\n" |
| 109 | "mov %%dr7, %5\n" |
| 110 | : "=r" (drx[0]), "=r" (drx[1]), "=r" (drx[2]), "=r" (drx[3]), "=r" (drx[4]), "=r" (drx[5])); |
| 111 | |
| 112 | pr_emerg("cpu %d: %s (%lu) at " PTR_FMT " (DR0: " PTR_FMT " DR1: " PTR_FMT " DR2: " PTR_FMT " DR3: " PTR_FMT " DR6: " PTR_FMT " DR7: " PTR_FMT ")" , |
| 113 | lapic_get_id(), name, regs->interrupt_number, regs->ip, drx[0], drx[1], drx[2], drx[3], drx[4], drx[5]); |
| 114 | |
| 115 | return; |
| 116 | } |
| 117 | case EXCEPTION_INVALID_OPCODE: |
| 118 | { |
| 119 | intr_type = "fault" ; |
| 120 | |
| 121 | if (MOS_IN_RANGE(regs->ip, (ptr_t) __MOS_KERNEL_CODE_START, (ptr_t) __MOS_KERNEL_CODE_END)) |
| 122 | { |
| 123 | // kernel mode invalid opcode, search for a bug entry |
| 124 | try_handle_kernel_panics(ip: regs->ip); |
| 125 | mos_panic("Invalid opcode in kernel mode" ); |
| 126 | } |
| 127 | break; |
| 128 | } |
| 129 | case EXCEPTION_DIVIDE_ERROR: |
| 130 | case EXCEPTION_OVERFLOW: |
| 131 | case EXCEPTION_BOUND_RANGE_EXCEEDED: |
| 132 | case EXCEPTION_DEVICE_NOT_AVAILABLE: |
| 133 | case EXCEPTION_COPROCESSOR_SEGMENT_OVERRUN: |
| 134 | case EXCEPTION_INVALID_TSS: |
| 135 | case EXCEPTION_SEGMENT_NOT_PRESENT: |
| 136 | case EXCEPTION_STACK_SEGMENT_FAULT: |
| 137 | case EXCEPTION_GENERAL_PROTECTION_FAULT: |
| 138 | case EXCEPTION_FPU_ERROR: |
| 139 | case EXCEPTION_ALIGNMENT_CHECK: |
| 140 | case EXCEPTION_SIMD_ERROR: |
| 141 | case EXCEPTION_VIRTUALIZATION_EXCEPTION: |
| 142 | case EXCEPTION_CONTROL_PROTECTION_EXCEPTION: |
| 143 | case EXCEPTION_HYPERVISOR_EXCEPTION: |
| 144 | case EXCEPTION_VMM_COMMUNICATION_EXCEPTION: |
| 145 | case EXCEPTION_SECURITY_EXCEPTION: |
| 146 | { |
| 147 | intr_type = "fault" ; |
| 148 | break; |
| 149 | } |
| 150 | |
| 151 | case EXCEPTION_BREAKPOINT: |
| 152 | { |
| 153 | mos_warn("Breakpoint not handled." ); |
| 154 | return; |
| 155 | } |
| 156 | |
| 157 | case EXCEPTION_PAGE_FAULT: |
| 158 | { |
| 159 | intr_type = "page fault" ; |
| 160 | |
| 161 | pagefault_t info = { |
| 162 | .is_present = (regs->error_code & 0x1) != 0, |
| 163 | .is_write = (regs->error_code & 0x2) != 0, |
| 164 | .is_user = (regs->error_code & 0x4) != 0, |
| 165 | .is_exec = (regs->error_code & 0x10) != 0, |
| 166 | .ip = regs->ip, |
| 167 | .regs = regs, |
| 168 | .backing_page = nullptr, |
| 169 | }; |
| 170 | |
| 171 | mm_handle_fault(x86_cpu_get_cr2(), info: &info); |
| 172 | goto done; |
| 173 | } |
| 174 | |
| 175 | case EXCEPTION_DOUBLE_FAULT: |
| 176 | case EXCEPTION_MACHINE_CHECK: |
| 177 | { |
| 178 | intr_type = "abort" ; |
| 179 | break; |
| 180 | } |
| 181 | case EXCEPTION_MAX: |
| 182 | case EXCEPTION_COUNT: |
| 183 | { |
| 184 | MOS_UNREACHABLE(); |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | if (current_thread) |
| 189 | { |
| 190 | pr_emerg("cpu %d: %s (%lu) at " PTR_FMT " (error code %lu)" , lapic_get_id(), name, regs->interrupt_number, regs->ip, regs->error_code); |
| 191 | signal_send_to_thread(current_thread, SIGKILL); |
| 192 | platform_dump_regs(regs); |
| 193 | platform_dump_current_stack(); |
| 194 | platform_dump_stack(regs); |
| 195 | } |
| 196 | else |
| 197 | { |
| 198 | platform_dump_regs(regs); |
| 199 | mos_panic("x86 %s:\nInterrupt #%lu ('%s', error code %lu)" , intr_type, regs->interrupt_number, name, regs->error_code); |
| 200 | } |
| 201 | |
| 202 | done: |
| 203 | return; |
| 204 | } |
| 205 | |
| 206 | static void x86_handle_irq(platform_regs_t *frame) |
| 207 | { |
| 208 | lapic_eoi(); |
| 209 | const int irq = frame->interrupt_number - IRQ_BASE; |
| 210 | interrupt_entry(irq); |
| 211 | } |
| 212 | |
| 213 | extern "C" platform_regs_t *x86_interrupt_entry(ptr_t rsp) |
| 214 | { |
| 215 | platform_regs_t *frame = (platform_regs_t *) rsp; |
| 216 | current_cpu->interrupt_regs = frame; |
| 217 | |
| 218 | reg_t syscall_ret = 0, syscall_nr = 0; |
| 219 | |
| 220 | const pf_point_t ev = profile_enter(); |
| 221 | |
| 222 | if (frame->interrupt_number < IRQ_BASE) |
| 223 | x86_handle_exception(regs: frame); |
| 224 | else if (frame->interrupt_number >= IRQ_BASE && frame->interrupt_number < IRQ_BASE + IRQ_MAX) |
| 225 | x86_handle_irq(frame); |
| 226 | else if (frame->interrupt_number >= IPI_BASE && frame->interrupt_number < IPI_BASE + IPI_TYPE_MAX) |
| 227 | ipi_do_handle(type: (ipi_type_t) (frame->interrupt_number - IPI_BASE)), lapic_eoi(); |
| 228 | else if (frame->interrupt_number == MOS_SYSCALL_INTR) |
| 229 | syscall_nr = frame->ax, syscall_ret = ksyscall_enter(number: frame->ax, arg1: frame->bx, arg2: frame->cx, arg3: frame->dx, arg4: frame->si, arg5: frame->di, arg6: frame->r9); |
| 230 | else |
| 231 | pr_warn("Unknown interrupt number: %lu" , frame->interrupt_number); |
| 232 | |
| 233 | profile_leave(ev, "x86.int.%lu" , frame->interrupt_number); |
| 234 | |
| 235 | if (unlikely(!current_thread)) |
| 236 | return frame; |
| 237 | |
| 238 | // jump to signal handler if there is a pending signal, and if we are coming from userspace |
| 239 | if (frame->cs & 0x3) |
| 240 | { |
| 241 | ptr<platform_regs_t> syscall_ret_regs; |
| 242 | if (frame->interrupt_number == MOS_SYSCALL_INTR) |
| 243 | syscall_ret_regs = signal_exit_to_user_prepare(regs: frame, syscall_nr, syscall_ret); |
| 244 | else |
| 245 | syscall_ret_regs = signal_exit_to_user_prepare(regs: frame); |
| 246 | |
| 247 | if (syscall_ret_regs) |
| 248 | *frame = *syscall_ret_regs; |
| 249 | } |
| 250 | |
| 251 | return frame; |
| 252 | } |
| 253 | |