diff options
author | dzwdz | 2022-07-16 13:33:00 +0200 |
---|---|---|
committer | dzwdz | 2022-07-16 13:33:00 +0200 |
commit | 912d2e3c7eb1baa71dda2c0a28aa5809eaa96f27 (patch) | |
tree | 4e27f3538466d5fd63a311d50916039a7a15a485 /src/kernel/arch/amd64 | |
parent | 1eeb66af44ab335888410d716d604e569f20866e (diff) |
amd64: barely boot into kernel code
Diffstat (limited to 'src/kernel/arch/amd64')
36 files changed, 1810 insertions, 0 deletions
diff --git a/src/kernel/arch/amd64/32/32 b/src/kernel/arch/amd64/32/32 new file mode 100644 index 0000000..9bd97e6 --- /dev/null +++ b/src/kernel/arch/amd64/32/32 @@ -0,0 +1,130 @@ +#include <kernel/arch/generic.h> +#include <kernel/arch/amd64/gdt.h> +#include <shared/mem.h> +#include <stdbool.h> +#include <stdint.h> + +extern char _isr_mini_stack; + +struct gdt_entry { + uint64_t limit_low : 16; + uint64_t base_low : 24; + uint64_t accessed : 1; // set by the processor + // CODE | DATA + uint64_t rw : 1; // readable? | writeable? + uint64_t conforming : 1; // conforming? | expands down? + uint64_t code : 1; // 1 | 0 + + uint64_t codeordata : 1; // 1 for everything other than TSS and LDT + uint64_t ring : 2; + uint64_t present : 1; // always 1 + uint64_t limit_high : 4; + uint64_t available : 1; // ??? + uint64_t long_mode : 1; + uint64_t x32 : 1; + uint64_t gran : 1; // 1 - 4kb, 0 - 1b + uint64_t base_high : 8; +} __attribute__((packed)); + +struct tss_entry { + uint32_t _unused0; + uint32_t esp0; // kernel mode stack pointer + uint32_t ss0; // kernel mode stack segment + uint8_t _unused1[0x5c]; +} __attribute__((packed)); + +struct lgdt_arg { + uint16_t limit; + uint32_t base; +} __attribute__((packed)); + +__attribute__((section(".shared"))) +static struct gdt_entry GDT[SEG_end]; +__attribute__((section(".shared"))) +static struct tss_entry TSS; +static struct lgdt_arg lgdt_arg; // probably doesn't need to be global + +static void gdt_fillout(struct gdt_entry* entry, uint8_t ring, bool code); +static void gdt_prepare(void); +static void gdt_load(void); + + +static void gdt_fillout(struct gdt_entry* entry, uint8_t ring, bool code) { + *entry = (struct gdt_entry) { + // set up the identity mapping + .limit_low = 0xFFFF, + .limit_high = 0xF, + .gran = 1, // 4KB * 0xFFFFF = (almost) 4GB + .base_low = 0, + .base_high = 0, + + .ring = ring, + .code = code, + + .accessed = 0, + .rw = 1, + .conforming = 0, + .codeordata = 1, + .present = 1, + .long_mode = 0, // ??? + .available = 1, // ??? + .x32 = 1, + }; +} + +static void gdt_prepare(void) { + GDT[SEG_null].present = 0; + + gdt_fillout(&GDT[SEG_r0code], 0, true); + gdt_fillout(&GDT[SEG_r0data], 0, false); + gdt_fillout(&GDT[SEG_r3code], 3, true); + gdt_fillout(&GDT[SEG_r3data], 3, false); + + // tss + memset(&TSS, 0, sizeof(TSS)); + TSS.ss0 = SEG_r0data << 3; // kernel data segment + TSS.esp0 = (uintptr_t) &_isr_mini_stack; + + GDT[SEG_TSS] = (struct gdt_entry) { + .limit_low = sizeof(TSS), + .limit_high = sizeof(TSS) >> 16, + .gran = 0, + .base_low = (uintptr_t) &TSS, + .base_high = ((uintptr_t) &TSS) >> 24, + + .accessed = 1, // 1 for TSS + .rw = 0, // 1 busy / 0 not busy + .conforming = 0, // 0 for TSS + .code = 1, // 32bit + .codeordata = 0, // is a system entry + .ring = 3, + .present = 1, + .available = 0, // 0 for TSS + .long_mode = 0, + .x32 = 0, // idk + }; +} + +static void gdt_load(void) { + lgdt_arg.limit = sizeof(GDT) - 1; + lgdt_arg.base = (uintptr_t) &GDT; + asm("lgdt (%0)" + : : "r" (&lgdt_arg) : "memory"); + + asm("ltr %%ax" + : : "a" (SEG_TSS << 3 | 3) : "memory"); + + // update all segment registers + gdt_farjump(SEG_r0code << 3); + asm("mov %0, %%ds;" + "mov %0, %%ss;" + "mov %0, %%es;" + "mov %0, %%fs;" + "mov %0, %%gs;" + : : "r" (SEG_r0data << 3) : "memory"); +} + +void gdt_init(void) { + gdt_prepare(); + gdt_load(); +} diff --git a/src/kernel/arch/amd64/32/boot.s b/src/kernel/arch/amd64/32/boot.s new file mode 100644 index 0000000..eb33c28 --- /dev/null +++ b/src/kernel/arch/amd64/32/boot.s @@ -0,0 +1,86 @@ +.section .text +.global _start +.type _start, @function +_start: + mov $_stack_top, %esp + push %ebx // save the address of the multiboot struct + + mov $0x80000000, %eax // check CPUID extended functions + cpuid + cmp $0x80000001, %eax + jb panic_early + + mov $0x80000001, %eax + cpuid + test $(1<<29), %edx // check long mode support + jz panic_early + + mov %cr4, %eax + or $(1<<5), %eax // PAE + mov %eax, %cr4 + + call pml4_identity_init + mov $pml4_identity, %eax + mov %eax, %cr3 + + mov $0xC0000080, %ecx // EFER MSR + rdmsr + or $(1 | 1<<8 | 1<<11), %eax // sysenter | long mode | NX + wrmsr + + mov %cr0, %eax + or $0x80000000, %eax + mov %eax, %cr0 + + call gdt_init + lgdt (lgdt_arg) + + pop %edi + + // TODO import gdt.h for globals + mov $(2<<3), %eax + mov %eax, %ds // SEG_r0data + mov %eax, %ss + mov %eax, %es + mov %eax, %fs + mov %eax, %gs + + ljmp $(1<<3), $boot64 // SEG_r0code + +panic_early: + // output a vga Fuck + movl $0x4F754F46, 0xB872A + movl $0x4F6B4F63, 0xB872E + jmp cpu_halt + +// TODO not part of anything yet + call sysenter_setup + // TODO will fail + push %ebx // address of the Multiboot struct + call kmain_early + +.global cpu_shutdown +.type cpu_shutdown, @function +cpu_shutdown: +/* This quits QEMU. While I couldn't find this officially documented anywhere, + * it is used by QEMU in tests/tcg/i386/system/boot.S (as of commit 40d6ee), so + * I assume that this is safe-ish to use */ + mov $0x604, %edx + mov $0x2000, %eax + outw %ax, %dx + +.global cpu_halt +.type cpu_halt, @function +cpu_halt: + cli +1: hlt + jmp 1b + + +.global cpu_pause +.type cpu_pause, @function +cpu_pause: + sti + hlt + cli + ret diff --git a/src/kernel/arch/amd64/32/farjump.s b/src/kernel/arch/amd64/32/farjump.s new file mode 100644 index 0000000..2885d2b --- /dev/null +++ b/src/kernel/arch/amd64/32/farjump.s @@ -0,0 +1,8 @@ +.section .text +.global gdt_farjump +.type gdt_farjump, @function +gdt_farjump: + /* retf pops off the return address and code segment off the stack. + * it turns out that in the i386 cdecl calling convention they're in + * the correct place already. */ + retf diff --git a/src/kernel/arch/amd64/32/gdt.c b/src/kernel/arch/amd64/32/gdt.c new file mode 100644 index 0000000..2229330 --- /dev/null +++ b/src/kernel/arch/amd64/32/gdt.c @@ -0,0 +1,118 @@ +#include <kernel/arch/amd64/32/gdt.h> +#include <kernel/arch/generic.h> +#include <stdbool.h> +#include <stdint.h> + +extern char _isr_mini_stack; + +struct gdt_entry { + uint64_t limit_low : 16; + uint64_t base_low : 24; + uint64_t accessed : 1; // set by the processor + // CODE | DATA + uint64_t rw : 1; // readable? | writeable? + uint64_t conforming : 1; // conforming? | expands down? + uint64_t code : 1; // 1 | 0 + + uint64_t codeordata : 1; // 1 for everything other than TSS and LDT + uint64_t ring : 2; + uint64_t present : 1; // always 1 + uint64_t limit_high : 4; + uint64_t available : 1; // ??? + uint64_t long_mode : 1; + uint64_t x32 : 1; + uint64_t gran : 1; // 1 - 4kb, 0 - 1b + uint64_t base_high : 8; +} __attribute__((packed)); + +struct tss_entry { + uint32_t reserved0; + uint64_t rsp[3]; + uint64_t ist[8]; + uint64_t reserved2; + uint16_t reserved3; + uint16_t iopb; +} __attribute__((packed)); + +struct lgdt_arg { + uint16_t limit; + uint32_t base; +} __attribute__((packed)); + +__attribute__((section(".shared"))) +struct gdt_entry GDT[SEG_end]; +__attribute__((section(".shared"))) +struct tss_entry TSS; + +struct lgdt_arg lgdt_arg; + + +static void *memset32(void *s, int c, size_t n) { + uint8_t *s2 = s; + for (size_t i = 0; i < n; i++) + s2[i] = c; + return s; +} + + +static void gdt_fillout(struct gdt_entry* entry, uint8_t ring, bool code) { + *entry = (struct gdt_entry) { + // set up the identity mapping + .limit_low = 0xFFFF, + .limit_high = 0xF, + .gran = 1, + .base_low = 0, + .base_high = 0, + + .ring = ring, + .code = code, + + .accessed = 0, + .rw = 1, + .conforming = 0, + .codeordata = 1, + .present = 1, + .long_mode = 1, + .available = 1, + .x32 = 0, + }; +} + +void gdt_init(void) { + GDT[SEG_null].present = 0; + + gdt_fillout(&GDT[SEG_r0code], 0, true); + gdt_fillout(&GDT[SEG_r0data], 0, false); + gdt_fillout(&GDT[SEG_r3code], 3, true); + gdt_fillout(&GDT[SEG_r3data], 3, false); + + lgdt_arg.limit = sizeof(GDT) - 1; + lgdt_arg.base = (uint64_t) &GDT; + + + memset32(&TSS, 0, sizeof(TSS)); + for (int i = 0; i < 3; i++) + TSS.rsp[i] = (uint64_t) &_isr_mini_stack; + TSS.ist[1] = (uint64_t) &_isr_mini_stack; + + uint64_t tss_addr = (uint64_t) &TSS; + GDT[SEG_TSS] = (struct gdt_entry) { + .limit_low = sizeof(TSS), + .limit_high = sizeof(TSS) >> 16, + .gran = 0, + .base_low = tss_addr, + .base_high = tss_addr >> 24, + + .accessed = 1, + .rw = 0, + .conforming = 0, + .code = 1, + .codeordata = 0, + .ring = 0, // was 3 pre-port + .present = 1, + .available = 1, + .long_mode = 0, + .x32 = 0, + }; + *((uint64_t*)&GDT[SEG_TSS2]) = (tss_addr >> 32); +} diff --git a/src/kernel/arch/amd64/32/gdt.h b/src/kernel/arch/amd64/32/gdt.h new file mode 100644 index 0000000..fbaf681 --- /dev/null +++ b/src/kernel/arch/amd64/32/gdt.h @@ -0,0 +1,17 @@ +#pragma once + +enum { + SEG_null, + // order dictated by SYSENTER + SEG_r0code, + SEG_r0data, + SEG_r3code, + SEG_r3data, + SEG_TSS, + SEG_TSS2, + + SEG_end +}; + +void gdt_init(void); +void gdt_farjump(int segment); diff --git a/src/kernel/arch/amd64/32/paging.c b/src/kernel/arch/amd64/32/paging.c new file mode 100644 index 0000000..975dd98 --- /dev/null +++ b/src/kernel/arch/amd64/32/paging.c @@ -0,0 +1,131 @@ +#include <stddef.h> +#include <stdint.h> + +struct pml4e { + uint64_t present : 1; + uint64_t writeable : 1; + uint64_t user : 1; + uint64_t writethru : 1; + + uint64_t uncached : 1; + uint64_t accessed : 1; + uint64_t _unused1 : 1; + uint64_t reserved : 1; // always 0 + + uint64_t _unused2 : 3; + uint64_t _unused3 : 1; // HLAT thing + + uint64_t address : 40; + + uint64_t _unused4 : 11; + uint64_t noexec : 1; +} __attribute__((packed)); + +struct pdpte { // page directory pointer table entry, 1gb page | 512 * pde + uint64_t present : 1; + uint64_t writeable : 1; + uint64_t user : 1; + uint64_t writethru : 1; + + uint64_t uncached : 1; + uint64_t accessed : 1; + uint64_t _unused1 : 1; + uint64_t large : 1; // 1gb page + + uint64_t _unused2 : 3; + uint64_t _unused3 : 1; // HLAT + + uint64_t address : 40; + + uint64_t _unused4 : 11; + uint64_t noexec : 1; +} __attribute__((packed)); + +struct pde { // page directory entry, 2mb page | 512 * pte + uint64_t present : 1; + uint64_t writeable : 1; + uint64_t user : 1; + uint64_t writethru : 1; + + uint64_t uncached : 1; + uint64_t accessed : 1; + uint64_t dirty : 1; // only if large + uint64_t large : 1; // 2mb + + uint64_t global : 1; // only if large ; TODO enable CR4.PGE + uint64_t _unused2 : 2; + uint64_t _unused3 : 1; // HLAT + + uint64_t address : 40; // highest bit - PAT + + uint64_t _unused4 : 7; + uint64_t pke : 4; + uint64_t noexec : 1; +} __attribute__((packed)); + +struct pte { // page table entry, 4kb page + uint64_t present : 1; + uint64_t writeable : 1; + uint64_t user : 1; + uint64_t writethru : 1; + + uint64_t uncached : 1; + uint64_t accessed : 1; + uint64_t dirty : 1; + uint64_t pat : 1; + + uint64_t global : 1; // TODO enable CR4.PGE + uint64_t _unused2 : 2; + uint64_t _unused3 : 1; // HLAT + + uint64_t address : 40; + + uint64_t _unused4 : 7; + uint64_t pke : 4; + uint64_t noexec : 1; +} __attribute__((packed)); + +__attribute__((aligned(4096))) +struct pml4e pml4_identity[512]; + +__attribute__((aligned(4096))) +struct pdpte pdpte_low[512]; // 0-512gb + +__attribute__((aligned(4096))) +struct pde pde_low[512]; // 0-1gb + + +static void *memset32(void *s, int c, size_t n) { + uint8_t *s2 = s; + for (size_t i = 0; i < n; i++) + s2[i] = c; + return s; +} + + +void pml4_identity_init(void) { + memset32(pml4_identity, 0, sizeof pml4_identity); + memset32(pdpte_low, 0, sizeof pdpte_low); + memset32(pde_low, 0, sizeof pde_low); + + pml4_identity[0] = (struct pml4e) { + .present = 1, + .writeable = 1, + .address = ((uintptr_t)pdpte_low) >> 12, + }; + + pdpte_low[0] = (struct pdpte) { + .present = 1, + .writeable = 1, + .address = ((uintptr_t)pde_low) >> 12, + }; + + for (int i = 0; i < 512; i++) { + pde_low[i] = (struct pde) { + .present = 1, + .writeable = 1, + .large = 1, + .address = (i * 2 * 1024 * 1024) >> 12, + }; + } +} diff --git a/src/kernel/arch/amd64/ata.c b/src/kernel/arch/amd64/ata.c new file mode 100644 index 0000000..371730e --- /dev/null +++ b/src/kernel/arch/amd64/ata.c @@ -0,0 +1,151 @@ +#include <kernel/arch/amd64/ata.h> +#include <kernel/arch/amd64/port_io.h> +#include <kernel/panic.h> +#include <stdbool.h> + +static struct { + enum { + DEV_UNKNOWN, + DEV_PATA, + DEV_PATAPI, + } type; + uint32_t sectors; +} ata_drives[4]; + +enum { + DATA = 0, + FEAT = 1, + SCNT = 2, + LBAlo = 3, + LBAmid = 4, + LBAhi = 5, + DRV = 6, + CMD = 7, + STATUS = 7, + + /* note: the OSDev wiki uses a different base port for the control port + * however i can just use this offset and stuff will just work tm */ + CTRL = 0x206, +}; // offsets + +// get I/O port base for drive +static uint16_t ata_iobase(int drive) { + bool secondary = drive&2; + return secondary ? 0x170 : 0x1F0; +} + +static void ata_400ns(void) { + uint16_t base = ata_iobase(0); // doesn't matter + for (int i = 0; i < 4; i++) + port_in8(base + STATUS); +} + +static void ata_driveselect(int drive, int block) { + uint8_t v = 0xE0; + if (drive&1) // slave? + v |= 0x10; // set drive number bit + // TODO account for block + port_out8(ata_iobase(drive) + DRV, v); +} + +static void ata_softreset(int drive) { + uint16_t iobase = ata_iobase(drive); + port_out8(iobase + CTRL, 4); + port_out8(iobase + CTRL, 0); + ata_400ns(); + + uint16_t timeout = 10000; + while (--timeout) { // TODO separate polling function + uint8_t v = port_in8(iobase + STATUS); + if (v & 0x80) continue; // still BSY, continue + if (v & 0x40) break; // RDY, break + // TODO check for ERR + } +} + +static void ata_detecttype(int drive) { + ata_softreset(drive); + ata_driveselect(drive, 0); + ata_400ns(); + switch (port_in8(ata_iobase(drive) + LBAmid)) { + case 0: + ata_drives[drive].type = DEV_PATA; + break; + case 0x14: + ata_drives[drive].type = DEV_PATAPI; + break; + default: + ata_drives[drive].type = DEV_UNKNOWN; + break; + } +} + +static bool ata_identify(int drive) { + uint16_t iobase = ata_iobase(drive); + uint16_t data[256]; + uint8_t v; + + ata_driveselect(drive, 0); + for (int i = 2; i < 6; i++) + port_out8(iobase + i, 0); + switch (ata_drives[drive].type) { + case DEV_PATA: + port_out8(iobase + CMD, 0xEC); // IDENTIFY + break; + case DEV_PATAPI: + port_out8(iobase + CMD, 0xA1); // IDENTIFY PACKET DEVICE + break; + default: panic_invalid_state(); + } + + v = port_in8(iobase + STATUS); + if (v == 0) return false; // nonexistent drive + while (port_in8(iobase + STATUS) & 0x80); + + /* pool until bit 3 (DRQ) or 0 (ERR) is set */ + while (!((v = port_in8(iobase + STATUS) & 0x9))); + if (v & 1) return false; /* ERR was set, bail */ + + for (int i = 0; i < 256; i++) + data[i] = port_in16(iobase); + ata_drives[drive].sectors = data[60] | (data[61] << 16); + return true; +} + +void ata_init(void) { + for (int i = 0; i < 4; i++) { + ata_detecttype(i); + if (ata_drives[i].type == DEV_PATA) + ata_identify(i); + } +} + +bool ata_available(int drive) { + return ata_drives[drive].type != DEV_UNKNOWN; +} + +int ata_read(int drive, uint32_t lba, void *buf) { + assert(ata_drives[drive].type == DEV_PATA); + int iobase = ata_iobase(drive); + + ata_driveselect(drive, lba); + port_out8(iobase + FEAT, 0); // supposedly pointless + port_out8(iobase + SCNT, 1); // sector count + port_out8(iobase + LBAlo, lba); + port_out8(iobase + LBAmid, lba >> 8); + port_out8(iobase + LBAhi, lba >> 16); + port_out8(iobase + CMD, 0x20); // READ SECTORS + + for (;;) { // TODO separate polling function + uint8_t v = port_in8(iobase + STATUS); + if (v & 0x80) continue; // still BSY, continue + if (v & 0x40) break; // RDY, break + // TODO check for ERR + } + + uint16_t *b = buf; + for (int i = 0; i < 256; i++) + b[i] = port_in16(iobase); + + return 512; +} diff --git a/src/kernel/arch/amd64/ata.h b/src/kernel/arch/amd64/ata.h new file mode 100644 index 0000000..82f4f81 --- /dev/null +++ b/src/kernel/arch/amd64/ata.h @@ -0,0 +1,7 @@ +#pragma once +#include <stdbool.h> +#include <stdint.h> + +void ata_init(void); +bool ata_available(int drive); +int ata_read(int drive, uint32_t lba, void *buf); diff --git a/src/kernel/arch/amd64/boot.c b/src/kernel/arch/amd64/boot.c new file mode 100644 index 0000000..0e61ed3 --- /dev/null +++ b/src/kernel/arch/amd64/boot.c @@ -0,0 +1,51 @@ +#include <kernel/arch/generic.h> +#include <kernel/arch/amd64/32/gdt.h> +#include <kernel/arch/amd64/ata.h> +#include <kernel/arch/amd64/boot.h> +#include <kernel/arch/amd64/driver/fsroot.h> +#include <kernel/arch/amd64/driver/ps2.h> +#include <kernel/arch/amd64/driver/serial.h> +#include <kernel/arch/amd64/interrupts/idt.h> +#include <kernel/arch/amd64/interrupts/irq.h> +#include <kernel/arch/amd64/multiboot.h> +#include <kernel/arch/amd64/tty/tty.h> +#include <kernel/main.h> +#include <kernel/mem/alloc.h> +#include <kernel/panic.h> + +static void find_init(struct multiboot_info *multiboot, struct kmain_info *info) +{ + struct multiboot_mod *module = (void*)multiboot->mods; + kprintf("mods count 0x%x", multiboot->mods_count); + if (multiboot->mods_count < 1) { + kprintf("can't find init! "); + panic_invalid_state(); + } + info->init.at = module->start; + info->init.size = module->end - module->start; +} + +void kmain_early(struct multiboot_info *multiboot) { + struct kmain_info info; + + tty_init(); + kprintf("idt..."); + idt_init(); + kprintf("irq..."); + irq_init(); + + info.memtop = (void*) (multiboot->mem_upper * 1024); + find_init(multiboot, &info); + kprintf("mem...\n"); + mem_init(&info); + + kprintf("rootfs..."); + vfs_root_init(); + ps2_init(); + serial_init(); + + kprintf("ata..."); + ata_init(); + + kmain(info); +} diff --git a/src/kernel/arch/amd64/boot.h b/src/kernel/arch/amd64/boot.h new file mode 100644 index 0000000..98adff5 --- /dev/null +++ b/src/kernel/arch/amd64/boot.h @@ -0,0 +1,4 @@ +#pragma once +#include <kernel/arch/amd64/multiboot.h> + +void kmain_early(struct multiboot_info *multiboot); diff --git a/src/kernel/arch/amd64/boot64.s b/src/kernel/arch/amd64/boot64.s new file mode 100644 index 0000000..1dca0ca --- /dev/null +++ b/src/kernel/arch/amd64/boot64.s @@ -0,0 +1,13 @@ +.global boot64 +boot64: + lgdt (lgdt_arg) // try reloading gdt again + mov $(5 << 3 | 3), %ax // SEG_TSS + ltr %ax + + xchgw %bx, %bx + push %rdi // preserve multiboot struct + call sysenter_setup + pop %rdi + + // multiboot struct in %rdi + jmp kmain_early diff --git a/src/kernel/arch/amd64/debug.c b/src/kernel/arch/amd64/debug.c new file mode 100644 index 0000000..52127da --- /dev/null +++ b/src/kernel/arch/amd64/debug.c @@ -0,0 +1,18 @@ +#include <kernel/arch/generic.h> + +void *debug_caller(size_t depth) { + void **rbp; + asm("mov %%rbp, %0" + : "=r" (rbp)); + while (depth--) { + if (!rbp) return NULL; + rbp = *rbp; + } + return rbp[1]; +} + +void debug_stacktrace(void) { + for (size_t i = 0; i < 16; i++) { + kprintf(" k/%08x\n", (uintptr_t)debug_caller(i)); + } +} diff --git a/src/kernel/arch/amd64/driver/fsroot.c b/src/kernel/arch/amd64/driver/fsroot.c new file mode 100644 index 0000000..02a4a21 --- /dev/null +++ b/src/kernel/arch/amd64/driver/fsroot.c @@ -0,0 +1,146 @@ +#include <kernel/arch/amd64/ata.h> +#include <kernel/mem/virt.h> +#include <kernel/panic.h> +#include <kernel/proc.h> +#include <kernel/util.h> +#include <kernel/arch/amd64/driver/fsroot.h> +#include <shared/mem.h> +#include <stdbool.h> + +enum { + HANDLE_ROOT, + HANDLE_VGA, + HANDLE_ATA_ROOT, + HANDLE_ATA, + _SKIP = HANDLE_ATA + 4, +}; + +static bool exacteq(struct vfs_request *req, const char *str) { + size_t len = strlen(str); + assert(req->input.kern); + return req->input.len == len && !memcmp(req->input.buf_kern, str, len); +} + +/* truncates the length */ +static void req_preprocess(struct vfs_request *req, size_t max_len) { + if (req->offset < 0) { + // TODO negative offsets + req->offset = 0; + } + + if (req->offset >= capped_cast32(max_len)) { + req->input.len = 0; + req->output.len = 0; + req->offset = max_len; + return; + } + + req->input.len = min(req->input.len, max_len - req->offset); + req->output.len = min(req->output.len, max_len - req->offset); + + assert(req->input.len + req->offset <= max_len); + assert(req->input.len + req->offset <= max_len); +} + + +static int handle(struct vfs_request *req) { + assert(req->caller); + int id = (int)req->id; + switch (req->type) { + case VFSOP_OPEN: + if (exacteq(req, "/")) return HANDLE_ROOT; + if (exacteq(req, "/vga")) return HANDLE_VGA; + + if (exacteq(req, "/ata/")) return HANDLE_ATA_ROOT; + if (exacteq(req, "/ata/0")) + return ata_available(0) ? HANDLE_ATA+0 : -1; + if (exacteq(req, "/ata/1")) + return ata_available(1) ? HANDLE_ATA+1 : -1; + if (exacteq(req, "/ata/2")) + return ata_available(2) ? HANDLE_ATA+2 : -1; + if (exacteq(req, "/ata/3")) + return ata_available(3) ? HANDLE_ATA+3 : -1; + + return -1; + + case VFSOP_READ: + switch (id) { + case HANDLE_ROOT: { + // TODO document directory read format + const char src[] = + "vga\0" + "com1\0" + "ps2\0" + "ata/"; + req_preprocess(req, sizeof src); + virt_cpy_to(req->caller->pages, req->output.buf, + src + req->offset, req->output.len); + return req->output.len; + } + case HANDLE_VGA: { + char *vga = (void*)0xB8000; + req_preprocess(req, 80*25*2); + virt_cpy_to(req->caller->pages, req->output.buf, + vga + req->offset, req->output.len); + return req->output.len; + } + case HANDLE_ATA_ROOT: { + char list[8] = {}; + size_t len = 0; + for (int i = 0; i < 4; i++) { + if (ata_available(i)) { + list[len] = '0' + i; + len += 2; + } + } + req_preprocess(req, len); + virt_cpy_to(req->caller->pages, req->output.buf, + list + req->offset, req->output.len); + return req->output.len; + } + case HANDLE_ATA: case HANDLE_ATA+1: + case HANDLE_ATA+2: case HANDLE_ATA+3: { + if (req->offset < 0) return 0; + char buf[512]; + uint32_t sector = req->offset / 512; + size_t len = min(req->output.len, 512 - ((size_t)req->offset & 511)); + ata_read(id - HANDLE_ATA, sector, buf); + virt_cpy_to(req->caller->pages, req->output.buf, buf, len); + return len; + } + default: panic_invalid_state(); + } + + case VFSOP_WRITE: + switch (id) { + case HANDLE_VGA: { + void *vga = (void*)0xB8000; + req_preprocess(req, 80*25*2); + virt_cpy_from(req->caller->pages, vga + req->offset, + req->input.buf, req->input.len); + return req->input.len; + } + default: return -1; + } + + case VFSOP_CLOSE: + return 0; + + default: panic_invalid_state(); + } +} + +static void accept(struct vfs_request *req) { + if (req->caller) { + vfsreq_finish_short(req, handle(req)); + } else { + vfsreq_finish_short(req, -1); + } +} + +static bool is_ready(struct vfs_backend __attribute__((unused)) *self) { + return true; +} + +static struct vfs_backend backend = BACKEND_KERN(is_ready, accept); +void vfs_root_init(void) { vfs_mount_root_register("", &backend); } diff --git a/src/kernel/arch/amd64/driver/fsroot.h b/src/kernel/arch/amd64/driver/fsroot.h new file mode 100644 index 0000000..dd72202 --- /dev/null +++ b/src/kernel/arch/amd64/driver/fsroot.h @@ -0,0 +1,2 @@ +#pragma once +void vfs_root_init(void); diff --git a/src/kernel/arch/amd64/driver/ps2.c b/src/kernel/arch/amd64/driver/ps2.c new file mode 100644 index 0000000..8a5a078 --- /dev/null +++ b/src/kernel/arch/amd64/driver/ps2.c @@ -0,0 +1,61 @@ +#include <kernel/arch/amd64/driver/ps2.h> +#include <kernel/arch/amd64/interrupts/irq.h> +#include <kernel/mem/virt.h> +#include <kernel/panic.h> +#include <kernel/vfs/request.h> +#include <shared/container/ring.h> +#include <shared/mem.h> + +#define BACKLOG_CAPACITY 64 +static volatile uint8_t backlog_buf[BACKLOG_CAPACITY]; +static volatile ring_t backlog = {(void*)backlog_buf, BACKLOG_CAPACITY, 0, 0}; + +static void accept(struct vfs_request *req); +static bool is_ready(struct vfs_backend *self); + +static struct vfs_request *blocked_on = NULL; +static struct vfs_backend backend = BACKEND_KERN(is_ready, accept); +void ps2_init(void) { vfs_mount_root_register("/ps2", &backend); } + + +void ps2_recv(uint8_t s) { + ring_put1b((void*)&backlog, s); + if (blocked_on) { + accept(blocked_on); + blocked_on = NULL; + vfs_backend_tryaccept(&backend); + } +} + +static void accept(struct vfs_request *req) { + // when you fix something here go also fix it in the COM1 driver + static uint8_t buf[32]; // pretty damn stupid + int ret; + bool valid; + switch (req->type) { + case VFSOP_OPEN: + valid = req->input.len == 0; + vfsreq_finish_short(req, valid ? 0 : -1); + break; + case VFSOP_READ: + if (ring_size((void*)&backlog) == 0) { + // nothing to read + blocked_on = req; + } else if (req->caller) { + ret = clamp(0, req->output.len, sizeof buf); + ret = ring_get((void*)&backlog, buf, ret); + virt_cpy_to(req->caller->pages, req->output.buf, buf, ret); + vfsreq_finish_short(req, ret); + } else { + vfsreq_finish_short(req, -1); + } + break; + default: + vfsreq_finish_short(req, -1); + break; + } +} + +static bool is_ready(struct vfs_backend __attribute__((unused)) *self) { + return blocked_on == NULL; +} diff --git a/src/kernel/arch/amd64/driver/ps2.h b/src/kernel/arch/amd64/driver/ps2.h new file mode 100644 index 0000000..54e8fb2 --- /dev/null +++ b/src/kernel/arch/amd64/driver/ps2.h @@ -0,0 +1,5 @@ +#pragma once +#include <stdint.h> + +void ps2_recv(uint8_t s); +void ps2_init(void); diff --git a/src/kernel/arch/amd64/driver/serial.c b/src/kernel/arch/amd64/driver/serial.c new file mode 100644 index 0000000..6dda657 --- /dev/null +++ b/src/kernel/arch/amd64/driver/serial.c @@ -0,0 +1,114 @@ +#include <kernel/arch/amd64/driver/serial.h> +#include <kernel/arch/amd64/interrupts/irq.h> +#include <kernel/arch/amd64/port_io.h> +#include <kernel/mem/virt.h> +#include <kernel/panic.h> +#include <shared/container/ring.h> +#include <shared/mem.h> +#include <stdint.h> + +#define BACKLOG_CAPACITY 64 +static volatile uint8_t backlog_buf[BACKLOG_CAPACITY]; +static volatile ring_t backlog = {(void*)backlog_buf, BACKLOG_CAPACITY, 0, 0}; + +static const int COM1 = 0x3f8; + +static void accept(struct vfs_request *req); +static bool is_ready(struct vfs_backend *self); + +static struct vfs_request *blocked_on = NULL; +static struct vfs_backend backend = BACKEND_KERN(is_ready, accept); +void serial_init(void) { vfs_mount_root_register("/com1", &backend); } + + +static void serial_selftest(void) { + char b = 0x69; + port_out8(COM1 + 4, 0b00011110); // enable loopback mode + port_out8(COM1, b); + assert(port_in8(COM1) == b); +} + +void serial_preinit(void) { + // see https://www.sci.muni.cz/docs/pc/serport.txt + // set baud rate divisor + port_out8(COM1 + 3, 0b10000000); // enable DLAB + port_out8(COM1 + 0, 0x01); // divisor = 1 (low byte) + port_out8(COM1 + 1, 0x00); // (high byte) + + port_out8(COM1 + 3, 0b00000011); // 8 bits, no parity, one stop bit + port_out8(COM1 + 1, 0x01); // enable the Data Ready IRQ + port_out8(COM1 + 2, 0b11000111); // enable FIFO with 14-bit trigger level (???) + + serial_selftest(); + + port_out8(COM1 + 4, 0b00001111); // enable everything in the MCR +} + + +void serial_irq(void) { + ring_put1b((void*)&backlog, port_in8(COM1)); + if (blocked_on) { + accept(blocked_on); + blocked_on = NULL; + vfs_backend_tryaccept(&backend); + } +} + + +static void serial_putchar(char c) { + while ((port_in8(COM1 + 5) & 0x20) == 0); // wait for THRE + port_out8(COM1, c); +} + +void serial_write(const char *buf, size_t len) { + for (size_t i = 0; i < len; i++) + serial_putchar(buf[i]); +} + + +static void accept(struct vfs_request *req) { + static char buf[32]; + int ret; + bool valid; + switch (req->type) { + case VFSOP_OPEN: + valid = req->input.len == 0; + vfsreq_finish_short(req, valid ? 0 : -1); + break; + case VFSOP_READ: + if (ring_size((void*)&backlog) == 0) { + /* nothing to read, join queue */ + assert(!req->postqueue_next); + struct vfs_request **slot = &blocked_on; + while (*slot) + slot = &(*slot)->postqueue_next; + *slot = req; + } else if (req->caller) { + ret = clamp(0, req->output.len, sizeof buf); + ret = ring_get((void*)&backlog, buf, ret); + virt_cpy_to(req->caller->pages, req->output.buf, buf, ret); + vfsreq_finish_short(req, ret); + } else { + vfsreq_finish_short(req, -1); + } + break; + case VFSOP_WRITE: + if (req->caller) { + struct virt_iter iter; + virt_iter_new(&iter, req->input.buf, req->input.len, + req->caller->pages, true, false); + while (virt_iter_next(&iter)) + serial_write(iter.frag, iter.frag_len); + ret = iter.prior; + } else ret = -1; + vfsreq_finish_short(req, ret); + break; + default: + vfsreq_finish_short(req, -1); + break; + } +} + +static bool is_ready(struct vfs_backend __attribute__((unused)) *self) { + return true; +} diff --git a/src/kernel/arch/amd64/driver/serial.h b/src/kernel/arch/amd64/driver/serial.h new file mode 100644 index 0000000..6a4876e --- /dev/null +++ b/src/kernel/arch/amd64/driver/serial.h @@ -0,0 +1,10 @@ +#pragma once +#include <kernel/vfs/request.h> +#include <stdbool.h> +#include <stddef.h> + +void serial_preinit(void); +void serial_irq(void); +void serial_write(const char *buf, size_t len); + +void serial_init(void); diff --git a/src/kernel/arch/amd64/interrupts/idt.c b/src/kernel/arch/amd64/interrupts/idt.c new file mode 100644 index 0000000..9c71000 --- /dev/null +++ b/src/kernel/arch/amd64/interrupts/idt.c @@ -0,0 +1,71 @@ +#include <kernel/arch/amd64/32/gdt.h> +#include <kernel/arch/amd64/interrupts/idt.h> +#include <kernel/arch/amd64/interrupts/isr.h> +#include <kernel/panic.h> +#include <stdbool.h> +#include <stdint.h> + +struct idt_entry { + uint16_t offset_low; + uint16_t seg; + uint8_t ist; + uint8_t type : 4; // 0xE - interrupt, 0xF - trap + uint8_t zero1 : 1; + uint8_t ring : 2; + uint8_t present : 1; + uint16_t offset_mid; + uint32_t offset_high; + uint32_t zero2; +} __attribute__((packed)); + +// is exactly the same as lgdt_arg, i should combine them into a single struct +// later +struct lidt_arg { + uint16_t limit; + uintptr_t base; +} __attribute__((packed)); + +__attribute__((section(".shared"))) +static struct idt_entry IDT[256]; +static struct lidt_arg lidt_arg; + +static void idt_prepare(void); +static void idt_load(void); +static void idt_test(void); + + +static void idt_prepare(void) { + for (int i = 0; i < 256; i++) { + uintptr_t offset = (uintptr_t) &_isr_stubs + i * 8; + + IDT[i] = (struct idt_entry) { + .offset_low = offset, + .offset_mid = offset >> 16, + .offset_high = offset >> 32, + .seg = SEG_r0code << 3, + .present = 1, + .type = 0xE, + .ist = 1, + }; + } +} + +static void idt_load(void) { + lidt_arg.limit = sizeof(IDT) - 1; + lidt_arg.base = (uintptr_t) &IDT; + asm("lidt (%0)" : : "r" (&lidt_arg) : "memory"); +} + +static void idt_test(void) { + kprintf("idt test?\n"); + asm("xchgw %%bx, %%bx" ::: "memory"); + asm("int $0x34" : : : "memory"); + assert(isr_test_interrupt_called); + kprintf("done.\n"); +} + +void idt_init(void) { + idt_prepare(); + idt_load(); + idt_test(); +} diff --git a/src/kernel/arch/amd64/interrupts/idt.h b/src/kernel/arch/amd64/interrupts/idt.h new file mode 100644 index 0000000..6576cf9 --- /dev/null +++ b/src/kernel/arch/amd64/interrupts/idt.h @@ -0,0 +1,3 @@ +#pragma once + +void idt_init(void); diff --git a/src/kernel/arch/amd64/interrupts/irq.c b/src/kernel/arch/amd64/interrupts/irq.c new file mode 100644 index 0000000..74bc48c --- /dev/null +++ b/src/kernel/arch/amd64/interrupts/irq.c @@ -0,0 +1,33 @@ +#include <kernel/arch/amd64/interrupts/irq.h> +#include <kernel/arch/amd64/port_io.h> +#include <stdint.h> + +static const int PIC1 = 0x20; +static const int PIC2 = 0xA0; + +void irq_init(void) { + port_out8(PIC1, 0x11); /* start init sequence */ + port_out8(PIC2, 0x11); + + port_out8(PIC1+1, 0x20); /* interrupt offsets */ + port_out8(PIC2+1, 0x30); + + port_out8(PIC1+1, 0x4); /* just look at the osdev wiki lol */ + port_out8(PIC2+1, 0x2); + + port_out8(PIC1+1, 0x1); /* 8086 mode */ + port_out8(PIC2+1, 0x1); + + uint8_t mask = 0xff; + mask &= ~(1 << 1); // keyboard + mask &= ~(1 << 4); // COM1 + + port_out8(PIC1+1, mask); + port_out8(PIC2+1, 0xff); +} + +void irq_eoi(uint8_t line) { + port_out8(PIC1, 0x20); + if (line >= 8) + port_out8(PIC2, 0x20); +} diff --git a/src/kernel/arch/amd64/interrupts/irq.h b/src/kernel/arch/amd64/interrupts/irq.h new file mode 100644 index 0000000..f523154 --- /dev/null +++ b/src/kernel/arch/amd64/interrupts/irq.h @@ -0,0 +1,6 @@ +#pragma once +#include <stdbool.h> +#include <stdint.h> + +void irq_init(void); +void irq_eoi(uint8_t line); diff --git a/src/kernel/arch/amd64/interrupts/isr.c b/src/kernel/arch/amd64/interrupts/isr.c new file mode 100644 index 0000000..b55bc8b --- /dev/null +++ b/src/kernel/arch/amd64/interrupts/isr.c @@ -0,0 +1,48 @@ +#include <kernel/arch/amd64/driver/ps2.h> +#include <kernel/arch/amd64/driver/serial.h> +#include <kernel/arch/amd64/interrupts/irq.h> +#include <kernel/arch/amd64/interrupts/isr.h> +#include <kernel/arch/amd64/port_io.h> +#include <kernel/arch/generic.h> +#include <kernel/panic.h> +#include <kernel/proc.h> +#include <stdbool.h> +#include <stdint.h> + +bool isr_test_interrupt_called = false; + +void isr_stage3(int interrupt, uint64_t *stackframe) { + if (interrupt == 0xe) stackframe++; + kprintf("interrupt %x, rip = k/%08x, cs 0x%x\n", interrupt, stackframe[0], stackframe[1]); + switch (interrupt) { + case 0x08: // double fault + kprintf("#DF"); + panic_invalid_state(); + + case 0xe: + uint64_t addr = 0x69; + asm("mov %%cr2, %0" : "=r"(addr)); + kprintf("error code 0x%x, addr 0x%x\n", stackframe[-1], addr); + panic_unimplemented(); + + case 0x34: + asm("nop" ::: "memory"); + isr_test_interrupt_called = true; + return; + + case 0x21: // keyboard irq + ps2_recv(port_in8(0x60)); + irq_eoi(1); + return; + + case 0x24: // COM1 irq + serial_irq(); + irq_eoi(1); + return; + + default: + // TODO check if the exception was in the kernel + process_kill(process_current, interrupt); + process_switch_any(); + } +} diff --git a/src/kernel/arch/amd64/interrupts/isr.h b/src/kernel/arch/amd64/interrupts/isr.h new file mode 100644 index 0000000..01f5236 --- /dev/null +++ b/src/kernel/arch/amd64/interrupts/isr.h @@ -0,0 +1,8 @@ +#pragma once +#include <stdbool.h> +#include <stdint.h> + +extern bool isr_test_interrupt_called; // used in the self-test in idt.c +extern const char _isr_stubs; + +void isr_stage3(int interrupt, uint64_t *stackframe); diff --git a/src/kernel/arch/amd64/interrupts/isr_stub.s b/src/kernel/arch/amd64/interrupts/isr_stub.s new file mode 100644 index 0000000..e45c1c1 --- /dev/null +++ b/src/kernel/arch/amd64/interrupts/isr_stub.s @@ -0,0 +1,81 @@ +.section .shared + +.global _isr_stubs +_isr_stubs: +.rept 256 + .set _stub_start, . + + cli + call _isr_stage2 + + .if . - _stub_start > 8 + .error "isr stubs over maximum size" + .abort + .endif + .align 8 +.endr + +_isr_stage2: + // pushal order, without %esp + push %rax + push %rcx + push %rdx + push %rbx + push %rbp + push %rsi + push %rdi + push %r8 + push %r9 + push %r10 + push %r11 + push %r12 + push %r13 + push %r14 + push %r15 + + // convert the return address into the vector nr + mov 120(%rsp), %rdi + sub $_isr_stubs, %rdi + shr $3, %rdi + + lea 128(%rsp), %rsi // second argument - IRET stack frame + + // load kernel paging + mov %cr3, %rbx + push %rbx + mov $pml4_identity, %rbx + mov %rbx, %cr3 + + mov %rsp, %rbp + mov $_isr_big_stack, %rsp + call isr_stage3 + + mov %rbp, %rsp + pop %rax // restore old cr3 + mov %rax, %cr3 + + // restore registers + pop %r15 + pop %r14 + pop %r13 + pop %r12 + pop %r11 + pop %r10 + pop %r9 + pop %r8 + pop %rdi + pop %rsi + pop %rbp + pop %rbx + pop %rdx + pop %rcx + pop %rax + + add $8, %rsp // skip call's return address + iretq + +.align 8 +// TODO overflow check +.skip 256 // seems to be enough +.global _isr_mini_stack +_isr_mini_stack: diff --git a/src/kernel/arch/amd64/multiboot.h b/src/kernel/arch/amd64/multiboot.h new file mode 100644 index 0000000..c6a2650 --- /dev/null +++ b/src/kernel/arch/amd64/multiboot.h @@ -0,0 +1,29 @@ +#pragma once +#include <stdint.h> + +struct multiboot_mod { + uint32_t start; + uint32_t end; + uint32_t str; + uint32_t _reserved; +} __attribute__((packed)); + +struct multiboot_info { + uint32_t flag_mem : 1; + uint32_t flag_boot_device : 1; + uint32_t flag_cmdline : 1; + uint32_t flag_mods : 1; + uint32_t _flag_other : 28; // unimplemented + + uint32_t mem_lower; + uint32_t mem_upper; + + uint32_t boot_device; + + uint32_t cmdline; + + uint32_t mods_count; + uint32_t mods; + + // [...] +} __attribute__((packed)); diff --git a/src/kernel/arch/amd64/multiboot.s b/src/kernel/arch/amd64/multiboot.s new file mode 100644 index 0000000..dc19b36 --- /dev/null +++ b/src/kernel/arch/amd64/multiboot.s @@ -0,0 +1,12 @@ +.set MAGIC, 0x1BADB002 + +/* 1<<0 - align modules on page boundaries. */ +.set FLAGS, 1<<0 +.set CHECKSUM, -(MAGIC + FLAGS) + +.section .multiboot +.align 4 +multiboot_header: + .long MAGIC + .long FLAGS + .long CHECKSUM diff --git a/src/kernel/arch/amd64/pagedir.c b/src/kernel/arch/amd64/pagedir.c new file mode 100644 index 0000000..334cfc2 --- /dev/null +++ b/src/kernel/arch/amd64/pagedir.c @@ -0,0 +1,213 @@ +#include <kernel/arch/generic.h> +#include <kernel/mem/alloc.h> +#include <shared/mem.h> +#include <stdint.h> + +/* <heat> nitpick: I highly recommend you dont use bitfields for paging + * structures + * <heat> you can't change them atomically and the way they're layed out + * in memory is implementation defined iirc + */ +struct pagetable_entry { + uint32_t present : 1; + uint32_t writeable : 1; + uint32_t user : 1; + uint32_t writethru : 1; + uint32_t uncached : 1; + uint32_t dirty : 1; + uint32_t always0 : 1; // memory type thing? + uint32_t global : 1; + uint32_t _unused : 3; + uint32_t address : 21; +}; + +struct pagedir_entry { + uint32_t present : 1; + uint32_t _writeable: 1; // don't use! not checked by multiple functions here + uint32_t _user : 1; // ^ + uint32_t writethru : 1; + uint32_t uncached : 1; + uint32_t accessed : 1; + uint32_t always0 : 1; + uint32_t large : 1; // 4 MiB instead of 4 KiB + uint32_t _unused : 3; + uint32_t address : 21; +} __attribute__((packed)); + +struct pagedir { + struct pagedir_entry e[1024]; +} __attribute__((packed)); + + +struct pagedir *pagedir_new(void) { + struct pagedir *dir = page_alloc(1); + for (int i = 0; i < 1024; i++) + dir->e[i].present = 0; + return dir; +} + +void pagedir_free(struct pagedir *dir) { + // assumes all user pages are unique and can be freed + struct pagetable_entry *pt; + void *page; + + for (int i = 0; i < 1024; i++) { + if (!dir->e[i].present) continue; + + pt = (void*)(dir->e[i].address << 11); + + for (int j = 0; j < 1024; j++) { + if (!pt[j].present) continue; + if (!pt[j].user) continue; + + page = (void*)(pt[j].address << 11); + page_free(page, 1); + } + page_free(pt, 1); + } + page_free(dir, 1); +} + +static struct pagetable_entry* +get_entry(struct pagedir *dir, const void __user *virt) { + uint32_t pd_idx = ((uintptr_t)virt) >> 22; + uint32_t pt_idx = ((uintptr_t)virt) >> 12 & 0x03FF; + struct pagetable_entry *pagetable; + + if (!dir->e[pd_idx].present) return NULL; + + pagetable = (void*)(dir->e[pd_idx].address << 11); + return &pagetable[pt_idx]; +} + +void *pagedir_unmap(struct pagedir *dir, void __user *virt) { + void *phys = pagedir_virt2phys(dir, virt, false, false); + struct pagetable_entry *page = get_entry(dir, virt); + page->present = false; + return phys; +} + +void pagedir_map(struct pagedir *dir, void __user *virt, void *phys, + bool user, bool writeable) +{ + kprintf("in pagedir_map, dir 0x%x\n", dir); + uintptr_t virt_cast = (uintptr_t) virt; + uint32_t pd_idx = virt_cast >> 22; + uint32_t pt_idx = virt_cast >> 12 & 0x03FF; + struct pagetable_entry *pagetable; + + kprintf("pre-if, accessing 0x%x because 0x%x\n", &dir->e[pd_idx], pd_idx); + if (dir->e[pd_idx].present) { + kprintf("present already\n"); + pagetable = (void*) (dir->e[pd_idx].address << 11); + } else { + kprintf("allocing\n"); + pagetable = page_alloc(1); + kprintf("alloc successful\n"); + for (int i = 0; i < 1024; i++) + pagetable[i].present = 0; + + dir->e[pd_idx] = (struct pagedir_entry) { + .present = 1, + ._writeable= 1, + ._user = 1, + .writethru = 1, + .uncached = 0, + .accessed = 0, + .always0 = 0, + .large = 0, + ._unused = 0, + .address = (uintptr_t) pagetable >> 11 + }; + } + + pagetable[pt_idx] = (struct pagetable_entry) { + .present = 1, + .writeable = writeable, + .user = user, + .writethru = 1, + .uncached = 0, + .dirty = 0, + .always0 = 0, + .global = 0, + ._unused = 0, + .address = (uintptr_t) phys >> 11 + }; + kprintf("out pagedir_map\n"); +} + +extern void *pagedir_current; +void pagedir_switch(struct pagedir *dir) { + pagedir_current = dir; +} + +// creates a new pagedir with exact copies of the user pages +struct pagedir *pagedir_copy(const struct pagedir *orig) { + struct pagedir *clone = page_alloc(1); + struct pagetable_entry *orig_pt, *clone_pt; + void *orig_page, *clone_page; + + for (int i = 0; i < 1024; i++) { + clone->e[i] = orig->e[i]; + if (!orig->e[i].present) continue; + + orig_pt = (void*)(orig->e[i].address << 11); + clone_pt = page_alloc(1); + clone->e[i].address = (uintptr_t) clone_pt >> 11; + + for (int j = 0; j < 1024; j++) { + clone_pt[j] = orig_pt[j]; + if (!orig_pt[j].present) continue; + if (!orig_pt[j].user) continue; + // i could use .global? + + orig_page = (void*)(orig_pt[j].address << 11); + clone_page = page_alloc(1); + clone_pt[j].address = (uintptr_t) clone_page >> 11; + + memcpy(clone_page, orig_page, PAGE_SIZE); + } + } + + return clone; +} + +bool pagedir_iskern(struct pagedir *dir, const void __user *virt) { + struct pagetable_entry *page = get_entry(dir, virt); + return page && page->present && !page->user; +} + +void *pagedir_virt2phys(struct pagedir *dir, const void __user *virt, + bool user, bool writeable) +{ + struct pagetable_entry *page; + uintptr_t phys; + page = get_entry(dir, virt); + if (!page || !page->present) return NULL; + if (user && !page->user) return NULL; + if (writeable && !page->writeable) return NULL; + + phys = page->address << 11; + phys |= ((uintptr_t)virt) & 0xFFF; + return (void*)phys; +} + +void __user *pagedir_findfree(struct pagedir *dir, char __user *start, size_t len) { + struct pagetable_entry *page; + char __user *iter; + start = (userptr_t)(((uintptr_t __force)start + PAGE_MASK) & ~PAGE_MASK); // round up to next page + iter = start; + + while (iter < (char __user *)0xFFF00000) { // TODO better boundary + page = get_entry(dir, iter); + if (page && page->present) { + start = iter + PAGE_SIZE; + } else { + if ((size_t)(iter + PAGE_SIZE - start) >= len) + return start; + } + iter += PAGE_SIZE; + } + + return NULL; +} diff --git a/src/kernel/arch/amd64/port_io.h b/src/kernel/arch/amd64/port_io.h new file mode 100644 index 0000000..eac9331 --- /dev/null +++ b/src/kernel/arch/amd64/port_io.h @@ -0,0 +1,22 @@ +#include <stdint.h> + +static inline void port_out8(uint16_t port, uint8_t val) { + asm volatile("outb %0, %1" : : "a" (val), "Nd" (port)); +} + +static inline void port_out16(uint16_t port, uint16_t val) { + asm volatile("outw %0, %1" : : "a" (val), "Nd" (port)); +} + +static inline uint8_t port_in8(uint16_t port) { + uint8_t val; + asm volatile("inb %1, %0" : "=a" (val) : "Nd" (port)); + return val; +} + +static inline uint16_t port_in16(uint16_t port) { + uint16_t val; + asm volatile("inw %1, %0" : "=a" (val) : "Nd" (port)); + return val; +} + diff --git a/src/kernel/arch/amd64/registers.h b/src/kernel/arch/amd64/registers.h new file mode 100644 index 0000000..e365e2c --- /dev/null +++ b/src/kernel/arch/amd64/registers.h @@ -0,0 +1,18 @@ +#pragma once +#include <shared/types.h> +#include <stdint.h> + +struct registers { + uint64_t edi, esi; + userptr_t ebp, esp; + uint64_t ebx, edx, ecx, eax; + + userptr_t eip; +} __attribute__((__packed__)); + +// saves a return value according to the SysV ABI +static inline uint64_t regs_savereturn(struct registers *regs, uint64_t value) { + regs->eax = value; + regs->edx = value >> 32; // TODO check ABI + return value; +} diff --git a/src/kernel/arch/amd64/sysenter.c b/src/kernel/arch/amd64/sysenter.c new file mode 100644 index 0000000..e42ec7d --- /dev/null +++ b/src/kernel/arch/amd64/sysenter.c @@ -0,0 +1,27 @@ +#include <kernel/arch/generic.h> +#include <kernel/arch/amd64/sysenter.h> +#include <kernel/proc.h> +#include <shared/syscalls.h> + +struct registers _sysexit_regs; + +void sysexit(struct registers regs) { + _sysexit_regs = regs; + _sysexit_regs.ecx = (uintptr_t) regs.esp; + _sysexit_regs.edx = (uintptr_t) regs.eip; + _sysexit_real(); + __builtin_unreachable(); +} + +_Noreturn void sysenter_stage2(void) { + kprintf("ring0 again!\n"); + struct registers *regs = &process_current->regs; + + *regs = _sysexit_regs; // save the registers + regs->esp = (userptr_t) regs->ecx; // fix them up + regs->eip = (userptr_t) regs->edx; + + _syscall(regs->eax, regs->ebx, + regs->esi, regs->edi, (uintptr_t)regs->ebp); + process_switch_any(); +} diff --git a/src/kernel/arch/amd64/sysenter.h b/src/kernel/arch/amd64/sysenter.h new file mode 100644 index 0000000..b88c186 --- /dev/null +++ b/src/kernel/arch/amd64/sysenter.h @@ -0,0 +1,8 @@ +#pragma once + +// sysenter.c +extern struct registers _sysexit_regs; +_Noreturn void sysenter_stage2(void); + +// sysenter.s +void _sysexit_real(void); diff --git a/src/kernel/arch/amd64/sysenter.s b/src/kernel/arch/amd64/sysenter.s new file mode 100644 index 0000000..4dfe19d --- /dev/null +++ b/src/kernel/arch/amd64/sysenter.s @@ -0,0 +1,93 @@ +/* TODO include gdt.h */ +.set SEG_r0code, 1 +.set SEG_r3code, 3 +.set SEG_r3data, 4 + +.set IA32_SYSENTER_CS, 0x174 +.set IA32_SYSENTER_ESP, 0x175 +.set IA32_SYSENTER_EIP, 0x176 + +.section .text +.global sysenter_setup +.type sysenter_setup, @function +sysenter_setup: + xor %rdx, %rdx + + mov $(SEG_r0code << 3), %rax + mov $IA32_SYSENTER_CS, %rcx + wrmsr + + mov $IA32_SYSENTER_ESP, %rcx + mov $0, %rax // unused + wrmsr + + mov $IA32_SYSENTER_EIP, %rcx + mov $sysenter_stage1, %rax + wrmsr + + ret + + +.section .shared + +.global stored_eax +stored_eax: +.long 0 + +.global pagedir_current +// a hack to maintain compat with the old arch api, TODO +pagedir_current: +.long 0 + +.global _sysexit_real +.type _sysexit_real, @function +_sysexit_real: + xchgw %bx, %bx + mov $(SEG_r3data << 3 | 3), %ax + mov %ax, %ds + mov %ax, %es + mov %ax, %fs + mov %ax, %gs + + // restore the registers + mov $_sysexit_regs, %rsp + pop %rdi + pop %rsi + pop %rbp + add $8, %rsp + pop %rbx + pop %rdx + pop %rcx + pop %rax + + // enable paging + // %rsp used as a scratch register + mov (pagedir_current), %rsp + mov %rsp, %cr3 + sysexit + +sysenter_stage1: + cli /* prevent random IRQs in the middle of kernel code */ + xchgw %bx, %bx + + // disable paging + // I don't want to damage any of the registers passed in by the user, + // so i'm using ESP as a temporary register. At this point there's nothing + // useful in it, it's == _bss_end. + mov %cr0, %rsp + and $0x7FFFFFFF, %rsp // disable paging + mov %rsp, %cr0 + + // save the registers + mov $(_sysexit_regs + 64), %rsp + push %rax + push %rcx + push %rdx + push %rbx + push $0x0 // pushal pushes %rsp here, but that's worthless + push %rbp + push %rsi + push %rdi + + mov $_bss_end, %rsp + jmp sysenter_stage2 diff --git a/src/kernel/arch/amd64/tty/tty.c b/src/kernel/arch/amd64/tty/tty.c new file mode 100644 index 0000000..6593ef6 --- /dev/null +++ b/src/kernel/arch/amd64/tty/tty.c @@ -0,0 +1,26 @@ +#include <kernel/arch/generic.h> +#include <kernel/arch/amd64/driver/serial.h> +#include <kernel/arch/amd64/tty/tty.h> +#include <shared/printf.h> + +void tty_init(void) { + vga_clear(); + serial_preinit(); + + vga_write("\x03 ", 2); // cp437 heart + serial_write("<3 ", 3); +} + +static void backend(void __attribute__((unused)) *arg, const char *buf, size_t len) { + vga_write(buf, len); + serial_write(buf, len); +} + +int kprintf(const char *fmt, ...) { + int ret; + va_list argp; + va_start(argp, fmt); + ret = __printf_internal(fmt, argp, backend, NULL); + va_end(argp); + return ret; +} diff --git a/src/kernel/arch/amd64/tty/tty.h b/src/kernel/arch/amd64/tty/tty.h new file mode 100644 index 0000000..b96003d --- /dev/null +++ b/src/kernel/arch/amd64/tty/tty.h @@ -0,0 +1,7 @@ +#pragma once +#include <stddef.h> + +void vga_write(const char *buf, size_t len); +void vga_clear(void); + +void tty_init(void); diff --git a/src/kernel/arch/amd64/tty/vga.c b/src/kernel/arch/amd64/tty/vga.c new file mode 100644 index 0000000..e5f2274 --- /dev/null +++ b/src/kernel/arch/amd64/tty/vga.c @@ -0,0 +1,33 @@ +#include <kernel/arch/amd64/tty/tty.h> + +struct vga_cell { + unsigned char c; + unsigned char style; +} __attribute__((__packed__)); + +static const size_t vga_len = 80 * 25; +static struct vga_cell *vga = (void*) 0xB8000; +static size_t vga_pos = 0; + +static void vga_scroll(void) { + for (size_t i = 0; i < vga_len - 80; i++) + vga[i] = vga[i + 80]; + vga_pos -= 80; +} + +static void vga_putchar(char c) { + if (vga_pos >= vga_len - 80) + vga_scroll(); + vga[vga_pos++].c = c; +} + +void vga_write(const char *buf, size_t len) { + for (size_t i = 0; i < len; i++) + vga_putchar(buf[i]); +} + +void vga_clear(void) { + for (size_t i = 0; i < vga_len; i++) + vga[i].c = ' '; + vga_pos = 0; +} |