From 6e5222e2e3679de8f7078c17ea2d145e90c84f4d Mon Sep 17 00:00:00 2001 From: Alexander Myltsev Date: Fri, 13 Jan 2023 12:56:54 +0400 Subject: [PATCH] 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; +}