diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..98f5968434d9d370e4b57f75f2a1836c8b8714eb --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.bin +*.o diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..31d811a391ea684b51aed961d2312b308fc910da --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +BSD 2-Clause License + +Copyright (c) 2026, Mitja Felicijan + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..80f13f25ad50cf7c5fa51d12a72f06291adec151 --- /dev/null +++ b/Makefile @@ -0,0 +1,40 @@ +# Variables +ASM = nasm +CC = gcc +LD = ld +QEMU = qemu-system-x86_64 + +# Flags +CFLAGS = -m32 -ffreestanding -fno-pie -fno-stack-protector -mno-sse -mno-mmx -mno-sse2 -c +LDFLAGS = -m elf_i386 -T link.ld --oformat binary + +# Objects +OBJS = entry.o kmain.o idt.o interrupts.o keyboard.o shell.o + +# Targets +all: os-image.bin + +os-image.bin: boot.bin kernel.bin + cat $^ > $@ + truncate -s 10k $@ + +boot.bin: boot.asm + $(ASM) $< -f bin -o $@ + +kernel.bin: $(OBJS) + $(LD) $(LDFLAGS) -o $@ $^ + +entry.o: entry.asm + $(ASM) $< -f elf -o $@ + +interrupts.o: interrupts.asm + $(ASM) $< -f elf -o $@ + +%.o: %.c + $(CC) $(CFLAGS) $< -o $@ + +run: os-image.bin + $(QEMU) -drive format=raw,file=$< + +clean: + rm -f *.bin *.o diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..4d6dd361e85791d0a03c27e8f3edb6bafdf0f5c4 --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +# TinyOS + +TinyOS is a minimal, 32-bit x86 operating system implemented to get familiar +with OS development internals. I made this for fun and exploration. + +## Specs + +- **Architecture:** x86 (i386) +- **Bootloader:** 16-bit real-mode (NASM) -> 32-bit protected-mode. +- **Kernel:** Freestanding C (GCC). +- **Video:** VGA Mode 13h (320x200, 256 colors). +- **Interrupts:** IDT for hardware IRQs and CPU exceptions. +- **Input:** PS/2 keyboard. +- **Shell:** Basic CLI. + +## Implementation Details + +- `boot.asm`: 16-bit real-mode bootloader. +- `entry.asm`: Protected-mode initialization and kernel entry. +- `kmain.c`: Main kernel loop. +- `idt.c`: IDT configuration and interrupt routing. +- `keyboard.c`: PS/2 scancode to ASCII mapping. +- `shell.c`: Basic CLI. +- `link.ld`: Memory layout definition. + +## Dependencies + +- `nasm` +- `gcc` (i386 target support) +- `binutils` (ld with elf_i386 support) +- `qemu-system-x86_64` +- `make` + +## Usage + +Build: +```bash +make +``` + +Run: +```bash +make run +``` + +Clean: +```bash +make clean +``` + +## Memory Map + +The kernel is loaded into memory starting at `0x1000`. The VGA buffer for Mode +13h is located at `0xA0000`. diff --git a/boot.asm b/boot.asm new file mode 100644 index 0000000000000000000000000000000000000000..f43c02b129727acc515537aa31671250956d3272 --- /dev/null +++ b/boot.asm @@ -0,0 +1,126 @@ +; This code runs in 16-bit Real Mode when the computer starts. +; It is responsible for loading the kernel and switching to 32-bit Protected Mode. + +[org 0x7c00] ; BIOS loads the bootloader at this memory address +KERNEL_OFFSET equ 0x1000 ; Memory address where we will load our kernel + +; Save the boot drive index provided by BIOS in DL register +mov [BOOT_DRIVE], dl + +; Initialize the stack pointer to a safe area in memory +mov bp, 0x9000 +mov sp, bp + +; Load the kernel from the disk into memory +call load_kernel + +; Switch to VGA Mode 13h: 320x200 resolution with 256 colors +; This is a graphical mode where each byte in video memory is a pixel color +mov ax, 0x0013 +int 0x10 + +; Enter 32-bit Protected Mode +call switch_to_pm + +; Infinite loop in case we ever return (we shouldn't) +jmp $ + +; Global Descriptor Table (GDT) +; The GDT defines memory segments and their access rights for Protected Mode. +gdt_start: + dd 0x0 ; The null descriptor is required at the beginning + dd 0x0 + +gdt_code: + ; Code segment descriptor + dw 0xffff ; Limit (bits 0-15) + dw 0x0 ; Base (bits 0-15) + db 0x0 ; Base (bits 16-23) + db 10011010b ; Access byte: Present, Privilege 0, Code, Executable, Readable + db 11001111b ; Flags: 4KB granularity, 32-bit mode, Limit (bits 16-19) + db 0x0 ; Base (bits 24-31) + +gdt_data: + ; Data segment descriptor + dw 0xffff ; Limit (bits 0-15) + dw 0x0 ; Base (bits 0-15) + db 0x0 ; Base (bits 16-23) + db 10010010b ; Access byte: Present, Privilege 0, Data, Writable + db 11001111b ; Flags: 4KB granularity, 32-bit mode, Limit (bits 16-19) + db 0x0 ; Base (bits 24-31) + +gdt_end: + +; The GDT descriptor is passed to the LGDT instruction +gdt_descriptor: + dw gdt_end - gdt_start - 1 ; Size of GDT (minus 1) + dd gdt_start ; Starting address of GDT + +; Constants for segment selectors (offsets within the GDT) +CODE_SEG equ gdt_code - gdt_start +DATA_SEG equ gdt_data - gdt_start + +; Switch to 32-bit Protected Mode +[bits 16] +switch_to_pm: + cli ; Disable interrupts while switching modes + lgdt [gdt_descriptor] ; Load the GDT descriptor + mov eax, cr0 ; Enable Protected Mode bit in Control Register 0 + or eax, 0x1 + mov cr0, eax + jmp CODE_SEG:init_pm ; Far jump to flush the CPU pipeline and switch to 32-bit mode + +[bits 32] +init_pm: + ; Update all segment registers to use the new Data Segment selector + mov ax, DATA_SEG + mov ds, ax + mov ss, ax + mov es, ax + mov fs, ax + mov gs, ax + + ; Re-initialize the stack for 32-bit mode at a higher memory address + mov ebp, 0x90000 + mov esp, ebp + + ; Call the entry point that leads to our kernel + call BEGIN_PM + +; Routine to load sectors from disk using BIOS interrupts +[bits 16] +load_kernel: + mov bx, KERNEL_OFFSET ; Destination address in memory + mov dh, 15 ; Number of sectors to load (adjust if kernel grows) + mov dl, [BOOT_DRIVE] ; Drive index to read from + call disk_load + ret + +disk_load: + push dx + mov ah, 0x02 ; BIOS Read Sector function + mov al, dh ; Number of sectors + mov ch, 0x00 ; Cylinder 0 + mov dh, 0x00 ; Head 0 + mov cl, 0x02 ; Sector 2 (Sector 1 is the bootloader) + int 0x13 ; Call BIOS disk interrupt + jc disk_error ; Jump if Carry Flag is set (indicates error) + pop dx + cmp dh, al ; Verify that we read the expected number of sectors + jne disk_error + ret + +disk_error: + ; Halt execution if disk read fails + jmp $ + +; 32-bit entry point to jump into the compiled C kernel +[bits 32] +BEGIN_PM: + call KERNEL_OFFSET ; Jump to the memory location where kernel was loaded + jmp $ ; Safety hang + +; Boot sector padding and signature +BOOT_DRIVE db 0 +times 510-($-$$) db 0 ; Pad the rest of the 512 bytes with zeros +dw 0xaa55 ; The standard boot signature required by BIOS diff --git a/entry.asm b/entry.asm new file mode 100644 index 0000000000000000000000000000000000000000..c89e7b11231614b62dbe3b52b3c0071b88cbaa91 --- /dev/null +++ b/entry.asm @@ -0,0 +1,12 @@ +; Kernel Entry Point +; This is a small assembly wrapper that ensures the C kernel is started correctly. +; Since C compilers don't guarantee the order of functions in the binary, +; this stub is linked at the very beginning of the kernel image. + +[bits 32] ; We are in 32-bit Protected Mode at this point +global _start +[extern kmain] ; Inform the assembler that 'kmain' is defined elsewhere (in kmain.c) + +_start: + call kmain ; Transfer control to the main function of our C kernel + jmp $ ; If the kernel returns, halt the CPU in an infinite loop diff --git a/font.h b/font.h new file mode 100644 index 0000000000000000000000000000000000000000..51d331f505285b90018c9dee7fb893b033905821 --- /dev/null +++ b/font.h @@ -0,0 +1,135 @@ +#ifndef FONT_H +#define FONT_H + +unsigned char font8x8_basic[128][8] = { + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0000 (nul) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0001 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0002 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0003 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0004 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0005 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0006 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0007 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0008 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0009 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000A + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000B + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000C + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000D + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000E + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000F + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0010 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0011 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0012 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0013 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0014 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0015 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0016 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0017 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0018 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0019 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001A + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001B + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001C + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001D + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001E + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001F + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0020 (space) + { 0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00}, // U+0021 (!) + { 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0022 (") + { 0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00}, // U+0023 (#) + { 0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00}, // U+0024 ($) + { 0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00}, // U+0025 (%) + { 0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00}, // U+0026 (&) + { 0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0027 (') + { 0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00}, // U+0028 (() + { 0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00}, // U+0029 ()) + { 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00}, // U+002A (*) + { 0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00}, // U+002B (+) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+002C (,) + { 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00}, // U+002D (-) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+002E (.) + { 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00}, // U+002F (/) + { 0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00}, // U+0030 (0) + { 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00}, // U+0031 (1) + { 0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00}, // U+0032 (2) + { 0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00}, // U+0033 (3) + { 0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00}, // U+0034 (4) + { 0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00}, // U+0035 (5) + { 0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00}, // U+0036 (6) + { 0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00}, // U+0037 (7) + { 0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+0038 (8) + { 0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00}, // U+0039 (9) + { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+003A (:) + { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+003B (;) + { 0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00}, // U+003C (<) + { 0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00}, // U+003D (=) + { 0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00}, // U+003E (>) + { 0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00}, // U+003F (?) + { 0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00}, // U+0040 (@) + { 0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00}, // U+0041 (A) + { 0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00}, // U+0042 (B) + { 0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00}, // U+0043 (C) + { 0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00}, // U+0044 (D) + { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00}, // U+0045 (E) + { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00}, // U+0046 (F) + { 0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00}, // U+0047 (G) + { 0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00}, // U+0048 (H) + { 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0049 (I) + { 0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00}, // U+004A (J) + { 0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00}, // U+004B (K) + { 0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00}, // U+004C (L) + { 0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00}, // U+004D (M) + { 0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00}, // U+004E (N) + { 0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00}, // U+004F (O) + { 0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00}, // U+0050 (P) + { 0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00}, // U+0051 (Q) + { 0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00}, // U+0052 (R) + { 0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00}, // U+0053 (S) + { 0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0054 (T) + { 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00}, // U+0055 (U) + { 0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0056 (V) + { 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00}, // U+0057 (W) + { 0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00}, // U+0058 (X) + { 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00}, // U+0059 (Y) + { 0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00}, // U+005A (Z) + { 0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00}, // U+005B ([) + { 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00}, // U+005C (\) + { 0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00}, // U+005D (]) + { 0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00}, // U+005E (^) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, // U+005F (_) + { 0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0060 (`) + { 0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00}, // U+0061 (a) + { 0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00}, // U+0062 (b) + { 0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00}, // U+0063 (c) + { 0x38, 0x30, 0x30, 0x3e, 0x33, 0x33, 0x6E, 0x00}, // U+0064 (d) + { 0x00, 0x00, 0x1E, 0x33, 0x3f, 0x03, 0x1E, 0x00}, // U+0065 (e) + { 0x1C, 0x36, 0x06, 0x0f, 0x06, 0x06, 0x0F, 0x00}, // U+0066 (f) + { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0067 (g) + { 0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00}, // U+0068 (h) + { 0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0069 (i) + { 0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E}, // U+006A (j) + { 0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00}, // U+006B (k) + { 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+006C (l) + { 0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00}, // U+006D (m) + { 0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00}, // U+006E (n) + { 0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00}, // U+006F (o) + { 0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F}, // U+0070 (p) + { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78}, // U+0071 (q) + { 0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00}, // U+0072 (r) + { 0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00}, // U+0073 (s) + { 0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00}, // U+0074 (t) + { 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00}, // U+0075 (u) + { 0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0076 (v) + { 0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00}, // U+0077 (w) + { 0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00}, // U+0078 (x) + { 0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0079 (y) + { 0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00}, // U+007A (z) + { 0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00}, // U+007B ({) + { 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00}, // U+007C (|) + { 0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00}, // U+007D (}) + { 0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+007E (~) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // U+007F +}; + +#endif diff --git a/idt.c b/idt.c new file mode 100644 index 0000000000000000000000000000000000000000..c8f0ebd1dad9e16122875190b7bf4dd84d17dda1 --- /dev/null +++ b/idt.c @@ -0,0 +1,58 @@ +#include "idt.h" +#include "io.h" + +struct idt_entry idt[256] __attribute__((aligned(16))); +struct idt_ptr idtp; + +// Function defined in interrupts.asm +extern void load_idt(uint32_t idt_ptr); + +void set_idt_gate(int n, uint32_t handler) { + idt[n].base_low = handler & 0xFFFF; + idt[n].selector = 0x08; // This matches CODE_SEG offset in GDT + idt[n].zero = 0; + idt[n].flags = 0x8E; // Present, Ring 0, Interrupt Gate + idt[n].base_high = (handler >> 16) & 0xFFFF; +} + +void remap_pic() { + // ICW1 + outb(0x20, 0x11); + outb(0xA0, 0x11); + + // ICW2 (Offsets) + outb(0x21, 0x20); // Master PIC starts at 0x20 + outb(0xA1, 0x28); // Slave PIC starts at 0x28 + + // ICW3 (Cascade) + outb(0x21, 0x04); + outb(0xA1, 0x02); + + // ICW4 (8086 mode) + outb(0x21, 0x01); + outb(0xA1, 0x01); + + // Unmask only Keyboard (IRQ 1) + outb(0x21, 0xFD); + outb(0xA1, 0xFF); +} + +extern void exception_handler_stub(); + +void init_idt() { + // Zero out the IDT + for (int i = 0; i < 256; i++) { + idt[i].base_low = 0; + idt[i].selector = 0x08; + idt[i].zero = 0; + idt[i].flags = 0x8E; + idt[i].base_high = 0; + set_idt_gate(i, (uint32_t)exception_handler_stub); + } + + idtp.limit = (sizeof(struct idt_entry) * 256) - 1; + idtp.base = (uint32_t)&idt; + + remap_pic(); + load_idt((uint32_t)&idtp); +} diff --git a/idt.h b/idt.h new file mode 100644 index 0000000000000000000000000000000000000000..f2585324ffd84d1218121da7f15ef376c3e05546 --- /dev/null +++ b/idt.h @@ -0,0 +1,24 @@ +#ifndef IDT_H +#define IDT_H + +#include + +// IDT Entry structure +struct idt_entry { + uint16_t base_low; // Lower 16 bits of handler address + uint16_t selector; // Kernel segment selector (CODE_SEG) + uint8_t zero; // Always zero + uint8_t flags; // Flags (Type/Attributes) + uint16_t base_high; // Upper 16 bits of handler address +} __attribute__((packed)); + +// IDT Pointer structure for LIDT +struct idt_ptr { + uint16_t limit; + uint32_t base; +} __attribute__((packed)); + +void init_idt(); +void set_idt_gate(int n, uint32_t handler); + +#endif diff --git a/interrupts.asm b/interrupts.asm new file mode 100644 index 0000000000000000000000000000000000000000..cd929f5e49094d0ff3fdcf8bfda58b5a10d69bc0 --- /dev/null +++ b/interrupts.asm @@ -0,0 +1,32 @@ +[bits 32] + +global load_idt +load_idt: + mov eax, [esp + 4] + lidt [eax] + ret + +global enable_interrupts +enable_interrupts: + sti + ret + +; ISR for Keyboard (IRQ 1 mapped to 0x21) +global keyboard_handler_stub +extern keyboard_handler + +keyboard_handler_stub: + pushad ; Save all registers + call keyboard_handler + popad ; Restore all registers + iretd ; Interrupt return (32-bit) + +; Generic handler for unhandled interrupts/exceptions +global exception_handler_stub +extern exception_handler + +exception_handler_stub: + pushad + call exception_handler + popad + iretd diff --git a/io.h b/io.h new file mode 100644 index 0000000000000000000000000000000000000000..01966ab58a8b953aba763289bd20729a92e22f82 --- /dev/null +++ b/io.h @@ -0,0 +1,21 @@ +#ifndef IO_H +#define IO_H + +// Read a byte from a port +static inline unsigned char inb(unsigned short port) { + unsigned char result; + __asm__ volatile("inb %1, %0" : "=a"(result) : "Nd"(port)); + return result; +} + +// Write a byte to a port +static inline void outb(unsigned short port, unsigned char data) { + __asm__ volatile("outb %0, %1" : : "a"(data), "Nd"(port)); +} + +// Small delay for PIC operations +static inline void io_wait(void) { + outb(0x80, 0); +} + +#endif diff --git a/keyboard.c b/keyboard.c new file mode 100644 index 0000000000000000000000000000000000000000..9ec3767177085c5029c1562ffaa4be1489dbe5a5 --- /dev/null +++ b/keyboard.c @@ -0,0 +1,65 @@ +#include "keyboard.h" +#include "io.h" +#include "idt.h" + +// Forward declaration of the ASM stub +extern void keyboard_handler_stub(); +extern void shell_handle_char(char c); + +unsigned char kbd_us[128] = { + 0, 27, '1', '2', '3', '4', '5', '6', '7', '8', /* 9 */ + '9', '0', '-', '=', '\b', /* Backspace */ + '\t', /* Tab */ + 'q', 'w', 'e', 'r', /* 19 */ + 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n', /* Enter key */ + 0, /* 29 - Control */ + 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', /* 39 */ + '\'', '`', 0, /* Left shift */ + '\\', 'z', 'x', 'c', 'v', 'b', 'n', /* 49 */ + 'm', ',', '.', '/', 0, /* Right shift */ + '*', + 0, /* Alt */ + ' ', /* Space bar */ + 0, /* Caps lock */ + 0, /* 59 - F1 key ... > */ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, /* < ... F10 */ + 0, /* 69 - Num lock*/ + 0, /* Scroll Lock */ + 0, /* Home key */ + 0, /* Up Arrow */ + 0, /* Page Up */ + '-', + 0, /* Left Arrow */ + 0, + 0, /* Right Arrow */ + '+', + 0, /* 79 - End key*/ + 0, /* Down Arrow */ + 0, /* Page Down */ + 0, /* Insert Key */ + 0, /* Delete Key */ + 0, 0, 0, + 0, /* F11 Key */ + 0, /* F12 Key */ + 0, /* All other keys are undefined */ +}; + +void init_keyboard() { + set_idt_gate(0x21, (uint32_t)keyboard_handler_stub); +} + +void keyboard_handler() { + unsigned char scancode = inb(0x60); + + // Check if the key was pressed (bit 7 is 0) + if (!(scancode & 0x80)) { + char c = kbd_us[scancode]; + if (c != 0) { + shell_handle_char(c); + } + } + + // Acknowledge interrupt (End Of Interrupt) + outb(0x20, 0x20); +} diff --git a/keyboard.h b/keyboard.h new file mode 100644 index 0000000000000000000000000000000000000000..6b1275d063dd4110c9946ba900e34babf738d416 --- /dev/null +++ b/keyboard.h @@ -0,0 +1,7 @@ +#ifndef KEYBOARD_H +#define KEYBOARD_H + +void init_keyboard(); +void keyboard_handler(); + +#endif diff --git a/kmain.c b/kmain.c new file mode 100644 index 0000000000000000000000000000000000000000..06152489fe8a3055cf87915a6fa2a12776ba04de --- /dev/null +++ b/kmain.c @@ -0,0 +1,58 @@ +#include "screen.h" +#include "idt.h" +#include "keyboard.h" +#include "shell.h" +#include "font.h" + +void draw_pixel(int x, int y, unsigned char color) { + unsigned char* vga = (unsigned char*) 0xA0000; + vga[y * 320 + x] = color; +} + +void draw_char(int x, int y, char c, unsigned char color) { + if (c < 0 || (unsigned char)c > 127) return; + + for (int i = 0; i < 8; i++) { + unsigned char row = font8x8_basic[(unsigned char)c][i]; + for (int j = 0; j < 8; j++) { + if ((row >> j) & 1) { + draw_pixel(x + j, y + i, color); + } + } + } +} + +void draw_string(int x, int y, char* str, unsigned char color) { + int i = 0; + while (str[i] != 0) { + draw_char(x + (i * 8), y, str[i], color); + i++; + } +} + +void exception_handler() { + draw_string(0, 0, "EXCEPTION OCCURRED!", 12); // Red text + while(1); +} + +void kmain() { + // Initialize Interrupts + init_idt(); + + // Initialize Drivers + init_keyboard(); + + // Start Shell + init_shell(); + + // Enable Interrupts + extern void enable_interrupts(); + enable_interrupts(); + + // Loop forever + while(1) { + // Busy wait instead of hlt to avoid power management issues in some VMs + // and to simplify debugging + __asm__ volatile("pause"); + } +} diff --git a/link.ld b/link.ld new file mode 100644 index 0000000000000000000000000000000000000000..ed449fc926544aac58656300f6a987ee9d3cba9d --- /dev/null +++ b/link.ld @@ -0,0 +1,19 @@ +ENTRY(_start) +OUTPUT_FORMAT("binary") + +SECTIONS +{ + . = 0x1000; + .text : { + *(.text) + } + .rodata : { + *(.rodata) + } + .data : { + *(.data) + } + .bss : { + *(.bss) + } +} diff --git a/screen.h b/screen.h new file mode 100644 index 0000000000000000000000000000000000000000..48f21f7044a25c36fb13d5c03d767b2cb7e3b605 --- /dev/null +++ b/screen.h @@ -0,0 +1,8 @@ +#ifndef SCREEN_H +#define SCREEN_H + +void draw_pixel(int x, int y, unsigned char color); +void draw_char(int x, int y, char c, unsigned char color); +void draw_string(int x, int y, char* str, unsigned char color); + +#endif diff --git a/shell.c b/shell.c new file mode 100644 index 0000000000000000000000000000000000000000..cf2b19f32ace697046e8f79567b4fcce19f42320 --- /dev/null +++ b/shell.c @@ -0,0 +1,99 @@ +#include "shell.h" + +// Forward declarations of drawing functions from kmain.c (or better, make a header) +extern void draw_string(int x, int y, char* str, unsigned char color); +extern void draw_char(int x, int y, char c, unsigned char color); +extern void draw_pixel(int x, int y, unsigned char color); + +#define MAX_BUFFER 64 +char command_buffer[MAX_BUFFER]; +int buffer_index = 0; + +int cursor_x = 10; +int cursor_y = 10; + +void clear_screen_area() { + // Clear the shell area (for simplicity, we'll just draw a black rectangle) + for (int y = 0; y < 200; y++) { + for (int x = 0; x < 320; x++) { + draw_pixel(x, y, 0); + } + } + cursor_x = 10; + cursor_y = 10; +} + +void print_prompt() { + draw_string(cursor_x, cursor_y, "> ", 14); // Yellow prompt + cursor_x += 16; +} + +void init_shell() { + clear_screen_area(); + print_prompt(); +} + +void execute_command() { + command_buffer[buffer_index] = 0; + cursor_x = 10; + cursor_y += 12; + + if (buffer_index == 0) { + // Empty command + } else if (command_buffer[0] == 'c' && command_buffer[1] == 'l' && command_buffer[2] == 's') { + clear_screen_area(); + } else if (command_buffer[0] == 'i' && command_buffer[1] == 'n' && command_buffer[2] == 'f' && command_buffer[3] == 'o') { + for (int y = 0; y < 200; y++) { + for (int x = 0; x < 320; x++) { + draw_pixel(x, y, (unsigned char)(x + y)); + } + } + draw_string(100, 80, "Hello World!", 15); + draw_string(80, 100, "TinyOS VGA Mode 13h", 14); + draw_string(110, 120, "256 Colors", 10); + cursor_x = 10; + cursor_y = 140; // Move cursor below the text + } else if (command_buffer[0] == 'h' && command_buffer[1] == 'e' && command_buffer[2] == 'l' && command_buffer[3] == 'p') { + draw_string(cursor_x, cursor_y, "Commands: help, cls, info", 11); + cursor_y += 12; + } else { + draw_string(cursor_x, cursor_y, "Unknown command", 12); + cursor_y += 12; + } + + buffer_index = 0; + print_prompt(); + + // Simple scrolling check + if (cursor_y > 180) { + clear_screen_area(); + print_prompt(); + } +} + +void shell_handle_char(char c) { + if (c == '\n') { + execute_command(); + } else if (c == '\b') { + if (buffer_index > 0) { + buffer_index--; + cursor_x -= 8; + // Manually clear the character area with black pixels + for (int i = 0; i < 8; i++) { + for (int j = 0; j < 8; j++) { + draw_pixel(cursor_x + j, cursor_y + i, 0); + } + } + } + } else if (buffer_index < MAX_BUFFER - 1) { + command_buffer[buffer_index++] = c; + draw_char(cursor_x, cursor_y, c, 15); // White text + cursor_x += 8; + } + + // Wrap check + if (cursor_x > 300) { + cursor_x = 10; + cursor_y += 12; + } +} diff --git a/shell.h b/shell.h new file mode 100644 index 0000000000000000000000000000000000000000..cab0ae66d80e616606a19e296797b4514608a952 --- /dev/null +++ b/shell.h @@ -0,0 +1,7 @@ +#ifndef SHELL_H +#define SHELL_H + +void init_shell(); +void shell_handle_char(char c); + +#endif