commit fecf3b2e42c1f3892a3775510fcc0ab1cc335818 Author: Freywar Ulvnaudgari Date: Thu Jun 4 21:52:04 2026 +0300 Initial commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..a319aef --- /dev/null +++ b/.clang-format @@ -0,0 +1,8 @@ +Language: Cpp +BasedOnStyle: LLVM +BreakBeforeBraces: Attach +IndentWidth: 2 +TabWidth: 2 +UseTab: Never +ColumnLimit: 140 +AllowShortFunctionsOnASingleLine: None diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..ec0b890 --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + CompilationDatabase: build diff --git a/.gdbinit b/.gdbinit new file mode 100644 index 0000000..0097237 --- /dev/null +++ b/.gdbinit @@ -0,0 +1,4 @@ +target remote localhost:1234 +set architecture i386:x86-64 +set disassembly-flavor intel +display/i ($cs * 16 + $rip) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af96791 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.vscode +build diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..2d6c5f8 --- /dev/null +++ b/build.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +export CC=clang + +meson setup build --reconfigure +meson compile -v -C build + +sector_size=512 +partition_offset=2048 + +dd if=/dev/zero of=build/os.img bs="${sector_size}" count="$((partition_offset + 20480))" +dd if=build/mbr.bin of=build/os.img bs="${sector_size}" count=1 conv=notrunc +dd if=build/bootloader.bin of=build/os.img bs="${sector_size}" seek=1 count="$((partition_offset - 1))" conv=notrunc +mkfs.fat -F 16 --offset "${partition_offset}" build/os.img +mcopy -i build/os.img@@"$((partition_offset * sector_size))" src ::src +mcopy -i build/os.img@@"$((partition_offset * sector_size))" build/kernel.bin ::kernel.bin diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..2f0c58a --- /dev/null +++ b/meson.build @@ -0,0 +1,84 @@ +project( + 'freywaros', + 'c', + default_options: [ + 'warning_level=2', + 'c_std=c11', + ], + version: '0.1.0', +) + +run_target( + 'compdb', + command: ['ninja', '-C', meson.project_build_root(), '-t', 'compdb'], +) + +nasm = find_program('nasm') + +custom_target( + 'boot', + input: 'src/mbr.asm', + output: 'mbr.bin', + command: [nasm, '-f', 'bin', '-l', 'mbr.lst', '@INPUT@', '-o', '@OUTPUT@'], + build_by_default: true, +) + +custom_target( + 'bootloader', + input: 'src/bootloader.asm', + output: 'bootloader.bin', + command: [nasm, '-f', 'bin', '-l', 'bootloader.lst', '@INPUT@', '-o', '@OUTPUT@'], + build_by_default: true, +) + +kernel_entry_o = custom_target( + 'kernel_entry', + input: 'src/kernel_entry.asm', + output: 'kernel_entry.o', + command: [nasm, '-f', 'elf64', '@INPUT@', '-o', '@OUTPUT@'], +) + +cc = meson.get_compiler('c') + +kernel_sources = files( + 'src/ata.c', + 'src/fat16.c', + 'src/fs.c', + 'src/idt.c', + 'src/kernel.c', + 'src/keyboard.c', + 'src/memory.c', + 'src/panic.c', + 'src/pic.c', + 'src/string.c', + 'src/terminal.c', + 'src/vga.c', +) + +kernel_elf = executable( + 'kernel.elf', + sources: [kernel_entry_o, kernel_sources], + c_args: [ + '-ffreestanding', + '-nostdlib', + '-nostartfiles', + '-mno-red-zone', + '-mgeneral-regs-only', + '-Wno-unused-command-line-argument', + '-Wconversion', + '-DVERSION="' + meson.project_version() + '"', + ], + link_args: [ + '-T', meson.project_source_root() / 'src/linker.ld', + '-nostdlib', + ], + link_depends: 'src/linker.ld', +) + +custom_target( + 'kernel_bin', + input: kernel_elf, + output: 'kernel.bin', + command: ['objcopy', '-O', 'binary', '@INPUT@', '@OUTPUT@'], + build_by_default: true, +) diff --git a/src/ata.c b/src/ata.c new file mode 100644 index 0000000..c43f134 --- /dev/null +++ b/src/ata.c @@ -0,0 +1,70 @@ +#include "src/ata.h" +#include "src/panic.h" +#include "src/util.h" + +#define BSY 0b10000000 +#define DF 0b00100000 +#define DRQ 0b00001000 +#define ERR 0b00000001 + +#define READ_SECTORS 0x20 +#define WRITE_SECTORS 0x30 +#define FLUSH_CACHE 0xE7 + +#define SECTOR_WORDS 256 + +void ata_read_sectors(uint32_t index, uint8_t count, void *to) { + while (inb(0x1F7) & BSY) { + } + + outb(0x1F2, count); + outb(0x1F3, index & 0xFF); + index = index >> 8; + outb(0x1F4, index & 0xFF); + index = index >> 8; + outb(0x1F5, index & 0xFF); + index = index >> 8; + outb(0x1F6, 0b11100000 | (index & 0b00001111)); + + outb(0x1F7, READ_SECTORS); + + while (count--) { + while (!(inb(0x1F7) & DRQ)) { + } + + insw(0x1F0, to, SECTOR_WORDS); + to = (uint16_t *)to + SECTOR_WORDS; + } + + ASSERT(!(inb(0x1F7) & (ERR | DF)), "ata_read_sectors: read error"); +} + +void ata_write_sectors(uint32_t index, uint8_t count, const void *from) { + while (inb(0x1F7) & BSY) { + } + + outb(0x1F2, count); + outb(0x1F3, index & 0xFF); + index = index >> 8; + outb(0x1F4, index & 0xFF); + index = index >> 8; + outb(0x1F5, index & 0xFF); + index = index >> 8; + outb(0x1F6, 0b11100000 | (index & 0b00001111)); + + outb(0x1F7, WRITE_SECTORS); + + while (count--) { + while (!(inb(0x1F7) & DRQ)) { + } + + outsw(0x1F0, from, SECTOR_WORDS); + from = (const uint16_t *)from + SECTOR_WORDS; + } + + outb(0x1F7, FLUSH_CACHE); + while (inb(0x1F7) & BSY) { + } + + ASSERT(!(inb(0x1F7) & (ERR | DF)), "ata_write_sectors: write error"); +} diff --git a/src/ata.h b/src/ata.h new file mode 100644 index 0000000..fe8b7c9 --- /dev/null +++ b/src/ata.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +void ata_read_sectors(uint32_t index, uint8_t count, void *to); + +void ata_write_sectors(uint32_t index, uint8_t count, const void *from); diff --git a/src/bootloader.asm b/src/bootloader.asm new file mode 100644 index 0000000..6be8331 --- /dev/null +++ b/src/bootloader.asm @@ -0,0 +1,337 @@ +bits 16 +org 0x8000 + +xor ax, ax +mov ds, ax +mov es, ax +mov ss, ax +mov sp, 0x7000 + +FIRST_PARTITION_SECTOR equ 2048 ; TODO Read MBR. +STAGE_BUFFER_SEGMENT equ 0x0900 +STAGE_BUFFER_OFFSET equ 0x0000 +KERNEL_BUFFER_SEGMENT equ 0x2000 + +jmp prepare_kernel + +dap: +.dap_size: db 16 +.reserved: db 0 +.src_sectors: dw 1 +.dst_offset: dw 0 +.dst_segment: dw 0 +.src_lba: dq 0 +dap_end: + +bpb: +.bytes_per_sector: dw 0 +.sectors_per_cluster: db 0 +.reserved_sectors: dw 0 +.fats_count: db 0 +.root_entry_count: dw 0 +.total_sectors_16: dw 0 +.media_type: db 0 +.sectors_per_fat: dw 0 +bpb_end: + +fat_start: dw 0 +root_start: dw 0 +root_size: dw 0 +data_start: dw 0 + +kernel_filename: db 'KERNEL BIN' +kernel_cluster: dw 0 +kernel_buffer_offset: dw 0 + +read_sectors: + xor bx, bx + mov ds, bx + + mov [dap.src_lba], eax + mov [dap.src_sectors], cx + mov [dap.dst_segment], es + mov [dap.dst_offset], di + mov ah, 0x42 + mov dl, 0x80 + mov si, dap + int 0x13 + jc disk_error + ret + +disk_error: + mov ah, 0x0E + mov al, 'E' + int 0x10 + jmp $ + +prepare_kernel: +load_bpb: + xor bx, bx + mov ds, bx + + mov eax, FIRST_PARTITION_SECTOR + mov cx, 1 + mov bx, STAGE_BUFFER_SEGMENT + mov es, bx + mov di, STAGE_BUFFER_OFFSET + call read_sectors + + mov bx, STAGE_BUFFER_SEGMENT + mov ds, bx + + mov si, STAGE_BUFFER_OFFSET + 11 + xor bx, bx + mov es, bx + mov di, bpb + mov cx, bpb_end - bpb + rep movsb + +find_data_start: + xor bx, bx + mov ds, bx + + mov ax, FIRST_PARTITION_SECTOR + add ax, [bpb.reserved_sectors] + mov [fat_start], ax + + xor ax, ax + mov al, [bpb.fats_count] + mul word [bpb.sectors_per_fat] + add ax, [fat_start] + mov [root_start], ax ; dx should be zero for sane FAT16 values + + mov ax, [bpb.root_entry_count] + shl ax, 5 ; * 32 + shr ax, 9 ; / 512 ; there should be at least 512 entries, so safe + mov [root_size], ax + + mov ax, [root_start] + add ax, [root_size] + mov [data_start], ax + +find_kernel: + xor bx, bx + mov ds, bx + + xor eax, eax + mov ax, [root_start] + mov cx, [root_size] + mov bx, STAGE_BUFFER_SEGMENT + mov es, bx + mov di, STAGE_BUFFER_OFFSET + call read_sectors + + mov cx, [bpb.root_entry_count] + mov bx, STAGE_BUFFER_SEGMENT + mov ds, bx + mov si, STAGE_BUFFER_OFFSET + +.check_entry: + mov bx, STAGE_BUFFER_SEGMENT + mov ds, bx + + cmp byte [si], 0x00 + je .no_more_entries + cmp byte [si], 0xE5 + je .continue + + push si + push cx + xor bx, bx + mov es, bx + mov di, kernel_filename + mov cx, 11 + rep cmpsb + pop cx + pop si + je .found + +.continue: + add si, 32 + loop .check_entry + +.no_more_entries: + mov ah, 0x0E + mov al, 'K' + int 0x10 + jmp $ + +.found: + mov bx, STAGE_BUFFER_SEGMENT + mov ds, bx + + mov ax, [si + 26] + + xor bx, bx + mov ds, bx + + mov [kernel_cluster], ax + +load_fat: + xor bx, bx + mov ds, bx + + xor eax, eax + mov ax, [fat_start] + mov cx, [bpb.sectors_per_fat] + mov bx, STAGE_BUFFER_SEGMENT + mov es, bx + mov di, STAGE_BUFFER_OFFSET + call read_sectors + +load_kernel_cluster: + xor bx, bx + mov ds, bx + + xor eax, eax + mov ax, [kernel_cluster] + sub ax, 2 + xor bx, bx + mov bl, [bpb.sectors_per_cluster] + mul bx + add ax, [data_start] + xor cx, cx + mov cl, [bpb.sectors_per_cluster] + mov bx, KERNEL_BUFFER_SEGMENT + mov es, bx + mov di, [kernel_buffer_offset] + call read_sectors + + mov ax, [bpb.sectors_per_cluster] + shl ax, 9 ; * 512 + add ax, [kernel_buffer_offset] + mov [kernel_buffer_offset], ax + +find_next_kernel_cluster: + xor bx, bx + mov ds, bx + + mov si, [kernel_cluster] + shl si, 1 ; * 2 + add si, STAGE_BUFFER_OFFSET + mov bx, STAGE_BUFFER_SEGMENT + mov ds, bx + mov ax, [si] + xor bx, bx + mov ds, bx + mov [kernel_cluster], ax + cmp ax, 0xFFF8 + jb load_kernel_cluster + +; ------------------------------------------------------------------------------ + +jmp load_gdt + +gdt_start: +gdt_0: dq 0x0000000000000000 +gdt_1: +.limit_low: dw 0xFFFF +.base_low: dw 0x0000 +.base_middle: db 0x00 +.access: db 10011010b ; present, ring 0, code, executable, readable +.flags: db 11001111b ; 32-bit, 4KB granularity +.base_high: db 0x00 +gdt_1_end: +gdt_2: +.limit_low: dw 0xFFFF +.base_low: dw 0x0000 +.base_middle: db 0x00 +.access: db 10011010b ; present, ring 0, code, executable, readable +.flags: db 10101111b ; long mode (L bit) +.base_high: db 0x00 +gdt_2_end: +gdt_3: +.limit_low: dw 0xFFFF +.base_low: dw 0x0000 +.base_middle: db 0x00 +.access: db 10010010b ; present, ring 0, data, writable +.flags: db 00000000b +.base_high: db 0x00 +gdt_3_end: +gdt_end: + +gdt_descriptor: +.size: dw gdt_end - gdt_start - 1 +.offset: dd gdt_start +gdt_descriptor_end: + +load_gdt: + cli + + lgdt [gdt_descriptor] + + mov eax, cr0 + or eax, 0x00000001 + mov cr0, eax + + jmp 0x0008:protected_mode + +; ------------------------------------------------------------------------------ + +bits 32 +protected_mode: + mov ax, 0x0018 ; data segment is now third entry = 0x18 + mov ds, ax + mov es, ax + mov ss, ax + mov esp, 0x00090000 + + ; zero page table memory at 0x00010000 (3 pages = 768 dwords) + mov edi, 0x00010000 + mov ecx, 768 + xor eax, eax + rep stosd + + ; PML4[0] -> PDPT at 0x00011000 + mov dword [0x00010000], 0x00011003 + ; PDPT[0] -> PD at 0x00012000 + mov dword [0x00011000], 0x00012003 + ; PD: fill 64 entries, each mapping 2MB + mov edi, 0x00012000 + mov eax, 0x00000083 ; present, writable, huge page, base 0 + mov ecx, 64 + +.fill_pd: + mov dword [edi], eax + add edi, 8 + add eax, 0x00200000 ; next 2MB + loop .fill_pd + + ; load PML4 into CR3 + mov eax, 0x00010000 + mov cr3, eax + + ; enable PAE + mov eax, cr4 + or eax, 0x00000020 + mov cr4, eax + + ; enable long mode in EFER MSR + mov ecx, 0xC0000080 + rdmsr + or eax, 0x00000100 + wrmsr + + ; enable paging (and keep protection enabled) + mov eax, cr0 + or eax, 0x80000000 + mov cr0, eax + + jmp 0x0010:long_mode ; 0x10 = second entry = 64-bit code segment + +; ------------------------------------------------------------------------------ + +bits 64 +long_mode: + mov ax, 0x0018 ; data segment + mov ds, ax + mov es, ax + mov ss, ax + mov rsp, 0x0000000000090000 + + xor rax, rax + xor rbx, rbx + xor rcx, rcx + xor rdx, rdx + + jmp 0x00020000 diff --git a/src/fat16.c b/src/fat16.c new file mode 100644 index 0000000..7de8e2b --- /dev/null +++ b/src/fat16.c @@ -0,0 +1,259 @@ +#include "src/fat16.h" +#include "src/ata.h" +#include "src/memory.h" +#include "src/string.h" +#include "src/util.h" +#include + +#define SECTOR_SIZE 512 +#define FIRST_PARTITION_SECTOR 2048 +#define ATTRIBUTE_SUBDIRECTORY 0x10 + +typedef struct __attribute__((packed)) { + uint16_t bytes_per_sector; + uint8_t sectors_per_cluster; + uint16_t reserved_sectors; + uint8_t fats_count; + uint16_t root_entry_count; + uint16_t total_sectors_16; + uint8_t media_type; + uint16_t sectors_per_fat; +} fat16_bpb_t; + +typedef struct __attribute__((packed)) { + char name[8]; + char ext[3]; + uint8_t attributes; + uint8_t reserved[10]; + uint16_t modified_time; + uint16_t modified_date; + uint16_t first_cluster; + uint32_t size; +} fat16_dir_entry_t; + +typedef struct { + fs_node_t base; + fat16_dir_entry_t entry; +} fat16_node_t; + +static fat16_bpb_t bpb; // Assuming one partition. +static fs_node_t *fs = NUL; +static uint16_t *fat = NUL; + +static void to_8_3(const char *name, char *output) { + memory_set(' ', 11, output); + output[11] = '\0'; + const char *c = name; + uint64_t i = 0; + uint8_t ext = 0; + while (*c) { + if (*c == '.') { + i = 8; + ext = 1; + } else if (i < (!ext ? 8 : 11)) { + output[i++] = *c >= 'a' && *c <= 'z' ? *c - 32 : *c; + } + c++; + } +} + +static void from_8_3(const char *name, const char *extension, char *output) { + memory_set(0, 13, output); + + uint16_t ni = 0, ei = 0, oi = 0; + while (ni < 8 && name[ni] != ' ') { + output[oi++] = name[ni++]; + } + + if (extension[ei] != ' ') { + output[oi++] = '.'; + while (ei < 3 && extension[ei] != ' ') { + output[oi++] = extension[ei++]; + } + } +} + +fs_node_t *fat16_mount() { // Assuming one partition. + uint8_t sector[512]; + ata_read_sectors(FIRST_PARTITION_SECTOR, 1, §or); + memory_copy(sector + 11, sizeof(fat16_bpb_t), &bpb); + fat16_node_t *node = memory_allocate(sizeof(fat16_node_t)); + node->base.type = FAT16; + node->base.name[0] = node->entry.name[0] = '/'; + node->base.size = node->entry.size = sizeof(fat16_dir_entry_t) * bpb.root_entry_count; + node->base.is_dir = 1; + return fs = (fs_node_t *)node; +} + +static void ensure_fat() { + ASSERT(bpb.sectors_per_fat<256, "ensure_fat: big FAT not implemented") + if (!fat) { + fat = memory_allocate(bpb.sectors_per_fat * SECTOR_SIZE); + ata_read_sectors(FIRST_PARTITION_SECTOR + bpb.reserved_sectors, (uint8_t)bpb.sectors_per_fat, fat); + } +} + +static fat16_dir_entry_t *load_directory(fat16_node_t *directory) { + fat16_dir_entry_t *entries; + + if (!directory->entry.first_cluster) { + uint8_t sectors = (uint8_t)((directory->base.size + SECTOR_SIZE - 1) / SECTOR_SIZE); + entries = memory_allocate(sectors * SECTOR_SIZE + sizeof(fat16_dir_entry_t)); // One extra as null terminator. + ata_read_sectors(FIRST_PARTITION_SECTOR + bpb.reserved_sectors + bpb.fats_count * bpb.sectors_per_fat, sectors, entries); + } else { + ensure_fat(); + + uint16_t next_cluster = directory->entry.first_cluster; + uint32_t cluster_count = 0; + while (next_cluster < 0xFFF8) { + cluster_count++; + next_cluster = fat[next_cluster]; + } + entries = + memory_allocate(cluster_count * bpb.sectors_per_cluster * SECTOR_SIZE + sizeof(fat16_dir_entry_t)); // One extra as null terminator. + + uint8_t *chunk = (uint8_t *)entries; + next_cluster = directory->entry.first_cluster; + while (next_cluster < 0xFFF8) { + ata_read_sectors(FIRST_PARTITION_SECTOR + bpb.reserved_sectors + bpb.sectors_per_fat * bpb.fats_count + + (bpb.root_entry_count * sizeof(fat16_dir_entry_t) + SECTOR_SIZE - 1) / SECTOR_SIZE + + bpb.sectors_per_cluster * (next_cluster - 2), + bpb.sectors_per_cluster, chunk); + chunk += bpb.sectors_per_cluster * SECTOR_SIZE; + next_cluster = fat[next_cluster]; + } + } + + return entries; +} + +static fs_node_t *open_entry(const char *name, const fat16_dir_entry_t *entry) { + if (!entry->name[0]) { + return NUL; + } + + fat16_node_t *result = memory_allocate(sizeof(fat16_node_t)); + memory_copy((char *)name, string_length(name) + 1, &(result->base.name)); + result->base.type = FAT16; + result->base.size = entry->size; + result->base.is_dir = entry->attributes & ATTRIBUTE_SUBDIRECTORY; + memory_copy((fat16_dir_entry_t *)entry, sizeof(fat16_dir_entry_t), (uint8_t *)result + sizeof(fs_node_t)); + + return (fs_node_t *)result; +} + +fs_node_t *fat16_open_by(const fs_node_t *directory, const char *name) { + ASSERT(directory->type == FAT16, "fat16_open_by: directory is not FAT16"); + ASSERT(directory->is_dir, "fat16_open_by: directory is not a directory"); + + char name_8_3[12]; + to_8_3(name, name_8_3); + + fat16_dir_entry_t *entries = load_directory((fat16_node_t *)directory); + + fat16_dir_entry_t *entry = entries; + while (entry->name[0]) { + if ((uint8_t)entry->name[0] != 0xE5 && (uint8_t)entry->attributes != 0x0F && bytes_equal(name_8_3, (char *)entry, 11)) { + break; + } + entry++; + } + + fs_node_t *result = open_entry(name, entry); + memory_free(entries); + return result; +} + +fs_node_t *fat16_open_at(const fs_node_t *directory, uint64_t index) { + ASSERT(directory->type == FAT16, "fat16_open_at: directory is not FAT16"); + ASSERT(directory->is_dir, "fat16_open_at: directory is not a directory"); + + fat16_dir_entry_t *entries = load_directory((fat16_node_t *)directory); + + uint64_t ei = 0, vi = 0; + while (entries[ei].name[0]) { + if ((uint8_t)entries[ei].name[0] != 0xE5 && (uint8_t)entries[ei].attributes != 0x0F) { + if (vi == index) { + break; + } + vi++; + } + ei++; + } + + if (!entries[ei].name[0]) { + return NUL; + } + + char name[13]; + from_8_3(entries[ei].name, entries[ei].ext, name); + + fs_node_t *result = open_entry(name, entries + ei); + memory_free(entries); + return result; +} + +void fat16_read(const fs_node_t *file, uint64_t offset, uint64_t size, void *to) { + ASSERT(file->type == FAT16, "fat16_read: file is not FAT16"); + ASSERT(!file->is_dir, "fat16_read: can not read directory"); + ASSERT(file->size >= offset + size, "fat16_read: offset/size are out of bounds"); + + ensure_fat(); + + fat16_node_t *fat_file = (fat16_node_t *)file; + + uint32_t cluster_size = bpb.sectors_per_cluster * SECTOR_SIZE; + uint32_t next_cluster = fat_file->entry.first_cluster; + + // Assuming filesystem is correct. TODO Check for real. + + while (offset >= cluster_size) { + next_cluster = fat[next_cluster]; + offset -= cluster_size; + } + + uint8_t *cursor = to; + + uint8_t *tmp = memory_allocate(cluster_size); + ata_read_sectors(FIRST_PARTITION_SECTOR + bpb.reserved_sectors + bpb.sectors_per_fat * bpb.fats_count + + (bpb.root_entry_count * sizeof(fat16_dir_entry_t) + SECTOR_SIZE - 1) / SECTOR_SIZE + + bpb.sectors_per_cluster * (next_cluster - 2), + bpb.sectors_per_cluster, tmp); + uint64_t prefix_size = size <= cluster_size - offset ? size : cluster_size - offset; + memory_copy(tmp + offset, prefix_size, cursor); + size -= prefix_size; + cursor += prefix_size; + next_cluster = fat[next_cluster]; + + while (size >= cluster_size) { + ata_read_sectors(FIRST_PARTITION_SECTOR + bpb.reserved_sectors + bpb.sectors_per_fat * bpb.fats_count + + (bpb.root_entry_count * sizeof(fat16_dir_entry_t) + SECTOR_SIZE - 1) / SECTOR_SIZE + + bpb.sectors_per_cluster * (next_cluster - 2), + bpb.sectors_per_cluster, cursor); + size -= cluster_size; + cursor += cluster_size; + next_cluster = fat[next_cluster]; + } + + if (size) { + ata_read_sectors(FIRST_PARTITION_SECTOR + bpb.reserved_sectors + bpb.sectors_per_fat * bpb.fats_count + + (bpb.root_entry_count * sizeof(fat16_dir_entry_t) + SECTOR_SIZE - 1) / SECTOR_SIZE + + bpb.sectors_per_cluster * (next_cluster - 2), + bpb.sectors_per_cluster, tmp); + memory_copy(tmp, size, cursor); + } + + memory_free(tmp); +} + +void fat16_close(fs_node_t *node) { + ASSERT(node->type == FAT16, "fat16_close: node is not FAT16"); + ASSERT(node != fs, "fat16_close: can not unmount FS"); + memory_free(node); +} + +void fat16_unmount(fs_node_t *node) { + ASSERT(node->type == FAT16, "fat16_unmount: node is not FAT16"); + ASSERT(node == fs, "fat16_unmount: node is not filesystem"); + ASSERT(0, "fat16_unmount: not implemented"); +} diff --git a/src/fat16.h b/src/fat16.h new file mode 100644 index 0000000..4a79b78 --- /dev/null +++ b/src/fat16.h @@ -0,0 +1,17 @@ +#pragma once + +#include "src/fs.h" + +#define FAT16 1 + +fs_node_t *fat16_mount(); + +fs_node_t *fat16_open_by(const fs_node_t *directory, const char *name); + +fs_node_t *fat16_open_at(const fs_node_t *directory, uint64_t index); + +void fat16_read(const fs_node_t *file, uint64_t offset, uint64_t size, void *to); + +void fat16_close(fs_node_t *node); + +void fat16_unmount(fs_node_t *fs); diff --git a/src/fs.c b/src/fs.c new file mode 100644 index 0000000..824f2b1 --- /dev/null +++ b/src/fs.c @@ -0,0 +1,26 @@ +#include "src/fs.h" +#include "src/fat16.h" + +fs_node_t *fs_mount() { + return fat16_mount(); +} + +fs_node_t *fs_open_by(const fs_node_t *directory, const char *name) { + return fat16_open_by(directory, name); +} + +fs_node_t *fs_open_at(const fs_node_t *directory, uint64_t index) { + return fat16_open_at(directory, index); +} + +void fs_read(const fs_node_t *file, uint64_t offset, uint64_t size, void *to) { + fat16_read(file, offset, size, to); +} + +void fs_close(fs_node_t *node) { + fat16_close(node); +} + +void fs_unmount(fs_node_t *fs) { + fat16_unmount(fs); +} diff --git a/src/fs.h b/src/fs.h new file mode 100644 index 0000000..acb9d48 --- /dev/null +++ b/src/fs.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#define FILENAME_SIZE_LIMIT 255 + +typedef struct fs_node { + char name[FILENAME_SIZE_LIMIT + 1]; + uint32_t size; + uint8_t is_dir; + uint8_t type; +} fs_node_t; + + +fs_node_t *fs_mount(); + +fs_node_t *fs_open_by(const fs_node_t *directory, const char *name); + +fs_node_t *fs_open_at(const fs_node_t *directory, uint64_t index); + +void fs_read(const fs_node_t *file, uint64_t offset, uint64_t size, void *to); + +void fs_close(fs_node_t *node); + +void fs_unmount(fs_node_t *fs); diff --git a/src/idt.c b/src/idt.c new file mode 100644 index 0000000..659b911 --- /dev/null +++ b/src/idt.c @@ -0,0 +1,40 @@ +#include "src/idt.h" + +struct idt_entry { + uint16_t offset_low; + uint16_t selector; + uint8_t ist; + uint8_t flags; + uint16_t offset_mid; + uint32_t offset_high; + uint32_t zero; +} __attribute__((packed)); + +struct idt_descriptor { + uint16_t size; + uint64_t offset; +} __attribute__((packed)); + +static struct idt_entry idt[256]; +static struct idt_descriptor idt_desc; + +static void idt_load() { + idt_desc.size = sizeof(idt) - 1; + idt_desc.offset = (uint64_t)&idt; + __asm__ volatile("lidt %0" : : "m"(idt_desc)); +} + +inline void idt_init() { + idt_load(); +} + +void idt_set_entry(int vector, void (*handler)(struct interrupt_frame *), uint8_t flags) { + uint64_t addr = (uint64_t)handler; + idt[vector].offset_low = addr & 0xFFFF; + idt[vector].selector = 0x0010; + idt[vector].ist = 0; + idt[vector].flags = flags; + idt[vector].offset_mid = (addr >> 16) & 0xFFFF; + idt[vector].offset_high = (addr >> 32) & 0xFFFFFFFF; + idt[vector].zero = 0; +} diff --git a/src/idt.h b/src/idt.h new file mode 100644 index 0000000..878a393 --- /dev/null +++ b/src/idt.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +struct interrupt_frame { + uint64_t ip; + uint64_t cs; + uint64_t flags; + uint64_t sp; + uint64_t ss; +}; + +void idt_init(); + +void idt_set_entry(int vector, void (*handler)(struct interrupt_frame *), uint8_t flags); diff --git a/src/kernel.c b/src/kernel.c new file mode 100644 index 0000000..f7700ca --- /dev/null +++ b/src/kernel.c @@ -0,0 +1,48 @@ +#include "src/idt.h" +#include "src/keyboard.h" +#include "src/memory.h" +#include "src/pic.h" +#include "src/terminal.h" +#include "src/util.h" +#include "src/vga.h" + +__attribute__((interrupt)) void isr_divide_by_zero([[maybe_unused]] struct interrupt_frame *frame) { + vga_set_string(VGA_HEIGHT - 1, 0, "EXCEPTION: divide by zero", 0x4F); + while (1) + ; +} + +__attribute__((interrupt)) void isr_page_fault([[maybe_unused]] struct interrupt_frame *frame) { + vga_set_string(VGA_HEIGHT - 1, 0, "EXCEPTION: page fault", 0x4F); + while (1) + ; +} + +__attribute__((interrupt)) void isr_general_violation([[maybe_unused]] struct interrupt_frame *frame) { + vga_set_string(VGA_HEIGHT - 1, 0, "EXCEPTION: general violation", 0x4F); + while (1) + ; +} + +__attribute__((interrupt)) void isr_ata_primary([[maybe_unused]] struct interrupt_frame *frame) { + outb(0x20, 0x20); + outb(0xA0, 0x20); +} + +void kernel_main() { + pic_init(); + idt_init(); + outb(0x21, inb(0x21) | 0x01); // mask out timer interrupt + idt_set_entry(0, isr_divide_by_zero, 0x8E); + idt_set_entry(0x0E, isr_page_fault, 0x8E); + idt_set_entry(0x0D, isr_general_violation, 0x8E); + idt_set_entry(46, isr_ata_primary, 0x8E); + __asm__ volatile("sti"); + + memory_init(); + keyboard_init(); + terminal_init(); + + while (1) + ; +} diff --git a/src/kernel_entry.asm b/src/kernel_entry.asm new file mode 100644 index 0000000..e1c04b8 --- /dev/null +++ b/src/kernel_entry.asm @@ -0,0 +1,3 @@ +bits 64 +extern kernel_main +jmp kernel_main diff --git a/src/keyboard.c b/src/keyboard.c new file mode 100644 index 0000000..dc985ae --- /dev/null +++ b/src/keyboard.c @@ -0,0 +1,22 @@ +#include "src/keyboard.h" +#include "src/idt.h" +#include "src/util.h" + +static keyboard_handler_t current_handler = 0; + +__attribute__((interrupt)) static void isr_keyboard([[maybe_unused]] struct interrupt_frame *frame) { + uint8_t scancode = inb(0x60); + outb(0x20, 0x20); + if (current_handler) { + current_handler(scancode); + } +} + +void keyboard_init() { + outb(0x21, inb(0x21) & ~0x02); + idt_set_entry(33, isr_keyboard, 0x8E); +} + +void keyboard_set_handler(keyboard_handler_t handler) { + current_handler = handler; +} diff --git a/src/keyboard.h b/src/keyboard.h new file mode 100644 index 0000000..7fb80e8 --- /dev/null +++ b/src/keyboard.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +void keyboard_init(); + +typedef void (*keyboard_handler_t)(uint8_t scancode); + +void keyboard_set_handler(keyboard_handler_t handler); diff --git a/src/linker.ld b/src/linker.ld new file mode 100644 index 0000000..07fcbff --- /dev/null +++ b/src/linker.ld @@ -0,0 +1,17 @@ +ENTRY(kernel_main) + +SECTIONS { + . = 0x0000000000020000; + + .text : { + *(.text) + } + + .data : { + *(.data) + } + + .bss : { + *(.bss) + } +} diff --git a/src/mbr.asm b/src/mbr.asm new file mode 100644 index 0000000..1487240 --- /dev/null +++ b/src/mbr.asm @@ -0,0 +1,38 @@ +bits 16 +org 0x7C00 + +setup: + xor ax, ax + mov ds, ax + mov es, ax + mov ss, ax + mov sp, 0x7C00 + +load_bootloader: + mov ah, 0x02 ; BIOS read sectors function + mov al, 63 ; number of sectors to read + mov bx, 0x8000 ; to memory address + mov ch, 0 ; from cylinder 0 + mov cl, 2 ; from sector 2 + mov dh, 0 ; from head 0 + int 0x13 + + jmp 0x8000 + +times 446 - ($ - $$) db 0 + +partition_table: +partition_1: +.bootable: db 0x80 +.chs_start: db 0x20, 0x21, 0x00 +.type: db 0x06 +.chs_end: db 0xFF, 0xFF, 0xFF +.lba_start: dd 0x00000800 +.lba_size dd 0x00005000 +partition_1_end: +partition_table_end: + +times 510 - ($ - $$) db 0 + +boot_signature: + dw 0xAA55 diff --git a/src/memory.c b/src/memory.c new file mode 100644 index 0000000..522707a --- /dev/null +++ b/src/memory.c @@ -0,0 +1,107 @@ +#include "src/memory.h" +#include "src/panic.h" +#include "src/util.h" +#include + +#define MEMORY_SIZE 134217728 +#define PAGE_SIZE 4096 +#define PAGE_COUNT (MEMORY_SIZE / PAGE_SIZE) +#define MAP_UNIT 64 +#define FULL_UNIT 0xFFFFFFFFFFFFFFFF + +// Claim 2MB for bootloader, kernel, and stuff. +static uint16_t free_page = MAP_UNIT * 8; +static uint64_t allocation[PAGE_COUNT / MAP_UNIT] = {FULL_UNIT, FULL_UNIT, FULL_UNIT, FULL_UNIT, + FULL_UNIT, FULL_UNIT, FULL_UNIT, FULL_UNIT}; + +static uint8_t get_allocated(uint16_t page) { + return !!allocation[page / MAP_UNIT] & ((uint64_t)1 << (page % MAP_UNIT)); +} + +static void set_allocated(uint16_t page, uint8_t allocated) { + if (allocated) { + allocation[page / MAP_UNIT] |= ((uint64_t)1 << (page % MAP_UNIT)); + } else { + allocation[page / MAP_UNIT] &= ~((uint64_t)1 << (page % MAP_UNIT)); + } +} + +static void *memory_page_allocate() { + ASSERT(free_page < PAGE_COUNT, "memory_page_allocate: out of memory"); + const uint16_t page = free_page; + set_allocated(page, 1); + for (uint16_t unit = free_page / MAP_UNIT; unit < PAGE_COUNT / MAP_UNIT; unit++) { + if (allocation[unit] != FULL_UNIT) { + uint16_t bit = (uint16_t)__builtin_ctzll(~allocation[unit]); + free_page = unit * MAP_UNIT + bit; + break; + } + } + return (void *)((uint64_t)page * PAGE_SIZE); +} + +[[maybe_unused]] static void memory_page_free(void *address) { + uint16_t page = (uint16_t)((uint64_t)address / PAGE_SIZE); + ASSERT(get_allocated(page), "memory_page_free: page not allocated") + set_allocated(page, 0); + if (page < free_page) { + free_page = page; + } +} + +#define HEAP_PAGE_COUNT 256 + +typedef struct free_chunk { + uint64_t size; // header + data + struct free_chunk *next; +} free_chunk_t; + +static free_chunk_t *free_list; + +void memory_init() { + free_list = memory_page_allocate(); + for (uint64_t i = 1; i < HEAP_PAGE_COUNT; i++) { + memory_page_allocate(); // TODO Map the pages. + } + free_list->size = HEAP_PAGE_COUNT * PAGE_SIZE; + free_list->next = NUL; +} + +void *memory_allocate(uint64_t size) { + size = sizeof(free_chunk_t) + (size + 7) & (uint64_t)~7; + + free_chunk_t *prev = NUL; + free_chunk_t *curr = free_list; + + while (curr) { + if (curr->size >= size + (sizeof(free_chunk_t) + 8)) { + free_chunk_t *remainder = (free_chunk_t *)((uint8_t *)curr + size); + remainder->size = curr->size - size; + remainder->next = curr->next; + curr->size = size; + curr->next = remainder; + } + if (curr->size >= size) { + if (prev) { + prev->next = curr->next; + } else { + free_list = curr->next; + } + void *result = (void *)((uint8_t *)curr + sizeof(free_chunk_t)); + memory_set(0, size - sizeof(free_chunk_t), result); + return result; + } + prev = curr; + curr = curr->next; + } + + ASSERT(0, "memory_heap_allocate: out of heap memory"); + return 0; +} + +void memory_free(void *pointer) { + free_chunk_t *chunk = (free_chunk_t *)((uint8_t *)pointer - sizeof(free_chunk_t)); + chunk->next = free_list; + free_list = chunk; + // TODO Merge adjacent free chunks. +} diff --git a/src/memory.h b/src/memory.h new file mode 100644 index 0000000..137e30c --- /dev/null +++ b/src/memory.h @@ -0,0 +1,30 @@ +#pragma once + +#include "src/panic.h" +#include + +void memory_init(); + +void *memory_allocate(uint64_t size); + +void memory_free(void *pointer); + +static inline void memory_set(uint8_t value, uint64_t bytes, void *to) { + __asm__ volatile("rep stosb" : "=D"(to), "=c"(bytes) : "D"(to), "a"(value), "c"(bytes) : "memory"); +} + +static inline void memory_move(void *from, uint64_t bytes, void *to) { + if (to == from) { + return; + } else if (to < from) { + __asm__ volatile("rep movsb" : "=D"(to), "=S"(from), "=c"(bytes) : "D"(to), "S"(from), "c"(bytes) : "memory"); + } else { + ASSERT(0, "memory_move: forward overlapping move not implemented"); + } +} + +static inline void memory_copy(const void *from, uint64_t bytes, void *to) { + ASSERT((uint64_t)to + bytes <= (uint64_t)from || (uint64_t)to >= (uint64_t)from + bytes, "memory_copy: overlapping backward copy"); + + __asm__ volatile("rep movsb" : "=D"(to), "=S"(from), "=c"(bytes) : "D"(to), "S"(from), "c"(bytes) : "memory"); +} diff --git a/src/panic.c b/src/panic.c new file mode 100644 index 0000000..38970cc --- /dev/null +++ b/src/panic.c @@ -0,0 +1,8 @@ +#include "src/panic.h" +#include "src/vga.h" + +void kernel_panic(const char *msg, [[maybe_unused]] const char *file, [[maybe_unused]] int line) { + vga_set_string(0, VGA_HEIGHT - 1, msg, 0x28); + while (1) + ; +} diff --git a/src/panic.h b/src/panic.h new file mode 100644 index 0000000..92ff484 --- /dev/null +++ b/src/panic.h @@ -0,0 +1,8 @@ +#pragma once + +#define ASSERT(cond, msg) \ + if (!(cond)) { \ + kernel_panic(msg " (assertion: " #cond ")", __FILE__, __LINE__); \ + } + +void kernel_panic(const char *msg, const char *file, int line); diff --git a/src/pic.c b/src/pic.c new file mode 100644 index 0000000..c599509 --- /dev/null +++ b/src/pic.c @@ -0,0 +1,35 @@ +#include "src/pic.h" +#include "src/util.h" + +static void pic_remap() { + uint8_t mask1 = inb(0x21); + uint8_t mask2 = inb(0xA1); + + outb(0x20, 0x11); + io_wait(); + outb(0xA0, 0x11); + io_wait(); + + outb(0x21, 0x20); + io_wait(); + outb(0xA1, 0x28); + io_wait(); + + outb(0x21, 0x04); + io_wait(); + outb(0xA1, 0x02); + io_wait(); + + outb(0x21, 0x01); + io_wait(); + outb(0xA1, 0x01); + io_wait(); + + outb(0x21, mask1); + outb(0xA1, mask2); + io_wait(); +} + +inline void pic_init() { + pic_remap(); +} diff --git a/src/pic.h b/src/pic.h new file mode 100644 index 0000000..f86a385 --- /dev/null +++ b/src/pic.h @@ -0,0 +1,3 @@ +#pragma once + +void pic_init(); diff --git a/src/string.c b/src/string.c new file mode 100644 index 0000000..add1fda --- /dev/null +++ b/src/string.c @@ -0,0 +1,230 @@ +#include "src/string.h" +#include "src/memory.h" +#include +#include + +uint8_t bytes_equal(const char *l, const char *r, uint64_t count) { + while (count--) { + if (*l++ != *r++) { + return 0; + } + } + return 1; +} + +uint8_t string_empty(const char *s) { + return *s == '\0'; +} + +uint64_t string_length(const char *s) { + const char *e = s; + while (*e) { + e++; + } + return (uint64_t)(e - s); +} + +uint8_t string_equal(const char *l, const char *r) { + while (*l && *r) { + if (*l != *r) { + return 0; + } + l++; + r++; + } + return *l == *r; +} + +uint64_t string_split(char *string, char separator, uint64_t max, char **result) { + uint64_t count = 1; + *result = string; + + while (*string && count < max) { + if (*string == separator) { + *string = '\0'; + result++; + *result = string + 1; + count++; + } + string++; + } + return count; +} + +uint64_t string_byte_to_hex(uint8_t value, uint64_t max, char *result) { + if (max == 0) { + return 0; + } + + if (max == 1) { + result[0] = '\0'; + return 0; + } + + if (max == 2) { + result[0] = '0'; + result[1] = '\0'; + return 1; + } + + char tmp[3] = "00"; + + uint8_t i = 0; + while (value) { + uint8_t nibble = value & 0xF; + tmp[i++] = nibble < 10 ? '0' + nibble : 'a' + nibble - 10; + value >>= 4; + } + + result[0] = '0'; + result[1] = 'x'; + + i = 2; + while (i < 4 && i < max - 1) { + result[i] = tmp[3 - i]; + i++; + } + result[i] = '\0'; + return i; +} + +uint64_t string_uint_to_decimal(uint64_t value, uint64_t max, char *result) { + if (max == 0) { + return 0; + } + + char tmp[20]; + uint8_t len = 0; + + if (value == 0) { + tmp[len++] = '0'; + } else { + while (value && len < 20) { + tmp[len++] = '0' + (value % 10); + value /= 10; + } + } + + uint8_t i = 0; + while (i < len && i < max - 1) { + result[i] = tmp[len - 1 - i]; + i++; + } + result[i] = '\0'; + return i; +} + +uint64_t string_int_to_decimal(int64_t value, uint64_t max, char *result) { + if (max == 0) { + return 0; + } + + if (value >= 0) { + return string_uint_to_decimal((uint64_t)value, max, result); + } + + result[0] = '-'; + return 1 + string_uint_to_decimal((uint64_t)(-value), max - 1, result + 1); +} + +uint64_t string_uint_to_hex(uint64_t value, uint64_t max, char *result) { + if (max == 0) { + return 0; + } + + if (max == 1) { + result[0] = '\0'; + return 0; + } + + if (max == 2) { + result[0] = '0'; + result[1] = '\0'; + return 1; + } + + char tmp[17] = "0000000000000000"; + + uint8_t i = 0; + while (value) { + uint8_t nibble = value & 0xF; + tmp[i++] = nibble < 10 ? '0' + nibble : 'A' + nibble - 10; + value >>= 4; + } + + result[0] = '0'; + result[1] = 'x'; + + i = 2; + while (i < 18 && i < max - 1) { + result[i] = tmp[17 - i]; + i++; + } + result[i] = '\0'; + return i; +} + +char *string_format(const char *format, ...) { + va_list args; + va_start(args, format); + + uint64_t limit = 1023; + + char *result = memory_allocate(limit + 1); + + uint64_t fi = 0, ri = 0; + while (format[fi] && ri < limit) { + if (format[fi] != '%') { + result[ri++] = format[fi++]; + } else { + switch (format[fi + 1]) { + case 'c': + result[ri++] = (char)va_arg(args, int); + fi += 2; + break; + case 'd': + string_int_to_decimal(va_arg(args, int64_t), limit - ri, result + ri); + while (result[ri]) { + ri++; + } + fi += 2; + break; + case 'u': + string_uint_to_decimal(va_arg(args, uint64_t), limit - ri, result + ri); + while (result[ri]) { + ri++; + } + fi += 2; + break; + case 'x': + string_uint_to_hex(va_arg(args, uint64_t), limit - ri, result + ri); + while (result[ri]) { + ri++; + } + fi += 2; + break; + case 's': { + char *s = va_arg(args, char *); + while (*s && ri < limit) { + result[ri++] = *s; + s++; + } + fi += 2; + break; + } + case '%': { + result[ri++] = '%'; + fi += 2; + break; + } + default: + result[ri++] = format[fi++]; + result[ri++] = format[fi++]; + break; + } + } + } + result[ri] = '\0'; + va_end(args); + return result; +} diff --git a/src/string.h b/src/string.h new file mode 100644 index 0000000..37de9f6 --- /dev/null +++ b/src/string.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +uint8_t bytes_equal(const char *l, const char *r, uint64_t count); + +uint8_t string_empty(const char *s); + +uint64_t string_length(const char *s); + +uint8_t string_equal(const char *l, const char *r); + +uint64_t string_split(char *string, char separator, uint64_t max, char **result); + +uint64_t string_byte_to_hex(uint8_t value, uint64_t max, char *result); + +uint64_t string_uint_to_decimal(uint64_t value, uint64_t max, char *result); + +uint64_t string_int_to_decimal(int64_t value, uint64_t max, char *result); + +uint64_t string_uint_to_hex(uint64_t value, uint64_t max, char *result); + +char *string_format(const char *format, ...); diff --git a/src/terminal.c b/src/terminal.c new file mode 100644 index 0000000..a74d1b1 --- /dev/null +++ b/src/terminal.c @@ -0,0 +1,397 @@ +#include "src/terminal.h" +#include "src/fs.h" +#include "src/keyboard.h" +#include "src/memory.h" +#include "src/string.h" +#include "src/util.h" +#include "src/vga.h" +#include + +#define KEYBOARD_STATE_LSHIFT 0b00000001 +#define KEYBOARD_STATE_RSHIFT 0b00000010 +#define KEYBOARD_STATE_LCTRL 0b00000100 +#define KEYBOARD_STATE_RCTRL 0b00001000 +#define KEYBOARD_STATE_LALT 0b00010000 +#define KEYBOARD_STATE_RALT 0b00100000 +#define KEYBOARD_STATE_SEQ 0b01000000 + +static uint8_t keyboard_state = 0; + +// clang-format off +static const char scancode_normal[128] = { + 0, 0, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b', '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', + 'i', 'o', 'p', '[', ']', '\n', 0, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', 0, '\\', 'z', 'x', + 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 0, '*', 0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, '-', 0, 0, 0, '+', 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static const char scancode_shifted[128] = { + 0, 0, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b', '\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', + 'I', 'O', 'P', '{', '}', '\n', 0, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~', 0, '|', 'Z', 'X', + 'C', 'V', 'B', 'N', 'M', '<', '>', '?', 0, '*', 0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, '-', 0, 0, 0, '+', 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; +// clang-format on + +#define PROMPT_PREFIX 2 +#define PROMPT_LENGTH 255 + +fs_node_t *root; + +static char prompt[PROMPT_LENGTH + 1]; +static char prompt_swap[PROMPT_LENGTH + 1]; +static uint8_t prompt_row = 0; +static uint8_t prompt_length = 0; +static uint8_t prompt_offset = 0; +static char prompt_buf[VGA_WIDTH + 1] = "$ "; + +static void render_prompt() { + uint8_t offset = PROMPT_PREFIX + prompt_offset < VGA_WIDTH ? 0 : PROMPT_PREFIX + prompt_offset - VGA_WIDTH + 1; + uint8_t length = PROMPT_PREFIX + prompt_length < VGA_WIDTH ? prompt_length : VGA_WIDTH - PROMPT_PREFIX; + prompt_buf[1] = PROMPT_PREFIX + prompt_offset < VGA_WIDTH ? ' ' : '<'; + memory_copy(prompt + offset, length, prompt_buf + PROMPT_PREFIX); + if (PROMPT_PREFIX + prompt_length > VGA_WIDTH && prompt_offset < prompt_length - 1) { + prompt_buf[VGA_WIDTH - 1] = '>'; + } + prompt_buf[PROMPT_PREFIX + length] = '\0'; + + vga_clear(prompt_row, 1); + vga_set_string(prompt_row, 0, prompt_buf, 0x0F); + vga_set_cursor(prompt_row, PROMPT_PREFIX + prompt_offset - offset); +} + +static void on_home_pressed() { + if (keyboard_state || !prompt_offset) { + return; + } + + prompt_offset = 0; + render_prompt(); +} + +static void on_left_pressed() { + if (keyboard_state || !prompt_offset) { + return; + } + + prompt_offset--; + render_prompt(); +} + +static void on_right_pressed() { + if (keyboard_state || prompt_offset == prompt_length) { + return; + } + + prompt_offset++; + render_prompt(); +} + +static void on_end_pressed() { + if (keyboard_state || prompt_offset == prompt_length) { + return; + } + + prompt_offset = prompt_length; + render_prompt(); +} + +static void advance_row(uint8_t rows) { + while (rows--) { + if (prompt_row == VGA_HEIGHT - 1) { + vga_copy(1, VGA_HEIGHT - 1, 0); + } else { + prompt_row++; + } + } +} + +static void print_line(const char *string) { + advance_row(1); + vga_clear(prompt_row - 1, 1); + vga_set_string(prompt_row - 1, 0, string, 0x0F); +} + +static void help(uint8_t argc, [[maybe_unused]] char **argv) { + if (argc > 1) { + print_line("help: accepts no arguments"); + } + + advance_row(4); + vga_set_string(prompt_row - 4, 0, "Available commands:", 0x0F); + vga_set_string(prompt_row - 3, 0, "help", 0x0F); + vga_set_string(prompt_row - 2, 0, "ls DIR", 0x0F); + vga_set_string(prompt_row - 1, 0, "cat FILE", 0x0F); +} + +static void ls(uint8_t argc, char **argv) { + if (argc != 2) { + print_line("ls: requires a single path"); + return; + } + + char *path_components[16]; + uint64_t path_length = string_split(argv[1], '/', 16, path_components); + if (!string_empty(path_components[0])) { + print_line("ls: relative paths not supported"); + return; + } + + fs_node_t *prev = NUL; + fs_node_t *curr = root; + uint8_t i = 1; + while (i < path_length) { + if (string_empty(path_components[i])) { + i++; + continue; + } + + if (!curr->is_dir) { + print_line("ls: can not navigate into a file"); + fs_close(curr); + return; + } + + prev = curr; + curr = fs_open_by(curr, path_components[i]); + if (prev != root) { + fs_close(prev); + } + + if (!curr) { + print_line("ls: invalid path"); + return; + } + i++; + } + + if (!curr->is_dir) { + print_line("ls: can not list file"); + fs_close(curr); + return; + } + + uint8_t j = 0; + fs_node_t *item = fs_open_at(curr, j++); + while (item) { + print_line(item->name); + fs_close(item); + item = fs_open_at(curr, j++); + } + if (curr != root) { + fs_close(curr); + } +} + +static void cat(uint8_t argc, char **argv) { + if (argc != 2) { + print_line("cat: requires a single path"); + return; + } + + char *path_components[16]; + uint64_t path_length = string_split(argv[1], '/', 16, path_components); + if (!string_empty(path_components[0])) { + print_line("cat: relative paths not supported"); + return; + } + + fs_node_t *prev = NUL; + fs_node_t *curr = root; + uint8_t i = 1; + while (i < path_length) { + if (string_empty(path_components[i])) { + i++; + continue; + } + + if (!curr->is_dir) { + print_line("cat: can not navigate into a file"); + fs_close(curr); + return; + } + + prev = curr; + curr = fs_open_by(curr, path_components[i]); + if (prev != root) { + fs_close(prev); + } + + if (!curr) { + print_line("cat: invalid path"); + return; + } + i++; + } + + if (curr->is_dir) { + print_line("cat: can not print a directory"); + if (curr != root) { + fs_close(curr); + } + return; + } + + char *content = memory_allocate(curr->size + 1); + fs_read(curr, 0, curr->size, content); + content[curr->size] = '\0'; + + char **lines = memory_allocate(curr->size * sizeof(char *)); + uint64_t lines_count = string_split(content, '\n', curr->size, lines); + + for (uint64_t i = 0; i < lines_count; i++) { + print_line(lines[i]); + } + + memory_free(lines); + memory_free(content); + fs_close(curr); +} + +static void on_enter_pressed() { + prompt[prompt_length] = '\0'; + + char *argv[16]; + uint8_t argc = (uint8_t)string_split(prompt, ' ', 16, argv); + + advance_row(1); + + if (string_equal(argv[0], "help")) { + help(argc, argv); + } else if (string_equal(argv[0], "ls")) { + ls(argc, argv); + } else if (string_equal(argv[0], "cat")) { + cat(argc, argv); + } else { + print_line("Unknown command"); + } + prompt_length = prompt_offset = 0; + render_prompt(); +} + +static void on_cancel_pressed() { + advance_row(1); + prompt_length = prompt_offset = 0; + render_prompt(); +} + +static void on_character_pressed(uint8_t code) { + if (keyboard_state && (keyboard_state & (KEYBOARD_STATE_LCTRL | KEYBOARD_STATE_RCTRL)) == keyboard_state) { + switch (code) { + case 0x2E: // C + on_cancel_pressed(); + } + return; + } + + if (keyboard_state & ~(KEYBOARD_STATE_LSHIFT | KEYBOARD_STATE_RSHIFT) || prompt_length >= PROMPT_LENGTH) { + return; + } + + if (prompt_offset == prompt_length) { + prompt[prompt_offset + 1] = '\0'; + } else { + memory_copy(prompt, prompt_length + 1, prompt_swap); + memory_copy(prompt_swap + prompt_offset, prompt_length - prompt_offset, prompt + prompt_offset + 1); + } + prompt[prompt_offset] = (keyboard_state & (KEYBOARD_STATE_LSHIFT | KEYBOARD_STATE_RSHIFT) ? scancode_shifted : scancode_normal)[code]; + prompt_offset++; + prompt_length++; + render_prompt(); +} + +static void on_backspace_pressed() { + if (keyboard_state || !prompt_offset) { + return; + } + + if (prompt_offset == prompt_length) { + prompt[prompt_offset - 1] = '\0'; + } else { + memory_copy(prompt, prompt_length + 1, prompt_swap); + memory_copy(prompt_swap + prompt_offset, prompt_length - prompt_offset, prompt + prompt_offset - 1); + } + prompt_offset--; + prompt_length--; + render_prompt(); +} + +static void on_delete_pressed() { + if (keyboard_state || prompt_offset == prompt_length) { + return; + } + + prompt_offset++; + on_backspace_pressed(); +} + +static void terminal_on_key(uint8_t scancode) { + const uint8_t pressed = !(scancode & 0x80); + const uint8_t code = scancode & ~0x80; + + if (!(keyboard_state & KEYBOARD_STATE_SEQ)) { + switch (code) { + case 0x60: + keyboard_state = keyboard_state | KEYBOARD_STATE_SEQ; + break; + case 0x2A: + keyboard_state = pressed ? keyboard_state | KEYBOARD_STATE_LSHIFT : keyboard_state & ~KEYBOARD_STATE_LSHIFT; + break; + case 0x36: + keyboard_state = pressed ? keyboard_state | KEYBOARD_STATE_RSHIFT : keyboard_state & ~KEYBOARD_STATE_RSHIFT; + break; + case 0x38: + keyboard_state = pressed ? keyboard_state | KEYBOARD_STATE_LALT : keyboard_state & ~KEYBOARD_STATE_LALT; + break; + case 0x1D: + keyboard_state = pressed ? keyboard_state | KEYBOARD_STATE_LCTRL : keyboard_state & ~KEYBOARD_STATE_LCTRL; + break; + case 0x0E: + pressed ? on_backspace_pressed() : 0; + break; + case 0x1C: + pressed ? on_enter_pressed() : 0; + break; + default: + pressed &&scancode_normal[code] ? on_character_pressed(code) : 0; + break; + } + } else { + keyboard_state = keyboard_state & ~KEYBOARD_STATE_SEQ; + switch (code) { + case 0x38: + keyboard_state = pressed ? keyboard_state | KEYBOARD_STATE_RALT : keyboard_state & ~KEYBOARD_STATE_RALT; + break; + case 0x1D: + keyboard_state = pressed ? keyboard_state | KEYBOARD_STATE_RCTRL : keyboard_state & ~KEYBOARD_STATE_RCTRL; + break; + case 0x47: + pressed ? on_home_pressed() : 0; + break; + case 0x4B: + pressed ? on_left_pressed() : 0; + break; + case 0x4D: + pressed ? on_right_pressed() : 0; + break; + case 0x4F: + pressed ? on_end_pressed() : 0; + break; + case 0x53: + pressed ? on_delete_pressed() : 0; + break; + } + } +} + +void terminal_init() { + root = fs_mount(); + + keyboard_set_handler(terminal_on_key); + vga_clear(0, VGA_HEIGHT); + char *greeting = string_format("Welcome to FreywarOS v%s!", VERSION); + vga_set_string(0, 0, greeting, 0x0F); + memory_free(greeting); + prompt_row += 2; + render_prompt(); +} diff --git a/src/terminal.h b/src/terminal.h new file mode 100644 index 0000000..64c6c41 --- /dev/null +++ b/src/terminal.h @@ -0,0 +1,3 @@ +#pragma once + +void terminal_init(); diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..b31c4c6 --- /dev/null +++ b/src/util.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#define NUL 0 // TODO Fix VSCode thinking `NULL` conflicts with some other definition. + +static inline uint8_t inb(uint16_t port) { + uint8_t value; + __asm__ volatile("inb %1, %0" : "=a"(value) : "Nd"(port)); + return value; +} + +static inline void outb(uint16_t port, uint8_t value) { + __asm__ volatile("outb %0, %1" : : "a"(value), "Nd"(port)); +} + +static inline uint16_t inw(uint16_t port) { + uint16_t value; + __asm__ volatile("inw %1, %0" : "=a"(value) : "Nd"(port)); + return value; +} + +static inline void outw(uint16_t port, uint16_t value) { + __asm__ volatile("outw %0, %1" : : "a"(value), "Nd"(port)); +} + +static inline void insw(uint16_t port, void *to, uint32_t count) { + __asm__ volatile("rep insw" : "=D"(to), "=c"(count) : "d"(port), "D"(to), "c"(count) : "memory"); +} + +static inline void outsw(uint16_t port, const void *buffer, uint32_t count) { + __asm__ volatile("rep outsw" : "=S"(buffer), "=c"(count) : "d"(port), "S"(buffer), "c"(count) : "memory"); +} + +static inline void io_wait() { + outb(0x80, 0x00); +} diff --git a/src/vga.c b/src/vga.c new file mode 100644 index 0000000..8e6d641 --- /dev/null +++ b/src/vga.c @@ -0,0 +1,48 @@ +#include "src/vga.h" +#include "src/memory.h" +#include "src/panic.h" +#include "src/util.h" + +static uint16_t *vga = (uint16_t *)0x000B8000; + +void vga_set_cursor(uint8_t row, uint8_t col) { + ASSERT(row < VGA_HEIGHT && col < VGA_WIDTH, "vga_set_cursor: invalid coordinates") + uint16_t pos = row * VGA_WIDTH + col; + outb(0x3D4, 0x0F); + outb(0x3D5, pos & 0xFF); + outb(0x3D4, 0x0E); + outb(0x3D5, (pos >> 8) & 0xFF); +} + +void vga_set_char(uint8_t row, uint8_t col, char c, uint8_t color) { + ASSERT(row < VGA_HEIGHT && col < VGA_WIDTH, "vga_set_char: invalid coordinates"); + + vga[row * VGA_WIDTH + col] = (uint16_t)(((uint16_t)color << 8) | (uint16_t)c); +} + +void vga_set_string(uint8_t row, uint8_t col, const char *str, uint8_t color) { + ASSERT(row < VGA_HEIGHT && col < VGA_WIDTH, "vga_set_string: invalid coordinates") + while (*str) { + vga_set_char(row, col++, *str++, color); + if (col >= VGA_WIDTH) { + col = 0; + row++; + ASSERT(row < VGA_HEIGHT && col < VGA_WIDTH, "vga_set_string: invalid coordinates") + } + } +} + +void vga_copy(uint8_t src_row, uint8_t rows, uint8_t dst_row) { + ASSERT(src_row < VGA_HEIGHT && dst_row < VGA_HEIGHT && src_row + rows <= VGA_HEIGHT && dst_row + rows <= VGA_HEIGHT, + "vga_copy: invalid copy dimensions"); + memory_move(vga + src_row * VGA_WIDTH, rows * VGA_WIDTH * 2, vga + dst_row * VGA_WIDTH); +} + +void vga_clear(uint8_t row, uint8_t rows) { + ASSERT(row < VGA_HEIGHT && rows <= VGA_HEIGHT && row + rows <= VGA_HEIGHT, "vga_clear: invalid clear dimensions"); + + uint16_t *dst = vga + row * VGA_WIDTH; + uint16_t fill = 0x0F20; + uint32_t count = VGA_WIDTH * rows; + __asm__ volatile("rep stosw" : "=D"(dst), "=c"(count) : "D"(dst), "a"(fill), "c"(count) : "memory"); +} diff --git a/src/vga.h b/src/vga.h new file mode 100644 index 0000000..1063313 --- /dev/null +++ b/src/vga.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +#define VGA_WIDTH 80 +#define VGA_HEIGHT 25 + +void vga_set_cursor(uint8_t row, uint8_t col); + +void vga_set_char(uint8_t row, uint8_t col, char c, uint8_t color); + +void vga_set_string(uint8_t row, uint8_t col, const char *str, uint8_t color); + +void vga_copy(uint8_t src_row, uint8_t rows, uint8_t dst_row); + +void vga_clear(uint8_t row, uint8_t rows); diff --git a/vm.sh b/vm.sh new file mode 100755 index 0000000..b678290 --- /dev/null +++ b/vm.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail + +debug_flags=() +if [[ ${1:-} == '--debug' ]] || [[ ${1:-} == '-d' ]]; then + debug_flags+=(-s -S) +fi + +qemu-system-x86_64 \ + "${debug_flags[@]}" \ + -monitor stdio \ + -drive file="build/os.img",format=raw,if=ide \ + -no-reboot \ + -d int