You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
432 lines
14 KiB
432 lines
14 KiB
/* |
|
* Copyright (C) 2014 René Kijewski <rene.kijewski@fu-berlin.de> |
|
* |
|
* This library is free software; you can redistribute it and/or |
|
* modify it under the terms of the GNU Lesser General Public |
|
* License as published by the Free Software Foundation; either |
|
* version 2.1 of the License, or (at your option) any later version. |
|
* |
|
* This library is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
* Lesser General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU Lesser General Public |
|
* License along with this library; if not, write to the Free Software |
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
|
*/ |
|
|
|
/** |
|
* @ingroup x86-irq |
|
* @{ |
|
* |
|
* @file |
|
* @brief Interrupt entry points for x86. |
|
* |
|
* @author René Kijewski <rene.kijewski@fu-berlin.de> |
|
* |
|
* @} |
|
*/ |
|
|
|
#include <stdint.h> |
|
#include <stdio.h> |
|
|
|
#include "kernel_defines.h" |
|
#include "cpu.h" |
|
#include "sched.h" |
|
#include "thread.h" |
|
#include "ucontext.h" |
|
|
|
#include "x86_interrupts.h" |
|
#include "x86_memory.h" |
|
#include "x86_ports.h" |
|
#include "x86_registers.h" |
|
#include "x86_threading.h" |
|
|
|
|
|
#define ASM_FUN_ATTRIBUTES \ |
|
__attribute__((noinline)) \ |
|
__attribute__((no_instrument_function)) \ |
|
__attribute__((optimize("Os", "omit-frame-pointer"))) |
|
|
|
static const char *const exception_name[X86_MAX_INTERRUPTS] = { |
|
[0x00] = "Divide by zero", |
|
[0x01] = "Debug", |
|
[0x02] = "Non-maskable Interrupt", |
|
[0x03] = "Breakpoint", |
|
[0x04] = "Overflow", |
|
[0x05] = "Bound range", |
|
[0x06] = "Invalid Opcode", |
|
[0x07] = "Device not available", |
|
[0x08] = "Double Fault", |
|
[0x09] = NULL, |
|
[0x0a] = NULL, |
|
[0x0b] = NULL, |
|
[0x0c] = NULL, |
|
[0x0d] = "General Protection Fault", |
|
[0x0e] = "Page Fault", |
|
[0x0f] = NULL, |
|
[0x10] = "x87 Floating-Point Exception", |
|
[0x11] = "Alignment Check", |
|
[0x12] = "Machine Check", |
|
[0x13] = NULL, |
|
[0x14] = NULL, |
|
[0x15] = NULL, |
|
[0x16] = NULL, |
|
[0x17] = NULL, |
|
[0x18] = NULL, |
|
[0x19] = NULL, |
|
[0x1a] = NULL, |
|
[0x1b] = NULL, |
|
[0x1c] = NULL, |
|
[0x1d] = NULL, |
|
[0x1e] = NULL, |
|
[0x1f] = NULL, |
|
[0x20] = "PIC PIT", |
|
[0x21] = "PIC KBC1", |
|
[0x22] = NULL, |
|
[0x23] = "PIC RS232 2/4", |
|
[0x24] = "PIC RS232 1/3", |
|
[0x25] = "PIC LPT2", |
|
[0x26] = "PIC floppy", |
|
[0x27] = "PIC LPT1", |
|
[0x28] = "PIC RTC", |
|
[0x29] = "PIC #9", |
|
[0x2a] = "PIC ATA4", |
|
[0x2b] = "PIC ATA3", |
|
[0x2c] = "PIC KBC2", |
|
[0x2d] = NULL, |
|
[0x2e] = "PIC ATA1", |
|
[0x2f] = "PIC ATA2", |
|
}; |
|
|
|
uint8_t x86_current_interrupt; |
|
unsigned long x86_current_interrupt_error_code; |
|
struct x86_pushad x86_interrupted_ctx; |
|
char x86_interrupt_handler_stack[2048]; |
|
|
|
static x86_intr_handler_t x86_registered_interrupts[X86_MAX_INTERRUPTS]; |
|
|
|
static void print_stacktrace(unsigned long bp, unsigned long ip) |
|
{ |
|
puts("<stack trace>"); |
|
printf(" %08lx\n", ip); |
|
for (unsigned max_depth = 0; max_depth < 30; ++max_depth) { |
|
uint64_t pte = x86_get_pte(bp) & x86_get_pte(bp + 4); |
|
if (!(pte & PT_P) || !(pte & PT_PWT)) { |
|
puts(" ???"); |
|
break; |
|
} |
|
|
|
unsigned long *bp_ = (void *) bp; |
|
ip = bp_[1]; |
|
if (ip == 0) { |
|
break; |
|
} |
|
printf(" %08lx\n", ip); |
|
bp = bp_[0]; |
|
if (bp == 0) { |
|
break; |
|
} |
|
} |
|
puts("</stack trace>"); |
|
} |
|
|
|
void x86_print_registers(struct x86_pushad *orig_ctx, unsigned long error_code) |
|
{ |
|
unsigned long *sp = (void *) orig_ctx->sp; /* ip, cs, flags */ |
|
printf("EAX=%08lx ECX=%08lx EDX=%08lx EBX=%08lx\n", orig_ctx->ax, orig_ctx->cx, orig_ctx->dx, orig_ctx->bx); |
|
printf("ESP=%08lx EBP=%08lx ESI=%08lx EDI=%08lx\n", orig_ctx->sp, orig_ctx->bp, orig_ctx->si, orig_ctx->di); |
|
printf("Error code=%08lx\n", error_code); |
|
printf("CR0=%08x CR2=%08x CR3=%08x CR4=%08x\n", cr0_read(), cr2_read(), cr3_read(), cr4_read()); |
|
printf("EIP=%04lx:%08lx EFLAGS=%08lx\n", sp[1], sp[0], sp[2]); |
|
|
|
print_stacktrace(orig_ctx->bp, sp[0]); |
|
} |
|
|
|
static void intr_handler_default(uint8_t intr_num, struct x86_pushad *orig_ctx, unsigned long error_code) |
|
{ |
|
printf("Unhandled interrupt 0x%02x (%s)\n", intr_num, exception_name[intr_num]); |
|
x86_print_registers(orig_ctx, error_code); |
|
puts("Halting."); |
|
x86_hlt(); |
|
} |
|
|
|
void x86_int_exit(void) NORETURN; |
|
|
|
static void continue_after_intr(void) |
|
{ |
|
ucontext_t *ctx = (ucontext_t *) sched_active_thread->sp; |
|
x86_interrupted_ctx = ctx->uc_context.registers; |
|
__asm__ volatile ( |
|
"push %0\n" /* flags */ |
|
"push $0x0008\n" /* cs */ |
|
"push %1\n" /* ip */ |
|
"jmp x86_int_exit" |
|
:: "g"(ctx->__intr.flags), "g"(ctx->__intr.ip) |
|
); |
|
__builtin_unreachable(); |
|
} |
|
|
|
static unsigned in_intr_handler = 0, old_intr; |
|
__attribute__((used)) void x86_int_handler(void) |
|
{ |
|
switch (in_intr_handler++) { |
|
case 0: |
|
break; |
|
case 1: |
|
printf("Interrupt 0x%02x (%s) while handling 0x%02x (%s)\n", |
|
x86_current_interrupt, exception_name[x86_current_interrupt], |
|
old_intr, exception_name[old_intr]); |
|
x86_print_registers(&x86_interrupted_ctx, x86_current_interrupt_error_code); |
|
puts("Halting."); |
|
x86_hlt(); |
|
default: |
|
x86_hlt(); |
|
} |
|
old_intr = x86_current_interrupt; |
|
|
|
bool old_in_isr = x86_in_isr; |
|
x86_in_isr = true; |
|
|
|
x86_intr_handler_t intr_handler = x86_registered_interrupts[x86_current_interrupt]; |
|
(intr_handler ? intr_handler : intr_handler_default)(x86_current_interrupt, &x86_interrupted_ctx, x86_current_interrupt_error_code); |
|
|
|
--in_intr_handler; |
|
|
|
unsigned long *sp = (void *) x86_interrupted_ctx.sp; /* ip, cs, flags */ |
|
if (!sched_context_switch_request || !(sp[2] & X86_IF)) { |
|
x86_in_isr = old_in_isr; |
|
return; |
|
} |
|
|
|
ucontext_t *ctx = (ucontext_t *) sched_active_thread->sp; |
|
ctx->uc_context.registers = x86_interrupted_ctx; |
|
ctx->uc_stack.ss_sp = x86_interrupt_handler_stack; |
|
ctx->uc_stack.ss_size = sizeof x86_interrupt_handler_stack; |
|
__asm__ volatile ("pushf; pop %0" : "=g"(ctx->uc_context.flags)); |
|
ctx->uc_context.ip = (void *) (uintptr_t) &continue_after_intr; |
|
ctx->__intr.ip = sp[0]; |
|
ctx->__intr.flags = sp[2]; |
|
|
|
thread_yield_higher(); |
|
__builtin_unreachable(); |
|
} |
|
|
|
__attribute__((used)) void ASM_FUN_ATTRIBUTES NORETURN x86_int_entry(void) |
|
{ |
|
__asm__ volatile ("mov %eax, (4*0 + x86_interrupted_ctx)"); |
|
__asm__ volatile ("mov %ecx, (4*1 + x86_interrupted_ctx)"); |
|
__asm__ volatile ("mov %edx, (4*2 + x86_interrupted_ctx)"); |
|
__asm__ volatile ("mov %ebx, (4*3 + x86_interrupted_ctx)"); |
|
__asm__ volatile ("mov %ebp, (4*5 + x86_interrupted_ctx)"); |
|
__asm__ volatile ("mov %esi, (4*6 + x86_interrupted_ctx)"); |
|
__asm__ volatile ("mov %edi, (4*7 + x86_interrupted_ctx)"); |
|
|
|
__asm__ volatile ("jnc 1f"); |
|
__asm__ volatile (" mov (%esp), %eax"); |
|
__asm__ volatile (" add $4, %esp"); |
|
__asm__ volatile (" jmp 2f"); |
|
__asm__ volatile ("1:"); |
|
__asm__ volatile (" xor %eax, %eax"); |
|
__asm__ volatile ("2:"); |
|
__asm__ volatile (" mov %eax, x86_current_interrupt_error_code"); |
|
|
|
__asm__ volatile ("mov %esp, (4*4 + x86_interrupted_ctx)"); |
|
__asm__ volatile ("mov %0, %%esp" :: "g"(&x86_interrupt_handler_stack[sizeof x86_interrupt_handler_stack])); |
|
__asm__ volatile ("call x86_int_handler"); |
|
__asm__ volatile ("jmp x86_int_exit"); |
|
__builtin_unreachable(); |
|
} |
|
|
|
__attribute__((used)) void ASM_FUN_ATTRIBUTES NORETURN x86_int_exit(void) |
|
{ |
|
__asm__ volatile ("mov (4*0 + x86_interrupted_ctx), %eax"); |
|
__asm__ volatile ("mov (4*1 + x86_interrupted_ctx), %ecx"); |
|
__asm__ volatile ("mov (4*2 + x86_interrupted_ctx), %edx"); |
|
__asm__ volatile ("mov (4*3 + x86_interrupted_ctx), %ebx"); |
|
__asm__ volatile ("mov (4*5 + x86_interrupted_ctx), %ebp"); |
|
__asm__ volatile ("mov (4*6 + x86_interrupted_ctx), %esi"); |
|
__asm__ volatile ("mov (4*7 + x86_interrupted_ctx), %edi"); |
|
__asm__ volatile ("mov (4*4 + x86_interrupted_ctx), %esp"); |
|
|
|
__asm__ volatile ("iret"); |
|
__builtin_unreachable(); |
|
} |
|
|
|
#define DECLARE_INT(NUM, HAS_ERROR_CODE, MNEMONIC) \ |
|
static void ASM_FUN_ATTRIBUTES NORETURN x86_int_entry_##NUM##h(void) \ |
|
{ \ |
|
__asm__ volatile ("movb %0, x86_current_interrupt" :: "n"(0x##NUM)); \ |
|
if ((HAS_ERROR_CODE)) { \ |
|
__asm__ volatile ("stc"); \ |
|
} \ |
|
else { \ |
|
__asm__ volatile ("clc"); \ |
|
}\ |
|
__asm__ volatile ("jmp x86_int_entry"); \ |
|
__builtin_unreachable(); \ |
|
} |
|
DECLARE_INT(00, 0, "#DE") |
|
DECLARE_INT(01, 0, "#DB") |
|
DECLARE_INT(02, 0, "NMI") |
|
DECLARE_INT(03, 0, "#BP") |
|
DECLARE_INT(04, 0, "#OF") |
|
DECLARE_INT(05, 0, "#BR") |
|
DECLARE_INT(06, 0, "#UD") |
|
DECLARE_INT(07, 0, "#NM") |
|
DECLARE_INT(08, 1, "#DF") |
|
DECLARE_INT(0d, 1, "#GP") |
|
DECLARE_INT(0e, 1, "#PF") |
|
DECLARE_INT(10, 0, "#MF") |
|
DECLARE_INT(11, 1, "#AC") |
|
DECLARE_INT(12, 0, "#MC") |
|
DECLARE_INT(20, 0, "PIC PIT") |
|
DECLARE_INT(21, 1, "PIC KBC1") |
|
DECLARE_INT(23, 0, "PIC RS232 2/4") |
|
DECLARE_INT(24, 0, "PIC RS232 1/3") |
|
DECLARE_INT(25, 0, "PIC LPT2") |
|
DECLARE_INT(26, 0, "PIC floppy") |
|
DECLARE_INT(27, 0, "PIC LPT1") |
|
DECLARE_INT(28, 0, "PIC RPC") |
|
DECLARE_INT(29, 0, "PIC #9") |
|
DECLARE_INT(2a, 0, "PIC ATA4") |
|
DECLARE_INT(2b, 0, "PIC ATA3") |
|
DECLARE_INT(2c, 0, "PIC KBC2") |
|
DECLARE_INT(2e, 0, "PIC ATA1") |
|
DECLARE_INT(2f, 0, "PIC ATA2") |
|
|
|
static struct idt_desc X86_IDT_ENTRIES[X86_MAX_INTERRUPTS]; |
|
static struct idtr_t idtr = { |
|
.limit = sizeof X86_IDT_ENTRIES, |
|
.base = &X86_IDT_ENTRIES[0], |
|
}; |
|
|
|
#define INTR_TEST_REG_AX (0xAA00AA00ul) |
|
#define INTR_TEST_REG_DX (0x00DD00DDul) |
|
#define INTR_TEST_REG_CX (0xC0C0C0C0ul) |
|
#define INTR_TEST_REG_BX (0x0B0B0B0Bul) |
|
#define INTR_TEST_REG_SI (0x00666600ul) |
|
#define INTR_TEST_REG_DI (0x33000033ul) |
|
|
|
static void intr_handler_test_int_bp(uint8_t intr_num, struct x86_pushad *orig_ctx, unsigned long error_code) |
|
{ |
|
(void) error_code; |
|
if (intr_num != X86_INT_BP || |
|
orig_ctx->ax != INTR_TEST_REG_AX || |
|
orig_ctx->dx != INTR_TEST_REG_DX || |
|
orig_ctx->cx != INTR_TEST_REG_CX || |
|
orig_ctx->bx != INTR_TEST_REG_BX || |
|
orig_ctx->si != INTR_TEST_REG_SI || |
|
orig_ctx->di != INTR_TEST_REG_DI |
|
) { |
|
puts("Interrupt handler test failed (int 3, capture registers)."); |
|
x86_hlt(); |
|
} |
|
orig_ctx->ax ^= -1; |
|
orig_ctx->dx ^= -2; |
|
orig_ctx->cx ^= -3; |
|
orig_ctx->bx ^= -4; |
|
orig_ctx->si ^= -5; |
|
orig_ctx->di ^= -6; |
|
} |
|
|
|
static void test_int_bp(void) |
|
{ |
|
x86_registered_interrupts[X86_INT_BP] = intr_handler_test_int_bp; |
|
|
|
unsigned long ax = INTR_TEST_REG_AX; |
|
unsigned long dx = INTR_TEST_REG_DX; |
|
unsigned long cx = INTR_TEST_REG_CX; |
|
unsigned long bx = INTR_TEST_REG_BX; |
|
unsigned long si; |
|
unsigned long di; |
|
unsigned long eflags_before, eflags_after; |
|
__asm__ volatile ( |
|
"mov %8, %%esi\n" |
|
"mov %9, %%edi\n" |
|
"pushf; pop %6\n" |
|
"int3\n" |
|
"pushf; pop %7\n" |
|
"mov %%esi, %4\n" |
|
"mov %%edi, %5\n" |
|
: "+a"(ax), "+d"(dx), "+c"(cx), "+b"(bx), "=g"(si), "=g"(di), "=g"(eflags_before), "=g"(eflags_after) |
|
: "n"(INTR_TEST_REG_SI), "n"(INTR_TEST_REG_DI) |
|
: "esi", "edi" |
|
); |
|
if (ax != (INTR_TEST_REG_AX ^ -1) || |
|
dx != (INTR_TEST_REG_DX ^ -2) || |
|
cx != (INTR_TEST_REG_CX ^ -3) || |
|
bx != (INTR_TEST_REG_BX ^ -4) || |
|
si != (INTR_TEST_REG_SI ^ -5) || |
|
di != (INTR_TEST_REG_DI ^ -6) || |
|
/* ignore EFLAGS.TF, hurts debugging */ |
|
((eflags_before != eflags_after) && ((eflags_before ^ eflags_after) != X86_TF)) |
|
) { |
|
puts("Interrupt handler test failed (int 3, return code)."); |
|
x86_hlt(); |
|
} |
|
|
|
x86_registered_interrupts[X86_INT_BP] = NULL; |
|
} |
|
|
|
static inline void __attribute__((always_inline)) set_idt_desc(void (*fun_)(void), unsigned num, unsigned pl) |
|
{ |
|
uintptr_t fun = (uintptr_t) fun_; |
|
X86_IDT_ENTRIES[num] = (struct idt_desc) { |
|
.offset_1 = fun & 0xffff, |
|
.selector = 8, |
|
.zero = 0, |
|
.type_attr = 14 | (0 << 4) | (pl << 5) | (1 << 7), |
|
.offset_2 = fun >> 16, |
|
}; |
|
} |
|
|
|
#define SET_IDT_DESC(NUM, HAS_ERROR_CODE, MNEMONIC, PL) \ |
|
(set_idt_desc(x86_int_entry_##NUM##h, 0x##NUM, PL)); |
|
|
|
static void load_interrupt_descriptor_table(void) |
|
{ |
|
SET_IDT_DESC(00, 0, "#DE", 0) |
|
SET_IDT_DESC(01, 0, "#DB", 0) |
|
SET_IDT_DESC(02, 0, "NMI", 0) |
|
SET_IDT_DESC(03, 0, "#BP", 3) |
|
SET_IDT_DESC(04, 0, "#OF", 0) |
|
SET_IDT_DESC(05, 0, "#BR", 0) |
|
SET_IDT_DESC(06, 0, "#UD", 0) |
|
SET_IDT_DESC(07, 0, "#NM", 0) |
|
SET_IDT_DESC(08, 1, "#DF", 0) |
|
SET_IDT_DESC(0d, 1, "#GP", 0) |
|
SET_IDT_DESC(0e, 1, "#PF", 0) |
|
SET_IDT_DESC(10, 0, "#MF", 0) |
|
SET_IDT_DESC(11, 1, "#AC", 0) |
|
SET_IDT_DESC(12, 0, "#MC", 0) |
|
SET_IDT_DESC(20, 0, "PIC PIT", 0) |
|
SET_IDT_DESC(21, 1, "PIC KBC1", 0) |
|
SET_IDT_DESC(23, 0, "PIC RS232 2/4", 0) |
|
SET_IDT_DESC(24, 0, "PIC RS232 1/3", 0) |
|
SET_IDT_DESC(25, 0, "PIC LPT2", 0) |
|
SET_IDT_DESC(26, 0, "PIC floppy", 0) |
|
SET_IDT_DESC(27, 0, "PIC LPT1", 0) |
|
SET_IDT_DESC(28, 0, "PIC RPC", 0) |
|
SET_IDT_DESC(29, 0, "PIC #9", 0) |
|
SET_IDT_DESC(2a, 0, "PIC ATA4", 0) |
|
SET_IDT_DESC(2b, 0, "PIC ATA3", 0) |
|
SET_IDT_DESC(2c, 0, "PIC KBC2", 0) |
|
SET_IDT_DESC(2e, 0, "PIC ATA1", 0) |
|
SET_IDT_DESC(2f, 0, "PIC ATA2", 0) |
|
|
|
__asm__ volatile ("lidt %0" :: "m"(idtr)); |
|
} |
|
|
|
void x86_init_interrupts(void) |
|
{ |
|
load_interrupt_descriptor_table(); |
|
test_int_bp(); |
|
puts("Interrupt handling initialized"); |
|
} |
|
|
|
void x86_interrupt_handler_set(unsigned num, x86_intr_handler_t handler) |
|
{ |
|
x86_registered_interrupts[num] = handler; |
|
}
|
|
|