From 18092face9a7a5edca134fe12e7505a934d94960 Mon Sep 17 00:00:00 2001 From: Martin Petr Date: Thu, 20 Nov 2025 21:13:50 +0100 Subject: [PATCH] Add interrupts and keyboard driver --- scripts/build-kernel.sh | 13 + src/boot/kernel.asm | 12 +- src/kernel/interrupt/idt.c | 22 + src/kernel/interrupt/idt.h | 40 ++ src/kernel/interrupt/interrupt.asm | 446 ++++++++++++++++++ src/kernel/interrupt/isr.c | 317 +++++++++++++ src/kernel/interrupt/isr.h | 78 +++ src/kernel/interrupt/types.h | 16 + src/kernel/kernel.c | 126 +++++ src/os/src/config/keyboard.ts | 57 +++ src/os/src/kernel/drivers/dev/keyboard.ts | 29 ++ src/os/src/kernel/index.ts | 15 +- src/os/src/kernel/modules/cpu/interrupts.ts | 43 ++ .../kernel/modules/drivers/drivers.kmod.ts | 7 +- src/os/src/kernel/modules/terminal/input.ts | 21 + .../kernel/modules/terminal/terminal.kmod.ts | 1 + src/os/src/lib/libts/byte.ts | 8 + src/os/src/types/c/bindings.d.ts | 6 + src/os/src/types/interrupt.ts | 9 + 19 files changed, 1259 insertions(+), 7 deletions(-) create mode 100644 src/kernel/interrupt/idt.c create mode 100644 src/kernel/interrupt/idt.h create mode 100644 src/kernel/interrupt/interrupt.asm create mode 100644 src/kernel/interrupt/isr.c create mode 100644 src/kernel/interrupt/isr.h create mode 100644 src/kernel/interrupt/types.h create mode 100644 src/os/src/config/keyboard.ts create mode 100644 src/os/src/kernel/drivers/dev/keyboard.ts create mode 100644 src/os/src/kernel/modules/cpu/interrupts.ts create mode 100644 src/os/src/kernel/modules/terminal/input.ts create mode 100644 src/os/src/kernel/modules/terminal/terminal.kmod.ts create mode 100644 src/os/src/types/interrupt.ts diff --git a/scripts/build-kernel.sh b/scripts/build-kernel.sh index 4061c58..69fcb10 100755 --- a/scripts/build-kernel.sh +++ b/scripts/build-kernel.sh @@ -37,6 +37,7 @@ python3 scripts/embed_js.py build/index.js > "$BUILD_DIR/embedded_js.h" # Compiler flags CFLAGS="-m32 -march=i686 -ffreestanding -nostdlib -fno-builtin" +CFLAGS="$CFLAGS -mno-sse -mno-sse2 -mno-mmx -mno-3dnow" CFLAGS="$CFLAGS -I$PICOLIBC_INSTALL/include" CFLAGS="$CFLAGS -I./lib/duktape/src" CFLAGS="$CFLAGS -I./src/lib" @@ -56,10 +57,19 @@ echo "=== Building kernel with picolibc ===" echo "Assembling boot code..." nasm -f elf32 src/boot/kernel.asm -o "$BUILD_DIR/kasm.o" +# Build interrupt assembly +echo "Assembling interrupt handlers..." +nasm -f elf32 src/kernel/interrupt/interrupt.asm -o "$BUILD_DIR/interrupt.o" + # Build duktape echo "Building Duktape..." gcc $CFLAGS -c lib/duktape/src/duktape.c -o "$BUILD_DIR/duktape.o" +# Build interrupt system +echo "Building interrupt system..." +gcc $CFLAGS -c src/kernel/interrupt/idt.c -o "$BUILD_DIR/idt.o" +gcc $CFLAGS -c src/kernel/interrupt/isr.c -o "$BUILD_DIR/isr.o" + # Build kernel echo "Building kernel..." gcc $CFLAGS -c src/kernel/kernel.c -o "$BUILD_DIR/kc.o" @@ -72,6 +82,9 @@ gcc $CFLAGS -c src/lib/syscalls.c -o "$BUILD_DIR/syscalls.o" echo "Linking kernel..." ld $LDFLAGS -T src/link.ld -o "$OUT_DIR/kernel" \ "$BUILD_DIR/kasm.o" \ + "$BUILD_DIR/interrupt.o" \ + "$BUILD_DIR/idt.o" \ + "$BUILD_DIR/isr.o" \ "$BUILD_DIR/kc.o" \ "$BUILD_DIR/duktape.o" \ "$BUILD_DIR/syscalls.o" \ diff --git a/src/boot/kernel.asm b/src/boot/kernel.asm index cd02156..ddb4346 100644 --- a/src/boot/kernel.asm +++ b/src/boot/kernel.asm @@ -8,10 +8,18 @@ section .text global start extern kmain ;kmain is defined in the kernel.c file +extern __stack_top start: - cli ; stop interrupts + cli ; stop interrupts during boot + mov esp, __stack_top + + ; Initialize FPU (x87) + finit ; Initialize FPU to default state call kmain + ; Note: kmain will call sti to enable interrupts after IDT is set up - hlt ; halt the CPU \ No newline at end of file +hang: + hlt ; halt the CPU + jmp hang \ No newline at end of file diff --git a/src/kernel/interrupt/idt.c b/src/kernel/interrupt/idt.c new file mode 100644 index 0000000..c5af242 --- /dev/null +++ b/src/kernel/interrupt/idt.c @@ -0,0 +1,22 @@ +#include "idt.h" + +/* Define the IDT and IDT register here */ +idt_gate_t idt[IDT_ENTRIES]; +idt_register_t idt_reg; + +void set_idt_gate(int n, u32 handler) +{ + idt[n].low_offset = low_16(handler); + idt[n].sel = KERNEL_CS; + idt[n].always0 = 0; + idt[n].flags = 0x8E; + idt[n].high_offset = high_16(handler); +} + +void set_idt() +{ + idt_reg.base = (u32)&idt; + idt_reg.limit = IDT_ENTRIES * sizeof(idt_gate_t) - 1; + /* Don't make the mistake of loading &idt -- always load &idt_reg */ + __asm__ __volatile__("lidtl (%0)" : : "r"(&idt_reg)); +} \ No newline at end of file diff --git a/src/kernel/interrupt/idt.h b/src/kernel/interrupt/idt.h new file mode 100644 index 0000000..e8b3aaf --- /dev/null +++ b/src/kernel/interrupt/idt.h @@ -0,0 +1,40 @@ +#ifndef IDT_H +#define IDT_H + +#include "types.h" + +/* Segment selectors */ +#define KERNEL_CS 0x08 + +/* How every interrupt gate (handler) is defined */ +typedef struct +{ + u16 low_offset; /* Lower 16 bits of handler function address */ + u16 sel; /* Kernel segment selector */ + u8 always0; + /* First byte + * Bit 7: "Interrupt is present" + * Bits 6-5: Privilege level of caller (0=kernel..3=user) + * Bit 4: Set to 0 for interrupt gates + * Bits 3-0: bits 1110 = decimal 14 = "32 bit interrupt gate" */ + u8 flags; + u16 high_offset; /* Higher 16 bits of handler function address */ +} __attribute__((packed)) idt_gate_t; + +/* A pointer to the array of interrupt handlers. + * Assembly instruction 'lidt' will read it */ +typedef struct +{ + u16 limit; + u32 base; +} __attribute__((packed)) idt_register_t; + +#define IDT_ENTRIES 256 +extern idt_gate_t idt[IDT_ENTRIES]; +extern idt_register_t idt_reg; + +/* Functions implemented in idt.c */ +void set_idt_gate(int n, u32 handler); +void set_idt(); + +#endif \ No newline at end of file diff --git a/src/kernel/interrupt/interrupt.asm b/src/kernel/interrupt/interrupt.asm new file mode 100644 index 0000000..f987c51 --- /dev/null +++ b/src/kernel/interrupt/interrupt.asm @@ -0,0 +1,446 @@ +; Defined in isr.c +[extern isr_handler] +[extern irq_handler] + +; Common ISR code +isr_common_stub: + ; 1. Save CPU state + pusha ; Pushes edi,esi,ebp,esp,ebx,edx,ecx,eax + mov ax, ds ; Lower 16-bits of eax = ds. + push eax ; save the data segment descriptor + mov ax, 0x10 ; kernel data segment descriptor + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + ; 2. Call C handler - pass pointer to registers_t struct on stack + push esp + call isr_handler + add esp, 4 + + ; 3. Restore state + pop eax + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + popa + add esp, 8 ; Cleans up the pushed error code and pushed ISR number + iret ; pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP (and restores interrupt flag) + +; Common IRQ code +irq_common_stub: + ; 1. Save CPU state + pusha + mov ax, ds + push eax + mov ax, 0x10 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + ; 2. Call C handler - pass pointer to registers_t struct on stack + push esp + call irq_handler + add esp, 4 + + ; 3. Restore state + pop ebx + mov ds, bx + mov es, bx + mov fs, bx + mov gs, bx + popa + add esp, 8 + iret ; iret restores the interrupt flag automatically from saved EFLAGS + +; We don't get information about which interrupt was caller +; when the handler is run, so we will need to have a different handler +; for every interrupt. +; Furthermore, some interrupts push an error code onto the stack but others +; don't, so we will push a dummy error code for those which don't, so that +; we have a consistent stack for all of them. + +; First make the ISRs global +global isr0 +global isr1 +global isr2 +global isr3 +global isr4 +global isr5 +global isr6 +global isr7 +global isr8 +global isr9 +global isr10 +global isr11 +global isr12 +global isr13 +global isr14 +global isr15 +global isr16 +global isr17 +global isr18 +global isr19 +global isr20 +global isr21 +global isr22 +global isr23 +global isr24 +global isr25 +global isr26 +global isr27 +global isr28 +global isr29 +global isr30 +global isr31 + +; 0: Divide By Zero Exception +isr0: + cli + push byte 0 + push byte 0 + jmp isr_common_stub + +; 1: Debug Exception +isr1: + cli + push byte 0 + push byte 1 + jmp isr_common_stub + +; 2: Non Maskable Interrupt Exception +isr2: + cli + push byte 0 + push byte 2 + jmp isr_common_stub + +; 3: Int 3 Exception +isr3: + cli + push byte 0 + push byte 3 + jmp isr_common_stub + +; 4: INTO Exception +isr4: + cli + push byte 0 + push byte 4 + jmp isr_common_stub + +; 5: Out of Bounds Exception +isr5: + cli + push byte 0 + push byte 5 + jmp isr_common_stub + +; 6: Invalid Opcode Exception +isr6: + cli + push byte 0 + push byte 6 + jmp isr_common_stub + +; 7: Coprocessor Not Available Exception +isr7: + cli + push byte 0 + push byte 7 + jmp isr_common_stub + +; 8: Double Fault Exception (With Error Code!) +isr8: + cli + push byte 8 + jmp isr_common_stub + +; 9: Coprocessor Segment Overrun Exception +isr9: + cli + push byte 0 + push byte 9 + jmp isr_common_stub + +; 10: Bad TSS Exception (With Error Code!) +isr10: + cli + push byte 10 + jmp isr_common_stub + +; 11: Segment Not Present Exception (With Error Code!) +isr11: + cli + push byte 11 + jmp isr_common_stub + +; 12: Stack Fault Exception (With Error Code!) +isr12: + cli + push byte 12 + jmp isr_common_stub + +; 13: General Protection Fault Exception (With Error Code!) +isr13: + cli + push byte 13 + jmp isr_common_stub + +; 14: Page Fault Exception (With Error Code!) +isr14: + cli + push byte 14 + jmp isr_common_stub + +; 15: Reserved Exception +isr15: + cli + push byte 0 + push byte 15 + jmp isr_common_stub + +; 16: Floating Point Exception +isr16: + cli + push byte 0 + push byte 16 + jmp isr_common_stub + +; 17: Alignment Check Exception +isr17: + cli + push byte 0 + push byte 17 + jmp isr_common_stub + +; 18: Machine Check Exception +isr18: + cli + push byte 0 + push byte 18 + jmp isr_common_stub + +; 19: Reserved +isr19: + cli + push byte 0 + push byte 19 + jmp isr_common_stub + +; 20: Reserved +isr20: + cli + push byte 0 + push byte 20 + jmp isr_common_stub + +; 21: Reserved +isr21: + cli + push byte 0 + push byte 21 + jmp isr_common_stub + +; 22: Reserved +isr22: + cli + push byte 0 + push byte 22 + jmp isr_common_stub + +; 23: Reserved +isr23: + cli + push byte 0 + push byte 23 + jmp isr_common_stub + +; 24: Reserved +isr24: + cli + push byte 0 + push byte 24 + jmp isr_common_stub + +; 25: Reserved +isr25: + cli + push byte 0 + push byte 25 + jmp isr_common_stub + +; 26: Reserved +isr26: + cli + push byte 0 + push byte 26 + jmp isr_common_stub + +; 27: Reserved +isr27: + cli + push byte 0 + push byte 27 + jmp isr_common_stub + +; 28: Reserved +isr28: + cli + push byte 0 + push byte 28 + jmp isr_common_stub + +; 29: Reserved +isr29: + cli + push byte 0 + push byte 29 + jmp isr_common_stub + +; 30: Reserved +isr30: + cli + push byte 0 + push byte 30 + jmp isr_common_stub + +; 31: Reserved +isr31: + cli + push byte 0 + push byte 31 + jmp isr_common_stub + +; IRQ handlers +global irq0 +global irq1 +global irq2 +global irq3 +global irq4 +global irq5 +global irq6 +global irq7 +global irq8 +global irq9 +global irq10 +global irq11 +global irq12 +global irq13 +global irq14 +global irq15 + +; IRQ 0: System Timer +irq0: + cli + push byte 0 + push byte 32 + jmp irq_common_stub + +; IRQ 1: Keyboard +irq1: + cli + push byte 0 + push byte 33 + jmp irq_common_stub + +; IRQ 2: Cascade (never raised) +irq2: + cli + push byte 0 + push byte 34 + jmp irq_common_stub + +; IRQ 3: COM2 +irq3: + cli + push byte 0 + push byte 35 + jmp irq_common_stub + +; IRQ 4: COM1 +irq4: + cli + push byte 0 + push byte 36 + jmp irq_common_stub + +; IRQ 5: LPT2 +irq5: + cli + push byte 0 + push byte 37 + jmp irq_common_stub + +; IRQ 6: Floppy Disk +irq6: + cli + push byte 0 + push byte 38 + jmp irq_common_stub + +; IRQ 7: LPT1 +irq7: + cli + push byte 0 + push byte 39 + jmp irq_common_stub + +; IRQ 8: CMOS Real-time Clock +irq8: + cli + push byte 0 + push byte 40 + jmp irq_common_stub + +; IRQ 9: Free for peripherals +irq9: + cli + push byte 0 + push byte 41 + jmp irq_common_stub + +; IRQ 10: Free for peripherals +irq10: + cli + push byte 0 + push byte 42 + jmp irq_common_stub + +; IRQ 11: Free for peripherals +irq11: + cli + push byte 0 + push byte 43 + jmp irq_common_stub + +; IRQ 12: PS/2 Mouse +irq12: + cli + push byte 0 + push byte 44 + jmp irq_common_stub + +; IRQ 13: FPU +irq13: + cli + push byte 0 + push byte 45 + jmp irq_common_stub + +; IRQ 14: Primary ATA Hard Disk +irq14: + cli + push byte 0 + push byte 46 + jmp irq_common_stub + +; IRQ 15: Secondary ATA Hard Disk +irq15: + cli + push byte 0 + push byte 47 + jmp irq_common_stub \ No newline at end of file diff --git a/src/kernel/interrupt/isr.c b/src/kernel/interrupt/isr.c new file mode 100644 index 0000000..dfcb5b9 --- /dev/null +++ b/src/kernel/interrupt/isr.c @@ -0,0 +1,317 @@ +#include "isr.h" +#include "idt.h" + +/* Port I/O functions */ +static inline void outb(u16 port, u8 val) +{ + __asm__ volatile("outb %0, %1" : : "a"(val), "Nd"(port)); +} + +static inline u8 inb(u16 port) +{ + u8 ret; + __asm__ volatile("inb %1, %0" : "=a"(ret) : "Nd"(port)); + return ret; +} + +/* PIC (Programmable Interrupt Controller) constants */ +#define PIC1_COMMAND 0x20 +#define PIC1_DATA 0x21 +#define PIC2_COMMAND 0xA0 +#define PIC2_DATA 0xA1 +#define PIC_EOI 0x20 + +/* Array to store custom IRQ handlers */ +static irq_handler_t irq_handlers[16] = {0}; + +/* Can't do this with a loop because we need the address + * of the function names */ +void isr_install() +{ + set_idt_gate(0, (u32)isr0); + set_idt_gate(1, (u32)isr1); + set_idt_gate(2, (u32)isr2); + set_idt_gate(3, (u32)isr3); + set_idt_gate(4, (u32)isr4); + set_idt_gate(5, (u32)isr5); + set_idt_gate(6, (u32)isr6); + set_idt_gate(7, (u32)isr7); + set_idt_gate(8, (u32)isr8); + set_idt_gate(9, (u32)isr9); + set_idt_gate(10, (u32)isr10); + set_idt_gate(11, (u32)isr11); + set_idt_gate(12, (u32)isr12); + set_idt_gate(13, (u32)isr13); + set_idt_gate(14, (u32)isr14); + set_idt_gate(15, (u32)isr15); + set_idt_gate(16, (u32)isr16); + set_idt_gate(17, (u32)isr17); + set_idt_gate(18, (u32)isr18); + set_idt_gate(19, (u32)isr19); + set_idt_gate(20, (u32)isr20); + set_idt_gate(21, (u32)isr21); + set_idt_gate(22, (u32)isr22); + set_idt_gate(23, (u32)isr23); + set_idt_gate(24, (u32)isr24); + set_idt_gate(25, (u32)isr25); + set_idt_gate(26, (u32)isr26); + set_idt_gate(27, (u32)isr27); + set_idt_gate(28, (u32)isr28); + set_idt_gate(29, (u32)isr29); + set_idt_gate(30, (u32)isr30); + set_idt_gate(31, (u32)isr31); + + set_idt(); // Load with ASM +} + +/* Remap the PIC to avoid conflicts with CPU exceptions */ +static void pic_remap(void) +{ + // Start initialization + outb(PIC1_COMMAND, 0x11); + outb(PIC2_COMMAND, 0x11); + + // Set vector offsets (IRQs 0-7 -> interrupts 32-39, IRQs 8-15 -> interrupts 40-47) + outb(PIC1_DATA, 0x20); + outb(PIC2_DATA, 0x28); + + // Tell master PIC there's a slave PIC at IRQ2 + outb(PIC1_DATA, 0x04); + // Tell slave PIC its cascade identity + outb(PIC2_DATA, 0x02); + + // Set to 8086 mode + outb(PIC1_DATA, 0x01); + outb(PIC2_DATA, 0x01); + + // Mask all interrupts initially + outb(PIC1_DATA, 0xFF); + outb(PIC2_DATA, 0xFF); +} + +/* Install IRQ handlers */ +void irq_install() +{ + // Remap the PIC + pic_remap(); + + // Install IRQ handlers (32-47) + set_idt_gate(32, (u32)irq0); + set_idt_gate(33, (u32)irq1); + set_idt_gate(34, (u32)irq2); + set_idt_gate(35, (u32)irq3); + set_idt_gate(36, (u32)irq4); + set_idt_gate(37, (u32)irq5); + set_idt_gate(38, (u32)irq6); + set_idt_gate(39, (u32)irq7); + set_idt_gate(40, (u32)irq8); + set_idt_gate(41, (u32)irq9); + set_idt_gate(42, (u32)irq10); + set_idt_gate(43, (u32)irq11); + set_idt_gate(44, (u32)irq12); + set_idt_gate(45, (u32)irq13); + set_idt_gate(46, (u32)irq14); + set_idt_gate(47, (u32)irq15); + + set_idt(); // Reload IDT +} + +/* To print the message which defines every exception */ +char *exception_messages[] = { + "Division By Zero", + "Debug", + "Non Maskable Interrupt", + "Breakpoint", + "Into Detected Overflow", + "Out of Bounds", + "Invalid Opcode", + "No Coprocessor", + + "Double Fault", + "Coprocessor Segment Overrun", + "Bad TSS", + "Segment Not Present", + "Stack Fault", + "General Protection Fault", + "Page Fault", + "Unknown Interrupt", + + "Coprocessor Fault", + "Alignment Check", + "Machine Check", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved"}; + +void isr_handler(registers_t *r) +{ + // Write exception to VGA memory + char *vidmem = (char *)0xb8000; + const char *msg = "EXCEPTION: "; + int i = 0; + + // Clear first line + for (i = 0; i < 80 * 2; i++) + { + vidmem[i] = 0; + } + + // Write message + i = 0; + while (msg[i]) + { + vidmem[i * 2] = msg[i]; + vidmem[i * 2 + 1] = 0x4F; // White on red + i++; + } + + // Write exception number + vidmem[i * 2] = '0' + (r->int_no / 10); + vidmem[i * 2 + 1] = 0x4F; + i++; + vidmem[i * 2] = '0' + (r->int_no % 10); + vidmem[i * 2 + 1] = 0x4F; + i++; + + // Write error code + vidmem[i * 2] = ' '; + vidmem[i * 2 + 1] = 0x4F; + i++; + vidmem[i * 2] = 'E'; + vidmem[i * 2 + 1] = 0x4F; + i++; + vidmem[i * 2] = 'R'; + vidmem[i * 2 + 1] = 0x4F; + i++; + vidmem[i * 2] = 'R'; + vidmem[i * 2 + 1] = 0x4F; + i++; + vidmem[i * 2] = ':'; + vidmem[i * 2 + 1] = 0x4F; + i++; + + // Write error code in hex + u32 err = r->err_code; + for (int j = 7; j >= 0; j--) + { + u8 nibble = (err >> (j * 4)) & 0xF; + vidmem[i * 2] = nibble < 10 ? '0' + nibble : 'A' + (nibble - 10); + vidmem[i * 2 + 1] = 0x4F; + i++; + } + + // Write EIP + i++; + vidmem[i * 2] = 'E'; + vidmem[i * 2 + 1] = 0x4F; + i++; + vidmem[i * 2] = 'I'; + vidmem[i * 2 + 1] = 0x4F; + i++; + vidmem[i * 2] = 'P'; + vidmem[i * 2 + 1] = 0x4F; + i++; + vidmem[i * 2] = ':'; + vidmem[i * 2 + 1] = 0x4F; + i++; + + // Write EIP in hex + u32 eip = r->eip; + for (int j = 7; j >= 0; j--) + { + u8 nibble = (eip >> (j * 4)) & 0xF; + vidmem[i * 2] = nibble < 10 ? '0' + nibble : 'A' + (nibble - 10); + vidmem[i * 2 + 1] = 0x4F; + i++; + } + + // Halt + __asm__ volatile("cli; hlt"); + while (1) + ; +} + +/* IRQ handler */ +void irq_handler(registers_t *r) +{ + // Calculate IRQ number (interrupts 32-47 map to IRQ 0-15) + u8 irq_no = r->int_no - 32; + + // Handle spurious IRQ7 (from master PIC) + if (irq_no == 7) + { + // Read In-Service Register (ISR) + outb(PIC1_COMMAND, 0x0B); + u8 isr = inb(PIC1_COMMAND); + if (!(isr & 0x80)) + { + // Spurious interrupt, don't send EOI + return; + } + } + + // Handle spurious IRQ15 (from slave PIC) + if (irq_no == 15) + { + // Read In-Service Register (ISR) of slave PIC + outb(PIC2_COMMAND, 0x0B); + u8 isr = inb(PIC2_COMMAND); + if (!(isr & 0x80)) + { + // Spurious interrupt from slave, send EOI to master only + outb(PIC1_COMMAND, PIC_EOI); + return; + } + } + + // Call custom handler if registered + if (irq_handlers[irq_no] != 0) + { + irq_handlers[irq_no](r); + } + + // Send EOI to PIC + if (r->int_no >= 40) + { + // IRQ came from slave PIC, send EOI to both + outb(PIC2_COMMAND, PIC_EOI); + } + outb(PIC1_COMMAND, PIC_EOI); +} + +/* Register a custom IRQ handler */ +void irq_register_handler(u8 irq, irq_handler_t handler) +{ + if (irq < 16) + { + irq_handlers[irq] = handler; + + // Unmask the IRQ on the PIC + u16 port; + u8 value; + + if (irq < 8) + { + port = PIC1_DATA; + } + else + { + port = PIC2_DATA; + irq -= 8; + } + + value = inb(port) & ~(1 << irq); + outb(port, value); + } +} \ No newline at end of file diff --git a/src/kernel/interrupt/isr.h b/src/kernel/interrupt/isr.h new file mode 100644 index 0000000..57f5494 --- /dev/null +++ b/src/kernel/interrupt/isr.h @@ -0,0 +1,78 @@ +#ifndef ISR_H +#define ISR_H + +#include "types.h" + +/* ISRs reserved for CPU exceptions */ +extern void isr0(); +extern void isr1(); +extern void isr2(); +extern void isr3(); +extern void isr4(); +extern void isr5(); +extern void isr6(); +extern void isr7(); +extern void isr8(); +extern void isr9(); +extern void isr10(); +extern void isr11(); +extern void isr12(); +extern void isr13(); +extern void isr14(); +extern void isr15(); +extern void isr16(); +extern void isr17(); +extern void isr18(); +extern void isr19(); +extern void isr20(); +extern void isr21(); +extern void isr22(); +extern void isr23(); +extern void isr24(); +extern void isr25(); +extern void isr26(); +extern void isr27(); +extern void isr28(); +extern void isr29(); +extern void isr30(); +extern void isr31(); + +/* IRQ handlers */ +extern void irq0(); +extern void irq1(); +extern void irq2(); +extern void irq3(); +extern void irq4(); +extern void irq5(); +extern void irq6(); +extern void irq7(); +extern void irq8(); +extern void irq9(); +extern void irq10(); +extern void irq11(); +extern void irq12(); +extern void irq13(); +extern void irq14(); +extern void irq15(); + +/* Struct which aggregates many registers */ +typedef struct +{ + u32 ds; /* Data segment selector */ + u32 edi, esi, ebp, esp, ebx, edx, ecx, eax; /* Pushed by pusha. */ + u32 int_no, err_code; /* Interrupt number and error code (if applicable) */ + u32 eip, cs, eflags, useresp, ss; /* Pushed by the processor automatically */ +} registers_t; + +/* Function pointer type for IRQ handlers */ +typedef void (*irq_handler_t)(registers_t *); + +void isr_install(); +void isr_handler(registers_t *r); +void irq_install(); +void irq_handler(registers_t *r); + +/* Register a custom IRQ handler */ +void irq_register_handler(u8 irq, irq_handler_t handler); + +#endif \ No newline at end of file diff --git a/src/kernel/interrupt/types.h b/src/kernel/interrupt/types.h new file mode 100644 index 0000000..50102a4 --- /dev/null +++ b/src/kernel/interrupt/types.h @@ -0,0 +1,16 @@ +#ifndef TYPES_H +#define TYPES_H + +/* Instead of using 'chars' to allocate non-character bytes, + * we will use these new type with no semantic meaning */ +typedef unsigned int u32; +typedef int s32; +typedef unsigned short u16; +typedef short s16; +typedef unsigned char u8; +typedef char s8; + +#define low_16(address) (u16)((address) & 0xFFFF) +#define high_16(address) (u16)(((address) >> 16) & 0xFFFF) + +#endif \ No newline at end of file diff --git a/src/kernel/kernel.c b/src/kernel/kernel.c index a670a93..2baff03 100644 --- a/src/kernel/kernel.c +++ b/src/kernel/kernel.c @@ -1,7 +1,9 @@ #include #include +#include #include #include "embedded_js.h" +#include "interrupt/isr.h" #define WHITE_TXT 0x0F @@ -56,6 +58,35 @@ duk_ret_t native_ptout(duk_context *ctx) return 0; } +duk_ret_t native_byte_in(duk_context *ctx) +{ + // Get the port (first argument) + uint16_t port = (uint16_t)duk_to_uint32(ctx, 0); + + uint8_t result; + __asm__ volatile("inb %1, %0" + : "=a"(result) + : "Nd"(port)); + + duk_push_uint(ctx, (duk_uint_t)result); + return 1; +} + +duk_ret_t native_byte_out(duk_context *ctx) +{ + // Get the port (first argument) + uint16_t port = (uint16_t)duk_to_uint32(ctx, 0); + + // Get the value to write (second argument) + uint8_t value = (uint8_t)duk_to_uint32(ctx, 1); + + __asm__ volatile("outb %0, %1" + : + : "a"(value), "Nd"(port)); + + return 0; +} + duk_ret_t native_dword_in(duk_context *ctx) { // Get the port (first argument) @@ -85,10 +116,90 @@ duk_ret_t native_dword_out(duk_context *ctx) return 0; } +// Global context for IRQ handlers +duk_context *global_ctx = NULL; + +// JavaScript IRQ wrapper +void js_irq_callback(registers_t *r) +{ + if (global_ctx == NULL) + return; + + // Get IRQ number + u8 irq_no = r->int_no - 32; + + // Build the key for this IRQ handler manually (avoid snprintf in interrupt) + char key[32] = "irq_handler_"; + int key_len = 12; + if (irq_no >= 10) + { + key[key_len++] = '0' + (irq_no / 10); + key[key_len++] = '0' + (irq_no % 10); + } + else + { + key[key_len++] = '0' + irq_no; + } + key[key_len] = '\0'; + + // Call the JavaScript handler + duk_push_global_stash(global_ctx); + + if (duk_get_prop_string(global_ctx, -1, key)) + { + // Function found, call it with IRQ number + duk_push_uint(global_ctx, irq_no); + + // Try protected call with error handler + duk_int_t rc = duk_pcall(global_ctx, 1); + duk_pop(global_ctx); // Pop result or error + } + else + { + duk_pop(global_ctx); // Pop undefined value + } + + duk_pop(global_ctx); // Pop stash +} + +// Native function to register IRQ from JavaScript +duk_ret_t native_irq_register(duk_context *ctx) +{ + // Get the IRQ number + u8 irq = (u8)duk_to_uint32(ctx, 0); + + // Get the handler function + if (!duk_is_function(ctx, 1)) + { + duk_push_boolean(ctx, 0); + return 1; + } + + // Store function in global stash + char key[32]; + snprintf(key, sizeof(key), "irq_handler_%d", irq); + + duk_push_global_stash(ctx); + duk_dup(ctx, 1); // Duplicate the function + duk_put_prop_string(ctx, -2, key); + duk_pop(ctx); // Pop stash + + // Register the C wrapper + irq_register_handler(irq, js_irq_callback); + + duk_push_boolean(ctx, 1); + return 1; +} + void kmain() { + // Initialize IDT and ISRs + isr_install(); + irq_install(); + // Initialize Duktape heap ctx = duk_create_heap_default(); + global_ctx = ctx; // Register native memory write function duk_push_c_function(ctx, native_addrw, 2); @@ -102,6 +213,14 @@ void kmain() duk_push_c_function(ctx, native_ptout, 2); duk_put_global_string(ctx, "$ptout"); + // Register native byte in function + duk_push_c_function(ctx, native_byte_in, 1); + duk_put_global_string(ctx, "$bytein"); + + // Register native byte out function + duk_push_c_function(ctx, native_byte_out, 2); + duk_put_global_string(ctx, "$byteout"); + // Register native dword in function duk_push_c_function(ctx, native_dword_in, 1); duk_put_global_string(ctx, "$dwordin"); @@ -110,6 +229,13 @@ void kmain() duk_push_c_function(ctx, native_dword_out, 2); duk_put_global_string(ctx, "$dwordout"); + // Register native IRQ registration function + duk_push_c_function(ctx, native_irq_register, 2); + duk_put_global_string(ctx, "$irqregister"); + + // Enable interrupts before JavaScript execution + __asm__ volatile("sti"); + // Execute embedded JavaScript code from build/index.js duk_push_string(ctx, embedded_js_code); duk_int_t returnCode = duk_peval(ctx); diff --git a/src/os/src/config/keyboard.ts b/src/os/src/config/keyboard.ts new file mode 100644 index 0000000..3a3f8fd --- /dev/null +++ b/src/os/src/config/keyboard.ts @@ -0,0 +1,57 @@ +export const Keycode = { + Q: 16, + W: 17, + E: 18, + R: 19, + T: 20, + Y: 21, + U: 22, + I: 23, + O: 24, + P: 25, + A: 30, + S: 31, + D: 32, + F: 33, + G: 34, + H: 35, + J: 36, + K: 37, + L: 38, + Z: 44, + X: 45, + C: 46, + V: 47, + B: 48, + N: 49, + M: 50, +}; + +export const RKeycode = { + 16: "Q", + 17: "W", + 18: "E", + 19: "R", + 20: "T", + 21: "Y", + 22: "U", + 23: "I", + 24: "O", + 25: "P", + 30: "A", + 31: "S", + 32: "D", + 33: "F", + 34: "G", + 35: "H", + 36: "J", + 37: "K", + 38: "L", + 44: "Z", + 45: "X", + 46: "C", + 47: "V", + 48: "B", + 49: "N", + 50: "M", +}; diff --git a/src/os/src/kernel/drivers/dev/keyboard.ts b/src/os/src/kernel/drivers/dev/keyboard.ts new file mode 100644 index 0000000..cfb952c --- /dev/null +++ b/src/os/src/kernel/drivers/dev/keyboard.ts @@ -0,0 +1,29 @@ +import { Keycode, RKeycode } from "../../../config/keyboard"; +import { Logger } from "../../../lib/libstd/logger/logger.kmod"; +import { bytein } from "../../../lib/libts/byte"; +import { kmod_cpu_irq_register } from "../../modules/cpu/interrupts"; +import { kmod_terminal_input_handleKeyboardInput } from "../../modules/terminal/input"; + +export function kdriver_dev_keyboard_handleInterrupt(): void { + const scancode = bytein(0x60); + + const key = RKeycode[scancode as keyof typeof RKeycode] as + | keyof typeof Keycode + | undefined; + + if (key == undefined) return; + + kmod_terminal_input_handleKeyboardInput(key); +} + +export function kdriver_dev_keyboard_init(): void { + if (!kmod_cpu_irq_register(1, kdriver_dev_keyboard_handleInterrupt, null)) { + Logger.log("[keyboard] Failed to register keyboard interrupt"); + return; + } + + let key = 0; + while ((key = bytein(0x64) & 1) == 1) { + bytein(0x60); + } +} diff --git a/src/os/src/kernel/index.ts b/src/os/src/kernel/index.ts index dbe0a56..43b26ba 100644 --- a/src/os/src/kernel/index.ts +++ b/src/os/src/kernel/index.ts @@ -22,6 +22,10 @@ import { kmod_graphics_vga_init, kmod_graphics_vga_pushLine, } from "./modules/graphics/graphics.kmod"; +import { + kmod_terminal_input_init, + kmod_terminal_input_onKeyboardInput, +} from "./modules/terminal/input"; export function kmain() { kmod_drivers_init(); @@ -33,14 +37,17 @@ export function kmain() { kmod_filesystem_init(); kmod_filesystem_mount("/sys", sysfs_driver); + Logger.log("[Kernel] Initializing terminal module..."); + kmod_terminal_input_init(); + Logger.log("[Kernel] Initializing PCI devices..."); kdriver_dev_pci_detectDevices(); Logger.log("[Kernel] Initializing VGA module..."); kmod_graphics_vga_init(); kmod_graphics_vga_pushLine("[Kernel] Kernel initialized successfully."); - kmod_graphics_vga_pushLine("Current date: " + getDate().toDateString()); - kmod_graphics_vga_pushLine( - Number(kmod_filesystem_readFile("/sys/pci/0:1:0/vendor")).toString(16) - ); + + kmod_terminal_input_onKeyboardInput(function (keycode) { + kmod_graphics_vga_pushLine(keycode); + }); } diff --git a/src/os/src/kernel/modules/cpu/interrupts.ts b/src/os/src/kernel/modules/cpu/interrupts.ts new file mode 100644 index 0000000..a579b80 --- /dev/null +++ b/src/os/src/kernel/modules/cpu/interrupts.ts @@ -0,0 +1,43 @@ +import { Logger } from "../../../lib/libstd/logger/logger.kmod"; +import type { KInterrupt, KInterruptHandler } from "../../../types/interrupt"; + +const kmod_cpu_irq_data: KInterrupt[] = []; + +export function kmod_cpu_irq_register( + irq: number, + handler: KInterruptHandler, + data: unknown +): boolean { + if (kmod_cpu_irq_data[irq]) { + Logger.log("[IRQ] IRQ " + irq + " is already registered."); + return false; + } + + if (irq > 15) { + Logger.log("[IRQ] IRQ " + irq + " is out of bounds."); + return false; + } + + kmod_cpu_irq_data[irq] = { + handler: handler, + data: data, + }; + + function wrapper(irqNum: number) { + const irqData = kmod_cpu_irq_data[irqNum]; + if (irqData) { + irqData.handler(irqNum, irqData.data); + } + } + + const success = $irqregister(irq, wrapper); + + if (!success) { + Logger.log("[IRQ] Failed to register IRQ " + irq + " with hardware."); + delete kmod_cpu_irq_data[irq]; + return false; + } + + Logger.log("[IRQ] Successfully registered IRQ " + irq); + return true; +} diff --git a/src/os/src/kernel/modules/drivers/drivers.kmod.ts b/src/os/src/kernel/modules/drivers/drivers.kmod.ts index f6fce60..e2159ec 100644 --- a/src/os/src/kernel/modules/drivers/drivers.kmod.ts +++ b/src/os/src/kernel/modules/drivers/drivers.kmod.ts @@ -1,7 +1,12 @@ +import { kdriver_dev_keyboard_init } from "../../drivers/dev/keyboard"; import { kdriver_dev_pci_init } from "../../drivers/dev/pci"; import { kdriver_etc_serial_init } from "../../drivers/etc/serial"; -const drivers = [kdriver_etc_serial_init, kdriver_dev_pci_init]; +const drivers = [ + kdriver_etc_serial_init, + kdriver_dev_pci_init, + kdriver_dev_keyboard_init, +]; export function kmod_drivers_init(): void {} diff --git a/src/os/src/kernel/modules/terminal/input.ts b/src/os/src/kernel/modules/terminal/input.ts new file mode 100644 index 0000000..f10aefd --- /dev/null +++ b/src/os/src/kernel/modules/terminal/input.ts @@ -0,0 +1,21 @@ +import type { Keycode } from "../../../config/keyboard"; + +const kmod_terminal_input_keyboardInputHandlers: Array< + (keycode: keyof typeof Keycode) => void +> = []; + +export function kmod_terminal_input_init(): void {} + +export function kmod_terminal_input_onKeyboardInput( + handler: (keycode: keyof typeof Keycode) => void +) { + kmod_terminal_input_keyboardInputHandlers.push(handler); +} + +export function kmod_terminal_input_handleKeyboardInput( + keycode: keyof typeof Keycode +): void { + for (let i = 0; i < kmod_terminal_input_keyboardInputHandlers.length; i++) { + kmod_terminal_input_keyboardInputHandlers[i]!(keycode); + } +} diff --git a/src/os/src/kernel/modules/terminal/terminal.kmod.ts b/src/os/src/kernel/modules/terminal/terminal.kmod.ts new file mode 100644 index 0000000..4ce4a88 --- /dev/null +++ b/src/os/src/kernel/modules/terminal/terminal.kmod.ts @@ -0,0 +1 @@ +export * from "./input"; diff --git a/src/os/src/lib/libts/byte.ts b/src/os/src/lib/libts/byte.ts index 24fe79e..bded289 100644 --- a/src/os/src/lib/libts/byte.ts +++ b/src/os/src/lib/libts/byte.ts @@ -1,3 +1,11 @@ +export function bytein(port: number): number { + return $bytein(port); +} + +export function byteout(port: number, value: number): void { + $byteout(port, value); +} + export function charc(char: string): number { return char.charCodeAt(0); } diff --git a/src/os/src/types/c/bindings.d.ts b/src/os/src/types/c/bindings.d.ts index ce9b92d..ccedf7d 100644 --- a/src/os/src/types/c/bindings.d.ts +++ b/src/os/src/types/c/bindings.d.ts @@ -1,5 +1,11 @@ declare function $addrw(address: number, value: number): void; declare function $ptin(port: number): number; declare function $ptout(port: number, value: number): void; +declare function $bytein(port: number): number; +declare function $byteout(port: number, value: number): void; declare function $dwordin(port: number): number; declare function $dwordout(port: number, value: number): void; +declare const $irqregister: ( + irq: number, + handler: (irqNum: number) => void +) => boolean; diff --git a/src/os/src/types/interrupt.ts b/src/os/src/types/interrupt.ts new file mode 100644 index 0000000..417db80 --- /dev/null +++ b/src/os/src/types/interrupt.ts @@ -0,0 +1,9 @@ +export type KInterruptHandler = ( + interruptNumber: number, + data?: unknown +) => void; + +export type KInterrupt = { + handler: KInterruptHandler; + data: unknown; +};