Initial commit

master
Freywar Ulvnaudgari 2 months ago
commit fecf3b2e42
  1. 8
      .clang-format
  2. 2
      .clangd
  3. 4
      .gdbinit
  4. 2
      .gitignore
  5. 17
      build.sh
  6. 84
      meson.build
  7. 70
      src/ata.c
  8. 7
      src/ata.h
  9. 337
      src/bootloader.asm
  10. 259
      src/fat16.c
  11. 17
      src/fat16.h
  12. 26
      src/fs.c
  13. 25
      src/fs.h
  14. 40
      src/idt.c
  15. 15
      src/idt.h
  16. 48
      src/kernel.c
  17. 3
      src/kernel_entry.asm
  18. 22
      src/keyboard.c
  19. 9
      src/keyboard.h
  20. 17
      src/linker.ld
  21. 38
      src/mbr.asm
  22. 107
      src/memory.c
  23. 30
      src/memory.h
  24. 8
      src/panic.c
  25. 8
      src/panic.h
  26. 35
      src/pic.c
  27. 3
      src/pic.h
  28. 230
      src/string.c
  29. 23
      src/string.h
  30. 397
      src/terminal.c
  31. 3
      src/terminal.h
  32. 37
      src/util.h
  33. 48
      src/vga.c
  34. 16
      src/vga.h
  35. 14
      vm.sh

@ -0,0 +1,8 @@
Language: Cpp
BasedOnStyle: LLVM
BreakBeforeBraces: Attach
IndentWidth: 2
TabWidth: 2
UseTab: Never
ColumnLimit: 140
AllowShortFunctionsOnASingleLine: None

@ -0,0 +1,2 @@
CompileFlags:
CompilationDatabase: build

@ -0,0 +1,4 @@
target remote localhost:1234
set architecture i386:x86-64
set disassembly-flavor intel
display/i ($cs * 16 + $rip)

2
.gitignore vendored

@ -0,0 +1,2 @@
.vscode
build

@ -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

@ -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,
)

@ -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");
}

@ -0,0 +1,7 @@
#pragma once
#include <stdint.h>
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);

@ -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

@ -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 <stdint.h>
#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, &sector);
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");
}

@ -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);

@ -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);
}

@ -0,0 +1,25 @@
#pragma once
#include <stdint.h>
#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);

@ -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;
}

@ -0,0 +1,15 @@
#pragma once
#include <stdint.h>
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);

@ -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)
;
}

@ -0,0 +1,3 @@
bits 64
extern kernel_main
jmp kernel_main

@ -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;
}

@ -0,0 +1,9 @@
#pragma once
#include <stdint.h>
void keyboard_init();
typedef void (*keyboard_handler_t)(uint8_t scancode);
void keyboard_set_handler(keyboard_handler_t handler);

@ -0,0 +1,17 @@
ENTRY(kernel_main)
SECTIONS {
. = 0x0000000000020000;
.text : {
*(.text)
}
.data : {
*(.data)
}
.bss : {
*(.bss)
}
}

@ -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

@ -0,0 +1,107 @@
#include "src/memory.h"
#include "src/panic.h"
#include "src/util.h"
#include <stdint.h>
#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.
}

@ -0,0 +1,30 @@
#pragma once
#include "src/panic.h"
#include <stdint.h>
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");
}

@ -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)
;
}

@ -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);

@ -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();
}

@ -0,0 +1,3 @@
#pragma once
void pic_init();

@ -0,0 +1,230 @@
#include "src/string.h"
#include "src/memory.h"
#include <stdarg.h>
#include <stdint.h>
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;
}

@ -0,0 +1,23 @@
#pragma once
#include <stdint.h>
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, ...);

@ -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 <stdint.h>
#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();
}

@ -0,0 +1,3 @@
#pragma once
void terminal_init();

@ -0,0 +1,37 @@
#pragma once
#include <stdint.h>
#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);
}

@ -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");
}

@ -0,0 +1,16 @@
#pragma once
#include <stdint.h>
#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);

14
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
Loading…
Cancel
Save