commit 1d8bcba8689f039b3aaafab83d124d205e2d327f
Author: Christos Margiolis <christos@margiolis.net>
Date: Sat, 24 Apr 2021 03:29:56 +0300
initial commit
Diffstat:
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