diff --git a/.gitignore b/.gitignore index fbba961..2d3323b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ *.o -*.bin *.img +*.bin *.elf *.dSYM tools/mkfs -ejudge.sh diff --git a/Makefile b/Makefile index 209951a..97b38ad 100644 --- a/Makefile +++ b/Makefile @@ -18,9 +18,10 @@ endif CFLAGS = -fno-pic -ffreestanding -static -fno-builtin -fno-strict-aliasing \ -mno-sse \ + -I. \ -Wall -ggdb -m32 -Werror -fno-omit-frame-pointer CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector) -ASMFLAGS = -m32 -ffreestanding -c -g +ASMFLAGS = -m32 -ffreestanding -c -g -I. ifeq ($(LLVM),on) @@ -38,7 +39,7 @@ endif OBJECTS = ./kernel.o ./console.o ./drivers/vga.o ./drivers/uart.o ./drivers/keyboard.o \ ./cpu/idt.o ./cpu/gdt.o ./cpu/swtch.o ./cpu/vectors.o ./lib/mem.o ./proc.o ./lib/string.o \ - ./fs/fs.o + ./fs/fs.o ./drivers/ata.o ./lib/string.o ./proc.o ./drivers/pit.o ./kernel/vm.o run: image.bin qemu-system-i386 -drive format=raw,file=$< -serial mon:stdio @@ -87,7 +88,7 @@ debug: image.bin qemu-system-i386 -drive format=raw,file=$< -s -S & $(GDB) kernel.bin \ -ex "target remote localhost:1234" \ - -ex "break _start" \ + -ex "break kmain" \ -ex "continue" debug-nox: image.bin @@ -97,16 +98,16 @@ debug-nox: image.bin -ex "break _start" \ -ex "continue" -fs.img: ./kernel.bin ./tools/mkfs ./user/false ./user/greet ./user/div0 - ./tools/mkfs $@ $< ./user/false ./user/greet ./user/div0 +fs.img: ./kernel.bin ./tools/mkfs ./user/false ./user/greet ./user/div0 ./user/shout + ./tools/mkfs $@ $< ./user/false ./user/greet ./user/div0 ./user/shout LDFLAGS=-m elf_i386 user/%: user/%.o user/crt.o - $(LD) $(LDFLAGS) -o $@ -Ttext 0x1000 $^ + $(LD) $(LDFLAGS) -o $@ -Ttext 0x401000 $^ kernel.bin: $(OBJECTS) - $(LD) $(LDFLAGS) $(LDKERNELFLAGS) -o $@ -Ttext 0x9000 $^ + $(LD) $(LDFLAGS) $(LDKERNELFLAGS) -o $@ -Ttext 0x80009000 $^ bootmain.o: bootmain.c $(CC) $(CFLAGS) -Os -c $< -o $@ diff --git a/bootmain.c b/bootmain.c index 95a10fd..77d3263 100644 --- a/bootmain.c +++ b/bootmain.c @@ -40,7 +40,7 @@ bootmain(void) ph = (Elf32_Phdr*)((uchar*)elf + elf->e_phoff); eph = ph + elf->e_phnum; for(; ph < eph; ph++) { - pa = (uchar*)ph->p_paddr; + pa = (uchar*)(ph->p_paddr & 0x0fffffff); readseg(pa, ph->p_filesz, ph->p_offset); if(ph->p_memsz > ph->p_filesz) stosb(pa + ph->p_filesz, 0, ph->p_memsz - ph->p_filesz); @@ -48,7 +48,7 @@ bootmain(void) // Call the entry point from the ELF header. // Does not return! - entry = (void(*)(void))(elf->e_entry); + entry = (void(*)(void))(elf->e_entry & 0x0fffffff); entry(); } diff --git a/console.c b/console.c index 519037b..1064b89 100644 --- a/console.c +++ b/console.c @@ -3,7 +3,7 @@ #include "drivers/uart.h" void printk(const char* msg) { - vga_print_string_noscroll(msg); + vga_print_string(msg); for (; *msg; ++msg) { uartputc(*msg); } diff --git a/cpu/gdt.c b/cpu/gdt.c index f511ef0..5a031d2 100644 --- a/cpu/gdt.c +++ b/cpu/gdt.c @@ -1,5 +1,8 @@ #include "gdt.h" +#include "x86.h" +#include "memlayout.h" #include "../lib/string.h" +#include "kernel/mem.h" #include @@ -31,10 +34,10 @@ struct seg_desc_t { struct seg_desc_t seg_desc[6]; void init_seg_desc() { - seg_desc[SEG_KCODE] = SEG(STA_X|STA_R, 0, 0xffffffff, 0); - seg_desc[SEG_KDATA] = SEG(STA_W, 0, 0xffffffff, 0); - seg_desc[SEG_UCODE] = SEG(STA_X|STA_R, USER_BASE, 0xffffffff - USER_BASE, DPL_USER); - seg_desc[SEG_UDATA] = SEG(STA_W, USER_BASE, 0xffffffff - USER_BASE, DPL_USER); + seg_desc[SEG_KCODE] = SEG(STA_X|STA_R, 0, 0xffffffff, 0); + seg_desc[SEG_KDATA] = SEG(STA_W, 0, 0xffffffff, 0); + seg_desc[SEG_UCODE] = SEG(STA_X|STA_R, 0, 0xffffffff, DPL_USER); + seg_desc[SEG_UDATA] = SEG(STA_W, 0, 0xffffffff, DPL_USER); } struct gdt_desc_t { @@ -51,7 +54,7 @@ void load_gdt() { asm("lgdt %0": : "m"(gdt_desc)); } -void switchuvm(struct taskstate *tss, void* esp) { +void switchuvm(struct taskstate *tss, void* esp, pde_t *pgdir) { memset(tss, 0, sizeof(*tss)); seg_desc[SEG_TSS] = SEG16(STS_T32A, tss, sizeof(*tss)-1, 0); seg_desc[SEG_TSS].s = 0; @@ -62,4 +65,6 @@ void switchuvm(struct taskstate *tss, void* esp) { tss->iomb = (ushort) 0xFFFF; asm("ltr %0": : "r"((ushort)(SEG_TSS << 3))); + + lcr3(V2P(pgdir)); } diff --git a/cpu/gdt.h b/cpu/gdt.h index 00dd40b..d67c11a 100644 --- a/cpu/gdt.h +++ b/cpu/gdt.h @@ -29,6 +29,7 @@ #define KERN_STACK_BASE 0x90000 #ifndef __ASSEMBLER__ +#include "kernel/mem.h" typedef unsigned uint; typedef unsigned short ushort; @@ -73,5 +74,5 @@ struct taskstate { }; void load_gdt(); -void switchuvm(struct taskstate *tss, void* esp); +void switchuvm(struct taskstate *tss, void* esp, pde_t *pgdir); #endif diff --git a/cpu/idt.c b/cpu/idt.c index 7adc01d..91d8d02 100644 --- a/cpu/idt.c +++ b/cpu/idt.c @@ -105,10 +105,32 @@ void trap(registers_t *r) { if (r->int_no < ARRLEN(exception_messages)) { msg = exception_messages[r->int_no]; } + if (r->cs & 3) { + // exception from user mode, kill offending process + printk("Exception: "); + printk(msg); + printk("\n"); + killproc(); + } panic(msg); } } +static void* get_userspace_ptr(uint32_t ptr) { + if (ptr >= 0xffffffff - USER_BASE) { + return 0; + } + return (void*)(ptr + USER_BASE); +} + +static int handle_puts(const char* s) { + if (!s) { + return -1; + } + printk(s); + return 0; +} + static void handle_syscall(registers_t* r) { switch (r->eax) { case SYS_exit: @@ -122,6 +144,13 @@ static void handle_syscall(registers_t* r) { printk("Hello world!\n"); r->eax = 0; break; + case SYS_putc: + printk((const char[]){r->ebx, '\0'}); + r->eax = 0; + break; + case SYS_puts: + r->eax = handle_puts(get_userspace_ptr(r->ebx)); + break; default: printk("Unknown syscall\n"); r->eax = -1; diff --git a/cpu/isr.h b/cpu/isr.h index b708e40..de12f30 100644 --- a/cpu/isr.h +++ b/cpu/isr.h @@ -21,19 +21,17 @@ enum { }; /* Struct which aggregates many registers. - * It matches exactly the pushes on vectors.S. From the bottom: - * - pushed by the processor automatically + * It matches exactly the pushes on interrupt.asm. From the bottom: + * - Pushed by the processor automatically * - `push byte`s on the isr-specific code: error code, then int number - * - segment registers - * - all the registers by pusha + * - All the registers by pusha + * - `push eax` whose lower 16-bits contain DS */ typedef struct { uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax; /* Pushed by pusha. */ - uint32_t gs, fs, es, ds; - uint32_t int_no, err_code; // Interrupt number and error code (if applicable) - uint32_t eip, cs, eflags; // Pushed by the processor automatically - - uint32_t useresp, ss; // Pushed by the processor for userspace interrupts + uint32_t gs, fs, es, ds; /* Data segment selector */ + uint32_t int_no, err_code; /* Interrupt number and error code (if applicable) */ + uint32_t eip, cs, eflags, useresp, ss; /* Pushed by the processor automatically */ } registers_t; void isr_install(); diff --git a/cpu/memlayout.h b/cpu/memlayout.h new file mode 100644 index 0000000..fd0dcbd --- /dev/null +++ b/cpu/memlayout.h @@ -0,0 +1,41 @@ +#pragma once + +#define KERNBASE 0x80000000 +#define PGSIZE 0x1000 +#define PHYSTOP 0x8000000 + +#define PGROUNDUP(sz) (((sz)+PGSIZE-1) & ~(PGSIZE-1)) +#define PGROUNDDOWN(a) (((a)) & ~((uintptr_t)(PGSIZE-1))) + +#define NPDENTRIES 1024 // # directory entries per page directory +#define NPTENTRIES 1024 // # PTEs per page table + +#define PTXSHIFT 12 // offset of PTX in a linear address +#define PDXSHIFT 22 // offset of PDX in a linear address + +#define PXMASK 0x3FF + +#ifndef __ASSEMBLER__ +#include +#define V2P(a) (((uintptr_t) (a)) - KERNBASE) +#define P2V(a) ((void *)(((uintptr_t) (a)) + KERNBASE)) + +// page directory index +#define PDX(va) (((uintptr_t)(va) >> PDXSHIFT) & PXMASK) + +// page table index +#define PTX(va) (((uintptr_t)(va) >> PTXSHIFT) & PXMASK) + +// Address in page table or page directory entry +#define PTE_ADDR(pte) ((uintptr_t)(pte) & ~0xFFF) +#endif + +// Page table/directory entry flags. +#define PTE_P 0x001 // Present +#define PTE_W 0x002 // Writeable +#define PTE_U 0x004 // User +#define PTE_PWT 0x008 // Write-Through +#define PTE_PCD 0x010 // Cache-Disable +#define PTE_A 0x020 // Accessed +#define PTE_D 0x040 // Dirty +#define PTE_PS 0x080 // Page Size diff --git a/cpu/x86.h b/cpu/x86.h new file mode 100644 index 0000000..fafd892 --- /dev/null +++ b/cpu/x86.h @@ -0,0 +1,19 @@ +#pragma once + +static inline void +stosl(void *addr, int data, int cnt) +{ + asm volatile("cld; rep stosl" : : "D"(addr), "c"(cnt), "a"(data) : "memory"); +} + +static inline void +stosb(void *addr, unsigned char data, int cnt) +{ + asm volatile("cld; rep stosb" : : "D"(addr), "c"(cnt), "a"(data) : "memory"); +} + +static inline void +lcr3(uint32_t val) +{ + asm volatile("mov %0,%%cr3" : : "r" (val)); +} diff --git a/drivers/ata.c b/drivers/ata.c index 5abee68..f3d9ac8 100644 --- a/drivers/ata.c +++ b/drivers/ata.c @@ -1,80 +1,74 @@ -// stolen from https://github.com/dhavalhirdhav/LearnOS/blob/master/drivers/ata/ata.c +// stolen from +// https://github.com/dhavalhirdhav/LearnOS/blob/master/drivers/ata/ata.c + +#include "ata.h" #include -#include "ata.h" #include "port.h" -/* - BSY: a 1 means that the controller is busy executing a command. No register should be accessed (except the digital output register) while this bit is set. -RDY: a 1 means that the controller is ready to accept a command, and the drive is spinning at correct speed.. -WFT: a 1 means that the controller detected a write fault. -SKC: a 1 means that the read/write head is in position (seek completed). -DRQ: a 1 means that the controller is expecting data (for a write) or is sending data (for a read). Don't access the data register while this bit is 0. -COR: a 1 indicates that the controller had to correct data, by using the ECC bytes (error correction code: extra bytes at the end of the sector that allows to verify its integrity and, sometimes, to correct errors). -IDX: a 1 indicates the the controller retected the index mark (which is not a hole on hard-drives). -ERR: a 1 indicates that an error occured. An error code has been placed in the error register. -*/ - #define STATUS_BSY 0x80 #define STATUS_RDY 0x40 #define STATUS_DRQ 0x08 -#define STATUS_DF 0x20 +#define STATUS_DF 0x20 #define STATUS_ERR 0x01 -//This is really specific to our OS now, assuming ATA bus 0 master -//Source - OsDev wiki +// This is really specific to our OS now, assuming ATA bus 0 master +// Source - OsDev wiki https://wiki.osdev.org/ATA_PIO_Mode + static void ATA_wait_BSY(); -static void ATA_wait_DRQ(); -void read_sectors_ATA_PIO(uint32_t target_address, uint32_t LBA, uint8_t sector_count) +static void ATA_wait_RDY(); + +void read_sectors_ATA_PIO(void* target_address, uint32_t LBA, uint8_t sector_count) { + ATA_wait_BSY(); + port_byte_out(0x1F6, 0xE0 | ((LBA >> 24) & 0xF)); + port_byte_out(0x1F2, sector_count); + port_byte_out(0x1F3, (uint8_t)LBA); + port_byte_out(0x1F4, (uint8_t)(LBA >> 8)); + port_byte_out(0x1F5, (uint8_t)(LBA >> 16)); + port_byte_out(0x1F7, 0x20); // Send the read command - ATA_wait_BSY(); - port_byte_out(0x1F6, 0xE0 | ((LBA >>24) & 0xF)); - port_byte_out(0x1F2, sector_count); - port_byte_out(0x1F3, (uint8_t) LBA); - port_byte_out(0x1F4, (uint8_t)(LBA >> 8)); - port_byte_out(0x1F5, (uint8_t)(LBA >> 16)); - port_byte_out(0x1F7, 0x20); //Send the read command + uint16_t *target = (uint16_t *)target_address; - uint16_t *target = (uint16_t*) target_address; - - for (int j = 0; j < sector_count; j++) - { - ATA_wait_BSY(); - ATA_wait_DRQ(); - for(int i = 0; i < 256; i++) - target[i] = port_word_in(0x1F0); - target += 256; - } + for (int j = 0; j < sector_count; j++) + { + ATA_wait_BSY(); + ATA_wait_RDY(); + for (int i = 0; i < 256; i++) + target[i] = port_word_in(0x1F0); + target += 256; + } } -void write_sectors_ATA_PIO(uint32_t LBA, uint8_t sector_count, uint32_t* bytes) +void write_sectors_ATA_PIO(uint32_t LBA, uint8_t sector_count, uint32_t *bytes) { - ATA_wait_BSY(); - port_byte_out(0x1F6,0xE0 | ((LBA >>24) & 0xF)); - port_byte_out(0x1F2,sector_count); - port_byte_out(0x1F3, (uint8_t) LBA); - port_byte_out(0x1F4, (uint8_t)(LBA >> 8)); - port_byte_out(0x1F5, (uint8_t)(LBA >> 16)); - port_byte_out(0x1F7,0x30); //Send the write command + ATA_wait_BSY(); + port_byte_out(0x1F6, 0xE0 | ((LBA >> 24) & 0xF)); + port_byte_out(0x1F2, sector_count); + port_byte_out(0x1F3, (uint8_t)LBA); + port_byte_out(0x1F4, (uint8_t)(LBA >> 8)); + port_byte_out(0x1F5, (uint8_t)(LBA >> 16)); + port_byte_out(0x1F7, 0x30); // Send the write command - for (int j =0;j -void read_sectors_ATA_PIO(uint32_t target_address, uint32_t LBA, uint8_t sector_count); -void write_sectors_ATA_PIO(uint32_t LBA, uint8_t sector_count, uint32_t* bytes); +void read_sectors_ATA_PIO(void* target_address, uint32_t LBA, uint8_t sector_count); diff --git a/drivers/keyboard.c b/drivers/keyboard.c index d7ade6e..c78615f 100644 --- a/drivers/keyboard.c +++ b/drivers/keyboard.c @@ -1,8 +1,9 @@ #include "keyboard.h" -#include "../cpu/isr.h" -#include "../console.h" +#include "cpu/isr.h" +#include "cpu/memlayout.h" +#include "console.h" #include "port.h" -#include "../lib/mem.h" +#include "kernel/mem.h" static const char sc_ascii[] = { '?', '?', '1', '2', '3', '4', '5', '6', @@ -12,7 +13,7 @@ static const char sc_ascii[] = { 'b', 'n', 'm', ',', '.', '/', '?', '?', '?', ' ', }; -enum { kbd_buf_capacity = 1024 }; +enum { kbd_buf_capacity = PGSIZE }; static void interrupt_handler(registers_t *r) { uint8_t scancode = port_byte_in(0x60); @@ -30,7 +31,7 @@ char* kbd_buf; unsigned kbd_buf_size; void init_keyboard() { - kbd_buf = kmalloc(kbd_buf_capacity); + kbd_buf = kalloc(); register_interrupt_handler(IRQ1, interrupt_handler); } diff --git a/drivers/misc.h b/drivers/misc.h index 353419b..bbb1701 100644 --- a/drivers/misc.h +++ b/drivers/misc.h @@ -4,8 +4,5 @@ __attribute__((noreturn)) static inline void qemu_shutdown() { port_word_out(0x604, 0x2000); - while (1) { - asm("hlt"); - } __builtin_unreachable(); } diff --git a/drivers/pit.c b/drivers/pit.c new file mode 100644 index 0000000..7b8edc1 --- /dev/null +++ b/drivers/pit.c @@ -0,0 +1,76 @@ +#include "pit.h" +#include "port.h" +#include "../cpu/isr.h" + +enum { + PIT_CRYSTAL_HZ = 1193182, + + CLOCK_PRECISION_HZ = 100, + + PIT_PROGRAM_REG = PIT_CRYSTAL_HZ / CLOCK_PRECISION_HZ, + + PIT_SELECT_CHANNEL0 = 0x0, + PIT_SELECT_CHANNEL1 = 0x1, + PIT_SELECT_CHANNEL2 = 0x2, + PIT_SELECT_CHANNEL_RB = 0x3, + PIT_ACCESS_MODE_LATCH_COUNT_VALUE_COMMAND = 0x0, + PIT_ACCESS_MODE_LOBYTE_ONLY = 0x1, + PIT_ACCESS_MODE_HIBYTE_ONLY = 0x2, + PIT_ACCESS_MODE_LOHIBYTE = 0x3, + PIT_MODE_INTERRUPT_ON_TERMINAL_COUNT = 0x0, + PIT_MODE_HW_ONESHOT = 0x1, + PIT_MODE_RATE_GENERATOR = 0x2, + PIT_MODES_SQUARE_WAVE_GENERATOR = 0x3, + PIT_MODES_SW_TRIGGERRED_STROBE = 0x4, + PIT_MODES_HW_TRIGGERRED_STROBE = 0x5, +}; + +static timer_callback callbacks[100]; +static int registered_callbacks = 0; + +void add_timer_callback(timer_callback tc) { + callbacks[registered_callbacks++] = tc; +} + +static void timer_interrupt_handler(registers_t *r) { + for (int i = 0; i < registered_callbacks; ++i) { + callbacks[i](); + } +} + +struct pit_command_t { + unsigned char bcd : 1; + unsigned char operating_mode : 3; + unsigned char access_mode : 2; + unsigned char select_channel : 2; +} __attribute__((packed)); + +static void dec_sleep_counter(void); + +void init_pit() { + struct pit_command_t cmd = { + .select_channel = PIT_SELECT_CHANNEL0, + .access_mode = PIT_ACCESS_MODE_LOHIBYTE, + .operating_mode = PIT_MODES_SQUARE_WAVE_GENERATOR, + .bcd = 0, + }; + port_byte_out(0x43, *(unsigned char *)(&cmd)); + port_byte_out(0x40, PIT_PROGRAM_REG & 0xff); + port_byte_out(0x40, (PIT_PROGRAM_REG & 0xff00) >> 8); + + register_interrupt_handler(IRQ0, timer_interrupt_handler); + add_timer_callback(dec_sleep_counter); +} + +static int sleep_counter = 0; + +static void dec_sleep_counter(void) { + sleep_counter--; +} + +void msleep(int ms) { + sleep_counter = ms / 10; + while (sleep_counter > 0) { + asm("hlt"); + } +} diff --git a/drivers/pit.h b/drivers/pit.h new file mode 100644 index 0000000..01b72ad --- /dev/null +++ b/drivers/pit.h @@ -0,0 +1,8 @@ +#pragma once + +typedef void (*timer_callback)(void); + +void init_pit(void); +void add_timer_callback(timer_callback tc); + +void msleep(int ms); diff --git a/drivers/vga.c b/drivers/vga.c index 274a653..ad0a364 100644 --- a/drivers/vga.c +++ b/drivers/vga.c @@ -1,12 +1,42 @@ #include "port.h" -#include "vga.h" +#include "../lib/string.h" +#include "cpu/memlayout.h" -char* const video_memory = (char*) 0xb8000; +static char* const video_memory = (char*) (KERNBASE + 0xb8000); + +enum colors16 { + black = 0, + blue, + green, + cyan, + red, + magenta, + brown, + light_gray, + dark_gray, + light_blue, + light_green, + light_cyan, + light_red, + light_magenta, + yellow, + white, +}; static unsigned char get_color(unsigned char fg, unsigned char bg) { return (bg << 4) + fg; } +enum { + ROWS = 25, + COLS = 80, + + VGA_CTRL_REGISTER = 0x3d4, + VGA_DATA_REGISTER = 0x3d5, + VGA_OFFSET_LOW = 0x0f, + VGA_OFFSET_HIGH = 0x0e, +}; + static unsigned get_offset(unsigned col, unsigned row) { return row * COLS + col; } @@ -15,6 +45,13 @@ static unsigned get_row_from_offset(unsigned offset) { return offset / COLS; } +void vga_set_cursor(unsigned offset) { + port_byte_out(VGA_CTRL_REGISTER, VGA_OFFSET_HIGH); + port_byte_out(VGA_DATA_REGISTER, (unsigned char) (offset >> 8)); + port_byte_out(VGA_CTRL_REGISTER, VGA_OFFSET_LOW); + port_byte_out(VGA_DATA_REGISTER, (unsigned char) (offset & 0xff)); +} + unsigned vga_get_cursor() { port_byte_out(VGA_CTRL_REGISTER, VGA_OFFSET_HIGH); unsigned offset = port_byte_in(VGA_DATA_REGISTER) << 8; @@ -28,15 +65,23 @@ void vga_set_char(unsigned offset, char c) { video_memory[2 * offset + 1] = get_color(light_gray, black); } -static unsigned offset; - void vga_clear_screen() { for (unsigned i = 0; i < ROWS * COLS; ++i) { vga_set_char(i, ' '); } + vga_set_cursor(0); } -void vga_print_string_noscroll(const char* s) { +static unsigned scroll() { + kmemmove(video_memory, video_memory + COLS, 2 * COLS * (ROWS-1)); + for (int col = 0; col < COLS; col++) { + vga_set_char(get_offset(col, ROWS - 1), ' '); + } + return get_offset(0, ROWS - 1); +} + +void vga_print_string(const char* s) { + unsigned offset = vga_get_cursor(); while (*s != 0) { if (*s == '\n') { offset = get_offset(0, get_row_from_offset(offset) + 1); @@ -44,7 +89,10 @@ void vga_print_string_noscroll(const char* s) { vga_set_char(offset, *s); offset++; } - offset %= COLS * ROWS; s++; + if (offset > COLS * ROWS) { + offset = scroll(); + } } + vga_set_cursor(offset); } diff --git a/drivers/vga.h b/drivers/vga.h index 9307fa6..4f09b11 100644 --- a/drivers/vga.h +++ b/drivers/vga.h @@ -1,38 +1,5 @@ #pragma once -extern char* const video_memory; - -enum { - ROWS = 25, - COLS = 80, - - VGA_CTRL_REGISTER = 0x3d4, - VGA_DATA_REGISTER = 0x3d5, - VGA_OFFSET_LOW = 0x0f, - VGA_OFFSET_HIGH = 0x0e, -}; - -enum colors16 { - black = 0, - blue, - green, - cyan, - red, - magenta, - brown, - light_gray, - dark_gray, - light_blue, - light_green, - light_cyan, - light_red, - light_magenta, - yellow, - white, -}; - void vga_clear_screen(); -void vga_set_char(unsigned offset, char c); void vga_print_string(const char* s); -void vga_print_string_noscroll(const char* s); diff --git a/fs/fs.c b/fs/fs.c index 5f594ce..7299058 100644 --- a/fs/fs.c +++ b/fs/fs.c @@ -1,12 +1,39 @@ #include "fs.h" #include "../lib/string.h" #include "../drivers/ata.h" -#include "../console.h" + +enum { + fs_start = 1, // sector where the FS starts +}; int stat(const char* name, struct stat *buf) { - panic("stat not implemented"); + struct dir dir; + read_sectors_ATA_PIO(&dir, fs_start, 1); + for (int i = 0; i < ents_in_dir; ++i) { + struct dirent *e = &dir.entries[i]; + if (!strncmp(e->name, name, sizeof(e->name))) { + buf->size = e->size_bytes; + buf->start_sector = e->offset_sectors; + return 0; + } + } + return -1; } -int read_file(const char* name, void* buf, uint32_t bufsize) { - panic("read_file not implemented"); +/* Find file by name and read it into buffer with size bufsize. + * At most (bufsize & -512) bytes will be read. + * Return number of bytes read or -1 on failure. */ +int read_file(const struct stat *statbuf, void* buf, uint32_t bufsize) { + uint32_t sector = fs_start + statbuf->start_sector; + uint32_t bytes_read = 0; + uint32_t file_sectors = (statbuf->size + sector_size - 1) / sector_size; + while (bufsize >= sector_size && file_sectors > 0) { + read_sectors_ATA_PIO(buf, sector, 1); + sector++; + file_sectors--; + bufsize -= sector_size; + buf += sector_size; + bytes_read += sector_size; + } + return bytes_read < statbuf->size ? bytes_read : statbuf->size; } diff --git a/fs/fs.h b/fs/fs.h index 67f01a2..a67d569 100644 --- a/fs/fs.h +++ b/fs/fs.h @@ -38,7 +38,8 @@ struct dir { struct stat { uint32_t size; - uint32_t reserved[3]; + uint32_t start_sector; + uint32_t reserved1, reserved2; }; /* Find file by name and fill information in buf. @@ -48,4 +49,4 @@ int stat(const char* name, struct stat *buf); /* Find file by name and read it into buffer with size bufsize. * At most (bufsize & ~511) bytes will be read. * Return number of bytes read or -1 on failure. */ -int read_file(const char* name, void* buf, uint32_t bufsize); +int read_file(const struct stat *statbuf, void* buf, uint32_t bufsize); diff --git a/kernel.c b/kernel.c index 5e7441f..5b309e5 100644 --- a/kernel.c +++ b/kernel.c @@ -1,21 +1,27 @@ -asm(".asciz \"kernel start\\n\""); - #include "console.h" #include "cpu/isr.h" #include "cpu/gdt.h" +#include "cpu/memlayout.h" #include "drivers/keyboard.h" #include "drivers/vga.h" #include "drivers/ata.h" #include "drivers/misc.h" +#include "drivers/pit.h" #include "drivers/uart.h" #include "fs/fs.h" #include "lib/string.h" #include "proc.h" +#include "kernel/mem.h" -void _start() { +void kmain() { + freerange(P2V(1u<<20), P2V(2u<<20)); // 1MB - 2MB + kvmalloc(); // map all of physical memory at KERNBASE + freerange(P2V(2u<<20), P2V(PHYSTOP)); + load_gdt(); init_keyboard(); + init_pit(); uartinit(); load_idt(); sti(); @@ -28,6 +34,11 @@ void _start() { if (kbd_buf_size > 0 && kbd_buf[kbd_buf_size-1] == '\n') { if (!strncmp("halt\n", kbd_buf, kbd_buf_size)) { qemu_shutdown(); + } else if (!strncmp("work\n", kbd_buf, kbd_buf_size)) { + for (int i = 0; i < 5; ++i) { + msleep(1000); + printk("."); + } } else if (!strncmp("run ", kbd_buf, 4)) { kbd_buf[kbd_buf_size-1] = '\0'; const char* cmd = kbd_buf + 4; diff --git a/kernel/kstart.S b/kernel/kstart.S new file mode 100644 index 0000000..e1e87b8 --- /dev/null +++ b/kernel/kstart.S @@ -0,0 +1,44 @@ +#include "cpu/memlayout.h" + +/* + Memory layout at this point (see https://wiki.osdev.org/Memory_Map_(x86) for more details): + 0x00500 - 0x08fff: usable memory + 0x09000 – 0x14fff: kernel code and global data + 0x15000 - 0x7ffff: usable memory + 0x80000 - 0xfffff: BDA and upper memory + 0x100000 - 0x8000000 (1 MiB - 128 MiB): usable memory +*/ + .intel_syntax noprefix + .global _start + .asciz "kernel start\n" +_start: + // zero out PD at 0x1000 + xor eax, eax + mov ecx, 1024 + rep stosd + + // Enable 4 MiB pages + mov eax, cr4 + or eax, 0x10 // Set the PSE bit (bit 4) + mov cr4, eax + + // Identity map low 4 MiB + mov dword ptr [0x1000], 0 | PTE_P | PTE_W | PTE_PS + + // KERNBASE = 0x8000_0000 + // Same mapping for the first 4 MiB after KERNBASE + mov dword ptr [0x1000 + ((KERNBASE >> 22) * 4)], 0 | PTE_P | PTE_W | PTE_PS + + // Load physical address of PD into CR3 + mov edi, 0x1000 + mov cr3, edi + + // Enable paging + mov eax, cr0 + or eax, 1 << 31 // Set the PG bit + mov cr0, eax + + // jump to the high half + add esp, KERNBASE + lea eax, kmain + jmp eax diff --git a/kernel/mem.c b/kernel/mem.c new file mode 100644 index 0000000..5fd7f82 --- /dev/null +++ b/kernel/mem.c @@ -0,0 +1,70 @@ +// Physical memory allocator, intended to allocate +// memory for user processes, kernel stacks, page table pages, +// and pipe buffers. Allocates 4096-byte pages. + +#include +#include "mem.h" +#include "console.h" +#include "cpu/memlayout.h" +#include "cpu/x86.h" + +struct run { + struct run *next; +}; + +struct { + struct run *freelist; +} kmem; + +void +freerange(void *vstart, void *vend) +{ + char *p; + p = (char*)PGROUNDUP((uintptr_t)vstart); + for(; p + PGSIZE <= (char*)vend; p += PGSIZE) + kfree(p); +} + +void* +memset(void *dst, unsigned c, uint64_t n) +{ + if ((uintptr_t)dst%4 == 0 && n%4 == 0){ + c &= 0xFF; + stosl(dst, (c<<24)|(c<<16)|(c<<8)|c, n/4); + } else + stosb(dst, c, n); + return dst; +} + +// Free the page of physical memory pointed at by v, +// which normally should have been returned by a +// call to kalloc(). (The exception is when +// initializing the allocator; see kinit above.) +void +kfree(void *v) +{ + struct run *r; + + if((uintptr_t)v % PGSIZE || V2P(v) >= PHYSTOP) + panic("kfree"); + + // Fill with junk to catch dangling refs. + memset(v, 1, PGSIZE); + + r = v; + r->next = kmem.freelist; + kmem.freelist = r; +} + +// Allocate one 4096-byte page of physical memory. +// Returns a pointer that the kernel can use. +// Returns 0 if the memory cannot be allocated. +void* kalloc(void) +{ + struct run *r; + + r = kmem.freelist; + if(r) + kmem.freelist = r->next; + return (char*)r; +} diff --git a/kernel/mem.h b/kernel/mem.h new file mode 100644 index 0000000..be1f5e6 --- /dev/null +++ b/kernel/mem.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +typedef uintptr_t pde_t; +typedef uintptr_t pte_t; + +void* memset(void *dst, unsigned c, uint64_t n); + +void freerange(void *vstart, void *vend); +void* kalloc(void); +void kfree(void*); + +pde_t *setupkvm(); +void kvmalloc(); +void switchkvm(); +int allocuvm(pde_t *pgdir, uintptr_t base, uintptr_t top); +void freevm(pde_t *pgdir); diff --git a/kernel/vm.c b/kernel/vm.c new file mode 100644 index 0000000..7ece1ec --- /dev/null +++ b/kernel/vm.c @@ -0,0 +1,112 @@ +#include "mem.h" +#include "cpu/memlayout.h" +#include "cpu/x86.h" +#include "console.h" + +pde_t *kvm; + +pde_t *setupkvm(void) { + pde_t *kvm = kalloc(); + memset(kvm, 0, PGSIZE); + + // Map physical memory to KERNBASE..KERNBASE+PHYSTOP + for (uintptr_t pa = 0; pa < PHYSTOP; pa += 4 << 20) { + uintptr_t va = KERNBASE + pa; + kvm[PDX(va)] = pa | PTE_P | PTE_W | PTE_PS; + } + return kvm; +} + +void kvmalloc() { + kvm = setupkvm(); + switchkvm(); +} + +void switchkvm(void) +{ + lcr3(V2P(kvm)); // switch to the kernel page table +} + +// Return the address of the PTE in page table pgdir +// that corresponds to virtual address va. If alloc!=0, +// create any required page table pages. +static pte_t * +walkpgdir(pde_t *pgdir, const void *va, int alloc) +{ + pde_t *pde; + pte_t *pgtab; + + pde = &pgdir[PDX(va)]; + if (*pde & PTE_P) { + pgtab = (pte_t *)P2V(PTE_ADDR(*pde)); + } else { + if (!alloc || (pgtab = (pte_t *)kalloc()) == 0) + return 0; + // Make sure all those PTE_P bits are zero. + memset(pgtab, 0, PGSIZE); + // The permissions here are overly generous, but they can + // be further restricted by the permissions in the page table + // entries, if necessary. + *pde = V2P(pgtab) | PTE_P | PTE_W | PTE_U; + } + return &pgtab[PTX(va)]; +} + +// Create PTEs for virtual addresses starting at va that refer to +// physical addresses starting at pa. va and size might not +// be page-aligned. +static int +mappages(pde_t *pgdir, void *va, uintptr_t size, uintptr_t pa, int perm) +{ + char *a, *last; + pte_t *pte; + + a = (char *)PGROUNDDOWN((uintptr_t)va); + last = (char *)PGROUNDDOWN(((uintptr_t)va) + size - 1); + for (;;) + { + if ((pte = walkpgdir(pgdir, a, 1)) == 0) + return -1; + if (*pte & PTE_P) + panic("remap"); + *pte = pa | perm | PTE_P; + if (a == last) + break; + a += PGSIZE; + pa += PGSIZE; + } + return 0; +} + +int allocuvm(pde_t *pgdir, uintptr_t base, uintptr_t top) { + for (uintptr_t a = PGROUNDUP(base); a < top; a += PGSIZE) { + char *pa = kalloc(); + if (pa == 0) { + return -1; + } + memset(pa, 0, PGSIZE); + if (mappages(pgdir, (void*)a, PGSIZE, V2P(pa), PTE_W | PTE_U) < 0) { + kfree(pa); + return -1; + } + } + return 0; +} + +static void freept(pte_t *pt) { + for (int i = 0; i < NPTENTRIES; i++) { + if (pt[i] & PTE_P) { + kfree((char*)P2V(PTE_ADDR(pt[i]))); + } + } + kfree((char*)pt); +} + +void freevm(pde_t *pgdir) { + for (int i = 0; i < NPDENTRIES / 2; i++) { + if (pgdir[i] & PTE_P) { + freept((pte_t*)P2V(PTE_ADDR(pgdir[i]))); + } + } + kfree(pgdir); +} diff --git a/lib/mem.c b/lib/mem.c deleted file mode 100644 index 0bf188b..0000000 --- a/lib/mem.c +++ /dev/null @@ -1,12 +0,0 @@ -#include "mem.h" - -static void* freeptr; - -void* kmalloc(size_t size) { - if (!freeptr) { - freeptr = (void*)(1<<20); - } - void* result = freeptr; - freeptr += size; - return result; -} diff --git a/lib/mem.h b/lib/mem.h deleted file mode 100644 index 1a77610..0000000 --- a/lib/mem.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -typedef unsigned size_t; - -void* kmalloc(size_t size); diff --git a/lib/string.c b/lib/string.c index fab905b..99e1171 100644 --- a/lib/string.c +++ b/lib/string.c @@ -1,5 +1,20 @@ #include "string.h" +void kmemmove(char* dst, char* src, size_t size) { + if (dst == src) return; + if (dst > src && dst < src + size) { // s d + // copy right-to-left + for (; size != 0; size--) { + dst[size - 1] = src[size - 1]; + } + } else { + // copy left-to-right + for (size_t i = 0; i < size; ++i) { + dst[i] = src[i]; + } + } +} + int strncmp(const char* s1, const char* s2, size_t size) { while (size && *s1 && *s2 && *s1 == *s2) { size--; @@ -11,10 +26,3 @@ int strncmp(const char* s1, const char* s2, size_t size) { } return (unsigned char)(*s1) - (unsigned char)(*s2); } - -void memset(void* b, char c, size_t len) { - char* p = b; - for (size_t i = 0; i < len; ++i) { - p[i] = c; - } -} diff --git a/lib/string.h b/lib/string.h index 6184c33..584f7d7 100644 --- a/lib/string.h +++ b/lib/string.h @@ -4,4 +4,3 @@ typedef unsigned size_t; void kmemmove(char* dst, char* src, size_t size); int strncmp(const char* s1, const char* s2, size_t size); -void memset(void* b, char c, size_t len); diff --git a/proc.c b/proc.c index 6ec0971..4bacc84 100644 --- a/proc.c +++ b/proc.c @@ -3,7 +3,8 @@ #include "fs/fs.h" #include "cpu/gdt.h" #include "cpu/isr.h" -#include "lib/mem.h" +#include "cpu/memlayout.h" +#include "kernel/mem.h" #include "lib/string.h" #include "console.h" @@ -23,31 +24,40 @@ struct kstack { struct task { struct taskstate tss; struct kstack stack; + pde_t *pgdir; }; struct vm { void *kernel_thread; - void *user_thread; struct task *user_task; -} *vm; +} vm; void trapret(); void swtch(void** oldstack, void* newstack); void run_elf(const char* name) { - if (!vm) { - vm = kmalloc(sizeof(struct vm)); - vm->user_task = kmalloc(sizeof(struct task)); - switchuvm(&vm->user_task->tss, vm->user_task->stack.bottom); + struct stat statbuf; + if (stat(name, &statbuf) != 0) { + printk(name); + printk(": file not found\n"); + return; } - if (read_file(name, (void*)USER_BASE, 100 << 20) <= 0) { + if (!vm.user_task) { + vm.user_task = kalloc(); + } + vm.user_task->pgdir = setupkvm(); + allocuvm(vm.user_task->pgdir, USER_BASE, USER_BASE + statbuf.size); + allocuvm(vm.user_task->pgdir, USER_STACK_BASE - 2 * PGSIZE, USER_STACK_BASE); + switchuvm(&vm.user_task->tss, vm.user_task->stack.bottom, vm.user_task->pgdir); + + if (read_file(&statbuf, (void*)USER_BASE, 100 << 20) <= 0) { printk(name); printk(": file not found\n"); return; } Elf32_Ehdr *hdr = (void*)USER_BASE; - struct kstack *u = &vm->user_task->stack; + struct kstack *u = &vm.user_task->stack; memset(u, 0, sizeof(*u)); u->context.eip = (uint32_t)trapret; @@ -63,13 +73,16 @@ void run_elf(const char* name) { tf->useresp = USER_STACK_BASE; // initialization done, now switch to the process - swtch(&vm->kernel_thread, &u->context); + swtch(&vm.kernel_thread, &u->context); // process has finished } _Noreturn void killproc() { void* task_stack; - swtch(&task_stack, vm->kernel_thread); + switchkvm(); + freevm(vm.user_task->pgdir); + sti(); + swtch(&task_stack, vm.kernel_thread); __builtin_unreachable(); } diff --git a/syscall.h b/syscall.h index 4c9ba03..7973ecb 100644 --- a/syscall.h +++ b/syscall.h @@ -4,6 +4,8 @@ enum { T_SYSCALL = 0x84, SYS_exit = 0, SYS_greet = 1, + SYS_putc = 2, + SYS_puts = 3, }; int syscall(int call, int arg); diff --git a/user/shout.c b/user/shout.c new file mode 100644 index 0000000..4f77e42 --- /dev/null +++ b/user/shout.c @@ -0,0 +1,16 @@ +#include "../syscall.h" +#include + +void userspace_puts(const char* s) { + char c; + while ((c = *s++)) { + syscall(SYS_putc, c); + } +} + +int main() { + const char* spell = "cra cra trif traf not sgnieflet\n"; + userspace_puts(spell); + syscall(SYS_puts, (uint32_t)spell); + return 0; +}