From bc8532688bdba5011c03da1051f74259616d207c Mon Sep 17 00:00:00 2001 From: tkchia Date: Fri, 30 Sep 2022 05:43:08 +0800 Subject: [PATCH] Enable CPU exception handling w/ IDT & TSS (#640) --- ape/ape.S | 25 ++++-- ape/ape.lds | 3 + ape/macros.internal.h | 23 +++++ libc/intrin/interrupts.S | 173 +++++++++++++++++++++++++++++++++++++ libc/log/die.c | 4 + libc/macros.internal.h | 9 ++ libc/runtime/pc.internal.h | 1 + 7 files changed, 231 insertions(+), 7 deletions(-) create mode 100644 libc/intrin/interrupts.S diff --git a/ape/ape.S b/ape/ape.S index aada551aa..7728a9fc2 100644 --- a/ape/ape.S +++ b/ape/ape.S @@ -1137,12 +1137,17 @@ sconf: .short 1843200/*hz*/ / 16/*wut*/ / 9600/*baud*/ .endobj sconf,global,hidden // Global Descriptor Table -// -// @note address portion only concern legacy modes .align 8 -gdt: .short 2f-1f # table byte length - .long REAL(1f),0 # table address - .zero 2 +_gdtrphy: + .short 2f-1f-1 # table byte length + .long REAL(1f) # table address (physical space) + .endobj _gdtrphy,global,hidden +_gdtr: + .short 2f-1f-1 # table byte length + .quad 1f # table address (final virtual space) + .endobj _gdtr,global,hidden + .align 8 +_gdt: 1: // ┌G:granularity (1 → limit *= 0x1000) // │┌D/B:default operation size (0 = 16|64bit, 1 = 32-bit) @@ -1172,7 +1177,9 @@ gdt: .short 2f-1f # table byte length .quad 0b0000000011001111100100100000000000000000000000001111111111111111 #32 .quad 0b0000000010101111100110110000000000000000000000001111111111111111 #40 .quad 0b0000000010101111100100110000000000000000000000001111111111111111 #48 -2: .endobj gdt,global,hidden +.tssdescstub _tss #56,64 +2: + .endobj _gdt,global,hidden /*─────────────────────────────────────────────────────────────────────────────╗ │ αcτµαlly pδrταblε εxεcµταblε § real mode │ @@ -1433,7 +1440,7 @@ golong: cli rdmsr or $EFER_LME|EFER_SCE,%eax wrmsr - lgdt REAL(gdt) + lgdt REAL(_gdtrphy) mov %cr0,%eax or $CR0_PE|CR0_PG|CR0_MP,%eax and $~CR0_EM,%eax @@ -1451,6 +1458,8 @@ long: xor %eax,%eax mov %eax,%fs mov %eax,%gs mov $0x80000,%esp + mov $GDT_LONG_TSS,%al + ltr %ax xor %r12d,%r12d xor %r13d,%r13d xor %r14d,%r14d @@ -1464,6 +1473,8 @@ long: xor %eax,%eax call __map_phdrs push $0x037f fldcw (%rsp) + lgdt _gdtr # reload GDTR for + # virtual memory space movabs $kernel,%rax jmp *%rax .endfn long diff --git a/ape/ape.lds b/ape/ape.lds index 05cee5ba1..a756c9340 100644 --- a/ape/ape.lds +++ b/ape/ape.lds @@ -572,6 +572,9 @@ HIDDEN(v_ape_realslacksectors = HIDDEN(v_ape_realpages = v_ape_realsectors / (4096 / 512)); HIDDEN(v_ape_highsectors = (ROUNDUP(RVA(_edata), 512) / 512) - v_ape_realsectors); +PROVIDE_HIDDEN(_tss = 0); +PROVIDE_HIDDEN(_tss_end = 0); +TSSDESCSTUB2(_tss, _tss, _tss_end ? _tss_end - _tss - 1 : 0); #endif /* ZIP End of Central Directory header */ diff --git a/ape/macros.internal.h b/ape/macros.internal.h index c17344116..7ba52bafa 100644 --- a/ape/macros.internal.h +++ b/ape/macros.internal.h @@ -180,6 +180,12 @@ .stub \name\()_bcs\n,long .endm +// Task State Segment Descriptor Entries. +.macro .tssdescstub name:req + .stub \name\()_desc_ent0,quad + .stub \name\()_desc_ent1,quad +.endm + /* clang-format on */ #elif defined(__LINKER__) @@ -257,5 +263,22 @@ (X) % 10 * 0x1000000 \ : 0xffffffffffffffff) +/** + * Laying out the GDT entries for a TSS for bare metal operation. + */ +#define TSSDESCSTUB2(SYM, BASE, LIM) \ + HIDDEN(SYM##_desc_ent0 = TSSDESC_ENT0(BASE, LIM)); \ + HIDDEN(SYM##_desc_ent1 = TSSDESC_ENT1(BASE)); \ + ASSERT((LIM) >= 0 && (LIM) <= 0xffff, "bare metal TSS is suspiciously fat") +#define TSSDESC_ENT0(BASE, LIM) \ + (((LIM) << 0 & 0x000000000000ffff) | \ + ((BASE) << 16 & 0x000000ffffff0000) | \ + 0x89 << 40 | \ + ((LIM) >> 16 << 48 & 0x000f000000000000) | \ + 0x2 << 52 | \ + ((BASE) >> 24 << 56 & 0xff00000000000000)) +#define TSSDESC_ENT1(BASE) \ + ((BASE) >> 32 << 0 & 0x00000000ffffffff) + #endif /* __ASSEMBLER__ */ #endif /* APE_MACROS_H_ */ diff --git a/libc/intrin/interrupts.S b/libc/intrin/interrupts.S new file mode 100644 index 000000000..d61bd0672 --- /dev/null +++ b/libc/intrin/interrupts.S @@ -0,0 +1,173 @@ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ This is free and unencumbered software released into the public domain. │ +│ │ +│ Anyone is free to copy, modify, publish, use, compile, sell, or │ +│ distribute this software, either in source code form or as a compiled │ +│ binary, for any purpose, commercial or non-commercial, and by any │ +│ means. │ +│ │ +│ In jurisdictions that recognize copyright laws, the author or authors │ +│ of this software dedicate any and all copyright interest in the │ +│ software to the public domain. We make this dedication for the benefit │ +│ of the public at large and to the detriment of our heirs and │ +│ successors. We intend this dedication to be an overt act of │ +│ relinquishment in perpetuity of all present and future rights to this │ +│ software under copyright law. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, │ +│ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF │ +│ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. │ +│ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR │ +│ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, │ +│ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR │ +│ OTHER DEALINGS IN THE SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/dce.h" +#include "libc/macros.internal.h" +#include "libc/runtime/pc.internal.h" + +// Code and data structures for bare metal interrupt handling. + +#define ISR_STK_SZ 0x10000 +#define ISR_STK_ALIGN 0x10 +// Interrupt stack to use for IRQs. TODO. +#define IRQ_IST 1 +// Interrupt stack to use for CPU exceptions. +#define EXCEP_IST 2 + +// Interrupt numbers to use for IRQs 0 & 8. TODO: implement these! +#define IRQ0 0x20 +#define IRQ8 0x28 + + .init.start 100,_init_isr + push %rdi + call isr_init + pop %rdi + .init.end 100,_init_isr + +// Interrupt service routines for CPU exceptions 0—31. + i = 31 + .rept 30 + push %rsi # preserve rsi + mov $i,%sil # rsi = exception number +1: jmp 1f # 🦘 + i = i - 1 + .endr +__excep1_isr: + push %rsi + mov $1,%sil +1: jmp 1f +__excep0_isr: + push %rsi + xor %esi,%esi +1: test $8,%esp # if no error code was pushed, + jnz 2f # stuff our own + pushq (%rsp) + orq $-1,8(%rsp) +2: movzbq %sil,%rsi # zero-extend the exception number + push %rcx # preserve registers which we will + push %rdx # use to call kprintf + push %r8 + mov 40(%rsp),%rcx # edx:rcx = "caller" cs:rip + mov 48(%rsp),%edx + mov 32(%rsp),%r8 # r8 = error code + push %rax # preserve other call-used registers + push %rdi + push %r9 + push %r10 + push %r11 + mov %ss,%eax # preserve ds, es, ss + push %rax + mov %ds,%eax + push %rax + mov %es,%eax + push %rax + mov $GDT_LONG_DATA,%eax # ...& load ds, es, ss correctly + mov %eax,%ss + mov %eax,%ds + mov %eax,%es + ezlea .excep_msg,di # stack should be 16-byte aligned now + xor %eax,%eax # kprintf is variadic; remember to + # pass no. of vector regs. used (= 0) + call kprintf # print error message + cli +9: hlt + jmp 9b + /* TODO: link up with sigaction etc. */ + +// Initialization code for setting up a Task State Segment (TSS) & +// Interrupt Descriptor Table (IDT) in bare metal mode, to start +// processing exceptions & asynchronous IRQs. +isr_init: + testb IsMetal() + jz 9f + ezlea _tss+0x24,di # fill up TSS; we already loaded + # task register in ape/ape.S + ezlea _isr_stk_1+ISR_STK_SZ,ax + and $-ISR_STK_ALIGN,%al # be paranoid & enforce correct + stosq # alignment of stack pointers + ezlea _isr_stk_2+ISR_STK_SZ,ax + and $-ISR_STK_ALIGN,%al + stosq + add $0x66-0x34,%rdi + movw $_tss_iopb-_tss,%ax + stosw + lidt _idtr # load IDTR + ezlea _idt,di + pushpop 32,%rcx # fill IDT entries for CPU exceptions + ezlea __excep0_isr,dx +1: mov %edx,%eax + stosw + mov %cs,%eax + stosw + mov %rdx,%rax +// ┌P:present +// │┌DPL:privilege +// ││ ┌system segment (0) +// ││ │ ┌gate type (interrupt gate, i.e. disable IRQs) +// ││ │ │ ┌reserved +// ││ │ │ │ ┌IST:interrupt stack table +// │├┐│┌┴─┐┌─┴─┐┌┴┐ + mov $0b1000111000000000|EXCEP_IST,%ax + stosl + shr $32,%rax + stosq + add $__excep1_isr-__excep0_isr,%rdx + loop 1b +9: ret + +// String constants. + .rodata.str1.1 +.excep_msg: + .asciz "CPU exception %d @ %#llx:%#llx err code %#llx\n" + .previous + +// IDTR value. + .rodata +_idtr: .short _idt_end-_idt-1 + .quad _idt + .endobj _idtr,globl,hidden + .balign 8 + .previous + + .bss + +// Space for the Task State Segment. +_tss: + .space 0x68 +_tss_iopb: +_tss_end: + .endobj _tss,globl,hidden + .endobj _tss_end,globl,hidden + +// Space for the Interrupt Descriptor Table. +_idt: .space (MAX(IRQ0,IRQ8)+8)*0x10 +_idt_end: + .endobj _idt,globl,hidden + .previous + +// Interrupt stacks. + .lcomm _isr_stk_1,ISR_STK_SZ + .lcomm _isr_stk_2,ISR_STK_SZ diff --git a/libc/log/die.c b/libc/log/die.c index c027c8a0b..f2858997b 100644 --- a/libc/log/die.c +++ b/libc/log/die.c @@ -28,6 +28,10 @@ #include "libc/runtime/internal.h" #include "libc/runtime/runtime.h" +#if SupportsMetal() +STATIC_YOINK("_idt"); +#endif + /** * Aborts process after printing a backtrace. * diff --git a/libc/macros.internal.h b/libc/macros.internal.h index c32950397..b8bf30816 100644 --- a/libc/macros.internal.h +++ b/libc/macros.internal.h @@ -16,9 +16,18 @@ #define IS2POW(X) (!((X) & ((X)-1))) #define ROUNDUP(X, K) (((X) + (K)-1) & -(K)) #define ROUNDDOWN(X, K) ((X) & -(K)) +#ifndef __ASSEMBLER__ #define ABS(X) ((X) >= 0 ? (X) : -(X)) #define MIN(X, Y) ((Y) > (X) ? (X) : (Y)) #define MAX(X, Y) ((Y) < (X) ? (X) : (Y)) +#else +// The GNU assembler does not grok the ?: ternary operator; furthermore, +// boolean expressions yield -1 and 0 for "true" and "false", not 1 and 0. +#define __MAPBOOL(P) (!!(P) / (!!(P) + !(P))) +#define __IFELSE(P, X, Y) (__MAPBOOL(P) * (X) + __MAPBOOL(!(P)) * (Y)) +#define MIN(X, Y) (__IFELSE((Y) > (X), (X), (Y))) +#define MAX(X, Y) (__IFELSE((Y) < (X), (X), (Y))) +#endif #define PASTE(A, B) __PASTE(A, B) #define STRINGIFY(A) __STRINGIFY(A) #define EQUIVALENT(X, Y) (__builtin_constant_p((X) == (Y)) && ((X) == (Y))) diff --git a/libc/runtime/pc.internal.h b/libc/runtime/pc.internal.h index 45ca8d919..6ff21bfdf 100644 --- a/libc/runtime/pc.internal.h +++ b/libc/runtime/pc.internal.h @@ -120,6 +120,7 @@ #define GDT_LEGACY_DATA 32 #define GDT_LONG_CODE 40 #define GDT_LONG_DATA 48 +#define GDT_LONG_TSS 56 #define PIC1 0x20 /* IO base address for master PIC */ #define PIC2 0xA0 /* IO base address for slave PIC */