commit
fecf3b2e42
@ -0,0 +1,8 @@ |
|||||||
|
Language: Cpp |
||||||
|
BasedOnStyle: LLVM |
||||||
|
BreakBeforeBraces: Attach |
||||||
|
IndentWidth: 2 |
||||||
|
TabWidth: 2 |
||||||
|
UseTab: Never |
||||||
|
ColumnLimit: 140 |
||||||
|
AllowShortFunctionsOnASingleLine: None |
||||||
@ -0,0 +1,4 @@ |
|||||||
|
target remote localhost:1234 |
||||||
|
set architecture i386:x86-64 |
||||||
|
set disassembly-flavor intel |
||||||
|
display/i ($cs * 16 + $rip) |
||||||
@ -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, §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"); |
||||||
|
} |
||||||
@ -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,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); |
||||||
@ -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…
Reference in new issue