From 9adde8f5c176318355edf40582eb1027cc9e86ad Mon Sep 17 00:00:00 2001 From: Achilleas Anagnostopoulos Date: Wed, 21 Jun 2017 17:57:15 +0100 Subject: [PATCH] Define and implement API for registering exception handlers --- Makefile | 1 + kernel/irq/handler_amd64.go | 41 +++++++++++++++ kernel/irq/handler_amd64.s | 36 +++++++++++++ kernel/irq/interrupt_amd64.go | 51 ++++++++++++++++++ kernel/irq/interrupt_amd64_test.go | 84 ++++++++++++++++++++++++++++++ 5 files changed, 213 insertions(+) create mode 100644 kernel/irq/handler_amd64.go create mode 100644 kernel/irq/handler_amd64.s create mode 100644 kernel/irq/interrupt_amd64.go create mode 100644 kernel/irq/interrupt_amd64_test.go diff --git a/Makefile b/Makefile index bc975ab..55c5c48 100644 --- a/Makefile +++ b/Makefile @@ -59,6 +59,7 @@ go.o: @echo "[objcopy] creating global symbol alias 'kernel.Kmain' for 'github.com/achilleasa/gopher-os/kernel.Kmain' in go.o" @objcopy \ --add-symbol kernel.Kmain=.text:0x`nm $(BUILD_DIR)/go.o | grep "kmain.Kmain$$" | cut -d' ' -f1` \ + --globalize-symbol _rt0_interrupt_handlers \ $(BUILD_DIR)/go.o $(BUILD_DIR)/go.o binutils_version_check: diff --git a/kernel/irq/handler_amd64.go b/kernel/irq/handler_amd64.go new file mode 100644 index 0000000..b289ab7 --- /dev/null +++ b/kernel/irq/handler_amd64.go @@ -0,0 +1,41 @@ +package irq + +// ExceptionNum defines an exception number that can be +// passed to the HandleException and HandleExceptionWithCode +// functions. +type ExceptionNum uint8 + +const ( + // DoubleFault occurs when an exception is unhandled + // or when an exception occurs while the CPU is + // trying to call an exception handler. + DoubleFault = ExceptionNum(8) + + // GPFException is raised when a general protection fault occurs. + GPFException = ExceptionNum(13) + + // PageFaultException is raised when a PDT or + // PDT-entry is not present or when a privilege + // and/or RW protection check fails. + PageFaultException = ExceptionNum(14) +) + +// ExceptionHandler is a function that handles an exception that does not push +// an error code to the stack. If the handler returns, any modifications to the +// supplied Frame and/or Regs pointers will be propagated back to the location +// where the exception occurred. +type ExceptionHandler func(*Frame, *Regs) + +// ExceptionHandlerWithCode is a function that handles an exception that pushes +// an error code to the stack. If the handler returns, any modifications to the +// supplied Frame and/or Regs pointers will be propagated back to the location +// where the exception occurred. +type ExceptionHandlerWithCode func(uint64, *Frame, *Regs) + +// HandleException registers an exception handler (without an error code) for +// the given interrupt number. +func HandleException(exceptionNum ExceptionNum, handler ExceptionHandler) + +// HandleExceptionWithCode registers an exception handler (with an error code) +// for the given interrupt number. +func HandleExceptionWithCode(exceptionNum ExceptionNum, handler ExceptionHandlerWithCode) diff --git a/kernel/irq/handler_amd64.s b/kernel/irq/handler_amd64.s new file mode 100644 index 0000000..f2aa3a9 --- /dev/null +++ b/kernel/irq/handler_amd64.s @@ -0,0 +1,36 @@ +#include "textflag.h" + +// The maximum number of interrupt handlers is 256 so we need to allocate space +// for 256 x 8-byte pointers. This symbol is made global by the Makefile so it +// can be accessed by the gate entries defined in the rt0 assembly code. +GLOBL _rt0_interrupt_handlers(SB), NOPTR, $2048 + +// In 64-bit mode SIDT stores 8+2 bytes for the IDT address and limit +GLOBL _rt0_idtr<>(SB), NOPTR, $10 + +TEXT ·HandleException(SB),NOSPLIT,$0 + JMP ·HandleExceptionWithCode(SB) + RET + +TEXT ·HandleExceptionWithCode(SB),NOSPLIT,$0 + // Install the handler address in _rt0_interrupt_handlers + LEAQ _rt0_interrupt_handlers+0(SB), CX + MOVBQZX exceptionNum+0(FP), AX // exceptionNum is a uint8 so we zero-extend it to 64bits + MOVQ handler+8(FP), BX + MOVQ 0(BX), BX // dereference pointer to handler fn + MOVQ BX, (CX)(AX*8) + + // To enable the handler we need to lookup the appropriate IDT entry + // and modify its type/attribute byte. To acquire the IDT base address + // we use the SIDT instruction. + MOVQ IDTR, _rt0_idtr<>+0(SB) + LEAQ _rt0_idtr<>(SB), CX + MOVQ 2(CX), CX // CX points to IDT base address + SHLQ $4, AX // Each IDT entry uses 16 bytes so we multiply num by 16 + ADDQ AX, CX // and add it to CX to get the address of the IDT entry + // we want to tweak + + MOVB $0x8e, 5(CX) // 32/64-bit ring-0 interrupt gate that is present + // see: http://wiki.osdev.org/Interrupt_Descriptor_Table + + RET diff --git a/kernel/irq/interrupt_amd64.go b/kernel/irq/interrupt_amd64.go new file mode 100644 index 0000000..dcab9ef --- /dev/null +++ b/kernel/irq/interrupt_amd64.go @@ -0,0 +1,51 @@ +package irq + +import "github.com/achilleasa/gopher-os/kernel/kfmt/early" + +// Regs contains a snapshot of the register values when an interrupt occurred. +type Regs struct { + RAX uint64 + RBX uint64 + RCX uint64 + RDX uint64 + RSI uint64 + RDI uint64 + RBP uint64 + R8 uint64 + R9 uint64 + R10 uint64 + R11 uint64 + R12 uint64 + R13 uint64 + R14 uint64 + R15 uint64 +} + +// Print outputs a dump of the register values to the active console. +func (r *Regs) Print() { + early.Printf("RAX = %16x RBX = %16x\n", r.RAX, r.RBX) + early.Printf("RCX = %16x RDX = %16x\n", r.RCX, r.RDX) + early.Printf("RSI = %16x RDI = %16x\n", r.RSI, r.RDI) + early.Printf("RBP = %16x\n", r.RBP) + early.Printf("R8 = %16x R9 = %16x\n", r.R8, r.R9) + early.Printf("R10 = %16x R11 = %16x\n", r.R10, r.R11) + early.Printf("R12 = %16x R13 = %16x\n", r.R12, r.R13) + early.Printf("R14 = %16x R15 = %16x\n", r.R14, r.R15) +} + +// Frame describes an exception frame that is automatically pushed by the CPU +// to the stack when an exception occurs. +type Frame struct { + RIP uint64 + CS uint64 + RFlags uint64 + RSP uint64 + SS uint64 +} + +// Print outputs a dump of the exception frame to the active console. +func (f *Frame) Print() { + early.Printf("RIP = %16x CS = %16x\n", f.RIP, f.CS) + early.Printf("RSP = %16x SS = %16x\n", f.RSP, f.SS) + early.Printf("RFL = %16x\n", f.RFlags) +} diff --git a/kernel/irq/interrupt_amd64_test.go b/kernel/irq/interrupt_amd64_test.go new file mode 100644 index 0000000..3c2d22a --- /dev/null +++ b/kernel/irq/interrupt_amd64_test.go @@ -0,0 +1,84 @@ +package irq + +import ( + "bytes" + "testing" + "unsafe" + + "github.com/achilleasa/gopher-os/kernel/driver/video/console" + "github.com/achilleasa/gopher-os/kernel/hal" +) + +func TestRegsPrint(t *testing.T) { + fb := mockTTY() + regs := Regs{ + RAX: 1, + RBX: 2, + RCX: 3, + RDX: 4, + RSI: 5, + RDI: 6, + RBP: 7, + R8: 8, + R9: 9, + R10: 10, + R11: 11, + R12: 12, + R13: 13, + R14: 14, + R15: 15, + } + regs.Print() + + exp := "RAX = 0000000000000001 RBX = 0000000000000002\nRCX = 0000000000000003 RDX = 0000000000000004\nRSI = 0000000000000005 RDI = 0000000000000006\nRBP = 0000000000000007\nR8 = 0000000000000008 R9 = 0000000000000009\nR10 = 000000000000000a R11 = 000000000000000b\nR12 = 000000000000000c R13 = 000000000000000d\nR14 = 000000000000000e R15 = 000000000000000f" + + if got := readTTY(fb); got != exp { + t.Fatalf("expected to get:\n%q\ngot:\n%q", exp, got) + } +} + +func TestFramePrint(t *testing.T) { + fb := mockTTY() + frame := Frame{ + RIP: 1, + CS: 2, + RFlags: 3, + RSP: 4, + SS: 5, + } + frame.Print() + + exp := "RIP = 0000000000000001 CS = 0000000000000002\nRSP = 0000000000000004 SS = 0000000000000005\nRFL = 0000000000000003" + + if got := readTTY(fb); got != exp { + t.Fatalf("expected to get:\n%q\ngot:\n%q", exp, got) + } + +} + +func readTTY(fb []byte) string { + var buf bytes.Buffer + for i := 0; i < len(fb); i += 2 { + ch := fb[i] + if ch == 0 { + if i+2 < len(fb) && fb[i+2] != 0 { + buf.WriteByte('\n') + } + continue + } + + buf.WriteByte(ch) + } + + return buf.String() +} + +func mockTTY() []byte { + // Mock a tty to handle early.Printf output + mockConsoleFb := make([]byte, 160*25) + mockConsole := &console.Ega{} + mockConsole.Init(80, 25, uintptr(unsafe.Pointer(&mockConsoleFb[0]))) + hal.ActiveTerminal.AttachTo(mockConsole) + + return mockConsoleFb +}