os

Toy OS
git clone git://git.christosmarg.xyz
Log | Files | Refs | README | LICENSE

commit 1d8bcba8689f039b3aaafab83d124d205e2d327f
Author: Christos Margiolis <christos@margiolis.net>
Date:   Sat, 24 Apr 2021 03:29:56 +0300

initial commit

Diffstat:
ALICENSE | 20++++++++++++++++++++
AMakefile | 10++++++++++
AREADME | 13+++++++++++++
Aboot/Makefile | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Aboot/boot.asm | 256+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aboot/extern.h | 11+++++++++++
Aboot/kernel.c | 8++++++++
Aboot/loader.asm | 6++++++
Aboot/string.c | 20++++++++++++++++++++
Aboot/string.h | 10++++++++++
Aboot/tty.c | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aboot/tty.h | 40++++++++++++++++++++++++++++++++++++++++
Aboot/unused/a20.asm | 153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
13 files changed, 657 insertions(+), 0 deletions(-)

diff --git a/LICENSE b/LICENSE @@ -0,0 +1,20 @@ +MIT License + +(c) 2021-Present Christos Margiolis <christos@christosmarg.xyz> + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the “Software”), to deal in +the Software without restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile @@ -0,0 +1,10 @@ +TGTDIR = build + +all: + cd boot && make install clean && cd .. + +run: + qemu-system-i386 -hdd ${TGTDIR}/os.bin + +clean: + rm -rf ${TGTDIR} diff --git a/README b/README @@ -0,0 +1,13 @@ +My OS +===== +Linux users are afraid of it. + +Build +----- + $ make + $ make run + +Features +-------- +- Own bootloader, no GRUB bloat. +- Nothing else yet. diff --git a/boot/Makefile b/boot/Makefile @@ -0,0 +1,51 @@ +BIN = os.bin +BINDIR = ../build + +CC = cc +ASM = nasm +LD = ld +CFLAGS = -g -m32 -ffreestanding -Wall -Wextra -std=c99 -O2 +LDFLAGS = -Ttext 0x1000 --oformat binary + +BOOTFILE = boot.asm +BOOT_BIN = boot.bin +KERNEL_BIN = kernel.bin +SRC = *.c *.asm +OBJ = loader.o \ + kernel.o \ + string.o \ + tty.o + +all: options ${BIN} + +options: + @echo ${BIN} build options + @echo CC = ${CC} + @echo ASM = ${ASM} + @echo LD = ${LD} + @echo CFLAGS = ${CFLAGS} + @echo LDFLAGS = ${LDFLAGS} + +${BIN}: ${OBJ} + mkdir -p ${BINDIR} + ${ASM} -fbin ${BOOTFILE} -o ${BOOT_BIN} + ${LD} ${LDFLAGS} ${OBJ} -o ${KERNEL_BIN} + dd if=/dev/zero bs=1000000 count=1 >> ${KERNEL_BIN} + cat ${BOOT_BIN} ${KERNEL_BIN} > $@ + +c.o: + ${CC} -c ${CFLAGS} $< + +.asm.o: + ${ASM} -felf $< + +install: all + mv ${BIN} ${BINDIR} + +run: all + qemu-system-i386 -hdd ${BINDIR}/${BIN} + +clean: + rm -rf *.bin *.o + +.PHONY: options all install run clean diff --git a/boot/boot.asm b/boot/boot.asm @@ -0,0 +1,256 @@ +; Load code to 0x7c00 (Boot Sector address). +[org 0x7c00] +[bits 16] + +KERNOFF equ 0x1000 + +section .text +global _start + +; Entry point. +_start: + cli ; Disable interrupts. + xor ax, ax ; Clear segment registers. + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + mov bp, 0x9000 ; Set the base and stack pointers. + mov sp, bp + cld ; Read strings from low to high. + sti ; Enable interrupts back. + + mov [BOOTDRV], dl ; Set boot drive. + call kernel_load + jmp pm_enter + jmp $ ; Safety hang. + +; Load kernel from disk to memory. +kernel_load: + pusha + mov ah, 0x42 + mov dl, [BOOTDRV] + mov si, disk_packet + int 0x13 + + jc diskerr ; There's an error. Too bad. + popa + ret +diskerr: + mov si, str_diskerr + call puts + jmp $ ; There's nothing we can do at this point. + +disk_packet: + .size db 0x10 + .zero db 0x00 + .count dw 0x0f + .off16 dw KERNOFF + .seg16 dw 0x0000 + .lba dq 1 + +[bits 16] +; Set up the GDT. +gdt: +gdt_null: + dd 0x0 + dd 0x0 + +gdt_kernel_code: + dw 0xffff + dw 0x0 + db 0x0 + db 10011010b + db 11001111b + db 0x0 + +gdt_kernel_data: + dw 0xffff + dw 0x0 + db 0x0 + db 10010010b + db 11001111b + db 0x0 + +gdt_userland_code: + dw 0xffff + dw 0x0 + db 0x0 + db 11111010b + db 11001111b + db 0x0 + +gdt_userland_data: + dw 0xffff + dw 0x0 + db 0x0 + db 11110010b + db 11001111b + db 0x0 + +gdt_ptr: + dw $ - gdt - 1 + dd gdt + +GDT_CODESEG equ gdt_kernel_code - gdt +GDT_DATASEG equ gdt_kernel_data - gdt + +; Go into Protected Mode and set PAE Paging and the GDT. +pm_enter: + cli ; Disable BIOS interrupts. + lgdt [gdt_ptr] ; Load the GDT. + + mov eax, cr0 + or eax, 1 << 0 ; Protected Mode Enable. + mov cr0, eax ; There's no going back now. + jmp GDT_CODESEG:pm_init + +; We're in Protected Mode. +[bits 32] +pm_init: + mov ax, GDT_DATASEG + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + + mov ebp, 0x90000 + mov esp, ebp + + call lm_check + call kernel_exec ; If we cannot get into Long Mode, start in PE. + jmp $ + +; Check for Long Mode. +lm_check: + pusha + pushfd ; Push EFLAGS register to the stack. + pop eax ; Pop EFLAGS registers to `eax`. + mov ecx, eax ; Keep a backup in `ecx`. + xor eax, 1 << 21 ; Flip the 21st bit if the it's not 1. + push eax + popfd ; Pop `eax` to EFLAGS. + + pushfd + pop eax ; Copy EFLAGS back to `eax`. + push ecx + popfd ; Restore the flipped version. + xor eax, ecx + jz lm_fail ; If EFLAGS' value hasn't changed, we have CPUID. + + mov eax, 0x80000000 ; CPUID argument. + cpuid ; CPU identification. `eax` is now populated with info. + cmp eax, 0x80000001 ; If it's less than 0x80000000 we cannot have Long Mode. + jb lm_fail + + mov eax, 0x80000001 ; Get extended processor information. + cpuid + test edx, 1 << 29 + jz lm_fail ; If the 29th bit is 0, we cannot have Long Mode. + + popa + ret + +; No long mode. +lm_fail: + popa + call kernel_exec + +; We'll check for (and switch to) Long Mode here. +lm_enter: + cli + mov eax, cr0 + or eax, 1 << 31 ; Paging Enable. + mov cr0, eax +; Set up PAE paging. + mov edi, 0x1000 ; Page table starts 4KiB in memory. + mov cr3, edi ; Hold the location of the highest page table. + xor eax, eax ; Clear memory space. + mov ecx, 4096 + rep stosd ; Write 4096 dwords with the value 0. + mov edi, 0x1000 ; Go to the initial location. + +; Page table locations: +; Page Map Level 4 Table (PML4T): 0x1000 +; Page Directory Pointer Table (PDPT): 0x2000 +; Page Directory Table (PDT): 0x3000 +; Page Table (PT); 0x4000 +; +; In order for each table to point to each other we'll use a size directive. +; Since tables are 4KiB away from each other we'll move dwords. + mov dword [edi], 0x2003 ; PML4T -> PDPT + add edi, 0x1000 ; Move 4KiB forward. + mov dword [edi], 0x3003 ; PDPT -> PDT + add edi, 0x1000 + mov dword [edi], 0x4003 ; PDT -> PT + add edi, 0x1000 + + mov dword ebx, 3 ; Map beginning of code. + mov ecx, 512 ; Do it 512 times. +setentry: + mov dword [edi], ebx + add ebx, 0x1000 + add edi, 8 ; Go to the next entry in the page table. + loop setentry + +; Enable the 6th bit in CR4 (Physical Address Extension Bit) to tell +; the CPU that we're using PAE paging. + mov eax, cr4 + or eax, 1 << 5 + mov cr4, eax + +; Activate Protected Mode and Paging using the EFER. + mov ecx, 0xc0000080 + rdmsr ; Copy the contents of EFER to `eax`. + or eax, 1 << 8 ; Set LME bit to enable Long Mode. + wrmsr ; Write `eax` back to EFER. + + lgdt [gdt_ptr] ; Load the GDT. + jmp GDT_CODESEG:lm_init ; Switch to Long Mode. + +; We're in Long Mode now. +[bits 64] +lm_init: + mov ax, GDT_DATASEG + mov ds, ax + mov ss, ax + mov es, ax + mov fs, ax + mov gs, ax + + mov rbp, 0x90000 + mov rsp, rbp + call kernel_exec + jmp $ + +; Hand control over to the C kernel. Godspeed. +kernel_exec: + call KERNOFF + jmp $ + +; Print a null-terminated string. +[bits 16] +puts: + pusha + mov ah, 0x0e ; Tell BIOS to display a character. +loop: + mov al, [si] ; Load character. + cmp al, 0 ; Check for \0. + jne putchar ; If it's not \0, print the character. + popa ; We're done, pop everything. + ret ; Return back to where we were. +putchar: + int 0x10 ; BIOS print interrupt. + inc si ; `str++` + jmp loop ; Go to the next character. + +; String declarations. +str_diskerr: db "Error loading disk.", 0x0a, 0x0d, 0x00 +BOOTDRV: db 0x80 + +; Padding to 512 bytes. The last 2 bytes will come from the magic number. +times 510 - ($ - $$) db 0 +; Magic number. +dw 0xaa55 diff --git a/boot/extern.h b/boot/extern.h @@ -0,0 +1,11 @@ +#ifndef _KERNEL_EXTERN_H +#define _KERNEL_EXTERN_H + +#include <stdarg.h> +#include <stddef.h> +#include <stdint.h> + +#include "tty.h" +#include "string.h" + +#endif /* _KERNEL_EXTERN_H */ diff --git a/boot/kernel.c b/boot/kernel.c @@ -0,0 +1,8 @@ +#include "extern.h" + +void +kernel_main(void) +{ + tty_init(); + tty_write("Nothing to see here yet. At least it booted.\n"); +} diff --git a/boot/loader.asm b/boot/loader.asm @@ -0,0 +1,6 @@ +[bits 32] +[extern kernel_main] + +_start: + call kernel_main + jmp $ diff --git a/boot/string.c b/boot/string.c @@ -0,0 +1,20 @@ +#include "string.h" + +size_t +strlen(const char *str) +{ + const char *s; + + for (s = str; *s != '\0'; s++) + ; + return (s - str); +} + +int +strcmp(const char *s1, const char *s2) +{ + while (*s1 == *s2++) + if (*s1++ == '\0') + return 0; + return *(const unsigned char *)s1 - *(const unsigned char *)(s2 - 1); +} diff --git a/boot/string.h b/boot/string.h @@ -0,0 +1,10 @@ +#ifndef _KERNEL_STRING_H +#define _KERNEL_STRING_H + +#include <stddef.h> +#include <stdint.h> + +extern size_t strlen(const char *); +extern int strcmp(const char *, const char *); + +#endif /* _KERNEL_STRING_H */ diff --git a/boot/tty.c b/boot/tty.c @@ -0,0 +1,59 @@ +#include "tty.h" + +#define _PUTC(c) (((uint16_t)tty.color << 8) | c) +#define _COLOR(f, b) (f | b << 4) + +static struct tty_info tty; + +void +tty_init(void) +{ + size_t y, x; + + tty.row = 0; + tty.col = 0; + tty.color = _COLOR(VGA_LIGHT_GREY, VGA_BLUE); + tty.buf = (uint16_t *)0xb8000; + for (x = 0; x < VGA_COLS; x++) + for (y = 0; y < VGA_ROWS; y++) + tty.buf[y * VGA_COLS + x] = _PUTC(' '); +} + +void +tty_putc(char c) +{ + switch (c) { + case '\n': + tty.row++; + tty.col = 0; + break; + case '\b': + tty.col--; + break; + case '\r': + tty.col = 0; + break; + case '\t': + tty.col += 8; + break; + default: + tty.buf[tty.row * VGA_COLS + tty.col] = _PUTC(c); + tty.col++; + } + + if (tty.row >= VGA_ROWS) { + tty.row++; + tty.col = 0; + } + if (tty.col >= VGA_COLS) { + tty.col = 0; + tty.row = 0; + } +} + +void +tty_write(const char *str) +{ + while (*str != '\0') + tty_putc(*str++); +} diff --git a/boot/tty.h b/boot/tty.h @@ -0,0 +1,40 @@ +#ifndef _KERNEL_TTY_H +#define _KERNEL_TTY_H + +#include <stddef.h> +#include <stdint.h> + +#define VGA_COLS 80 +#define VGA_ROWS 25 + +struct tty_info { + volatile uint16_t *buf; + size_t row; + size_t col; + uint8_t color; +}; + +enum vga_color { + VGA_BLACK = 0, + VGA_BLUE, + VGA_GREEN, + VGA_CYAN, + VGA_RED, + VGA_MAGENTA, + VGA_BROWN, + VGA_LIGHT_GREY, + VGA_DARK_GREY, + VGA_LIGHT_BLUE, + VGA_LIGHT_GREEN, + VGA_LIGHT_CYAN, + VGA_LIGHT_RED, + VGA_LIGHT_MAGENTA, + VGA_LIGHT_BROWN, + VGA_WHITE, +}; + +extern void tty_init(void); +extern void tty_putc(char); +extern void tty_write(const char *); + +#endif /* _KERNEL_TTY_H */ diff --git a/boot/unused/a20.asm b/boot/unused/a20.asm @@ -0,0 +1,153 @@ +call a20_test +call a20_enable + +; Test to see if the A20 line is enabled. +; As a reference we will use the magic number 0xaa55 since we know it's always +; going to have the same value and be located at address 0x7dfe (0x7c00 + 510). +; If the return code in `ax` is 1, the A20 is enabled, and if it's 0 it's disabled. +a20_test: + pusha + + mov ax, [0x7dfe] ; 0x7c00 + 510. The magic number is there. + mov dx, ax + +; We'll try to advance 1MB in memory. If the end result hasn't wrapped up +; around zero, it means that our processor is able to access more than 1MB +; of addresses, so the A20 is enabled. Using 0x7dfe again, the expected +; address after the 1MB advance should be: +; +; 0x100000 + 0x7dfe = 0x107dfe +; +; The formula for memory segmentation is: +; +; address = segment * 16 + offset +; +; To find the offset, the segment will be 0xffff, so `0xffff * 16 = 0xffff0`. +; Applying the formula to calculate our offset, we have: +; +; 0xffff0 + offset = 0x107dfe => +; offset = 0x107dfe - 0xffff0 => +; offset = 0x7e0e + + push bx + mov bx, 0xffff + mov es, bx ; Assign value to the segment register. + pop bx + + mov bx, 0x7e0e + mov dx, [es:bx] ; If the A20 line is disabled we get 0xaa55. + + cmp ax, dx + je cont ; If they're equal, the A20 line might be disabled. + popa + mov ax, 0x01 ; Success code 1. + ret +cont: + mov ax, [0x7dff] + mov dx, ax + + push bx + mov bx, 0xffff ; Make another segment. + mov es, bx + pop bx + + mov bx, 0x7e0f + mov dx, [es:bx] + + cmp ax, dx ; Now it really is disabled if ax == [es:bx]. + je exit + popa + mov ax, 0x01 ; Success code 1. + ret +exit: + popa + xor ax, ax ; Error code 0. + ret + +; Enable the A20 line. We'll try enabling it using the following ways: +; - BIOS interrupt. +; - Keyboard controller. +; - FastA20. +a20_enable: + pusha + +; BIOS interrupt. + mov ax, 0x2401 ; A20-Gate Active. + int 0x15 + + call a20_test + cmp ax, 0x01 + je a20_done + jmp a20_fail + +; Keyboard controller. + sti ; Enable interrupts. + + call a20_waitc + mov al, 0xad ; Disable controller. + out 0x64, al ; Send data to port 0x64. + call a20_waitc + mov al, 0xd0 ; We want to read from the controller. + out 0x64, al + + call a20_waitd + in al, 0x60 ; Read data from port 0x60. + push ax ; Save data. + + call a20_waitc + mov al, 0xd1 ; We want to send data. + out 0x64, al + + call a20_waitc + pop ax + or al, 0x02 ; Write the second bit back. + out 0x60, al ; Send data to the data port. + + call a20_waitc + mov al, 0xae ; Enable controller. + out 0x64, al + + call a20_waitc + sti ; Enable interrupts again. + + call a20_test + cmp ax, 0x01 + je a20_done + jmp a20_fail + +; Wait for the keyboard controller to be available for receiving commands. +a20_waitc: + in al, 0x64 ; Read from port 0x64. + test al, 0x02 ; Test the second bit. + jnz a20_waitc ; If it's 1, it's busy. + ret +; Wait for the keyboard controller to send data back. +a20_waitd: + in al, 0x64 ; Read from port 0x64 again. + test al, 0x01 ; Test the first bit. + jz a20_waitd ; If it's 1, the data is not ready to be sent. + ret + +; FastA20 + in al, 0x92 ; FastA20 uses port 0x92. + or al, 0x02 ; Mask second bit. + out 0x92, al ; Send data back to port 0x92. + + call a20_test + cmp ax, 0x01 + je a20_done + jmp a20_fail + +; The A20 is enabled. Go on. +a20_done: + popa + ret + +; The A20 is disabled. +a20_fail: + mov si, str_a20_fail + call puts + jmp $ + + +str_a20_fail: db "The A20 Line is disabled", 0x0a, 0x0d, 0x00