1 | // SPDX-License-Identifier: GPL-3.0-or-later |
2 | |
3 | #include "mos/interrupt/interrupt.h" |
4 | #include "mos/ksyscall_entry.h" |
5 | #include "mos/misc/panic.h" |
6 | #include "mos/misc/profiling.h" |
7 | #include "mos/tasks/signal.h" |
8 | |
9 | #include <mos/interrupt/ipi.h> |
10 | #include <mos/lib/structures/list.h> |
11 | #include <mos/mm/cow.h> |
12 | #include <mos/platform/platform.h> |
13 | #include <mos/syscall/dispatcher.h> |
14 | #include <mos/syslog/printk.h> |
15 | #include <mos/x86/cpu/cpu.h> |
16 | #include <mos/x86/devices/port.h> |
17 | #include <mos/x86/interrupt/apic.h> |
18 | #include <mos/x86/tasks/context.h> |
19 | #include <mos/x86/x86_interrupt.h> |
20 | #include <mos/x86/x86_platform.h> |
21 | #include <mos_stdio.h> |
22 | #include <mos_stdlib.h> |
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(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(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 | }; |
169 | |
170 | mm_handle_fault(x86_cpu_get_cr2(), info: &info); |
171 | goto done; |
172 | } |
173 | |
174 | case EXCEPTION_DOUBLE_FAULT: |
175 | case EXCEPTION_MACHINE_CHECK: |
176 | { |
177 | intr_type = "abort" ; |
178 | break; |
179 | } |
180 | case EXCEPTION_MAX: |
181 | case EXCEPTION_COUNT: |
182 | { |
183 | MOS_UNREACHABLE(); |
184 | } |
185 | } |
186 | |
187 | if (current_thread) |
188 | { |
189 | pr_emerg("cpu %d: %s (%lu) at " PTR_FMT " (error code %lu)" , lapic_get_id(), name, regs->interrupt_number, regs->ip, regs->error_code); |
190 | signal_send_to_thread(current_thread, SIGKILL); |
191 | platform_dump_regs(regs); |
192 | platform_dump_current_stack(); |
193 | platform_dump_stack(regs); |
194 | } |
195 | else |
196 | { |
197 | platform_dump_regs(regs); |
198 | mos_panic("x86 %s:\nInterrupt #%lu ('%s', error code %lu)" , intr_type, regs->interrupt_number, name, regs->error_code); |
199 | } |
200 | |
201 | done: |
202 | return; |
203 | } |
204 | |
205 | static void x86_handle_irq(platform_regs_t *frame) |
206 | { |
207 | lapic_eoi(); |
208 | const int irq = frame->interrupt_number - IRQ_BASE; |
209 | interrupt_entry(irq); |
210 | } |
211 | |
212 | void x86_interrupt_entry(ptr_t rsp) |
213 | { |
214 | platform_regs_t *frame = (platform_regs_t *) rsp; |
215 | current_cpu->interrupt_regs = frame; |
216 | |
217 | reg_t syscall_ret = 0, syscall_nr = 0; |
218 | |
219 | const pf_point_t ev = profile_enter(); |
220 | |
221 | if (frame->interrupt_number < IRQ_BASE) |
222 | x86_handle_exception(regs: frame); |
223 | else if (frame->interrupt_number >= IRQ_BASE && frame->interrupt_number < IRQ_BASE + IRQ_MAX) |
224 | x86_handle_irq(frame); |
225 | else if (frame->interrupt_number >= IPI_BASE && frame->interrupt_number < IPI_BASE + IPI_TYPE_MAX) |
226 | ipi_do_handle(type: (ipi_type_t) (frame->interrupt_number - IPI_BASE)), lapic_eoi(); |
227 | else if (frame->interrupt_number == MOS_SYSCALL_INTR) |
228 | 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); |
229 | else |
230 | pr_warn("Unknown interrupt number: %lu" , frame->interrupt_number); |
231 | |
232 | profile_leave(ev, "x86.int.%lu" , frame->interrupt_number); |
233 | |
234 | if (unlikely(!current_thread)) |
235 | { |
236 | x86_interrupt_return_impl(regs: frame); |
237 | MOS_UNREACHABLE(); |
238 | } |
239 | |
240 | // jump to signal handler if there is a pending signal, and if we are coming from userspace |
241 | if (frame->cs & 0x3) |
242 | { |
243 | if (frame->interrupt_number == MOS_SYSCALL_INTR) |
244 | signal_exit_to_user_prepare_syscall(regs: frame, syscall_nr, syscall_ret); |
245 | else |
246 | signal_exit_to_user_prepare(regs: frame); |
247 | } |
248 | |
249 | x86_interrupt_return_impl(regs: frame); |
250 | MOS_UNREACHABLE(); |
251 | } |
252 | |