├── .gitignore ├── targets └── x86_64 │ ├── iso │ ├── .gitignore │ └── boot │ │ └── grub │ │ └── grub.cfg │ └── linker.ld ├── src ├── intf │ ├── x86_64 │ │ ├── rtc.h │ │ ├── ps2.h │ │ ├── pic.h │ │ ├── idt.h │ │ ├── port.h │ │ └── gdt.h │ ├── bool.h │ ├── keyboard.h │ └── print.h └── impl │ ├── x86_64 │ ├── port.c │ ├── ps2.c │ ├── boot │ │ ├── main64.asm │ │ ├── header.asm │ │ └── main.asm │ ├── port_.asm │ ├── idt_.asm │ ├── rtc.c │ ├── keyboard.c │ ├── idt.c │ ├── pic.c │ └── print.c │ └── kernel │ └── main.c ├── REVISIONS.md ├── Makefile └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /dist/ 3 | -------------------------------------------------------------------------------- /targets/x86_64/iso/.gitignore: -------------------------------------------------------------------------------- 1 | boot/kernel.bin 2 | -------------------------------------------------------------------------------- /src/intf/x86_64/rtc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | uint8_t rtc_seconds(); 4 | -------------------------------------------------------------------------------- /src/intf/x86_64/ps2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | uint8_t ps2_read_scan_code(); 4 | -------------------------------------------------------------------------------- /src/intf/bool.h: -------------------------------------------------------------------------------- 1 | #define bool uint8_t 2 | #define true 1 3 | #define false 0 4 | -------------------------------------------------------------------------------- /src/impl/x86_64/port.c: -------------------------------------------------------------------------------- 1 | #include "x86_64/port.h" 2 | 3 | void port_wait() { 4 | port_inb(0x80); 5 | } 6 | -------------------------------------------------------------------------------- /src/intf/x86_64/pic.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void pic_remap(); 4 | void pic_eoi_master(); 5 | void pic_eoi_slave(); 6 | -------------------------------------------------------------------------------- /src/intf/x86_64/idt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void idt_init(); 6 | void idt_set_handler_keyboard(void (*handler)()); 7 | -------------------------------------------------------------------------------- /targets/x86_64/iso/boot/grub/grub.cfg: -------------------------------------------------------------------------------- 1 | set timeout=0 2 | set default=0 3 | 4 | menuentry "my os" { 5 | multiboot2 /boot/kernel.bin 6 | boot 7 | } 8 | -------------------------------------------------------------------------------- /src/impl/x86_64/ps2.c: -------------------------------------------------------------------------------- 1 | #include "x86_64/port.h" 2 | 3 | #define PORT_PS2_DATA 0x60 4 | 5 | uint8_t ps2_read_scan_code() { 6 | return port_inb(PORT_PS2_DATA); 7 | } 8 | -------------------------------------------------------------------------------- /src/intf/x86_64/port.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | uint8_t port_inb(uint16_t port); 6 | void port_outb(uint16_t port, uint8_t data); 7 | void port_wait(); 8 | -------------------------------------------------------------------------------- /targets/x86_64/linker.ld: -------------------------------------------------------------------------------- 1 | ENTRY(start) 2 | 3 | SECTIONS 4 | { 5 | . = 1M; 6 | 7 | .boot : 8 | { 9 | KEEP(*(.multiboot_header)) 10 | } 11 | 12 | .text : 13 | { 14 | *(.text) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/intf/x86_64/gdt.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define GDT_RING_0 0 4 | #define GDT_RING_1 1 // legacy 5 | #define GDT_RING_2 2 // legacy 6 | #define GDT_RING_3 3 7 | 8 | #define GDT_SELECTOR_INDEX 3 9 | 10 | #define GDT_SELECTOR_CS_KERNEL (1 << GDT_SELECTOR_INDEX) | GDT_RING_0 11 | -------------------------------------------------------------------------------- /src/impl/x86_64/boot/main64.asm: -------------------------------------------------------------------------------- 1 | global long_mode_start 2 | extern kernel_main 3 | 4 | section .text 5 | bits 64 6 | long_mode_start: 7 | ; load null into all data segment registers 8 | mov ax, 0 9 | mov ss, ax 10 | mov ds, ax 11 | mov es, ax 12 | mov fs, ax 13 | mov gs, ax 14 | 15 | call kernel_main 16 | hlt 17 | -------------------------------------------------------------------------------- /src/intf/keyboard.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | enum { 6 | KEYBOARD_EVENT_TYPE_MAKE = 0, 7 | KEYBOARD_EVENT_TYPE_BREAK = 1, 8 | }; 9 | 10 | struct KeyboardEvent { 11 | uint8_t type; 12 | uint16_t code; 13 | }; 14 | 15 | void keyboard_init(); 16 | void keyboard_set_handler(void (*handler)(struct KeyboardEvent event)); 17 | -------------------------------------------------------------------------------- /REVISIONS.md: -------------------------------------------------------------------------------- 1 | # Revisions 2 | 3 | You can [browse tags here](https://github.com/davidcallanan/os-series/tags). 4 | 5 | ## Episode 1 6 | 7 | - [Latest revision](https://github.com/davidcallanan/os-series/tree/ep1) (browse tree). 8 | 9 | ## Episode 2 10 | 11 | - [Latest revision](https://github.com/davidcallanan/os-series/tree/ep2) (browse tree). 12 | -------------------------------------------------------------------------------- /src/impl/x86_64/boot/header.asm: -------------------------------------------------------------------------------- 1 | section .multiboot_header 2 | header_start: 3 | ; magic number 4 | dd 0xe85250d6 ; multiboot2 5 | ; architecture 6 | dd 0 ; protected mode i386 7 | ; header length 8 | dd header_end - header_start 9 | ; checksum 10 | dd 0x100000000 - (0xe85250d6 + 0 + (header_end - header_start)) 11 | 12 | ; end tag 13 | dw 0 14 | dw 0 15 | dd 8 16 | header_end: 17 | -------------------------------------------------------------------------------- /src/impl/x86_64/port_.asm: -------------------------------------------------------------------------------- 1 | global port_inb 2 | global port_outb 3 | 4 | port_inb: 5 | mov dx, di ; load `port` argument 6 | in al, dx ; read from port given by `port` 7 | movzx rax, al ; zero-extend 8 | ret 9 | 10 | port_outb: 11 | mov dx, di ; load `port` argument 12 | mov al, sil ; load `data` argument 13 | out dx, al ; write to port given by `port` with data given by `data` 14 | ret 15 | -------------------------------------------------------------------------------- /src/impl/x86_64/idt_.asm: -------------------------------------------------------------------------------- 1 | extern idt_handler_keyboard 2 | global idt_load 3 | 4 | idt_load: 5 | lidt [rdi] 6 | ret 7 | 8 | %macro WRAPPED_HANDLER 1 9 | global %1_wrapped 10 | 11 | %1_wrapped: 12 | ; save general-purpose registers 13 | push rax 14 | push rbx 15 | push rcx 16 | push rdx 17 | push rbp 18 | push rsi 19 | push rdi 20 | push r8 21 | push r9 22 | push r10 23 | push r11 24 | push r12 25 | push r13 26 | push r14 27 | push r15 28 | 29 | call %1 30 | 31 | ; restore general-purpose registers 32 | pop r15 33 | pop r14 34 | pop r13 35 | pop r12 36 | pop r11 37 | pop r10 38 | pop r9 39 | pop r8 40 | pop rdi 41 | pop rsi 42 | pop rbp 43 | pop rdx 44 | pop rcx 45 | pop rbx 46 | pop rax 47 | 48 | iretq 49 | %endmacro 50 | 51 | WRAPPED_HANDLER idt_handler_keyboard 52 | -------------------------------------------------------------------------------- /src/intf/print.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | enum { 7 | PRINT_COLOR_BLACK = 0, 8 | PRINT_COLOR_BLUE = 1, 9 | PRINT_COLOR_GREEN = 2, 10 | PRINT_COLOR_CYAN = 3, 11 | PRINT_COLOR_RED = 4, 12 | PRINT_COLOR_MAGENTA = 5, 13 | PRINT_COLOR_BROWN = 6, 14 | PRINT_COLOR_LIGHT_GRAY = 7, 15 | PRINT_COLOR_DARK_GRAY = 8, 16 | PRINT_COLOR_LIGHT_BLUE = 9, 17 | PRINT_COLOR_LIGHT_GREEN = 10, 18 | PRINT_COLOR_LIGHT_CYAN = 11, 19 | PRINT_COLOR_LIGHT_RED = 12, 20 | PRINT_COLOR_PINK = 13, 21 | PRINT_COLOR_YELLOW = 14, 22 | PRINT_COLOR_WHITE = 15, 23 | }; 24 | 25 | void print_clear(); 26 | void print_char(char character); 27 | void print_str(char* string); 28 | void print_set_color(uint8_t foreground, uint8_t background); 29 | void print_uint64_dec(uint64_t value); 30 | void print_uint64_hex(uint64_t value); 31 | void print_uint64_bin(uint64_t value); 32 | -------------------------------------------------------------------------------- /src/impl/x86_64/rtc.c: -------------------------------------------------------------------------------- 1 | #include "x86_64/port.h" 2 | 3 | #define PORT_RTC_COMMAND 0x70 4 | #define PORT_RTC_DATA 0x71 5 | #define RTC_REGISTER_SECONDS 0x00 6 | #define RTC_REGISTER_STATUS_A 0x0A 7 | #define RTC_REGISTER_STATUS_B 0x0B 8 | #define RTC_UPDATE_IN_PROGRESS 0x80 9 | #define RTC_DATA_MODE (1 << 2) 10 | 11 | uint8_t rtc_read_register(uint8_t reg) { 12 | port_outb(PORT_RTC_COMMAND, reg); 13 | return port_inb(PORT_RTC_DATA); 14 | } 15 | 16 | void rtc_wait() { 17 | while (rtc_read_register(RTC_REGISTER_STATUS_A) & RTC_UPDATE_IN_PROGRESS); 18 | } 19 | 20 | uint8_t rtc_is_bcd() { 21 | return !(rtc_read_register(RTC_REGISTER_STATUS_B) & RTC_DATA_MODE); 22 | } 23 | 24 | uint8_t rtc_seconds() { 25 | uint8_t seconds_a; 26 | uint8_t seconds_b; 27 | uint8_t is_bcd = rtc_is_bcd(); 28 | 29 | do { 30 | rtc_wait(); 31 | seconds_a = rtc_read_register(RTC_REGISTER_SECONDS); 32 | rtc_wait(); 33 | seconds_b = rtc_read_register(RTC_REGISTER_SECONDS); 34 | } while (seconds_a != seconds_b); 35 | 36 | if (is_bcd) { 37 | return (seconds_b & 0x0F) + ((seconds_b & 0xF0) >> 4) * 10; 38 | } 39 | 40 | return seconds_b; 41 | } 42 | -------------------------------------------------------------------------------- /src/impl/x86_64/keyboard.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "bool.h" 3 | #include "keyboard.h" 4 | #include "x86_64/idt.h" 5 | #include "x86_64/ps2.h" 6 | 7 | #define KEYBOARD_EXTENDED_SCAN_CODE 0xE0 8 | 9 | void (*keyboard_handler_user)(struct KeyboardEvent event); 10 | 11 | void keyboard_handler() { 12 | static bool is_extended = 0; 13 | 14 | uint8_t scan_code = ps2_read_scan_code(); 15 | 16 | if (scan_code == KEYBOARD_EXTENDED_SCAN_CODE) { 17 | is_extended = true; 18 | return; 19 | } 20 | 21 | if (keyboard_handler_user == NULL) { 22 | return; 23 | } 24 | 25 | uint16_t fat_code = scan_code & 0x7F; 26 | 27 | if (is_extended) { 28 | is_extended = false; 29 | fat_code |= KEYBOARD_EXTENDED_SCAN_CODE << 8; 30 | } 31 | 32 | struct KeyboardEvent event; 33 | 34 | if ((scan_code & 0x80) == 0) { 35 | event.type = KEYBOARD_EVENT_TYPE_MAKE; 36 | } else { 37 | event.type = KEYBOARD_EVENT_TYPE_BREAK; 38 | } 39 | 40 | event.code = fat_code; 41 | 42 | keyboard_handler_user(event); 43 | } 44 | 45 | void keyboard_init() { 46 | idt_init(); 47 | idt_set_handler_keyboard(keyboard_handler); 48 | } 49 | 50 | void keyboard_set_handler(void (*handler)(struct KeyboardEvent event)) { 51 | keyboard_handler_user = handler; 52 | } 53 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC := x86_64-elf-gcc 2 | LD := x86_64-elf-ld 3 | 4 | kernel_source_files := $(shell find src/impl/kernel -name *.c) 5 | kernel_object_files := $(patsubst src/impl/kernel/%.c, build/kernel/%.o, $(kernel_source_files)) 6 | 7 | x86_64_c_source_files := $(shell find src/impl/x86_64 -name *.c) 8 | x86_64_c_object_files := $(patsubst src/impl/x86_64/%.c, build/x86_64/%.o, $(x86_64_c_source_files)) 9 | 10 | x86_64_asm_source_files := $(shell find src/impl/x86_64 -name *.asm) 11 | x86_64_asm_object_files := $(patsubst src/impl/x86_64/%.asm, build/x86_64/%.o, $(x86_64_asm_source_files)) 12 | 13 | x86_64_object_files := $(x86_64_c_object_files) $(x86_64_asm_object_files) 14 | 15 | build/kernel/%.o: src/impl/kernel/%.c 16 | mkdir -p $(dir $@) 17 | $(CC) -c -I src/intf -ffreestanding $(patsubst build/kernel/%.o, src/impl/kernel/%.c, $@) -o $@ 18 | 19 | build/x86_64/%.o: src/impl/x86_64/%.c 20 | mkdir -p $(dir $@) 21 | $(CC) -c -I src/intf -ffreestanding $(patsubst build/x86_64/%.o, src/impl/x86_64/%.c, $@) -o $@ 22 | 23 | build/x86_64/%.o: src/impl/x86_64/%.asm 24 | mkdir -p $(dir $@) 25 | nasm -f elf64 $(patsubst build/x86_64/%.o, src/impl/x86_64/%.asm, $@) -o $@ 26 | 27 | .PHONY: build-x86_64 28 | build-x86_64: $(kernel_object_files) $(x86_64_object_files) 29 | mkdir -p dist/x86_64 30 | $(LD) -n -o dist/x86_64/kernel.bin -T targets/x86_64/linker.ld $(kernel_object_files) $(x86_64_object_files) 31 | cp dist/x86_64/kernel.bin targets/x86_64/iso/boot/kernel.bin 32 | grub-mkrescue /usr/lib/grub/i386-pc -o dist/x86_64/kernel.iso targets/x86_64/iso 33 | 34 | .PHONY: clean 35 | clean: 36 | rm -rf build dist 37 | -------------------------------------------------------------------------------- /src/impl/x86_64/idt.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "x86_64/gdt.h" 4 | #include "x86_64/idt.h" 5 | #include "x86_64/pic.h" 6 | 7 | #define IDT_IRQ0_TIMER 0x20 8 | #define IDT_IRQ1_KEYBOARD 0x21 9 | 10 | #define IDT_GATE_PRESENT (1 << 7) 11 | #define IDT_GATE_DPL0 (0b00 << 5) 12 | #define IDT_GATE_DPL1 (0b01 << 5) 13 | #define IDT_GATE_DPL2 (0b10 << 5) 14 | #define IDT_GATE_DPL3 (0b11 << 5) 15 | #define IDT_GATE_TYPE_INTERRUPT 0xE 16 | 17 | #define IDT_ENTRY_TYPE_INTERRUPT (IDT_GATE_PRESENT | IDT_GATE_DPL0 | IDT_GATE_TYPE_INTERRUPT) 18 | 19 | struct IdtEntry { 20 | uint16_t offset_low; 21 | uint16_t selector; 22 | uint8_t ist; 23 | uint8_t type; 24 | uint16_t offset_mid; 25 | uint32_t offset_high; 26 | uint32_t reserved; 27 | } __attribute__((packed)); 28 | 29 | struct IdtPtr { 30 | uint16_t limit; 31 | uint64_t base; 32 | } __attribute__((packed)); 33 | 34 | struct IdtEntry idt[256] __attribute__((aligned(16))); 35 | struct IdtPtr idt_ptr; 36 | 37 | void (*idt_handler_keyboard_user)(); 38 | 39 | extern void idt_load(struct IdtPtr* idt_ptr); 40 | 41 | void idt_set_entry(uint8_t vector, uint64_t isr_addr, uint16_t selector, uint8_t type) { 42 | idt[vector] = (struct IdtEntry) { 43 | .offset_low = (uint16_t) (isr_addr >> 0), 44 | .selector = selector, 45 | .ist = 0, 46 | .type = type, 47 | .offset_mid = (uint16_t) (isr_addr >> 16), 48 | .offset_high = (uint32_t) (isr_addr >> 32), 49 | .reserved = 0, 50 | }; 51 | } 52 | 53 | extern void idt_handler_keyboard_wrapped(); 54 | 55 | void idt_handler_keyboard() { 56 | if (idt_handler_keyboard_user != NULL) { 57 | idt_handler_keyboard_user(); 58 | } 59 | 60 | pic_eoi_master(); 61 | } 62 | 63 | void idt_init() { 64 | pic_remap(); 65 | 66 | idt_ptr.limit = (sizeof(struct IdtEntry) * 256) - 1; 67 | idt_ptr.base = (uint64_t) &idt; 68 | 69 | idt_set_entry(IDT_IRQ1_KEYBOARD, (uint64_t) idt_handler_keyboard_wrapped, GDT_SELECTOR_CS_KERNEL, IDT_ENTRY_TYPE_INTERRUPT); 70 | 71 | idt_load(&idt_ptr); 72 | 73 | asm volatile("sti"); 74 | } 75 | 76 | void idt_set_handler_keyboard(void (*handler)()) { 77 | idt_handler_keyboard_user = handler; 78 | } 79 | -------------------------------------------------------------------------------- /src/impl/x86_64/pic.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "x86_64/port.h" 3 | 4 | #define PORT_PIC1_COMMAND 0x20 5 | #define PORT_PIC1_DATA 0x21 6 | #define PORT_PIC2_COMMAND 0xA0 7 | #define PORT_PIC2_DATA 0xA1 8 | 9 | #define IRQ0_TIMER 0x00 10 | #define IRQ1_KEYBOARD 0x01 11 | #define IRQ2_SLAVE 0x02 12 | 13 | #define ICW1_INIT 0x10 14 | #define ICW1_HAS_ICW4 0x01 15 | 16 | #define ICW3_IRQ2_HAS_SLAVE (1 << IRQ2_SLAVE) 17 | 18 | #define ICW4_MODE_8086 (1 << 0) 19 | 20 | #define PIC_EOI 0x20 21 | #define PIC_OFFSET_MASTER 0x20 22 | #define PIC_OFFSET_SLAVE 0x28 23 | 24 | void pic_eoi_master() { 25 | port_outb(PORT_PIC1_COMMAND, PIC_EOI); 26 | port_wait(); 27 | } 28 | 29 | void pic_eoi_slave() { 30 | port_outb(PORT_PIC2_COMMAND, PIC_EOI); 31 | port_wait(); 32 | } 33 | 34 | void pic_remap() { 35 | // ICW1 - begin initialization sequence 36 | port_outb(PORT_PIC1_COMMAND, ICW1_INIT | ICW1_HAS_ICW4); // master 37 | port_wait(); 38 | port_outb(PORT_PIC2_COMMAND, ICW1_INIT | ICW1_HAS_ICW4); // slave 39 | port_wait(); 40 | 41 | // ICW2 - update interrupt vector offsets 42 | port_outb(PORT_PIC1_DATA, PIC_OFFSET_MASTER); 43 | port_wait(); 44 | port_outb(PORT_PIC2_DATA, PIC_OFFSET_SLAVE); 45 | port_wait(); 46 | 47 | // ICW3 - configure cascading between master and slave 48 | port_outb(PORT_PIC1_DATA, ICW3_IRQ2_HAS_SLAVE); 49 | port_wait(); 50 | port_outb(PORT_PIC2_DATA, IRQ2_SLAVE); 51 | port_wait(); 52 | 53 | // ICW4 - configure 8086 mode 54 | port_outb(PORT_PIC1_DATA, ICW4_MODE_8086); 55 | port_wait(); 56 | port_outb(PORT_PIC2_DATA, ICW4_MODE_8086); 57 | port_wait(); 58 | 59 | // Mask (ignore) all interrupts 60 | port_outb(PORT_PIC1_DATA, 0xFF); 61 | port_wait(); 62 | port_outb(PORT_PIC2_DATA, 0xFF); 63 | port_wait(); 64 | 65 | // Unmask desired interrupts 66 | uint8_t mask_master = port_inb(PORT_PIC1_DATA); 67 | uint8_t mask_slave = port_inb(PORT_PIC2_DATA); 68 | mask_master &= ~(1 << IRQ1_KEYBOARD); 69 | port_outb(PORT_PIC1_DATA, mask_master); 70 | port_wait(); 71 | port_outb(PORT_PIC2_DATA, mask_slave); 72 | port_wait(); 73 | 74 | // Wait for outstanding interrupts to clear 75 | pic_eoi_master(); 76 | pic_eoi_slave(); 77 | } 78 | -------------------------------------------------------------------------------- /src/impl/x86_64/boot/main.asm: -------------------------------------------------------------------------------- 1 | global start 2 | extern long_mode_start 3 | 4 | section .text 5 | bits 32 6 | start: 7 | mov esp, stack_top 8 | 9 | call check_multiboot 10 | call check_cpuid 11 | call check_long_mode 12 | 13 | call setup_page_tables 14 | call enable_paging 15 | 16 | lgdt [gdt64.pointer] 17 | jmp gdt64.code_segment:long_mode_start 18 | 19 | hlt 20 | 21 | check_multiboot: 22 | cmp eax, 0x36d76289 23 | jne .no_multiboot 24 | ret 25 | .no_multiboot: 26 | mov al, "M" 27 | jmp error 28 | 29 | check_cpuid: 30 | pushfd 31 | pop eax 32 | mov ecx, eax 33 | xor eax, 1 << 21 34 | push eax 35 | popfd 36 | pushfd 37 | pop eax 38 | push ecx 39 | popfd 40 | cmp eax, ecx 41 | je .no_cpuid 42 | ret 43 | .no_cpuid: 44 | mov al, "C" 45 | jmp error 46 | 47 | check_long_mode: 48 | mov eax, 0x80000000 49 | cpuid 50 | cmp eax, 0x80000001 51 | jb .no_long_mode 52 | 53 | mov eax, 0x80000001 54 | cpuid 55 | test edx, 1 << 29 56 | jz .no_long_mode 57 | 58 | ret 59 | .no_long_mode: 60 | mov al, "L" 61 | jmp error 62 | 63 | setup_page_tables: 64 | mov eax, page_table_l3 65 | or eax, 0b11 ; present, writable 66 | mov [page_table_l4], eax 67 | 68 | mov eax, page_table_l2 69 | or eax, 0b11 ; present, writable 70 | mov [page_table_l3], eax 71 | 72 | mov ecx, 0 ; counter 73 | .loop: 74 | 75 | mov eax, 0x200000 ; 2MiB 76 | mul ecx 77 | or eax, 0b10000011 ; present, writable, huge page 78 | mov [page_table_l2 + ecx * 8], eax 79 | 80 | inc ecx ; increment counter 81 | cmp ecx, 512 ; checks if the whole table is mapped 82 | jne .loop ; if not, continue 83 | 84 | ret 85 | 86 | enable_paging: 87 | ; pass page table location to cpu 88 | mov eax, page_table_l4 89 | mov cr3, eax 90 | 91 | ; enable PAE 92 | mov eax, cr4 93 | or eax, 1 << 5 94 | mov cr4, eax 95 | 96 | ; enable long mode 97 | mov ecx, 0xC0000080 98 | rdmsr 99 | or eax, 1 << 8 100 | wrmsr 101 | 102 | ; enable paging 103 | mov eax, cr0 104 | or eax, 1 << 31 105 | mov cr0, eax 106 | 107 | ret 108 | 109 | error: 110 | ; print "ERR: X" where X is the error code 111 | mov dword [0xb8000], 0x4f524f45 112 | mov dword [0xb8004], 0x4f3a4f52 113 | mov dword [0xb8008], 0x4f204f20 114 | mov byte [0xb800a], al 115 | hlt 116 | 117 | section .bss 118 | align 4096 119 | page_table_l4: 120 | resb 4096 121 | page_table_l3: 122 | resb 4096 123 | page_table_l2: 124 | resb 4096 125 | stack_bottom: 126 | resb 4096 * 4 127 | stack_top: 128 | 129 | section .rodata 130 | gdt64: 131 | dq 0 ; zero entry 132 | .code_segment: equ $ - gdt64 133 | dq (1 << 43) | (1 << 44) | (1 << 47) | (1 << 53) ; code segment 134 | .pointer: 135 | dw $ - gdt64 - 1 ; length 136 | dq gdt64 ; address 137 | -------------------------------------------------------------------------------- /src/impl/kernel/main.c: -------------------------------------------------------------------------------- 1 | #include "print.h" 2 | #include "keyboard.h" 3 | #include "x86_64/rtc.h" 4 | 5 | #define KEY_CODE_A 0x1E 6 | #define KEY_CODE_B 0x30 7 | #define KEY_CODE_C 0x2E 8 | #define KEY_CODE_D 0x20 9 | #define KEY_CODE_E 0x12 10 | #define KEY_CODE_F 0x21 11 | #define KEY_CODE_G 0x22 12 | #define KEY_CODE_H 0x23 13 | #define KEY_CODE_I 0x17 14 | #define KEY_CODE_J 0x24 15 | #define KEY_CODE_K 0x25 16 | #define KEY_CODE_L 0x26 17 | #define KEY_CODE_M 0x32 18 | #define KEY_CODE_N 0x31 19 | #define KEY_CODE_O 0x18 20 | #define KEY_CODE_P 0x19 21 | #define KEY_CODE_Q 0x10 22 | #define KEY_CODE_R 0x13 23 | #define KEY_CODE_S 0x1F 24 | #define KEY_CODE_T 0x14 25 | #define KEY_CODE_U 0x16 26 | #define KEY_CODE_V 0x2F 27 | #define KEY_CODE_W 0x11 28 | #define KEY_CODE_X 0x2D 29 | #define KEY_CODE_Y 0x15 30 | #define KEY_CODE_Z 0x2C 31 | #define KEY_CODE_SPACE 0x39 32 | #define KEY_CODE_ENTER 0x1C 33 | 34 | char to_ascii(uint16_t code) { 35 | switch (code) { 36 | case KEY_CODE_A: return 'A'; 37 | case KEY_CODE_B: return 'B'; 38 | case KEY_CODE_C: return 'C'; 39 | case KEY_CODE_D: return 'D'; 40 | case KEY_CODE_E: return 'E'; 41 | case KEY_CODE_F: return 'F'; 42 | case KEY_CODE_G: return 'G'; 43 | case KEY_CODE_H: return 'H'; 44 | case KEY_CODE_I: return 'I'; 45 | case KEY_CODE_J: return 'J'; 46 | case KEY_CODE_K: return 'K'; 47 | case KEY_CODE_L: return 'L'; 48 | case KEY_CODE_M: return 'M'; 49 | case KEY_CODE_N: return 'N'; 50 | case KEY_CODE_O: return 'O'; 51 | case KEY_CODE_P: return 'P'; 52 | case KEY_CODE_Q: return 'Q'; 53 | case KEY_CODE_R: return 'R'; 54 | case KEY_CODE_S: return 'S'; 55 | case KEY_CODE_T: return 'T'; 56 | case KEY_CODE_U: return 'U'; 57 | case KEY_CODE_V: return 'V'; 58 | case KEY_CODE_W: return 'W'; 59 | case KEY_CODE_X: return 'X'; 60 | case KEY_CODE_Y: return 'Y'; 61 | case KEY_CODE_Z: return 'Z'; 62 | case KEY_CODE_SPACE: return ' '; 63 | case KEY_CODE_ENTER: return '\n'; 64 | } 65 | 66 | return '?'; 67 | } 68 | 69 | void handle_input(struct KeyboardEvent event) { 70 | if (event.type == KEYBOARD_EVENT_TYPE_MAKE) { 71 | print_set_color(PRINT_COLOR_BLUE, PRINT_COLOR_WHITE); 72 | print_char(to_ascii(event.code)); 73 | } else if (event.type == KEYBOARD_EVENT_TYPE_BREAK) { 74 | } 75 | } 76 | 77 | void kernel_main() { 78 | print_clear(); 79 | print_set_color(PRINT_COLOR_YELLOW, PRINT_COLOR_BLACK); 80 | print_str("Welcome to our 64-bit kernel!"); 81 | 82 | keyboard_init(); 83 | keyboard_set_handler(handle_input); 84 | 85 | uint8_t prev_seconds = 0; 86 | 87 | for (uint8_t i = 0; i < 5;) { 88 | uint8_t seconds = rtc_seconds(); 89 | 90 | if (seconds != prev_seconds) { 91 | i++; 92 | print_set_color(PRINT_COLOR_GREEN, PRINT_COLOR_BLACK); 93 | print_str("\nSeconds: "); 94 | print_uint64_dec(seconds); 95 | } 96 | 97 | prev_seconds = seconds; 98 | } 99 | 100 | print_str(" - Seconds loop disabled.\n"); 101 | 102 | while (1); 103 | } 104 | -------------------------------------------------------------------------------- /src/impl/x86_64/print.c: -------------------------------------------------------------------------------- 1 | #include "print.h" 2 | 3 | const static size_t NUM_COLS = 80; 4 | const static size_t NUM_ROWS = 25; 5 | 6 | struct Char { 7 | uint8_t character; 8 | uint8_t color; 9 | }; 10 | 11 | struct Char* buffer = (struct Char*) 0xb8000; 12 | size_t col = 0; 13 | size_t row = 0; 14 | uint8_t color = PRINT_COLOR_WHITE | PRINT_COLOR_BLACK << 4; 15 | 16 | void clear_row(size_t row) { 17 | struct Char empty = (struct Char) { 18 | character: ' ', 19 | color: color, 20 | }; 21 | 22 | for (size_t col = 0; col < NUM_COLS; col++) { 23 | buffer[col + NUM_COLS * row] = empty; 24 | } 25 | } 26 | 27 | void print_clear() { 28 | for (size_t i = 0; i < NUM_ROWS; i++) { 29 | clear_row(i); 30 | } 31 | } 32 | 33 | void print_newline() { 34 | col = 0; 35 | 36 | if (row < NUM_ROWS - 1) { 37 | row++; 38 | return; 39 | } 40 | 41 | for (size_t row = 1; row < NUM_ROWS; row++) { 42 | for (size_t col = 0; col < NUM_COLS; col++) { 43 | struct Char character = buffer[col + NUM_COLS * row]; 44 | buffer[col + NUM_COLS * (row - 1)] = character; 45 | } 46 | } 47 | 48 | clear_row(NUM_ROWS - 1); 49 | } 50 | 51 | void print_char(char character) { 52 | if (character == '\n') { 53 | print_newline(); 54 | return; 55 | } 56 | 57 | if (col > NUM_COLS) { 58 | print_newline(); 59 | } 60 | 61 | buffer[col + NUM_COLS * row] = (struct Char) { 62 | character: (uint8_t) character, 63 | color: color, 64 | }; 65 | 66 | col++; 67 | } 68 | 69 | void print_str(char* str) { 70 | for (size_t i = 0; 1; i++) { 71 | char character = (uint8_t) str[i]; 72 | 73 | if (character == '\0') { 74 | return; 75 | } 76 | 77 | print_char(character); 78 | } 79 | } 80 | 81 | void print_set_color(uint8_t foreground, uint8_t background) { 82 | color = foreground + (background << 4); 83 | } 84 | 85 | void print_uint64_dec(uint64_t value) { 86 | if (value == 0) { 87 | print_char('0'); 88 | return; 89 | } 90 | 91 | char buffer[20]; 92 | int i = 0; 93 | 94 | while (value > 0) { 95 | buffer[i++] = (value % 10) + '0'; 96 | value /= 10; 97 | } 98 | 99 | while (i-- > 0) { 100 | print_char(buffer[i]); 101 | } 102 | } 103 | 104 | void print_uint64_hex(uint64_t value) { 105 | if (value == 0) { 106 | print_char('0'); 107 | return; 108 | } 109 | 110 | char buffer[16]; 111 | int i = 0; 112 | 113 | while (value > 0) { 114 | uint8_t digit = value & 0xF; 115 | 116 | if (digit < 10) { 117 | buffer[i++] = digit + '0'; 118 | } else { 119 | buffer[i++] = digit - 10 + 'A'; 120 | } 121 | 122 | value >>= 4; 123 | } 124 | 125 | while (i-- > 0) { 126 | print_char(buffer[i]); 127 | } 128 | } 129 | 130 | void print_uint64_bin(uint64_t value) { 131 | char buffer[64]; 132 | 133 | for (size_t i = 0; i < 64; i++) { 134 | buffer[i] = (value & 1) + '0'; 135 | value >>= 1; 136 | } 137 | 138 | for (size_t i = 64; i > 0; i--) { 139 | print_char(buffer[i - 1]); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Write Your Own 64-bit Operating System Kernel From Scratch 2 | 3 | This respository holds all the source code for [this YouTube tutorial series](https://www.youtube.com/playlist?list=PLZQftyCk7_SeZRitx5MjBKzTtvk0pHMtp). 4 | 5 | You can find the revision for a specific episode on [this page](https://github.com/davidcallanan/os-series/blob/master/REVISIONS.md). 6 | 7 | Please make sure to [**select the revision corresponding to the episode you are working on**](https://github.com/davidcallanan/os-series/blob/master/REVISIONS.md). The code for each revision might not match the video content exactly, as some corrections and adjustments are made from time to time. 8 | 9 | You can find pre-built ISO files for this kernel at [this repository](https://github.com/davidcallanan/os-series-isos). 10 | 11 | Considering supporting this work via [my Patreon page](http://patreon.com/codepulse). 12 | 13 | ## Prerequisites 14 | 15 | - A text editor such as [VS Code](https://code.visualstudio.com/). 16 | - [Docker](https://www.docker.com/) for creating our build-environment. 17 | - [Qemu](https://www.qemu.org/) for emulating our operating system. 18 | - Remember to add Qemu to the path so that you can access it from your command-line. ([Windows instructions here](https://dev.to/whaleshark271/using-qemu-on-windows-10-home-edition-4062)) 19 | 20 | ## Setup 21 | 22 | Build an image for our build-environment: 23 | - `docker build buildenv -t myos-buildenv` 24 | 25 | ## Build 26 | 27 | Enter build environment: 28 | - Linux or MacOS: `docker run --rm -it -v "$(pwd)":/root/env myos-buildenv` 29 | - Windows (CMD): `docker run --rm -it -v "%cd%":/root/env myos-buildenv` 30 | - Windows (PowerShell): `docker run --rm -it -v "${pwd}:/root/env" myos-buildenv` 31 | - Please use the linux command if you are using `WSL`, `msys2` or `git bash` 32 | - NOTE: If you are having trouble with an unshared drive, ensure your docker daemon has access to the drive you're development environment is in. For Docker Desktop, this is in "Settings > Shared Drives" or "Settings > Resources > File Sharing". 33 | 34 | Build for x86 (other architectures may come in the future): 35 | - `make build-x86_64` 36 | - If you are using Qemu, please close it before running this command to prevent errors. 37 | 38 | To leave the build environment, enter `exit`. 39 | 40 | ## Emulate 41 | 42 | You can emulate your operating system using [Qemu](https://www.qemu.org/): (Don't forget to [add qemu to your path](https://dev.to/whaleshark271/using-qemu-on-windows-10-home-edition-4062#:~:text=2.-,Add%20Qemu%20path%20to%20environment%20variables%20settings,-Copy%20the%20Qemu)!) 43 | 44 | - `qemu-system-x86_64 -cdrom dist/x86_64/kernel.iso` 45 | - Note: Close the emulator when finished, so as to not block writing to `kernel.iso` for future builds. 46 | 47 | If the above command fails, try one of the following: 48 | - Windows: [`qemu-system-x86_64 -cdrom dist/x86_64/kernel.iso -L "C:\Program Files\qemu"`](https://stackoverflow.com/questions/66266448/qemu-could-not-load-pc-bios-bios-256k-bin) 49 | - Linux: [`qemu-system-x86_64 -cdrom dist/x86_64/kernel.iso -L /usr/share/qemu/`](https://unix.stackexchange.com/questions/134893/cannot-start-kvm-vm-because-missing-bios) 50 | - Alternatively, install a custom BIOS binary file and link it to Qemu using the `-L` option. 51 | 52 | Alternatively, you should be able to load the operating system on a USB drive and boot into it when you turn on your computer. (I haven't actually tested this yet.) 53 | 54 | ## Cleanup 55 | 56 | Remove the build-evironment image: 57 | - `docker rmi myos-buildenv -f` 58 | --------------------------------------------------------------------------------