From 6e5222e2e3679de8f7078c17ea2d145e90c84f4d Mon Sep 17 00:00:00 2001 From: Alexander Myltsev Date: Fri, 13 Jan 2023 12:56:54 +0400 Subject: [PATCH 1/7] Initial commit --- Makefile | 80 ++++++++++++++++++++++ README.md | 13 ++++ console.c | 19 ++++++ console.h | 4 ++ cpu/gdt.h | 77 +++++++++++++++++++++ drivers/port.h | 25 +++++++ drivers/uart.c | 44 ++++++++++++ drivers/uart.h | 4 ++ drivers/vga.c | 48 +++++++++++++ drivers/vga.h | 38 +++++++++++ fs/fs.h | 51 ++++++++++++++ kernel.c | 12 ++++ mbr.S | 178 +++++++++++++++++++++++++++++++++++++++++++++++++ setup.sh | 10 +++ tools/mkfs.c | 79 ++++++++++++++++++++++ 15 files changed, 682 insertions(+) create mode 100644 Makefile create mode 100644 README.md create mode 100644 console.c create mode 100644 console.h create mode 100644 cpu/gdt.h create mode 100644 drivers/port.h create mode 100644 drivers/uart.c create mode 100644 drivers/uart.h create mode 100644 drivers/vga.c create mode 100644 drivers/vga.h create mode 100644 fs/fs.h create mode 100644 kernel.c create mode 100644 mbr.S create mode 100755 setup.sh create mode 100644 tools/mkfs.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ec27cdb --- /dev/null +++ b/Makefile @@ -0,0 +1,80 @@ +GDB=gdb + +ifeq ($(shell uname -s),Darwin) +AS=x86_64-elf-as +LD=x86_64-elf-ld +CC=x86_64-elf-gcc +GDB=x86_64-elf-gdb +endif + +CFLAGS = -fno-pic -ffreestanding -static -fno-builtin -fno-strict-aliasing \ + -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) + +run: image.bin + qemu-system-i386 -drive format=raw,file=$< -serial mon:stdio + +run-nox: image.bin + qemu-system-i386 -nographic -drive format=raw,file=$< -serial mon:stdio + +debug-boot-nox: image.bin mbr.elf + qemu-system-i386 -nographic -drive format=raw,file=$< -s -S & + $(GDB) mbr.elf \ + -ex "set architecture i8086" \ + -ex "target remote localhost:1234" \ + -ex "break *0x7c00" \ + -ex "continue" + +debug-boot: image.bin mbr.elf + qemu-system-i386 -drive format=raw,file=$< -s -S & + $(GDB) mbr.elf \ + -ex "set architecture i8086" \ + -ex "target remote localhost:1234" \ + -ex "break *0x7c00" \ + -ex "continue" + +debug: image.bin + qemu-system-i386 -drive format=raw,file=$< -s -S & + $(GDB) kernel.bin \ + -ex "target remote localhost:1234" \ + -ex "break _start" \ + -ex "continue" + +debug-nox: image.bin + qemu-system-i386 -nographic -drive format=raw,file=$< -s -S & + $(GDB) kernel.bin \ + -ex "target remote localhost:1234" \ + -ex "break _start" \ + -ex "continue" + +fs.img: kernel.bin tools/mkfs + tools/mkfs $@ $< + +LDFLAGS=-m elf_i386 + +user/%: user/%.o user/crt.o + $(LD) $(LDFLAGS) -o $@ -Ttext 0x1000 $^ + +image.bin: mbr.bin fs.img + cat $^ >$@ + +kernel.bin: kernel.o console.o drivers/vga.o drivers/uart.o + $(LD) $(LDFLAGS) -o $@ -Ttext 0x1000 $^ + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +%.o: %.S + $(CC) -m32 -ffreestanding -c -g $^ -o $@ + +mbr.bin: mbr.o + $(LD) -m elf_i386 -Ttext=0x7c00 --oformat=binary $^ -o $@ + +mbr.elf: mbr.o + $(LD) -m elf_i386 -Ttext=0x7c00 $^ -o $@ + +clean: + rm -f *.elf *.img *.bin *.o */*.o tools/mkfs + +tools/%: tools/%.c + gcc -Wall -Werror -g $^ -o $@ diff --git a/README.md b/README.md new file mode 100644 index 0000000..4422fad --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +Yet Another BootLoader, OS Kernel and Other stuff + +Quickstart: +``` +$ ./setup.sh +$ make +``` + +Includes code from: +* https://github.com/mit-pdos/xv6-public +* https://github.com/FRosner/FrOS +* https://github.com/dhavalhirdhav/LearnOS +* https://wiki.osdev.org diff --git a/console.c b/console.c new file mode 100644 index 0000000..519037b --- /dev/null +++ b/console.c @@ -0,0 +1,19 @@ +#include "console.h" +#include "drivers/vga.h" +#include "drivers/uart.h" + +void printk(const char* msg) { + vga_print_string_noscroll(msg); + for (; *msg; ++msg) { + uartputc(*msg); + } +} + +void panic(const char* msg) { + printk("\nKernel panic: "); + printk(msg); + asm("cli"); + while (1) { + asm("hlt"); + } +} diff --git a/console.h b/console.h new file mode 100644 index 0000000..2e5c22a --- /dev/null +++ b/console.h @@ -0,0 +1,4 @@ +#pragma once + +void printk(const char* msg); +void panic(const char* msg); diff --git a/cpu/gdt.h b/cpu/gdt.h new file mode 100644 index 0000000..00dd40b --- /dev/null +++ b/cpu/gdt.h @@ -0,0 +1,77 @@ +#pragma once + +#define STA_X 0x8 // Executable segment +#define STA_W 0x2 // Writeable (non-executable segments) +#define STA_R 0x2 // Readable (executable segments) + +// System segment type bits +#define STS_T32A 0x9 // Available 32-bit TSS +#define STS_IG32 0xE // 32-bit Interrupt Gate +#define STS_TG32 0xF // 32-bit Trap Gate + +#define DPL_USER 3 + +#define FL_IF 0x00000200 + +#define SEG_KCODE 1 +#define SEG_KDATA 2 +#define SEG_UCODE 3 +#define SEG_UDATA 4 +#define SEG_TSS 5 + +#define SEG_ASM(type,base,lim) \ + .word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \ + .byte (((base) >> 16) & 0xff), (0x90 | (type)), \ + (0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff) + +#define USER_BASE 0x400000 // 4 MB +#define USER_STACK_BASE 0xf00000 // 15 MB +#define KERN_STACK_BASE 0x90000 + +#ifndef __ASSEMBLER__ +typedef unsigned uint; +typedef unsigned short ushort; + +struct taskstate { + uint link; // Old ts selector + uint esp0; // Stack pointers and segment selectors + ushort ss0; // after an increase in privilege level + ushort padding1; + uint *esp1; + ushort ss1; + ushort padding2; + uint *esp2; + ushort ss2; + ushort padding3; + void *cr3; // Page directory base + uint *eip; // Saved state from last task switch + uint eflags; + uint eax; // More saved state (registers) + uint ecx; + uint edx; + uint ebx; + uint *esp; + uint *ebp; + uint esi; + uint edi; + ushort es; // Even more saved state (segment selectors) + ushort padding4; + ushort cs; + ushort padding5; + ushort ss; + ushort padding6; + ushort ds; + ushort padding7; + ushort fs; + ushort padding8; + ushort gs; + ushort padding9; + ushort ldt; + ushort padding10; + ushort t; // Trap on task switch + ushort iomb; // I/O map base address +}; + +void load_gdt(); +void switchuvm(struct taskstate *tss, void* esp); +#endif diff --git a/drivers/port.h b/drivers/port.h new file mode 100644 index 0000000..380272a --- /dev/null +++ b/drivers/port.h @@ -0,0 +1,25 @@ +#pragma once + +static inline unsigned char port_byte_in(unsigned short port) { + unsigned char result; + __asm__("in %%dx, %%al" : "=a" (result) : "d" (port)); + return result; +} + +static inline unsigned short port_word_in(unsigned short port) { + unsigned short result; + __asm__("in %%dx, %%ax" : "=a" (result) : "d" (port)); + return result; +} + +static inline void port_byte_out(unsigned short port, unsigned char data) { + __asm__("outb %%al, %%dx" : : "a" (data), "d" (port)); +} + +static inline void port_word_out(unsigned short port, unsigned short data) { + __asm__("outw %%ax, %%dx" : : "a" (data), "d" (port)); +} + +static inline void port_long_out(unsigned short port, unsigned int data) { + __asm__("outl %%eax, %%dx" : : "a" (data), "d" (port)); +} diff --git a/drivers/uart.c b/drivers/uart.c new file mode 100644 index 0000000..613567f --- /dev/null +++ b/drivers/uart.c @@ -0,0 +1,44 @@ +#include "uart.h" +#include "port.h" + +static int uart; + +enum { + COM1 = 0x3f8, +}; + +void uartinit() { + // Turn off the FIFO + port_byte_out(COM1+2, 0); + + // 9600 baud, 8 data bits, 1 stop bit, parity off. + port_byte_out(COM1+3, 0x80); // Unlock divisor + port_byte_out(COM1+0, 115200/9600); + port_byte_out(COM1+1, 0); + port_byte_out(COM1+3, 0x03); // Lock divisor, 8 data bits. + port_byte_out(COM1+4, 0); + port_byte_out(COM1+1, 0x01); // Enable receive interrupts. + + // If status is 0xFF, no serial port. + if(port_byte_in(COM1+5) == 0xFF) + return; + uart = 1; + + // Acknowledge pre-existing interrupt conditions; + // enable interrupts. + port_byte_in(COM1+2); + port_byte_in(COM1+0); +} + +void +uartputc(char c) +{ + int i; + + if (!uart) + return; + for (i = 0; i < 128 && !(port_byte_in(COM1+5) & 0x20); i++) { + asm("pause"); + } + port_byte_out(COM1+0, c); +} diff --git a/drivers/uart.h b/drivers/uart.h new file mode 100644 index 0000000..dfbc8da --- /dev/null +++ b/drivers/uart.h @@ -0,0 +1,4 @@ +#pragma once + +void uartputc(char c); +void uartinit(); diff --git a/drivers/vga.c b/drivers/vga.c new file mode 100644 index 0000000..5a605cd --- /dev/null +++ b/drivers/vga.c @@ -0,0 +1,48 @@ +#include "port.h" +#include "vga.h" + +static unsigned char get_color(unsigned char fg, unsigned char bg) { + return (bg << 4) + fg; +} + +static unsigned get_offset(unsigned col, unsigned row) { + return row * COLS + col; +} + +static unsigned get_row_from_offset(unsigned offset) { + return offset / COLS; +} + +unsigned vga_get_cursor() { + port_byte_out(VGA_CTRL_REGISTER, VGA_OFFSET_HIGH); + unsigned offset = port_byte_in(VGA_DATA_REGISTER) << 8; + port_byte_out(VGA_CTRL_REGISTER, VGA_OFFSET_LOW); + offset += port_byte_in(VGA_DATA_REGISTER); + return offset; +} + +void vga_set_char(unsigned offset, char c) { + video_memory[2 * offset] = 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, ' '); + } +} + +void vga_print_string_noscroll(const char* s) { + while (*s != 0) { + if (*s == '\n') { + offset = get_offset(0, get_row_from_offset(offset) + 1); + } else { + vga_set_char(offset, *s); + offset++; + } + offset %= COLS * ROWS; + s++; + } +} diff --git a/drivers/vga.h b/drivers/vga.h new file mode 100644 index 0000000..dc21424 --- /dev/null +++ b/drivers/vga.h @@ -0,0 +1,38 @@ +#pragma once + +static char* const video_memory = (char*) 0xb8000; + +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.h b/fs/fs.h new file mode 100644 index 0000000..67f01a2 --- /dev/null +++ b/fs/fs.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +/* Directory structure: + + 32-byte entries +┌───────────────────────────────┐ +│Reserved │ +│char[32] │ +├──────┬──────┬────────┬────────┤ +│Offset│Size │Reserved│Name │ +│uint32│uint32│uint32 │char[20]│ +├──────┼──────┼────────┼────────┤ +│ ... │ │ │ │ + +Offset is in sectors (zero-based), +size is in bytes, name is 0-terminated. + +*/ + +enum { + sector_size = 512, + ents_in_dir = 15, +}; + +struct dirent { + uint32_t offset_sectors; + uint32_t size_bytes; + uint32_t reserved; + char name[20]; +}; + +struct dir { + char reserved[32]; + struct dirent entries[ents_in_dir]; +}; + +struct stat { + uint32_t size; + uint32_t reserved[3]; +}; + +/* Find file by name and fill information in buf. + * Returns zero on success, nonzero on failure. */ +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); diff --git a/kernel.c b/kernel.c new file mode 100644 index 0000000..17b5a5c --- /dev/null +++ b/kernel.c @@ -0,0 +1,12 @@ +#include "console.h" +#include "drivers/vga.h" +#include "drivers/uart.h" + + +void _start() { + uartinit(); + + vga_clear_screen(); + printk("\nYABLOKO\n"); + asm("hlt"); +} diff --git a/mbr.S b/mbr.S new file mode 100644 index 0000000..20b5fe3 --- /dev/null +++ b/mbr.S @@ -0,0 +1,178 @@ +#include "cpu/gdt.h" + + .code16 + .global _start +_start: + mov %dl, boot_drive + mov $banner, %si + call print_string + + call get_drive_geometry + call load_kernel + call switch_to_32bit + + hlt + jmp . // loop forever + +get_drive_geometry: + mov $8, %ah + mov boot_drive, %dl + int $0x13 + inc %dh // number of heads + mov %dh, disk_heads + and 0x3f, %cl + mov %cl, sectors_per_track + ret + + +.equ ELF32_ENTRY_OFFSET, 0x18 +.equ ELF32_PHDR_OFFSET, 0x1c +.equ ELF32_PHENTSIZE_OFFSET, ELF32_PHDR_OFFSET + 14 +.equ ELF32_PHNUM_OFFSET, ELF32_PHENTSIZE_OFFSET + 2 +.equ ELF32_PHDR_P_OFFSET, 4 +.equ ELF32_PHDR_PTYPE_OFFSET, 0 +.equ ELF32_PHDR_FILESZ_OFFSET, 4*4 +.equ KERNEL_OFFSET, 0x1000 + +.equ PT_LOAD, 1 + +.equ MBR_SECTORS, 2 +.equ SECTOR_BASE, 1 +.equ ELFHDR_SECTORS, 8 + +.equ SECTOR_SIZE, 512 +.equ SECTOR_SHIFT, 9 + +load_kernel: + mov $1, %al // sectors to read + mov $SECTOR_BASE + MBR_SECTORS, %cl // start after MBR + call bios_disk_read + + mov KERNEL_OFFSET + ELF32_ENTRY_OFFSET, %si + mov %si, entry // store entry point + + mov KERNEL_OFFSET + ELF32_PHNUM_OFFSET, %si +read_segment: + dec %si // no offset to the first entry + mov %si, %ax + mulb KERNEL_OFFSET + ELF32_PHENTSIZE_OFFSET + mov %ax, %di + add KERNEL_OFFSET + ELF32_PHDR_OFFSET, %di + // now di holds offset to the phentry + mov KERNEL_OFFSET + ELF32_PHDR_PTYPE_OFFSET(%di), %ax + cmp $PT_LOAD, %ax + jnz read_segment // not a PT_LOAD segment + mov KERNEL_OFFSET + ELF32_PHDR_FILESZ_OFFSET(%di), %ax + test %ax, %ax + jz read_segment // empty segment + + // now di holds offset to the last phentry loaded from file, ax its filesz + + add KERNEL_OFFSET + ELF32_PHDR_P_OFFSET(%di), %ax + sub $0x1000, %ax // we won't load the header + add $SECTOR_SIZE - 1, %ax + shr $SECTOR_SHIFT, %ax // round up to sector count + + mov $SECTOR_BASE + MBR_SECTORS + ELFHDR_SECTORS, %cl //start after ELF header + call bios_disk_read + ret + +bios_disk_read: + // expects %al to specify number of sectors, %cl the initial sector + xor %ah, %ah + mov %ax, %si + mov $0, %ch // cylinder 0 + mov $0, %dh // head 0 + mov $KERNEL_OFFSET, %bx // bx -> destination + mov boot_drive, %dl // dl -> disk + mov $1, %al + +1: + mov $2, %ah // read mode + int $0x13 + jc fail + add $SECTOR_SIZE, %bx + inc %cl + dec %si + jnz 1b + ret + +fail: + mov $read_error, %si + call print_string + hlt + jmp . + +switch_to_32bit: + mov $2, %al + out %al, $0x92 // enable A20 + + cli // 1. disable interrupts + lgdt gdt_descriptor // 2. load GDT descriptor + mov %cr0, %eax + or $1, %eax // 3. enable protected mode + mov %eax, %cr0 + ljmp $SEG_KCODE << 3, $init_32bit // 4. far jump + + +.code32 +init_32bit: + mov $SEG_KDATA << 3, %ax // 5. update segment registers + mov %ax, %ds + mov %ax, %ss + mov %ax, %es + mov %ax, %fs + mov %ax, %gs + + mov $KERN_STACK_BASE, %ebp // 6. setup stack + mov %ebp, %esp + + movzwl entry, %esi + call *%esi // 7. jump to the kernel + jmp . // 8. loop forever + + +.code16 +print_string: + mov $0x0e, %ah // "teletype output" +repeat: + lodsb // equivalent to mov (%si), %al; inc %si + + test %al, %al + je done + + int $0x10 // bios interrupt + jmp repeat +done: + ret + + . = _start + 256 # pad to 256 bytes +boot_drive: + .byte 0 +banner: + .asciz "YABLOKO bootloader started\n\r" +read_error: + .asciz "Read error\n\r" + + .balign 2 +entry: + .word 0 +disk_heads: + .byte 0 +sectors_per_track: + .byte 0 + + .balign 4 +gdt_start: + .quad 0x0 // null descriptor + SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg + SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg +gdt_end: + +// GDT descriptor +gdt_descriptor: + .word gdt_end - gdt_start - 1 // size (16 bit) + .int gdt_start // address (32 bit) + + . = _start + 510 # pad to 510 bytes + .byte 0x55, 0xaa # boot sector magic value diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..d51750f --- /dev/null +++ b/setup.sh @@ -0,0 +1,10 @@ +#!/bin/sh -x + +if [ `uname` = Darwin ]; then + brew install \ + x86_64-elf-binutils x86_64-elf-gcc \ + x86_64-elf-gdb qemu +elif [ `uname` = Linux ]; then + sudo apt-get update + sudo apt-get install qemu-system-x86 +fi diff --git a/tools/mkfs.c b/tools/mkfs.c new file mode 100644 index 0000000..32b1b86 --- /dev/null +++ b/tools/mkfs.c @@ -0,0 +1,79 @@ +#include "../fs/fs.h" + +#include +#include + +char* basename(char* path) { + char* c = strrchr(path, '/'); + if (c && *c) { + return c + 1; + } + return path; +} + +int main(int argc, char* argv[]) { + char sector[sector_size]; + struct dir dir = {{0}}; + + if (argc < 3) { + fprintf(stderr, "Usage: %s OUT.FS KERNEL.BIN [FILES...]\n", argv[0]); + return 1; + } + + FILE* image = fopen(argv[1], "wb"); + if (!image) { + perror(argv[1]); + return 1; + } + + if (fwrite(&dir, sizeof(dir), 1, image) < 1) { + perror("fwrite"); + return 1; + } + uint32_t sector_offset = 1; + + for (int i = 2; i < argc; ++i) { + char* name = argv[i]; + struct dirent *dirent = &dir.entries[i-2]; + dirent->offset_sectors = sector_offset; + dirent->size_bytes = 0; + + FILE* file = fopen(name, "rb"); + if (!file) { + perror(name); + return 1; + } + + size_t read_size; + while ((read_size = fread(sector, 1, sizeof(sector), file))) { + if (fwrite(sector, 1, sizeof(sector), image) != sizeof(sector)) { + perror(name); + return 1; + } + sector_offset++; + dirent->size_bytes += read_size; + } + + if (fclose(file)) { + perror(name); + return 1; + } + + dirent->reserved = 0; + dirent->name[sizeof(dirent->name) - 1] = '\0'; + strncpy(dirent->name, basename(name), sizeof(dirent->name) - 1); + } + + fseek(image, 0, SEEK_SET); + if (fwrite(&dir, sizeof(dir), 1, image) < 1) { + perror("fwrite"); + return 1; + } + + if (fclose(image)) { + perror(argv[0]); + return 1; + } + + return 0; +} From 57704ff8dd41f308c34a95fe5b9c1dff9eb4d023 Mon Sep 17 00:00:00 2001 From: Alexander Myltsev Date: Sat, 14 Jan 2023 12:12:26 +0400 Subject: [PATCH 2/7] Rule for ejudge.sh; fix declaration of video_memory. --- Makefile | 4 ++++ drivers/vga.c | 2 ++ drivers/vga.h | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ec27cdb..bc289ee 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,10 @@ run: image.bin run-nox: image.bin qemu-system-i386 -nographic -drive format=raw,file=$< -serial mon:stdio +ejudge.sh: image.bin + echo >$@ "#!/bin/sh\nexec qemu-system-i386 -nographic -drive format=raw,file=$< -serial mon:stdio" + chmod +x $@ + debug-boot-nox: image.bin mbr.elf qemu-system-i386 -nographic -drive format=raw,file=$< -s -S & $(GDB) mbr.elf \ diff --git a/drivers/vga.c b/drivers/vga.c index 5a605cd..274a653 100644 --- a/drivers/vga.c +++ b/drivers/vga.c @@ -1,6 +1,8 @@ #include "port.h" #include "vga.h" +char* const video_memory = (char*) 0xb8000; + static unsigned char get_color(unsigned char fg, unsigned char bg) { return (bg << 4) + fg; } diff --git a/drivers/vga.h b/drivers/vga.h index dc21424..9307fa6 100644 --- a/drivers/vga.h +++ b/drivers/vga.h @@ -1,6 +1,6 @@ #pragma once -static char* const video_memory = (char*) 0xb8000; +extern char* const video_memory; enum { ROWS = 25, From 6984aab0af132b068b1b4444f2e398a12c68b45e Mon Sep 17 00:00:00 2001 From: Alexander Myltsev Date: Sat, 14 Jan 2023 15:36:24 +0400 Subject: [PATCH 3/7] add qemu_shutdown. --- drivers/misc.h | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 drivers/misc.h diff --git a/drivers/misc.h b/drivers/misc.h new file mode 100644 index 0000000..bbb1701 --- /dev/null +++ b/drivers/misc.h @@ -0,0 +1,8 @@ +#pragma once +#include "port.h" + +__attribute__((noreturn)) +static inline void qemu_shutdown() { + port_word_out(0x604, 0x2000); + __builtin_unreachable(); +} From 160525abdee84451b5551b5e33b7373b2d88235d Mon Sep 17 00:00:00 2001 From: Alexander Myltsev Date: Sun, 15 Jan 2023 18:30:57 +0400 Subject: [PATCH 4/7] ejudge.sh: pack image.bin inside the script. --- Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index bc289ee..1ae7f1c 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,11 @@ run-nox: image.bin qemu-system-i386 -nographic -drive format=raw,file=$< -serial mon:stdio ejudge.sh: image.bin - echo >$@ "#!/bin/sh\nexec qemu-system-i386 -nographic -drive format=raw,file=$< -serial mon:stdio" + echo >$@ "#!/bin/sh" + echo >>$@ "base64 -d <<===EOF | gunzip >image.bin" + gzip <$^ | base64 >>$@ + echo >>$@ "===EOF" + echo >>$@ "exec qemu-system-i386 -nographic -drive format=raw,file=image.bin -serial mon:stdio" chmod +x $@ debug-boot-nox: image.bin mbr.elf From 199f3a56bbbab1b1e85949eee7b824860ea6989a Mon Sep 17 00:00:00 2001 From: Alexander Myltsev Date: Mon, 16 Jan 2023 09:48:27 +0400 Subject: [PATCH 5/7] Add .gitignore. --- .gitignore | 7 +++++++ Makefile | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fbba961 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.o +*.bin +*.img +*.elf +*.dSYM +tools/mkfs +ejudge.sh diff --git a/Makefile b/Makefile index 1ae7f1c..4c8b27a 100644 --- a/Makefile +++ b/Makefile @@ -82,7 +82,7 @@ mbr.elf: mbr.o $(LD) -m elf_i386 -Ttext=0x7c00 $^ -o $@ clean: - rm -f *.elf *.img *.bin *.o */*.o tools/mkfs + rm -f *.elf *.img *.bin *.o */*.o tools/mkfs ejudge.sh tools/%: tools/%.c gcc -Wall -Werror -g $^ -o $@ From 4c075b8b6f3be0dbcd3eee9d94b3a73caf94f560 Mon Sep 17 00:00:00 2001 From: Alexander Myltsev Date: Thu, 19 Jan 2023 23:07:49 +0400 Subject: [PATCH 6/7] qemu_shutdown: hlt until the emulator really shuts down. --- drivers/misc.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/misc.h b/drivers/misc.h index bbb1701..353419b 100644 --- a/drivers/misc.h +++ b/drivers/misc.h @@ -4,5 +4,8 @@ __attribute__((noreturn)) static inline void qemu_shutdown() { port_word_out(0x604, 0x2000); + while (1) { + asm("hlt"); + } __builtin_unreachable(); } From f3816c1088f3987324f304dc885ec99a5ce43422 Mon Sep 17 00:00:00 2001 From: Alexander Myltsev Date: Sat, 21 Jan 2023 13:55:26 +0400 Subject: [PATCH 7/7] Interrupt handling and ATA driver. --- Makefile | 3 +- cpu/idt.c | 152 +++++++++++++++++++++++++++++++++++++++++++++ cpu/isr.h | 49 +++++++++++++++ cpu/vectors.S | 55 ++++++++++++++++ drivers/ata.c | 80 ++++++++++++++++++++++++ drivers/ata.h | 5 ++ drivers/keyboard.c | 36 +++++++++++ drivers/keyboard.h | 6 ++ kernel.c | 11 +++- lib/mem.c | 12 ++++ lib/mem.h | 5 ++ 11 files changed, 411 insertions(+), 3 deletions(-) create mode 100644 cpu/idt.c create mode 100644 cpu/isr.h create mode 100644 cpu/vectors.S create mode 100644 drivers/ata.c create mode 100644 drivers/ata.h create mode 100644 drivers/keyboard.c create mode 100644 drivers/keyboard.h create mode 100644 lib/mem.c create mode 100644 lib/mem.h diff --git a/Makefile b/Makefile index 4c8b27a..49e5ae7 100644 --- a/Makefile +++ b/Makefile @@ -66,7 +66,8 @@ user/%: user/%.o user/crt.o image.bin: mbr.bin fs.img cat $^ >$@ -kernel.bin: kernel.o console.o drivers/vga.o drivers/uart.o +kernel.bin: kernel.o console.o drivers/vga.o drivers/uart.o drivers/keyboard.o \ + cpu/idt.o cpu/vectors.o lib/mem.o $(LD) $(LDFLAGS) -o $@ -Ttext 0x1000 $^ %.o: %.c diff --git a/cpu/idt.c b/cpu/idt.c new file mode 100644 index 0000000..78f41f0 --- /dev/null +++ b/cpu/idt.c @@ -0,0 +1,152 @@ +#include "isr.h" +#include "gdt.h" +#include "../drivers/port.h" +#include "../console.h" + +enum { + IDT_HANDLERS = 256, +}; + +typedef struct { + uint16_t low_offset; + uint16_t selector; + uint8_t always0; + uint8_t type: 4; + uint8_t s: 1; + uint8_t dpl: 2; + uint8_t p: 1; + uint16_t high_offset; +} __attribute__((packed)) idt_gate_t; + +idt_gate_t idt[IDT_HANDLERS]; + +#define low_16(address) (uint16_t)((address) & 0xFFFF) +#define high_16(address) (uint16_t)(((address) >> 16) & 0xFFFF) + +#define STS_IG32 0xE // 32-bit Interrupt Gate +#define STS_TG32 0xF // 32-bit Trap Gate + +void set_idt_gate(int n, _Bool istrap, uint32_t handler, uint8_t dpl) { + idt[n].low_offset = low_16(handler); + idt[n].selector = 0x08; // see GDT + idt[n].always0 = 0; + idt[n].type = istrap ? STS_TG32 : STS_IG32; + idt[n].s = 0; + idt[n].dpl = dpl; + idt[n].p = 1; + idt[n].high_offset = high_16(handler); +} + +// defined in vectors.S +extern const uint32_t default_handlers[]; + +void init_idt() { + if (default_handlers[0] == 0) { + panic("handler table empty\n"); + } + for (int i = 0; i < IDT_HANDLERS; i++) { + set_idt_gate(i, 0, default_handlers[i], 0); + } +} + +const char * const exception_messages[] = { + [0] = "Division By Zero", + [1] = "Debug", + [2] = "Non Maskable Interrupt", + [3] = "Breakpoint", + [4] = "Into Detected Overflow", + [5] = "Out of Bounds", + [6] = "Invalid Opcode", + [7] = "No Coprocessor", + + [8] = "Double Fault", + [9] = "Coprocessor Segment Overrun", + [10] = "Bad TSS", + [11] = "Segment Not Present", + [12] = "Stack Fault", + [13] = "General Protection Fault", + [14] = "Page Fault", + [15] = "Unknown Interrupt", + + [16] = "Coprocessor Fault", + [17] = "Alignment Check", + [18] = "Machine Check", +}; + +#define ARRLEN(a) (sizeof(a) / sizeof(a[0])) + +static isr_t interrupt_handlers[IDT_HANDLERS]; + +void register_interrupt_handler(uint8_t i, isr_t handler) { + interrupt_handlers[i] = handler; +} + +void trap(registers_t *r) { + if (r->int_no < 32) { + const char* msg = "Reserved"; + if (r->int_no < ARRLEN(exception_messages)) { + msg = exception_messages[r->int_no]; + } + panic(msg); + } + + if (interrupt_handlers[r->int_no] != 0) { + isr_t handler = interrupt_handlers[r->int_no]; + handler(r); + } + + // EOI + if (r->int_no >= 40) { + port_byte_out(0xA0, 0x20); /* follower */ + } + if (r->int_no >= 32) { + port_byte_out(0x20, 0x20); /* leader */ + } +} + +static void init_pic() { + // ICW1 + port_byte_out(0x20, 0x11); + port_byte_out(0xA0, 0x11); + + // ICW2 + port_byte_out(0x21, 0x20); + port_byte_out(0xA1, 0x28); + + // ICW3 + port_byte_out(0x21, 0x04); + port_byte_out(0xA1, 0x02); + + // ICW4 + port_byte_out(0x21, 0x01); + port_byte_out(0xA1, 0x01); + + // OCW1 + port_byte_out(0x21, 0x0); + port_byte_out(0xA1, 0x0); +} + +typedef struct { + uint16_t limit; + void* base; +} __attribute__((packed)) idt_register_t; + +static idt_register_t idt_reg; + +void load_idt() { + init_idt(); + + idt_reg.base = &idt; + idt_reg.limit = sizeof(idt) - 1; + asm("lidt (%0)" : : "r"(&idt_reg)); + + init_pic(); +} + +void cli() { + asm("cli"); +} + +void sti() { + asm("sti"); +} diff --git a/cpu/isr.h b/cpu/isr.h new file mode 100644 index 0000000..b708e40 --- /dev/null +++ b/cpu/isr.h @@ -0,0 +1,49 @@ +#pragma once +#include + +enum { + IRQ0 = 32, + IRQ1, + IRQ2, + IRQ3, + IRQ4, + IRQ5, + IRQ6, + IRQ7, + IRQ8, + IRQ9, + IRQ10, + IRQ11, + IRQ12, + IRQ13, + IRQ14, + IRQ15, +}; + +/* Struct which aggregates many registers. + * It matches exactly the pushes on vectors.S. 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 + */ +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 +} registers_t; + +void isr_install(); + +void isr_handler(registers_t *r); + +typedef void (*isr_t)(registers_t *); + +void register_interrupt_handler(uint8_t n, isr_t handler); + +void load_idt(); +void cli(); +void sti(); diff --git a/cpu/vectors.S b/cpu/vectors.S new file mode 100644 index 0000000..b1131fb --- /dev/null +++ b/cpu/vectors.S @@ -0,0 +1,55 @@ + .global trapret + +alltraps: + # Build trap frame. + pushl %ds + pushl %es + pushl %fs + pushl %gs + pushal + + mov $0x10, %ax + mov %ax, %ds + + # Call trap(tf), where tf=%esp + pushl %esp + call trap + add $4, %esp + // execution falls through to trapret + +trapret: + popal + popl %gs + popl %fs + popl %es + popl %ds + addl $0x8, %esp # trapno and errcode + iret + +.macro handler i +vector\i : + .if (!(\i == 8 || (\i >= 10 && \i <= 14) || \i == 17)) + pushl $0 + .endif + pushl $\i + jmp alltraps +.endm + +.altmacro + +.macro irq_insertX number + .section .text + handler \number + + .section .rodata + .long vector\number +.endm + +.section .rodata +.global default_handlers +default_handlers: +.set i,0 +.rept 256 + irq_insertX %i + .set i, i+1 +.endr diff --git a/drivers/ata.c b/drivers/ata.c new file mode 100644 index 0000000..5abee68 --- /dev/null +++ b/drivers/ata.c @@ -0,0 +1,80 @@ +// stolen from https://github.com/dhavalhirdhav/LearnOS/blob/master/drivers/ata/ata.c + +#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_ERR 0x01 + +//This is really specific to our OS now, assuming ATA bus 0 master +//Source - OsDev wiki +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) +{ + + 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; + + 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; + } +} + +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 + + 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); diff --git a/drivers/keyboard.c b/drivers/keyboard.c new file mode 100644 index 0000000..d7ade6e --- /dev/null +++ b/drivers/keyboard.c @@ -0,0 +1,36 @@ +#include "keyboard.h" +#include "../cpu/isr.h" +#include "../console.h" +#include "port.h" +#include "../lib/mem.h" + +static const char sc_ascii[] = { + '?', '?', '1', '2', '3', '4', '5', '6', + '7', '8', '9', '0', '-', '=', '?', '?', 'q', 'w', 'e', 'r', 't', 'y', + 'u', 'i', 'o', 'p', '[', ']', '\n', '?', 'a', 's', 'd', 'f', 'g', + 'h', 'j', 'k', 'l', ';', '\'', '`', '?', '\\', 'z', 'x', 'c', 'v', + 'b', 'n', 'm', ',', '.', '/', '?', '?', '?', ' ', +}; + +enum { kbd_buf_capacity = 1024 }; + +static void interrupt_handler(registers_t *r) { + uint8_t scancode = port_byte_in(0x60); + if (scancode < sizeof(sc_ascii)) { + char c = sc_ascii[scancode]; + if (kbd_buf_size < kbd_buf_capacity) { + kbd_buf[kbd_buf_size++] = c; + } + char string[] = {c, '\0'}; + printk(string); + } +} + +char* kbd_buf; +unsigned kbd_buf_size; + +void init_keyboard() { + kbd_buf = kmalloc(kbd_buf_capacity); + + register_interrupt_handler(IRQ1, interrupt_handler); +} diff --git a/drivers/keyboard.h b/drivers/keyboard.h new file mode 100644 index 0000000..c13d4bb --- /dev/null +++ b/drivers/keyboard.h @@ -0,0 +1,6 @@ +#pragma once + +void init_keyboard(); + +extern unsigned kbd_buf_size; +extern char *kbd_buf; diff --git a/kernel.c b/kernel.c index 17b5a5c..481c60b 100644 --- a/kernel.c +++ b/kernel.c @@ -1,12 +1,19 @@ #include "console.h" #include "drivers/vga.h" #include "drivers/uart.h" +#include "drivers/keyboard.h" +#include "cpu/isr.h" void _start() { uartinit(); + init_keyboard(); + load_idt(); + sti(); vga_clear_screen(); - printk("\nYABLOKO\n"); - asm("hlt"); + printk("\nYABLOKO\n> "); + while (1) { + asm("hlt"); + } } diff --git a/lib/mem.c b/lib/mem.c new file mode 100644 index 0000000..0bf188b --- /dev/null +++ b/lib/mem.c @@ -0,0 +1,12 @@ +#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 new file mode 100644 index 0000000..1a77610 --- /dev/null +++ b/lib/mem.h @@ -0,0 +1,5 @@ +#pragma once + +typedef unsigned size_t; + +void* kmalloc(size_t size);