35 Commits

Author SHA1 Message Date
9df33b49d8 flanterm PAGE FAULT, tries to access NULL or NULL+small offset 2026-03-02 11:32:24 +01:00
1f055ab31c Flanterm integration? but page fault in flanterm_fb_double_buffer_flush 2026-02-22 18:27:57 +01:00
70f19ab299 symbols build files added to gitignore 2026-02-21 19:36:44 +01:00
9470dedb61 Stack trace with double linking to get symbol names 2026-02-21 19:28:17 +01:00
4cf4fb0dda Task switching fix? but doesnt exit process gracefully 2026-02-20 16:01:34 +01:00
ac7216d84a Setup kernel stack; but process is failing 2026-02-17 23:01:32 +01:00
458ba375f3 better panic 2026-02-07 02:18:15 +01:00
b920c87bab Merge pull request 'process' (#9) from process into main
Reviewed-on: #9
2026-02-06 21:46:07 +01:00
4fbd9b3987 minor fix 2026-02-06 21:44:51 +01:00
8aad1235c3 A bit of cleaning 2026-02-06 14:39:19 +01:00
38710653be Config header file + comment header 2026-02-06 13:59:46 +01:00
7f997f6611 alloc_stack ok (HHDM mapped from mmap) 2026-02-05 21:18:21 +01:00
7bb542d901 bump-allocated PID but kheap needs fix to kmalloc more than PAGE_SIZE 2026-02-02 11:05:27 +01:00
4a90de9521 10ms Round Robin scheduler (blank processes) 2026-02-01 11:25:43 +01:00
c46157fad0 Process linked list 2026-01-31 14:13:48 +01:00
6e633b44b7 Merge pull request 'term_fix' (#8) from term_fix into main
Reviewed-on: #8
2026-01-25 09:53:45 +01:00
b8a155fada Who cares 2026-01-25 09:51:28 +01:00
091f94f89e Broken term scrolling 2026-01-10 14:43:51 +01:00
b469952d91 scroll kinda works but keyboard is random 2026-01-10 11:32:27 +01:00
9cbecc1689 GP Fault handler 2026-01-10 11:04:08 +01:00
12ab12f1b2 serial Kernel panic 2026-01-10 09:45:20 +01:00
0f72987bc1 use boot_ctx 2026-01-04 11:18:20 +01:00
d9dfd4c749 version splash 2026-01-04 11:00:30 +01:00
be1be41a64 Merge pull request 'memory' (#7) from memory into main
Reviewed-on: #7
2026-01-04 09:27:59 +01:00
923758a4ea Remove useless code/comments 2026-01-04 09:24:25 +01:00
e18b73c8a0 Small kernel heap for VMM internals, kmalloc/kfree 2026-01-03 13:48:10 +01:00
c065df6ff3 Paging: mapped kernel, fb, early-mem, HHDM 2026-01-02 13:40:44 +01:00
bb5fb9db33 Cleaner include paths + some paging definitions 2026-01-02 11:24:24 +01:00
075058a958 PMM: init with freelist 2025-12-31 17:42:26 +01:00
05a862e97a PMM: init (find biggest usable region) 2025-12-31 12:02:41 +01:00
8f5e2eae3e First steps: getting memory map from Limine request and looking at it 2025-12-30 21:33:38 +01:00
cf4915d9f4 Update README.md 2025-12-30 18:13:53 +01:00
834891fd2a DEBUG fix 2025-12-28 12:32:29 +01:00
3853a1ace3 Efficient DEBUG logging system with __FILE__ and fctprintf 2025-12-28 12:15:32 +01:00
ead0ed6ae1 Folder restructuration 2025-12-28 11:39:39 +01:00
56 changed files with 5941 additions and 316 deletions

3
.gitignore vendored
View File

@@ -7,3 +7,6 @@ iso_root
*.gch
*/*.gch
*/*/*.gch
.gdb_history
symbols.map
symbols.S

24
DOOM.txt Normal file
View File

@@ -0,0 +1,24 @@
up to doom:
- Return from pedicel_main() normally (to idle)
** Checkpoint: ring0 process working
- VFS layer (open/read/write/...) with USTar filesystem (for initrd)
** Checkpoint: files not linked to but accessible by the kernel
- Ring3 memory mappings
- Ring3 privilege switch
** Checkpoint: welcome to userland
- Syscall interface
- Implement syscalls needed for doom
** Checkpoint: can run simple programs, ring 3, loaded from filesystem
- Properly handle the keyboard interrupt (keyboard buffer)
- Port DOOMgeneric (few functions with Framebuffer/ticks/etc.)
** Achievement: It runs doom!

View File

@@ -1,8 +1,17 @@
SOURCES = src/io/term/flanterm_backends/fb.c src/io/term/flanterm.c src/debug/panic.c src/debug/stacktrace.c src/boot/boot.c src/sched/scheduler.c src/sched/process.c src/mem/heap/kheap.c src/mem/paging/vmm.c src/mem/paging/paging.c src/mem/paging/pmm.c src/string/string.c src/io/kbd/ps2.c src/io/serial/serial.c src/io/term/printf.c src/io/term/term.c src/idt/idt.c src/mem/gdt/gdt.c src/mem/misc/utils.c src/time/timer.c src/kmain.c
PROBLEMATIC_FLAGS=-Wno-unused-parameter -Wno-unused-variable
build:
rm -f *.o
x86_64-elf-gcc -g -c -I src src/kbd/ps2.c src/time/timer.c src/idt/idt.c src/mem/utils.c src/mem/gdt.c src/io/serial.c src/io/term.c src/io/printf.c src/kmain.c -Wall -Wextra -std=gnu99 -nostdlib -ffreestanding -fno-stack-protector -fno-stack-check -fno-PIC -ffunction-sections -fdata-sections -mcmodel=kernel
x86_64-elf-gcc -g -c -Isrc $(SOURCES) $(PROBLEMATIC_FLAGS) -Wall -Wextra -std=gnu99 -nostdlib -ffreestanding -fno-stack-protector -fno-omit-frame-pointer -fno-stack-check -fno-PIC -ffunction-sections -fdata-sections -mcmodel=kernel
objcopy -O elf64-x86-64 -B i386 -I binary zap-light16.psf zap-light16.o
nasm -f elf64 src/idt/idt.S -o idt_stub.o
nasm -f elf64 src/entry.S -o entry.o
x86_64-elf-ld -o pepperk -T linker.ld *.o
nm -n pepperk | awk '$$2 ~ /[TtDdBbRr]/ {print $$1, $$3}' > symbols.map
python3 symbols.py
nasm -f elf64 symbols.S -o symbols.o
x86_64-elf-ld -o pepperk -T linker.ld *.o
limine/limine:
@@ -28,11 +37,15 @@ build-iso: limine/limine build
./limine/limine bios-install pepper.iso
debug:
qemu-system-x86_64 -drive file=pepper.iso -s -S -d int -no-reboot &
/usr/bin/qemu-system-x86_64 -drive file=pepper.iso -s -S -d int -no-reboot -no-shutdown &
gdb pepperk --command=debug.gdb
debug2:
/usr/bin/qemu-system-x86_64 -drive file=pepper.iso -s -S -d int -no-reboot -no-shutdown &
pwndbg pepperk --command=debug.gdb
run: build-iso
qemu-system-x86_64 -cdrom pepper.iso -serial stdio
/usr/bin/qemu-system-x86_64 -cdrom pepper.iso -serial stdio
clean:
rm -rf *.o pepperk iso_root pepper.iso limine
rm -rf *.o symbols.map symbols.S pepperk iso_root pepper.iso limine

View File

@@ -2,11 +2,39 @@
## Trying the kernel
First install the dependencies: `sudo apt install xorriso make qemu-system`
First install the dependencies: `sudo apt install python3 xorriso make qemu-system`
Then, to compile the kernel and make an ISO image file: `make build-iso`
To run it with QEMU, `make run`
## TODO
The basics that I'm targeting are:
### Basic utility of what we call a "kernel"
- Fix terminal driver (backspace issues, scrolling) OR add Flanterm or equivalent
- Implement tasks, and task switching + context switching and spinlock acquire/release
- Load an executable
- Filesystem (TAR for read-only initfs, then maybe read-write using FAT12/16/32 or easier fs) w/ VFS layer
- Getting to userspace (syscalls)
- Porting musl libc or equivalent
### Scalability/maintenance/expansion features
- Documentation
- SOME error handling in functions
- Unit tests
- Good error codes (like Linux kernel: ENOMEM, ENOENT, ...)
- Make the panic function work within itself without dependencies + error message (and still get cpu context?)
### Optional features
In the future, maybe?
- SMP support (Limine provides functionality to make this easier)
- Parsing the ACPI tables and using them for something
- Replacing the PIT timer with APIC
## Thanks
PepperOS wouldn't be possible without the following freely-licensed software:

View File

@@ -1,3 +1,3 @@
target remote localhost:1234
set disassembly-flavor intel
display/8i $rip
display/4i $rip

View File

@@ -1,6 +1,6 @@
OUTPUT_FORMAT(elf64-x86-64)
ENTRY(kmain)
ENTRY(_start)
PHDRS
{

41
src/boot/boot.c Normal file
View File

@@ -0,0 +1,41 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Limine requests for boot
* @license GPL-3.0-only
*/
#include <limine.h>
// Framebuffer request
__attribute__((used, section(".limine_requests")))
volatile struct limine_framebuffer_request framebuffer_request = {
.id = LIMINE_FRAMEBUFFER_REQUEST,
.revision = 0
};
// Memory map request
__attribute__((used, section(".limine_requests")))
volatile struct limine_memmap_request memmap_request = {
.id = LIMINE_MEMMAP_REQUEST,
.revision = 0
};
// Higher Half Direct Map
__attribute__((used, section(".limine_requests")))
volatile struct limine_hhdm_request hhdm_request = {
.id = LIMINE_HHDM_REQUEST,
.revision = 0
};
// Executable Address/Kernel Address (find base phys/virt address of kernel)
__attribute__((used, section(".limine_requests")))
volatile struct limine_kernel_address_request kerneladdr_request = {
.id = LIMINE_KERNEL_ADDRESS_REQUEST,
.revision = 0
};
__attribute__((used, section(".limine_requests_start")))
volatile LIMINE_REQUESTS_START_MARKER;
__attribute__((used, section(".limine_requests_end")))
volatile LIMINE_REQUESTS_END_MARKER;

38
src/config.h Normal file
View File

@@ -0,0 +1,38 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief PepperOS configuration file
* @license GPL-3.0-only
*/
#ifndef CONFIG_H
#define CONFIG_H
/* version */
#define PEPPEROS_VERSION_MAJOR "0"
#define PEPPEROS_VERSION_MINOR "0"
#define PEPPEROS_VERSION_PATCH "58"
#define PEPPEROS_SPLASH "pepperOS version "PEPPEROS_VERSION_MAJOR"."PEPPEROS_VERSION_MINOR"."PEPPEROS_VERSION_PATCH"\n"
/* process */
#define PROCESS_NAME_MAX 64
#define PROCESS_STACK_SIZE 0x10000 // 64kb
#define PROCESS_BASE 0x400000
#define PROCESS_STACK_BASE 0x1000000
/* sched */
// 1 tick = 1 ms => quantum = 10ms
#define SCHEDULER_QUANTUM 10
/* kernel */
#define KERNEL_BASE 0xFFFFFFFF80000000ULL
// 2 MB should be enough (as of now, the whole kernel ELF is around 75kb)
#define KERNEL_SIZE 0x200000
#define KERNEL_STACK_SIZE 65536
/* heap */
#define KHEAP_SIZE (16*1024*1024)
/* term */
#define TERM_HISTORY_MAX_LINES 256
#endif

25
src/debug/panic.c Normal file
View File

@@ -0,0 +1,25 @@
#include <stddef.h>
#include "idt/idt.h"
#include "io/serial/serial.h"
#include "kernel.h"
void panic(struct cpu_status_t* ctx, const char* str)
{
CLEAR_INTERRUPTS;
if (ctx == NULL)
{
DEBUG("\x1b[38;5;231m\x1b[48;5;196mKernel panic!!!\x1b[0m Something went horribly wrong! (no cpu ctx)");
DIE_DEBUG(str);
skputc('\r');
skputc('\n');
DEBUG("\x1b[38;5;231m\x1b[48;5;196mend Kernel panic - halting...\x1b[0m");
hcf();
}
DEBUG("\x1b[38;5;231m\x1b[48;5;196mKernel panic!!!\x1b[0m at rip=%p\r\nSomething went horribly wrong! (%s) vect=0x%.2x errcode=0x%x\n\rrax=%p rbx=%p rcx=%p rdx=%p\n\rrsi=%p rdi=%p r8=%p r9=%p\n\rr10=%p r11=%p r12=%p r13=%p\n\rr14=%p r15=%p\n\n\rflags=%p\n\rHalting...",
ctx->iret_rip,
str,
ctx->vector_number, ctx->error_code, ctx->rax, ctx->rbx, ctx->rcx, ctx->rdx, ctx->rsi, ctx->rdi,
ctx->r8, ctx->r9, ctx->r10, ctx->r11, ctx->r12, ctx->r13, ctx->r14, ctx->r15, ctx->iret_flags);
debug_stack_trace(100);
hcf();
}

75
src/debug/stacktrace.c Normal file
View File

@@ -0,0 +1,75 @@
#include <stdint.h>
#include "kernel.h"
void debug_stack_trace(unsigned int max_frames)
{
DEBUG("*** begin stack trace ***");
// Thanks GCC :)
uintptr_t* rbp = (uintptr_t*)__builtin_frame_address(0);
for (unsigned int frame=0; frame<max_frames && rbp != NULL; frame++)
{
// Return address, 1 word above saved rbp
uintptr_t rip = rbp[1];
uintptr_t offset = 0;
const char* name = debug_find_symbol(rip, &offset);
DEBUG("[%u] <0x%p> (%s+0x%x)", frame, (void*)rip, name, offset);
uintptr_t* next_rbp = (uintptr_t*)rbp[0];
// invalid rbp or we're at the end
if (next_rbp <= rbp || next_rbp == NULL)
{
break;
}
rbp = next_rbp;
}
DEBUG("*** end stack trace ***");
}
typedef struct
{
uint64_t addr;
const char *name;
} __attribute__((packed)) kernel_symbol_t;
__attribute__((weak)) extern kernel_symbol_t symbol_table[];
__attribute__((weak)) extern uint64_t symbol_count;
// binary search
const char* debug_find_symbol(uintptr_t rip, uintptr_t* offset)
{
if (!symbol_table || symbol_count == 0)
{
if (offset) *offset = 0;
return "???";
}
int low = 0, high = (int)symbol_count - 1;
int best = -1;
while (low <= high)
{
int mid = (low + high) / 2;
if (symbol_table[mid].addr <= rip)
{
best = mid;
low = mid + 1;
} else {
high = mid - 1;
}
}
if (best != -1)
{
if (offset)
{
*offset = rip - symbol_table[best].addr;
}
return symbol_table[best].name;
}
if (offset) *offset = 0;
return "unknown";
}

23
src/entry.S Normal file
View File

@@ -0,0 +1,23 @@
bits 64
global _start
extern kmain
extern kernel_stack
KERNEL_STACK_SIZE equ 65536
section .text
_start:
cli
; load kernel stack
lea rsp, [kernel_stack+KERNEL_STACK_SIZE]
; rbp=0 so last frame in stack trace
xor rbp, rbp
; 16 byte align
and rsp, -16
call kmain

View File

@@ -1,4 +1,8 @@
; Assembly stub for the IDT
;
; @author xamidev <xamidev@riseup.net>
; @brief Stub for Interrupt Descriptor Table handlers
; @license GPL-3.0-only
;
bits 64

View File

@@ -1,8 +1,19 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Interrupt Descriptor Table setup and dispatching
* @license GPL-3.0-only
*/
#include "idt.h"
#include <stdint.h>
#include <stddef.h>
#include "../io/serial.h"
#include "../kbd/ps2.h"
#include "io/serial/serial.h"
#include "io/kbd/ps2.h"
#include <kernel.h>
#include <stdbool.h>
#include "sched/scheduler.h"
#include "config.h"
#include "sched/process.h"
struct interrupt_descriptor idt[256];
struct idtr idt_reg;
@@ -49,93 +60,163 @@ void idt_init()
idt_set_entry(i, vector_0_handler + (i*16), 0);
}
idt_load(&idt);
serial_kputs("kernel: idt: Initialized IDT!\n");
DEBUG("IDT initialized");
}
static inline uint64_t read_cr2(void)
{
uint64_t val;
asm volatile ("mov %%cr2, %0" : "=r"(val));
return val;
}
static void page_fault_handler(struct cpu_status_t* ctx)
{
// It could be used to remap pages etc. to fix the fault, but right now what I'm more
// interested in is getting more info out of those numbers cause i'm lost each time i have
// to read all this mess
uint64_t cr2 = read_cr2();
DEBUG("\x1b[38;5;231mPage Fault at rip=0x%p, err=%u (%s%s%s%s%s%s%s%s) when accessing addr=0x%p\x1b[0m", ctx->iret_rip, ctx->error_code,
CHECK_BIT(ctx->error_code, 0) ? "PAGE_PROTECTION_VIOLATION " : "PAGE_NOT_PRESENT ",
CHECK_BIT(ctx->error_code, 1) ? "ON_WRITE " : "ON_READ ",
CHECK_BIT(ctx->error_code, 2) ? "IN_USER_MODE" : "IN_KERNEL_MODE",
CHECK_BIT(ctx->error_code, 3) ? " WAS_RESERVED" : "",
CHECK_BIT(ctx->error_code, 4) ? " ON_INSTRUCTION_FETCH" : "",
CHECK_BIT(ctx->error_code, 5) ? " PK_VIOLATION" : "",
CHECK_BIT(ctx->error_code, 6) ? " ON_SHADOWSTACK_ACCESS" : "",
CHECK_BIT(ctx->error_code, 7) ? " SGX_VIOLATION" : "",
cr2);
panic(ctx, "page fault");
}
static void gp_fault_handler(struct cpu_status_t* ctx)
{
DEBUG("\x1b[38;5;231mGeneral Protection Fault at rip=0x%p, err=%u (%s)\x1b[0m",
ctx->iret_rip,
ctx->error_code,
(ctx->error_code == 0) ? "NOT_SEGMENT_RELATED" : "SEGMENT_RELATED");
// Segment-related
if (ctx->error_code != 0)
{
bool is_external = CHECK_BIT(ctx->error_code, 0);
// is it IDT, GDT, LDT?
uint8_t table = ctx->error_code & 0x6; // 0b110 (isolate table)
uint16_t index = ctx->error_code & 0xFFF8; // 13*1 1111111111111 + 000 = 1111111111111000
char* table_names[4] = {"GDT", "IDT", "LDT", "IDT"};
DEBUG("\x1b[38;5;231m%s in %s index %u\x1b[0m",
is_external ? "EXTERNAL" : "INTERNAL",
table_names[table],
index);
}
panic(ctx, "gp fault");
}
struct cpu_status_t* interrupt_dispatch(struct cpu_status_t* context)
{
if (context == NULL)
{
panic(NULL, "Interrupt dispatch recieved NULL context!");
}
switch(context->vector_number)
{
case 0:
serial_kputs("kernel: idt: Divide Error!\n");
DEBUG("Divide Error!");
break;
case 1:
serial_kputs("kernel: idt: Debug Exception!\n");
DEBUG("Debug Exception!");
break;
case 2:
serial_kputs("kernel: idt: NMI Interrupt!\n");
DEBUG("NMI Interrupt!");
break;
case 3:
serial_kputs("kernel: idt: Breakpoint Interrupt!\n");
DEBUG("Breakpoint Interrupt!");
break;
case 4:
serial_kputs("kernel: idt: Overflow Trap!\n");
DEBUG("Overflow Trap!");
break;
case 5:
serial_kputs("kernel: idt: BOUND Range Exceeded!\n");
DEBUG("BOUND Range Exceeded!");
break;
case 6:
serial_kputs("kernel: idt: Invalid Opcode!\n");
DEBUG("Invalid Opcode!");
panic(context, "Invalid Opcode!");
break;
case 7:
serial_kputs("kernel: idt: Device Not Available!\n");
DEBUG("Device Not Available!");
break;
case 8:
serial_kputs("kernel: idt: Double Fault!\n");
DEBUG("Double Fault!");
break;
case 9:
serial_kputs("kernel: idt: Coprocessor Segment Overrun!\n");
DEBUG("Coprocessor Segment Overrun!");
break;
case 10:
serial_kputs("kernel: idt: Invalid TSS!\n");
DEBUG("Invalid TSS!");
break;
case 11:
serial_kputs("kernel: idt: Segment Not Present!\n");
DEBUG("Segment Not Present!");
break;
case 12:
serial_kputs("kernel: idt: Stack-Segment Fault!\n");
DEBUG("Stack-Segment Fault!");
break;
case 13:
serial_kputs("kernel: idt: General Protection Fault!\n");
gp_fault_handler(context);
break;
case 14:
serial_kputs("kernel: idt: Page Fault!\n");
// Better debugging for page faults...
page_fault_handler(context);
break;
case 15:
serial_kputs("kernel: idt: Intel Reserved Interrupt! (Achievement unlocked: How Did We Get Here?)\n");
DEBUG("Intel Reserved Interrupt! (Achievement unlocked: How Did We Get Here?)");
break;
case 16:
serial_kputs("kernel: idt: x87 Floating-Point Error!\n");
DEBUG("x87 Floating-Point Error!");
break;
case 17:
serial_kputs("kernel: idt: Alignment Check Fault!\n");
DEBUG("Alignment Check Fault!");
break;
case 18:
serial_kputs("kernel: idt: Machine Check!\n");
DEBUG("Machine Check!");
break;
case 19:
serial_kputs("kernel: idt: SIMD Floating-Point Exception!\n");
DEBUG("SIMD Floating-Point Exception!");
break;
case 20:
serial_kputs("kernel: idt: Virtualization Exception!\n");
DEBUG("Virtualization Exception!");
break;
case 21:
serial_kputs("kernel: idt: Control Protection Exception!\n");
DEBUG("Control Protection Exception!");
break;
case 32:
//serial_kputs("Tick!");
case 32: // Timer Interrupt
ticks++;
// Send an EOI so that we can continue having interrupts
outb(0x20, 0x20);
if (ticks % SCHEDULER_QUANTUM == 0)
{
return scheduler_schedule(context);
//struct cpu_status_t* current_ctx = scheduler_schedule(context);
//process_switch(current_ctx->iret_rsp, current_ctx->iret_rip);
//SET_INTERRUPTS;
}
break;
case 33:
DEBUG("Keyboard Interrupt");
keyboard_handler();
break;
default:
serial_kputs("kernel: idt: Unexpected interrupt\n");
DEBUG("Unexpected interrupt");
break;
}

View File

@@ -1,3 +1,9 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Interrupt Descriptor Table setup and dispatching
* @license GPL-3.0-only
*/
#ifndef IDT_H
#define IDT_H

View File

@@ -1,10 +1,14 @@
// PS/2 Keyboard support
/*
* @author xamidev <xamidev@riseup.net>
* @brief PS/2 Keyboard driver
* @license GPL-3.0-only
*/
#include "../io/serial.h"
#include "../io/printf.h"
#include "io/serial/serial.h"
#include "ps2.h"
#include <stdint.h>
#include "../io/term.h"
#include "io/term/term.h"
#include <kernel.h>
// The key status bitfield will be used to see if ALT, CONTROL, or SHIFT is pressed
uint8_t key_status = 0b00000000;
@@ -201,12 +205,11 @@ void keyboard_handler()
if (c)
{
putchar(c);
// Should probably have a keyboard buffer here... instead of this
_putchar(c);
}
}
}
serial_kputs("key pressed!\n");
}
// End of Interrupt (to master PIC)
@@ -230,7 +233,14 @@ void keyboard_init(unsigned char layout)
break;
default:
serial_kputs("Unsupported layout.");
break;
skputs("Unsupported layout.");
return;
}
// Unmask IRQ1
uint8_t mask = inb(0x21);
mask &= ~(1 << 1);
outb(0x21, mask);
DEBUG("PS/2 Keyboard initialized");
}

View File

@@ -1,3 +1,9 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief PS/2 Keyboard driver
* @license GPL-3.0-only
*/
#ifndef PS2_H
#define PS2_H

View File

@@ -1,10 +0,0 @@
#ifndef SERIAL_H
#define SERIAL_H
void outb(int port, unsigned char data);
unsigned char inb(int port);
int serial_init();
void serial_kputs(const char* str);
#endif

View File

@@ -1,4 +1,10 @@
#include "../kernel.h"
/*
* @author xamidev <xamidev@riseup.net>
* @brief Debug serial driver
* @license GPL-3.0-only
*/
#include <kernel.h>
#include "serial.h"
void outb(int port, unsigned char data)
@@ -36,7 +42,7 @@ int serial_init()
// Set normal operation mode
outb(PORT + 4, 0x0F);
serial_kputs("\n\nkernel: serial: Serial initialization OK!\n");
DEBUG("serial initialized");
return 0;
}
@@ -45,18 +51,20 @@ static int is_transmit_empty()
return inb(PORT + 5) & 0x20;
}
void write_serial(char c)
// Serial kernel putchar
void skputc(char c)
{
while (!is_transmit_empty()); // wait for free spot
outb(PORT, c);
}
void serial_kputs(const char* str)
// Serial kernel putstring
void skputs(const char* str)
{
unsigned int i=0;
while (str[i])
{
write_serial(str[i]);
skputc(str[i]);
i++;
}
}

17
src/io/serial/serial.h Normal file
View File

@@ -0,0 +1,17 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Debug serial driver
* @license GPL-3.0-only
*/
#ifndef SERIAL_H
#define SERIAL_H
void outb(int port, unsigned char data);
unsigned char inb(int port);
int serial_init();
void skputs(const char* str);
void skputc(char c);
#endif

View File

@@ -1,143 +0,0 @@
// Terminal output
#include <limine.h>
#include <stddef.h>
#include "kernel.h"
#include "term.h"
extern struct limine_framebuffer* framebuffer;
// Importing the PSF object file
extern unsigned char _binary_zap_light16_psf_start[];
extern unsigned char _binary_zap_light16_psf_end[];
PSF1_Header* font = (PSF1_Header*)_binary_zap_light16_psf_start;
uint8_t* glyphs = _binary_zap_light16_psf_start + sizeof(PSF1_Header);
#define FONT_WIDTH 8
#define FONT_HEIGHT font->characterSize
// Character cursor
typedef struct
{
unsigned int x;
unsigned int y;
} Cursor;
Cursor cursor = {0};
unsigned char* fb;
int term_init()
{
// Get framebuffer address from Limine struct
if (framebuffer)
{
fb = framebuffer->address;
return 0;
}
return -ENOMEM;
}
// These are marked "static" because we don't wanna expose them all around
// AKA they should just be seen here (kind of like private functions in cpp)
static void putpixel(int x, int y, int color)
{
// Depth isn't part of limine_framebuffer attributes so it will be 4
unsigned pos = x*4 + y*framebuffer->pitch;
fb[pos] = color & 255; // blue channel
fb[pos+1] = (color >> 8) & 255; // green
fb[pos+2] = (color >> 16) & 255; // blue
}
static void draw_char(char c, int px, int py, int fg, int bg)
{
uint8_t* glyph = glyphs + ((unsigned char)c * FONT_HEIGHT);
for (size_t y=0; y<FONT_HEIGHT; y++)
{
uint8_t row = glyph[y];
for (size_t x=0; x<8; x++)
{
int color = (row & (0x80 >> x)) ? fg : bg;
putpixel(px+x, py+y, color);
}
}
}
static void erase_char(int px, int py)
{
for (size_t y=0; y<FONT_HEIGHT; y++)
{
for (size_t x=0; x<8; x++)
{
// Black
putpixel(px+x, py+y, 0);
}
}
}
void putchar(char c)
{
if (c == '\n')
{
cursor.x = 0;
cursor.y++;
return;
}
// TODO: Improperly handled.
// When we're on an empty line it should get to the upper line's last character
// NOT just the last position possible; we would need to track the last line's character amount for that
if (c == '\b')
{
if (cursor.x == 0 && cursor.y == 0)
{
// Top-left corner
return;
}
if (cursor.x == 0)
{
cursor.y--;
cursor.x = (framebuffer->width / FONT_WIDTH) -1; // here
}
else {
cursor.x--;
}
int px = cursor.x * FONT_WIDTH;
int py = cursor.y * FONT_HEIGHT;
erase_char(px, py);
return;
}
if ((cursor.x+1)*FONT_WIDTH >= framebuffer->width)
{
cursor.x = 0;
cursor.y++;
}
int px = cursor.x * FONT_WIDTH;
int py = cursor.y * FONT_HEIGHT;
draw_char(c, px, py, WHITE, BLACK);
cursor.x++;
}
// Overhead that could be avoided, right? (for printf)
void _putchar(char character)
{
putchar(character);
}
// Debug-printing
void kputs(const char* str)
{
unsigned int i=0;
while (str[i] != 0)
{
putchar(str[i]);
i++;
}
}

2129
src/io/term/flanterm.c Normal file

File diff suppressed because it is too large Load Diff

72
src/io/term/flanterm.h Normal file
View File

@@ -0,0 +1,72 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/* Copyright (C) 2022-2026 Mintsuki and contributors.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef FLANTERM_H
#define FLANTERM_H 1
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
#define FLANTERM_CB_DEC 10
#define FLANTERM_CB_BELL 20
#define FLANTERM_CB_PRIVATE_ID 30
#define FLANTERM_CB_STATUS_REPORT 40
#define FLANTERM_CB_POS_REPORT 50
#define FLANTERM_CB_KBD_LEDS 60
#define FLANTERM_CB_MODE 70
#define FLANTERM_CB_LINUX 80
#define FLANTERM_CB_OSC 90
#ifdef FLANTERM_IN_FLANTERM
#include "flanterm_private.h"
#else
struct flanterm_context;
#endif
void flanterm_write(struct flanterm_context *ctx, const char *buf, size_t count);
void flanterm_flush(struct flanterm_context *ctx);
void flanterm_full_refresh(struct flanterm_context *ctx);
void flanterm_deinit(struct flanterm_context *ctx, void (*_free)(void *ptr, size_t size));
void flanterm_get_dimensions(struct flanterm_context *ctx, size_t *cols, size_t *rows);
void flanterm_set_autoflush(struct flanterm_context *ctx, bool state);
void flanterm_set_callback(struct flanterm_context *ctx, void (*callback)(struct flanterm_context *, uint64_t, uint64_t, uint64_t, uint64_t));
#ifdef __cplusplus
}
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,79 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/* Copyright (C) 2022-2026 Mintsuki and contributors.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef FLANTERM_FB_H
#define FLANTERM_FB_H 1
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
#include "../flanterm.h"
#ifdef FLANTERM_IN_FLANTERM
#include "fb_private.h"
#endif
#define FLANTERM_FB_ROTATE_0 0
#define FLANTERM_FB_ROTATE_90 1
#define FLANTERM_FB_ROTATE_180 2
#define FLANTERM_FB_ROTATE_270 3
struct flanterm_context *flanterm_fb_init(
/* If _malloc and _free are nulled, use the bump allocated instance (1 use only). */
void *(*_malloc)(size_t size),
void (*_free)(void *ptr, size_t size),
uint32_t *framebuffer, size_t width, size_t height, size_t pitch,
uint8_t red_mask_size, uint8_t red_mask_shift,
uint8_t green_mask_size, uint8_t green_mask_shift,
uint8_t blue_mask_size, uint8_t blue_mask_shift,
uint32_t *canvas, /* If nulled, no canvas. */
uint32_t *ansi_colours, uint32_t *ansi_bright_colours, /* If nulled, default. */
uint32_t *default_bg, uint32_t *default_fg, /* If nulled, default. */
uint32_t *default_bg_bright, uint32_t *default_fg_bright, /* If nulled, default. */
/* If font is null, use default font and font_width and font_height ignored. */
void *font, size_t font_width, size_t font_height, size_t font_spacing,
/* If scale_x and scale_y are 0, automatically scale font based on resolution. */
size_t font_scale_x, size_t font_scale_y,
size_t margin,
/* One of FLANTERM_FB_ROTATE_* values. */
int rotation
);
void flanterm_fb_set_flush_callback(struct flanterm_context *ctx, void (*flush_callback)(volatile void *address, size_t length));
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,127 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/* Copyright (C) 2022-2026 Mintsuki and contributors.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef FLANTERM_FB_PRIVATE_H
#define FLANTERM_FB_PRIVATE_H 1
#ifndef FLANTERM_IN_FLANTERM
#error "Do not use fb_private.h. Use interfaces defined in fb.h only."
#endif
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
#define FLANTERM_FB_FONT_GLYPHS 256
struct flanterm_fb_char {
uint32_t c;
uint32_t fg;
uint32_t bg;
};
struct flanterm_fb_queue_item {
size_t x, y;
struct flanterm_fb_char c;
};
struct flanterm_fb_context {
struct flanterm_context term;
void (*plot_char)(struct flanterm_context *ctx, struct flanterm_fb_char *c, size_t x, size_t y);
void (*flush_callback)(volatile void *address, size_t length);
size_t font_width;
size_t font_height;
size_t glyph_width;
size_t glyph_height;
size_t font_scale_x;
size_t font_scale_y;
size_t offset_x, offset_y;
volatile uint32_t *framebuffer;
size_t pitch;
size_t width;
size_t height;
size_t phys_height;
size_t bpp;
uint8_t red_mask_size, red_mask_shift;
uint8_t green_mask_size, green_mask_shift;
uint8_t blue_mask_size, blue_mask_shift;
int rotation;
size_t font_bits_size;
uint8_t *font_bits;
size_t font_bool_size;
bool *font_bool;
uint32_t ansi_colours[8];
uint32_t ansi_bright_colours[8];
uint32_t default_fg, default_bg;
uint32_t default_fg_bright, default_bg_bright;
size_t canvas_size;
uint32_t *canvas;
size_t grid_size;
size_t queue_size;
size_t map_size;
struct flanterm_fb_char *grid;
struct flanterm_fb_queue_item *queue;
size_t queue_i;
struct flanterm_fb_queue_item **map;
uint32_t text_fg;
uint32_t text_bg;
size_t cursor_x;
size_t cursor_y;
uint32_t saved_state_text_fg;
uint32_t saved_state_text_bg;
size_t saved_state_cursor_x;
size_t saved_state_cursor_y;
size_t old_cursor_x;
size_t old_cursor_y;
};
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,133 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/* Copyright (C) 2022-2026 Mintsuki and contributors.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef FLANTERM_PRIVATE_H
#define FLANTERM_PRIVATE_H 1
#ifndef FLANTERM_IN_FLANTERM
#error "Do not use flanterm_private.h. Use interfaces defined in flanterm.h only."
#endif
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
#define FLANTERM_MAX_ESC_VALUES 16
struct flanterm_context {
/* internal use */
size_t tab_size;
bool autoflush;
bool cursor_enabled;
bool scroll_enabled;
bool wrap_enabled;
bool origin_mode;
bool control_sequence;
bool escape;
bool osc;
bool osc_escape;
size_t osc_buf_i;
uint8_t osc_buf[256];
bool rrr;
bool discard_next;
bool bold;
bool bg_bold;
bool reverse_video;
bool dec_private;
bool insert_mode;
bool csi_unhandled;
uint64_t code_point;
size_t unicode_remaining;
uint8_t g_select;
uint8_t charsets[2];
size_t current_charset;
size_t escape_offset;
size_t esc_values_i;
size_t saved_cursor_x;
size_t saved_cursor_y;
size_t current_primary;
size_t current_bg;
size_t scroll_top_margin;
size_t scroll_bottom_margin;
uint32_t esc_values[FLANTERM_MAX_ESC_VALUES];
uint8_t last_printed_char;
bool last_was_graphic;
bool saved_state_bold;
bool saved_state_bg_bold;
bool saved_state_reverse_video;
bool saved_state_origin_mode;
bool saved_state_wrap_enabled;
size_t saved_state_current_charset;
uint8_t saved_state_charsets[2];
size_t saved_state_current_primary;
size_t saved_state_current_bg;
/* to be set by backend */
size_t rows, cols;
void (*raw_putchar)(struct flanterm_context *, uint8_t c);
void (*clear)(struct flanterm_context *, bool move);
void (*set_cursor_pos)(struct flanterm_context *, size_t x, size_t y);
void (*get_cursor_pos)(struct flanterm_context *, size_t *x, size_t *y);
void (*set_text_fg)(struct flanterm_context *, size_t fg);
void (*set_text_bg)(struct flanterm_context *, size_t bg);
void (*set_text_fg_bright)(struct flanterm_context *, size_t fg);
void (*set_text_bg_bright)(struct flanterm_context *, size_t bg);
void (*set_text_fg_rgb)(struct flanterm_context *, uint32_t fg);
void (*set_text_bg_rgb)(struct flanterm_context *, uint32_t bg);
void (*set_text_fg_default)(struct flanterm_context *);
void (*set_text_bg_default)(struct flanterm_context *);
void (*set_text_fg_default_bright)(struct flanterm_context *);
void (*set_text_bg_default_bright)(struct flanterm_context *);
void (*move_character)(struct flanterm_context *, size_t new_x, size_t new_y, size_t old_x, size_t old_y);
void (*scroll)(struct flanterm_context *);
void (*revscroll)(struct flanterm_context *);
void (*swap_palette)(struct flanterm_context *);
void (*save_state)(struct flanterm_context *);
void (*restore_state)(struct flanterm_context *);
void (*double_buffer_flush)(struct flanterm_context *);
void (*full_refresh)(struct flanterm_context *);
void (*deinit)(struct flanterm_context *, void (*)(void *, size_t));
/* to be set by client */
void (*callback)(struct flanterm_context *, uint64_t, uint64_t, uint64_t, uint64_t);
};
void flanterm_context_reinit(struct flanterm_context *ctx);
#ifdef __cplusplus
}
#endif
#endif

127
src/io/term/term.c Normal file
View File

@@ -0,0 +1,127 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Framebuffer-based terminal driver
* @license GPL-3.0-only
*/
// Terminal output
/*
There are a couple of bugs here and there but for now I don't care too much
because this shitty implementation will be replaced one day by Flanterm
(once memory management is okay: paging & kernel malloc)
*/
#include <limine.h>
#include <stddef.h>
#include <kernel.h>
#include "term.h"
#include "mem/misc/utils.h"
#include "config.h"
#include "flanterm.h"
extern struct boot_context boot_ctx;
// Importing the PSF object file
extern unsigned char _binary_zap_light16_psf_start[];
extern unsigned char _binary_zap_light16_psf_end[];
PSF1_Header* font = (PSF1_Header*)_binary_zap_light16_psf_start;
uint8_t* glyphs = _binary_zap_light16_psf_start + sizeof(PSF1_Header);
#define FONT_WIDTH 8
#define FONT_HEIGHT font->characterSize
extern struct flanterm_context* ft_ctx;
// Character cursor
typedef struct
{
size_t x;
size_t y;
} Cursor;
static Cursor cursor = {0, 0};
static uint8_t* fb;
static struct limine_framebuffer* framebuffer;
uint8_t lines_length[TERM_HISTORY_MAX_LINES];
static inline size_t term_max_cols(void)
{
return framebuffer->width / FONT_WIDTH;
}
static inline size_t term_max_lines(void)
{
return framebuffer->height / FONT_HEIGHT;
}
int term_init()
{
// Get framebuffer address from Limine struct
if (!boot_ctx.fb)
{
return -ENOMEM;
}
framebuffer = boot_ctx.fb;
fb = (uint8_t*)framebuffer->address;
memset(lines_length, 0, sizeof(lines_length));
DEBUG("terminal initialized, fb=0x%p (width=%u height=%u pitch=%u bpp=%u)", fb, framebuffer->width, framebuffer->height, framebuffer->pitch, framebuffer->bpp);
return 0;
}
// These are marked "static" because we don't wanna expose them all around
// AKA they should just be seen here (kind of like private functions in cpp)
static inline void putpixel(size_t x, size_t y, uint32_t color)
{
// Guard so we don't write past fb boundaries
if (x >= framebuffer->width || y >= framebuffer->height) return;
// Depth isn't part of limine_framebuffer attributes so it will be 4
size_t pos = x*4 + y*framebuffer->pitch;
fb[pos] = color & 255; // blue channel
fb[pos+1] = (color >> 8) & 255; // green
fb[pos+2] = (color >> 16) & 255; // blue
}
void term_scroll()
{
// Erase first text line
memset(fb, 255, FONT_HEIGHT*framebuffer->pitch);
// Move whole framebuffer up by one text line
memmove(fb, fb+(FONT_HEIGHT*framebuffer->pitch), (framebuffer->height-FONT_HEIGHT)*framebuffer->pitch);
// Clear last text line
size_t clear_start = (framebuffer->height - FONT_HEIGHT) * framebuffer->pitch;
memset(fb + clear_start, 255, FONT_HEIGHT * framebuffer->pitch);
// Shift line lengths by 1 (for backspace handling)
size_t max_lines = term_max_lines();
for (size_t i = 1; i < max_lines; i++)
{
lines_length[i - 1] = lines_length[i];
}
lines_length[max_lines - 1] = 0;
}
// Overhead that could be avoided, right? (for printf)
void _putchar(char character)
{
flanterm_write(ft_ctx, &character, 1);
}
// Debug-printing
void kputs(const char* str)
{
size_t i=0;
while (str[i] != 0)
{
_putchar(str[i]);
i++;
}
}

View File

@@ -1,9 +1,15 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Framebuffer-based terminal driver
* @license GPL-3.0-only
*/
#ifndef TERM_H
#define TERM_H
int term_init();
void kputs(const char* str);
void putchar(char c);
void _putchar(char character);
enum TermColors
{
@@ -20,4 +26,7 @@ typedef struct
uint8_t characterSize; // height
} PSF1_Header;
// debug
void term_scroll();
#endif

View File

@@ -1,3 +1,9 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Kernel global macros
* @license GPL-3.0-only
*/
#ifndef KERNEL_H
#define KERNEL_H
@@ -10,4 +16,39 @@ enum ErrorCodes
#define CLEAR_INTERRUPTS __asm__ volatile("cli")
#define SET_INTERRUPTS __asm__ volatile("sti")
#include "io/serial/serial.h"
#include "io/term/printf.h"
#include "idt/idt.h"
#define DEBUG(log, ...) fctprintf((void*)&skputc, 0, "debug: [%s]: " log "\r\n", __FILE__, ##__VA_ARGS__)
/* #define DEBUG(log, ...) \
printf("debug: [%s]: " log "\r\n", __FILE__, ##__VA_ARGS__); \
fctprintf((void*)&skputc, 0, "debug: [%s]: " log "\r\n", __FILE__, ##__VA_ARGS__)
*/
#define DIE_DEBUG(str) fctprintf((void*)&skputc, 0, str)
#define CHECK_BIT(var,pos) ((var) & (1<<(pos)))
// printf("debug: [%s]: " log "\n", __FILE__, ##__VA_ARGS__);
void panic(struct cpu_status_t* ctx, const char* str);
void hcf();
void idle();
void debug_stack_trace(unsigned int max_frames);
const char* debug_find_symbol(uintptr_t rip, uintptr_t* offset);
#define assert(check) do { if(!(check)) hcf(); } while(0)
struct boot_context
{
struct limine_framebuffer* fb;
struct limine_memmap_response* mmap;
struct limine_hhdm_response* hhdm;
struct limine_kernel_address_response* kaddr;
};
#endif

View File

@@ -1,68 +1,143 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief PepperOS kernel entry point
* @license GPL-3.0-only
*/
#include <stdbool.h>
#include <stddef.h>
#include <limine.h>
#include "io/term.h"
#include "io/printf.h"
#include "io/serial.h"
#include "mem/gdt.h"
#include "mem/utils.h"
#include "io/term/term.h"
#include "io/term/printf.h"
#include "io/serial/serial.h"
#include "mem/gdt/gdt.h"
#include "mem/misc/utils.h"
#include "idt/idt.h"
#include "kernel.h"
#include "time/timer.h"
#include "kbd/ps2.h"
#include "io/kbd/ps2.h"
#include "mem/paging/pmm.h"
#include "mem/paging/paging.h"
#include "mem/paging/vmm.h"
#include "mem/heap/kheap.h"
#include "sched/process.h"
#include "sched/scheduler.h"
#include "config.h"
#include "io/term/flanterm.h"
#include "io/term/flanterm_backends/fb.h"
// Limine version used
__attribute__((used, section(".limine_requests")))
static volatile LIMINE_BASE_REVISION(3);
volatile LIMINE_BASE_REVISION(3);
// Framebuffer request
__attribute__((used, section(".limine_requests")))
static volatile struct limine_framebuffer_request framebuffer_request = {
.id = LIMINE_FRAMEBUFFER_REQUEST,
.revision = 0
};
struct flanterm_context *ft_ctx;
__attribute__((used, section(".limine_requests_start")))
static volatile LIMINE_REQUESTS_START_MARKER;
__attribute__((used, section(".limine_requests_end")))
static volatile LIMINE_REQUESTS_END_MARKER;
struct limine_framebuffer* framebuffer;
// Panic
static void hcf()
// Halt and catch fire (makes machine stall)
void hcf()
{
for (;;)
{
asm("hlt");
CLEAR_INTERRUPTS; for (;;)asm("hlt");
}
// Doing nothing (can be interrupted)
void idle() {SET_INTERRUPTS; for(;;)asm("hlt");}
uint8_t kernel_stack[KERNEL_STACK_SIZE] __attribute__((aligned(16)));
struct boot_context boot_ctx;
extern volatile struct limine_framebuffer_request framebuffer_request;
extern volatile struct limine_memmap_request memmap_request;
extern volatile struct limine_hhdm_request hhdm_request;
extern volatile struct limine_kernel_address_request kerneladdr_request;
extern struct process_t* processes_list;
extern struct process_t* current_process;
void pedicel_main(void* arg)
{
//printf("Hello, world from a KERNEL PROCESS!");
}
void two_main(void* arg)
{
//printf("...process 2 speaking!!!");
}
void idle_main(void* arg)
{
for(;;)asm("hlt");
}
// This is our entry point
void kmain()
{
if (!LIMINE_BASE_REVISION_SUPPORTED) hcf();
if (framebuffer_request.response == NULL || framebuffer_request.response->framebuffer_count < 1) hcf();
// Get the first framebuffer from the response
framebuffer = framebuffer_request.response->framebuffers[0];
// Populate boot context
boot_ctx.fb = framebuffer_request.response ? framebuffer_request.response->framebuffers[0] : NULL;
boot_ctx.mmap = memmap_request.response ? memmap_request.response : NULL;
boot_ctx.hhdm = hhdm_request.response ? hhdm_request.response : NULL;
boot_ctx.kaddr = kerneladdr_request.response ? kerneladdr_request.response : NULL;
if (term_init()) hcf();
pmm_init(boot_ctx.mmap, boot_ctx.hhdm);
if (serial_init()) kputs("kernel: serial: error: Cannot init serial communication!");
// Remap kernel , HHDM and framebuffer
paging_init(boot_ctx.kaddr, boot_ctx.fb);
uint32_t bgColor = 0x252525;
ft_ctx = flanterm_fb_init(
NULL,
NULL,
boot_ctx.fb->address, boot_ctx.fb->width, boot_ctx.fb->height, boot_ctx.fb->pitch,
boot_ctx.fb->red_mask_size, boot_ctx.fb->red_mask_shift,
boot_ctx.fb->green_mask_size, boot_ctx.fb->green_mask_shift,
boot_ctx.fb->blue_mask_size, boot_ctx.fb->blue_mask_shift,
NULL,
NULL, NULL,
&bgColor, NULL, // &bgColor
NULL, NULL,
NULL, 0, 0, 1,
0, 0,
0,
0
);
serial_init();
memmap_display(boot_ctx.mmap);
hhdm_display(boot_ctx.hhdm);
DEBUG("kernel: phys_base=0x%p virt_base=0x%p", boot_ctx.kaddr->physical_base, boot_ctx.kaddr->virtual_base);
CLEAR_INTERRUPTS;
gdt_init();
idt_init();
timer_init();
kheap_init();
vmm_init();
struct process_t* idle_proc = process_create("idle", (void*)idle_main, 0);
struct process_t* pedicel = process_create("pedicel", (void*)pedicel_main, 0);
struct process_t* two = process_create("two", (void*)two_main, 0);
process_display_list(processes_list);
scheduler_init();
current_process = idle_proc;
current_process->status = RUNNING;
SET_INTERRUPTS;
keyboard_init(FR);
//term_init();
// Draw something
printf("%s, %s!", "Hello", "world");
//printf(PEPPEROS_SPLASH);
//printf("%d", 4/0);
hcf();
//printf("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur eu diam id sem tincidunt vestibulum. Etiam sed congue nisl, vitae aliquet orci. Donec magna turpis, semper sed ipsum eget, semper blandit dolor. Morbi faucibus posuere sapien. Vestibulum aliquet mi vel orci finibus, vestibulum rhoncus tellus sagittis. Vivamus a arcu suscipit sem iaculis volutpat vel nec est. Nulla malesuada, urna vel pretium pretium, enim tortor pulvinar velit, porttitor dictum lectus turpis id tortor. Quisque egestas ultricies lorem, egestas ultrices tellus elementum porta. Fusce consequat nisi in diam placerat fermentum. Suspendisse tempus turpis nec turpis condimentum fringilla. Maecenas nec orci pharetra, feugiat enim vel, viverra neque. Vivamus placerat purus in tincidunt ultricies. Pellentesque vel mi molestie, congue nibh nec, cursus nisl. Cras dapibus lectus mauris, sed interdum risus tristique non. ");
flanterm_write(ft_ctx, "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur eu diam id sem tincidunt vestibulum. Etiam sed congue nisl, vitae aliquet orci. Donec magna turpis, semper sed ipsum eget, semper blandit dolor. Morbi faucibus posuere sapien. Vestibulum aliquet mi vel orci finibus, vestibulum rhoncus tellus sagittis. Vivamus a arcu suscipit sem iaculis volutpat vel nec est. Nulla malesuada, urna vel pretium pretium, enim tortor pulvinar velit, porttitor dictum lectus turpis id tortor. Quisque egestas ultricies lorem, egestas ultrices tellus elementum porta. Fusce consequat nisi in diam placerat fermentum. Suspendisse tempus turpis nec turpis condimentum fringilla. Maecenas nec orci pharetra, feugiat enim vel, viverra neque. Vivamus placerat purus in tincidunt ultricies. Pellentesque vel mi molestie, congue nibh nec, cursus nisl. Cras dapibus lectus mauris, sed interdum risus tristique non.", sizeof("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur eu diam id sem tincidunt vestibulum. Etiam sed congue nisl, vitae aliquet orci. Donec magna turpis, semper sed ipsum eget, semper blandit dolor. Morbi faucibus posuere sapien. Vestibulum aliquet mi vel orci finibus, vestibulum rhoncus tellus sagittis. Vivamus a arcu suscipit sem iaculis volutpat vel nec est. Nulla malesuada, urna vel pretium pretium, enim tortor pulvinar velit, porttitor dictum lectus turpis id tortor. Quisque egestas ultricies lorem, egestas ultrices tellus elementum porta. Fusce consequat nisi in diam placerat fermentum. Suspendisse tempus turpis nec turpis condimentum fringilla. Maecenas nec orci pharetra, feugiat enim vel, viverra neque. Vivamus placerat purus in tincidunt ultricies. Pellentesque vel mi molestie, congue nibh nec, cursus nisl. Cras dapibus lectus mauris, sed interdum risus tristique non. "));
idle();
}

View File

@@ -1,6 +1,13 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Global Descriptor Table (for legacy reasons)
* @license GPL-3.0-only
*/
#include "gdt.h"
#include <stdint.h>
#include "../io/serial.h"
#include "io/serial/serial.h"
#include <kernel.h>
// Descriptors are 8-byte wide (64bits)
// So the selectors will be (in bytes): 0x0, 0x8, 0x10, 0x18, etc..
@@ -78,5 +85,5 @@ void gdt_init()
gdt_load();
gdt_flush();
serial_kputs("kernel: gdt: Initialized GDT!\n");
DEBUG("GDT initialized");
}

View File

@@ -1,3 +1,9 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Global Descriptor Table (for legacy reasons)
* @license GPL-3.0-only
*/
#ifndef GDT_H
#define GDT_H

147
src/mem/heap/kheap.c Normal file
View File

@@ -0,0 +1,147 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Kernel heap
* @license GPL-3.0-only
*/
#include "kheap.h"
#include "mem/paging/paging.h"
#include "mem/paging/pmm.h"
#include <stddef.h>
#include <kernel.h>
#include "sched/process.h"
#include "config.h"
extern uint64_t kernel_phys_base;
extern uint64_t kernel_virt_base;
uintptr_t kheap_start;
static struct heap_block_t* head = NULL;
static uintptr_t end;
// Kernel root table (level 4)
extern uint64_t *kernel_pml4;
static void kheap_grow(size_t size)
{
size_t pages = ALIGN_UP(size + sizeof(struct heap_block_t), PAGE_SIZE) / PAGE_SIZE;
if (pages == 0) pages = 1;
for (size_t i = 0; i < pages; i++)
{
kheap_map_page();
}
}
void kheap_map_page()
{
uintptr_t phys = pmm_alloc();
paging_map_page(kernel_pml4, end, phys, PTE_PRESENT | PTE_WRITABLE | PTE_NOEXEC);
end += PAGE_SIZE;
//DEBUG("Mapped first kheap page");
}
void kheap_init()
{
kheap_start = ALIGN_UP(kernel_virt_base + KERNEL_SIZE, PAGE_SIZE);
end = kheap_start;
// At least 1 page must be mapped for it to work
kheap_map_page();
// Give linked list head its properties
head = (struct heap_block_t*)kheap_start;
head->size = PAGE_SIZE - sizeof(struct heap_block_t);
head->free = true;
head->next = NULL;
DEBUG("kheap initialized, head=0x%p, size=%u", head, head->size);
}
void* kmalloc(size_t size)
{
// No size, no memory allocated!
if (!size) return NULL;
size = ALIGN(size);
struct heap_block_t* curr = head;
while (curr)
{
// Is block free and big enough for us?
if (curr->free && curr->size >= size)
{
// We split the block if it is big enough
if (curr->size >= size + BLOCK_MIN_SIZE)
{
//struct heap_block_t* new_block = (struct heap_block_t*)((uintptr_t)curr + sizeof(struct heap_block_t) + size);
struct heap_block_t* split = (struct heap_block_t*)((uintptr_t)curr + sizeof(*curr) + size);
split->size = curr->size - size - sizeof(*curr);
split->free = true;
split->next = curr->next;
curr->next = split;
curr->size = size;
}
// Found a good block, we return it
curr->free = false;
return (void*)((uintptr_t)curr + sizeof(struct heap_block_t));
}
// Continue browsing the list if nothing good was found yet
curr = curr->next;
}
// If we're here it means we didn't have enough memory
// for the block allocation. So we will allocate more..
uintptr_t old_end = end;
kheap_grow(size + sizeof(struct heap_block_t));
struct heap_block_t* block = (struct heap_block_t*)old_end;
block->size = ALIGN_UP(end - old_end - sizeof(struct heap_block_t), 16);
block->free = true;
block->next = NULL;
// Put the block at the end of the list
curr = head;
while (curr->next)
{
curr = curr->next;
}
curr->next = block;
return kmalloc(size);
}
void kfree(void* ptr)
{
// Nothing to free
if (!ptr) return;
// Set it free!
struct heap_block_t* block = (struct heap_block_t*)((uintptr_t)ptr - sizeof(struct heap_block_t));
block->free = true;
// merge adjacent free blocks (coalescing)
struct heap_block_t* curr = head;
while (curr && curr->next)
{
if (curr->free && curr->next->free)
{
curr->size += sizeof(*curr) + curr->next->size;
curr->next = curr->next->next;
continue;
}
curr = curr->next;
}
}
// Should alloc enough for a stack (at least 64kb) to be used for a process.
// Should return a pointer to top of the stack (as stack grows DOWNWARDS)
void* kalloc_stack()
{
uint8_t* ptr = kmalloc(PROCESS_STACK_SIZE); // As it's out of kmalloc, stack is already mapped into kernel space
return ptr ? ptr+PROCESS_STACK_SIZE : NULL;
}

31
src/mem/heap/kheap.h Normal file
View File

@@ -0,0 +1,31 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Kernel heap
* @license GPL-3.0-only
*/
#ifndef KHEAP_H
#define KHEAP_H
// We need some kind of simple kernel heap to make our linked list
// for the VMM, as we need "malloc" and "free" for that data structure.
// When the kernel heap is ready, we can alloc our VM object linked list
// and then continue working on the VMM.
#include <stdbool.h>
#include <stddef.h>
struct heap_block_t
{
size_t size;
bool free;
struct heap_block_t* next;
};
void kheap_init();
void* kmalloc(size_t size);
void kfree(void* ptr);
void* kalloc_stack();
void kheap_map_page();
#endif

128
src/mem/misc/utils.c Normal file
View File

@@ -0,0 +1,128 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Common memory utilities
* @license GPL-3.0-only
*/
#include <stddef.h>
#include <stdint.h>
#include <limine.h>
#include "kernel.h"
#include "string/string.h"
// We won't be linked to standard library, but still need the basic mem* functions
// so everything goes allright with the compiler
// We use the "restrict" keyword on pointers so that the compiler knows it can
// do more optimization on them (and as it's a much used function, it's good to
// be able to do that)
void* memcpy(void* restrict dest, const void* restrict src, size_t n)
{
uint8_t* restrict pdest = (uint8_t* restrict)dest;
const uint8_t* restrict psrc = (const uint8_t* restrict)src;
for (size_t i=0; i<n; i++)
{
pdest[i] = psrc[i];
}
return dest;
}
void* memset(void* s, int c, size_t n)
{
uint8_t* p = (uint8_t*)s;
for (size_t i=0; i<n; i++)
{
p[i] = (uint8_t)c;
}
return s;
}
void* memmove(void *dest, const void* src, size_t n)
{
uint8_t* pdest = (uint8_t*)dest;
const uint8_t* psrc = (uint8_t*)src;
if (src > dest)
{
for (size_t i=0; i<n; i++)
{
pdest[i] = psrc[i];
}
} else if (src < dest)
{
for (size_t i=n; i>0; i--)
{
pdest[i-1] = psrc[i-1];
}
}
return dest;
}
int memcmp(const void* s1, const void* s2, size_t n)
{
const uint8_t* p1 = (const uint8_t*)s1;
const uint8_t* p2 = (const uint8_t*)s2;
for (size_t i=0; i<n; i++)
{
if (p1[i] != p2[i])
{
return p1[i] < p2[i] ? -1 : 1;
}
}
return 0;
}
// Display the memmap so we see how the memory is laid out at handoff
void memmap_display(struct limine_memmap_response* response)
{
DEBUG("Got memory map from Limine: revision %u, %u entries", response->revision, response->entry_count);
for (size_t i=0; i<response->entry_count; i++)
{
struct limine_memmap_entry* entry = response->entries[i];
char type[32] = {0};
switch(entry->type)
{
case LIMINE_MEMMAP_USABLE:
strcpy(type, "USABLE");
break;
case LIMINE_MEMMAP_RESERVED:
strcpy(type, "RESERVED");
break;
case LIMINE_MEMMAP_ACPI_RECLAIMABLE:
strcpy(type, "ACPI_RECLAIMABLE");
break;
case LIMINE_MEMMAP_ACPI_NVS:
strcpy(type, "ACPI_NVS");
break;
case LIMINE_MEMMAP_BAD_MEMORY:
strcpy(type, "BAD_MEMORY");
break;
case LIMINE_MEMMAP_BOOTLOADER_RECLAIMABLE:
strcpy(type, "BOOTLOADER_RECLAIMABLE");
break;
case LIMINE_MEMMAP_KERNEL_AND_MODULES:
strcpy(type, "KERNEL_AND_MODULES");
break;
case LIMINE_MEMMAP_FRAMEBUFFER:
strcpy(type, "FRAMEBUFFER");
break;
default:
strcpy(type, "UNKNOWN");
break;
}
DEBUG("entry %02u: [0x%016x | %016u bytes] - %s", i, entry->base, entry->length, type);
}
}
// Display the HHDM
void hhdm_display(struct limine_hhdm_response* hhdm)
{
DEBUG("Got HHDM revision=%u offset=0x%p", hhdm->revision, hhdm->offset);
}

View File

@@ -1,3 +1,9 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Common memory utilities
* @license GPL-3.0-only
*/
#ifndef MEM_UTILS_H
#define MEM_UTILS_H
@@ -8,4 +14,8 @@ void* memset(void* s, int c, size_t n);
void* memmove(void *dest, const void* src, size_t n);
int memcmp(const void* s1, const void* s2, size_t n);
// DEBUG
void memmap_display(struct limine_memmap_response* response);
void hhdm_display(struct limine_hhdm_response* hhdm);
#endif

201
src/mem/paging/paging.c Normal file
View File

@@ -0,0 +1,201 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief x64 4-level paging implementation
* @license GPL-3.0-only
*/
#include "paging.h"
#include "pmm.h"
#include <kernel.h>
#include <stddef.h>
#include <limine.h>
#include "config.h"
/*
Paging on x86 uses four different page table levels:
cr3 register contains the phys address for the PML4 (root directory)
Each directory/table is made of 512 entries, each one uint64_t
Each of these entries have special bits (PRESENT/WRITEABLE/USER/etc.)
that dictates their attributes. Also these bits fall back on children tables.
If we use 1GB huge pages: PML4 -> PDPT -> 1gb pages
2MB huge pages: PML4 -> PDPT -> PD -> 2mb pages
4KB (regular size): PML4 -> PDPT -> PD -> PT -> 4kb pages
*/
void load_cr3(uint64_t value) {
asm volatile ("mov %0, %%cr3" :: "r"(value) : "memory");
}
// To flush TLB
static inline void invlpg(void *addr)
{
asm volatile("invlpg (%0)" :: "r"(addr) : "memory");
}
// Allocates a 512-entry 64bit page table/directory/whatever (zeroed)
static uint64_t* alloc_page_table()
{
uint64_t* virt = (uint64_t*)PHYS_TO_VIRT(pmm_alloc());
for (size_t i=0; i<512; i++)
{
virt[i] = 0;
}
return virt;
}
// Kernel paging root table, that will be placed in cr3
__attribute__((aligned(4096)))
uint64_t *kernel_pml4;
// Map a page, taking virt and phys address. This will go through the paging structures
// beginning at the given root table, translate the virtual address in indexes in
// page table/directories, and then mapping the correct page table entry with the
// given physical address + flags
void paging_map_page(uint64_t* root_table, uint64_t virt, uint64_t phys, uint64_t flags)
{
virt = PAGE_ALIGN_DOWN(virt);
phys = PAGE_ALIGN_DOWN(phys);
// Translate the virt address into page table indexes
uint64_t pml4_i = PML4_INDEX(virt);
uint64_t pdpt_i = PDPT_INDEX(virt);
uint64_t pd_i = PD_INDEX(virt);
uint64_t pt_i = PT_INDEX(virt);
uint64_t *pdpt, *pd, *pt;
// PML4
// If the entry at index is not present, allocate enough space for it
// then populate the entry with correct addr + flags
if (!(root_table[pml4_i] & PTE_PRESENT))
{
pdpt = alloc_page_table();
root_table[pml4_i] = VIRT_TO_PHYS(pdpt) | PTE_PRESENT | PTE_WRITABLE;
}
else {
pdpt = (uint64_t *)PHYS_TO_VIRT(root_table[pml4_i] & PTE_ADDR_MASK);
}
// PDPT: same here
if (!(pdpt[pdpt_i] & PTE_PRESENT))
{
pd = alloc_page_table();
pdpt[pdpt_i] = VIRT_TO_PHYS(pd) | PTE_PRESENT | PTE_WRITABLE;
}
else {
pd = (uint64_t *)PHYS_TO_VIRT(pdpt[pdpt_i] & PTE_ADDR_MASK);
}
// PD: and here
if (!(pd[pd_i] & PTE_PRESENT))
{
pt = alloc_page_table();
pd[pd_i] = VIRT_TO_PHYS(pt) | PTE_PRESENT | PTE_WRITABLE;
}
else {
pt = (uint64_t *)PHYS_TO_VIRT(pd[pd_i] & PTE_ADDR_MASK);
}
// PT: finally, populate the page table entry
pt[pt_i] = phys | flags | PTE_PRESENT;
// Flush TLB (apply changes)
invlpg((void *)virt);
}
uint64_t kernel_phys_base;
uint64_t kernel_virt_base;
extern struct boot_context boot_ctx;
void paging_init()
{
// We should map the kernel, GDT, IDT, stack, framebuffer.
// Optionally we could map ACPI tables (we can find them in the Limine memmap)
kernel_phys_base = boot_ctx.kaddr->physical_base;
kernel_virt_base = boot_ctx.kaddr->virtual_base;
struct limine_framebuffer* fb = boot_ctx.fb;
DEBUG("Kernel lives at virt=0x%p phys=0x%p", kernel_virt_base, kernel_phys_base);
kernel_pml4 = alloc_page_table();
// for debug
uint64_t page_count = 0;
// Find max physical address from limine memmap
uint64_t max_phys = 0;
for (uint64_t i=0; i<boot_ctx.mmap->entry_count; i++)
{
struct limine_memmap_entry* entry = boot_ctx.mmap->entries[i];
if (entry->length == 0)
{
continue;
}
uint64_t top = entry->base + entry->length;
if (top > max_phys)
{
max_phys = top;
}
//DEBUG("max_phys=0x%p", max_phys);
}
// 4GB
if (max_phys > 0x100000000)
{
DEBUG("WARNING: max_phys capped to 4GB (0x100000000) (from max_phys=%p)", max_phys);
max_phys = 0x100000000;
}
// HHDM map up to max_phys or 4GB, whichever is smaller, using given offset
for (uint64_t i=0; i<max_phys; i += PAGE_SIZE)
{
//paging_kmap_page(i+hhdm_off, i, PTE_WRITABLE);
paging_map_page(kernel_pml4, i+hhdm_off, i, PTE_WRITABLE | PTE_PRESENT);
page_count++;
}
DEBUG("Mapped %u pages up to 0x%p (HHDM)", page_count, max_phys); page_count = 0;
// Map the kernel (according to virt/phys_base given by Limine)
// SOME DAY when we want a safer kernel we should map .text as Read/Exec
// .rodata as Read and .data as Read/Write
// For now who gives a shit, let's RWX all kernel
for (uint64_t i = 0; i < KERNEL_SIZE; i += PAGE_SIZE)
{
//paging_kmap_page(kernel_virt_base+i, kernel_phys_base+i, PTE_WRITABLE);
paging_map_page(kernel_pml4, kernel_virt_base+i, kernel_phys_base+i, PTE_WRITABLE);
page_count++;
}
DEBUG("Mapped %u pages for kernel", page_count); page_count = 0;
// Get the framebuffer phys/virt address, and size
uint64_t fb_virt = (uint64_t)fb->address;
uint64_t fb_phys = VIRT_TO_PHYS(fb_virt);
uint64_t fb_size = fb->pitch * fb->height;
uint64_t fb_pages = (fb_size + PAGE_SIZE-1)/PAGE_SIZE;
// Map the framebuffer (with cache-disable & write-through)
for (uint64_t i=0; i<fb_pages; i++)
{
//paging_kmap_page(fb_virt+i*PAGE_SIZE, fb_phys+i*PAGE_SIZE, PTE_WRITABLE | PTE_PCD | PTE_PWT);
paging_map_page(kernel_pml4, fb_virt+i*PAGE_SIZE, fb_phys+i*PAGE_SIZE, PTE_WRITABLE | PTE_PCD | PTE_PWT);
page_count++;
}
DEBUG("Mapped %u pages for framebuffer", page_count);
// test for flanterm
// When 10 pages are mapped, SOMETIMES (1 out of 50 times) it prints everything without problem!
// Other times it prints garbage (almost full cursors) and/or panics.
/* for (uint64_t i=0; i<10; i++)
{
paging_map_page(kernel_pml4, 0, kernel_phys_base+KERNEL_SIZE+i*PAGE_SIZE, PTE_WRITABLE);
} */
// Finally, we load the physical address of our PML4 (root table) into cr3
load_cr3(VIRT_TO_PHYS(kernel_pml4));
DEBUG("cr3 loaded, we're still alive");
}

51
src/mem/paging/paging.h Normal file
View File

@@ -0,0 +1,51 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief x64 4-level paging implementation
* @license GPL-3.0-only
*/
#ifndef PAGING_H
#define PAGING_H
#define PAGE_SIZE 4096
#include <stdint.h>
#include <limine.h>
#include "mem/heap/kheap.h"
void paging_init();
void paging_map_page(uint64_t* root_table, uint64_t virt, uint64_t phys, uint64_t flags);
// To swap root page tables
void load_cr3(uint64_t value);
extern uint64_t hhdm_off;
#define PHYS_TO_VIRT(x) ((void*)((uintptr_t)(x) + hhdm_off))
#define VIRT_TO_PHYS(x) ((uintptr_t)(x) - hhdm_off)
#define PTE_ADDR_MASK 0x000FFFFFFFFFF000
// Stole it
#define ALIGN_UP(x, align) (((x) + ((align) - 1)) & ~((align) - 1))
#define ALIGN_DOWN(x, align) ((x) & ~((align) - 1))
#define PAGE_ALIGN_DOWN(x) ((x) & PTE_ADDR_MASK)
#define ALIGN(size) ALIGN_UP(size, 16)
#define BLOCK_MIN_SIZE (sizeof(struct heap_block_t) + 16)
#define PML4_INDEX(x) (((x) >> 39) & 0x1FF)
#define PDPT_INDEX(x) (((x) >> 30) & 0x1FF)
#define PD_INDEX(x) (((x) >> 21) & 0x1FF)
#define PT_INDEX(x) (((x) >> 12) & 0x1FF)
// Page entry special bits
// Bits set on a parent (directory, table) fall back to their children
#define PTE_PRESENT (1ULL << 0)
#define PTE_WRITABLE (1ULL << 1)
#define PTE_USER (1ULL << 2)
#define PTE_PWT (1ULL << 3)
#define PTE_PCD (1ULL << 4)
#define PTE_HUGE (1ULL << 7)
#define PTE_NOEXEC (1ULL << 63)
#endif

110
src/mem/paging/pmm.c Normal file
View File

@@ -0,0 +1,110 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Physical memory manager from freelist
* @license GPL-3.0-only
*/
/*
pmm - Physical Memory Manager
will manage 4kb pages physically
it will probably need to get some info from Limine,
to see which pages are used by kernel/bootloader/mmio/fb etc.
*/
#include "paging.h"
#include <limine.h>
#include <stddef.h>
#include <stdint.h>
#include <kernel.h>
#include "mem/misc/utils.h"
#include "pmm.h"
/*
First we'll have to discover the physical memory layout,
and for that we can use a Limine request.
*/
/*
We will look for the biggest usable physical memory region
and use this for the bitmap. The reserved memory will be ignored.
*/
struct limine_memmap_entry* biggest_entry;
static void pmm_find_biggest_usable_region(struct limine_memmap_response* memmap, struct limine_hhdm_response* hhdm)
{
// Max length of a usable memory region
uint64_t length_max = 0;
uint64_t offset = hhdm->offset;
DEBUG("Usable Memory:");
for (size_t i=0; i<memmap->entry_count; i++)
{
struct limine_memmap_entry* entry = memmap->entries[i];
if (entry->type == LIMINE_MEMMAP_USABLE)
{
DEBUG("0x%p-0x%p mapped at 0x%p-0x%p", entry->base, entry->base+entry->length,
entry->base+offset, entry->base+entry->length+offset);
if (entry->length > length_max)
{
length_max = entry->length;
biggest_entry = entry;
}
}
}
DEBUG("Biggest usable memory region:");
DEBUG("0x%p-0x%p mapped at 0x%p-0x%p", biggest_entry->base, biggest_entry->base + biggest_entry->length,
biggest_entry->base+offset, biggest_entry->base+biggest_entry->length+offset);
}
// Offset from Higher Half Direct Map
uint64_t hhdm_off;
static uintptr_t g_freelist = 0;
uintptr_t pmm_alloc()
{
if (!g_freelist)
{
panic(NULL, "PMM is out of memory!");
}
uintptr_t addr = g_freelist;
g_freelist = *(uintptr_t*) PHYS_TO_VIRT(g_freelist);
return addr;
}
void pmm_free(uintptr_t addr)
{
*(uintptr_t*) PHYS_TO_VIRT(addr) = g_freelist;
g_freelist = addr;
}
static void pmm_init_freelist()
{
// We simply call pmm_free() on each page that is marked USABLE
// in our big memory region.
uint64_t base = ALIGN_UP(biggest_entry->base, PAGE_SIZE);
uint64_t end = ALIGN_DOWN(biggest_entry->base + biggest_entry->length, PAGE_SIZE);
uint64_t page_count=0;
for (uint64_t addr = base; addr < end; addr += PAGE_SIZE)
{
pmm_free(addr);
//DEBUG("page %u lives at phys 0x%p (virt 0x%p)", page_count, addr, PHYS_TO_VIRT(addr));
page_count++;
}
DEBUG("%u frames in freelist, available for use (%u bytes)", page_count, page_count*PAGE_SIZE);
}
void pmm_init(struct limine_memmap_response* memmap, struct limine_hhdm_response* hhdm)
{
hhdm_off = hhdm->offset;
pmm_find_biggest_usable_region(memmap, hhdm);
//pmm_allocate_bitmap(hhdm); too complicated for my small brain
// Now we have biggest USABLE region,
// so to populate the free list we just iterate through it
pmm_init_freelist();
}

16
src/mem/paging/pmm.h Normal file
View File

@@ -0,0 +1,16 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Physical memory manager from freelist
* @license GPL-3.0-only
*/
#ifndef PAGING_PMM_H
#define PAGING_PMM_H
#include <limine.h>
void pmm_init(struct limine_memmap_response* memmap, struct limine_hhdm_response* hhdm);
void pmm_free(uintptr_t addr);
uintptr_t pmm_alloc();
#endif

72
src/mem/paging/vmm.c Normal file
View File

@@ -0,0 +1,72 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Virtual memory manager
* @license GPL-3.0-only
*/
/*
The VMM (virtual memory manager) will have two roles:
- mapping pages
- unmapping pages
in a specified virtual space
compared to the PMM which allocs/frees 4kb frames ("physical pages").
*/
#include "vmm.h"
#include "paging.h"
#include <stddef.h>
#include "pmm.h"
#include <kernel.h>
void* vmm_pt_root = 0;
// Linked list head for virtual memory objects
struct vm_object* vm_objs = NULL;
uint64_t convert_x86_vm_flags(size_t flags)
{
uint64_t value = 0;
if (flags & VM_FLAG_WRITE)
{
value |= PTE_WRITABLE;
}
if (flags & VM_FLAG_USER)
{
value |= PTE_USER;
}
if ((flags & VM_FLAG_EXEC) == 0)
{
value |= PTE_NOEXEC;
}
return value;
}
extern uint64_t *kernel_pml4;
void vmm_setup_pt_root()
{
// We alloc a physical page (frame) for the pointer, then map it
// to virt (pointer)
uintptr_t phys = pmm_alloc();
vmm_pt_root = (void*)kernel_pml4;
paging_map_page(kernel_pml4, (uint64_t)vmm_pt_root, phys, convert_x86_vm_flags(VM_FLAG_WRITE | VM_FLAG_EXEC));
DEBUG("VMM setup: vmm_pt_root=0x%p (phys=0x%p)", vmm_pt_root, phys);
}
/* void* vmm_alloc(size_t length, size_t flags)
{
// We will try to allocate at least length bytes, which have to be rounded UP to
// the next page so its coherent with the PMM
size_t len = ALIGN_UP(length, PAGE_SIZE);
// Need to implement this (as linked list)
// but for now kernel heap is sufficient
// The VMM will prob be more useful when we have userspace
} */
void vmm_init()
{
vmm_setup_pt_root();
}

35
src/mem/paging/vmm.h Normal file
View File

@@ -0,0 +1,35 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Virtual memory manager
* @license GPL-3.0-only
*/
#ifndef VMM_H
#define VMM_H
#include <stdint.h>
#include <stddef.h>
/*
This will be our linked list of virtual memory objects.
Flags here aren't x86 flags, they are platform-agnostic
kernel-defined flags.
*/
struct vm_object
{
uintptr_t base;
size_t length;
size_t flags;
struct vm_object* next;
};
// Flags bitfield
#define VM_FLAG_NONE 0
#define VM_FLAG_WRITE (1 << 0)
#define VM_FLAG_EXEC (1 << 1)
#define VM_FLAG_USER (1 << 2)
void vmm_init();
#endif

View File

@@ -1,70 +0,0 @@
#include <stddef.h>
#include <stdint.h>
// We won't be linked to standard library, but still need the basic mem* functions
// so everything goes allright with the compiler
// We use the "restrict" keyword on pointers so that the compiler knows it can
// do more optimization on them (and as it's a much used function, it's good to
// be able to do that)
void* memcpy(void* restrict dest, const void* restrict src, size_t n)
{
uint8_t* restrict pdest = (uint8_t* restrict)dest;
const uint8_t* restrict psrc = (const uint8_t* restrict)src;
for (size_t i=0; i<n; i++)
{
pdest[i] = psrc[i];
}
return dest;
}
void* memset(void* s, int c, size_t n)
{
uint8_t* p = (uint8_t*)s;
for (size_t i=0; i<n; i++)
{
p[i] = (uint8_t)c;
}
return s;
}
void* memmove(void *dest, const void* src, size_t n)
{
uint8_t* pdest = (uint8_t*)dest;
const uint8_t* psrc = (uint8_t*)src;
if (src > dest)
{
for (size_t i=0; i<n; i++)
{
pdest[i] = psrc[i];
}
} else if (src < dest)
{
for (size_t i=n; i>0; i--)
{
pdest[i-1] = psrc[i-1];
}
}
return dest;
}
int memcmp(const void* s1, const void* s2, size_t n)
{
const uint8_t* p1 = (const uint8_t*)s1;
const uint8_t* p2 = (const uint8_t*)s2;
for (size_t i=0; i<n; i++)
{
if (p1[i] != p2[i])
{
return p1[i] < p2[i] ? -1 : 1;
}
}
return 0;
}

176
src/sched/process.c Normal file
View File

@@ -0,0 +1,176 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Process linked list implementation
* @license GPL-3.0-only
*/
#include <stddef.h>
#include "process.h"
#include "mem/heap/kheap.h"
#include "kernel.h"
#include "string/string.h"
#include "mem/gdt/gdt.h"
#include "config.h"
#include "io/serial/serial.h"
struct process_t* processes_list;
struct process_t* current_process;
extern uint64_t *kernel_pml4;
size_t next_free_pid = 0;
void process_init()
{
processes_list = NULL;
current_process = NULL;
}
// Only for debug
void process_display_list(struct process_t* processes_list)
{
int process_view_id = 0;
struct process_t* tmp = processes_list;
while (tmp != NULL)
{
DEBUG("{%d: %p} -> ", process_view_id, tmp);
tmp = tmp->next;
process_view_id++;
}
DEBUG("NULL");
}
struct process_t* process_create(char* name, void(*function)(void*), void* arg)
{
CLEAR_INTERRUPTS;
struct process_t* proc = (struct process_t*)kmalloc(sizeof(struct process_t));
struct cpu_status_t* ctx = (struct cpu_status_t*)kmalloc(sizeof(struct cpu_status_t));
// No more memory?
if (!proc) return NULL;
if (!ctx) return NULL;
strncpy(proc->name, name, PROCESS_NAME_MAX);
proc->pid = next_free_pid++;
proc->status = READY;
uint64_t* stack_top = (uint64_t*)kalloc_stack();
// push return address to the stack so when "ret" hits we jmp to exit instead of idk what
// stack grows DOWNWARDS!!
*(--stack_top) = (uint64_t)process_exit;
proc->context = ctx;
proc->context->iret_ss = KERNEL_DATA_SEGMENT; // process will live in kernel mode
proc->context->iret_rsp = (uint64_t)stack_top;
proc->context->iret_flags = 0x202; //bit 2 and 9 set (Interrupt Flag)
proc->context->iret_cs = KERNEL_CODE_SEGMENT;
proc->context->iret_rip = (uint64_t)function; // beginning of executable code
proc->context->rdi = (uint64_t)arg; // 1st arg is in rdi (as per x64 calling convention)
proc->context->rbp = 0;
// Kernel PML4 as it already maps code/stack (when switching to userland we'll have to change that)
proc->root_page_table = kernel_pml4;
proc->next = 0;
process_add(&processes_list, proc);
SET_INTERRUPTS;
return proc;
}
void process_add(struct process_t** processes_list, struct process_t* process)
{
if (!process) return;
process->next = NULL;
if (*processes_list == NULL)
{
// List is empty
*processes_list = process;
return;
}
struct process_t* tmp = *processes_list;
while (tmp->next != NULL)
{
tmp = tmp->next;
}
// We're at last process before NULL
tmp->next = process;
// process->next = NULL;
}
void process_delete(struct process_t** processes_list, struct process_t* process)
{
if (!processes_list || !*processes_list || !process) return;
if (*processes_list == process)
{
// process to delete is at head
*processes_list = process->next;
process->next = NULL;
kfree(process);
return;
}
struct process_t* tmp = *processes_list;
while (tmp->next && tmp->next != process)
{
tmp = tmp->next;
}
if (tmp->next == NULL)
{
// Didn't find the process
return;
}
// We're at process before the one we want to delete
tmp->next = process->next;
process->next = NULL;
kfree(process);
}
struct process_t* process_get_next(struct process_t* process)
{
if (!process) return NULL;
return process->next;
}
// (from gdt) This will switch tasks ONLY in ring 0
// KERNEL CS = 0x08
// KERNEL SS = 0x10
__attribute__((naked, noreturn))
void process_switch(uint64_t stack_addr, uint64_t code_addr)
{
asm volatile(" \
push $0x10 \n\
push %0 \n\
push $0x202 \n\
push $0x08 \n\
push %1 \n\
iretq \n\
" :: "r"(stack_addr), "r"(code_addr));
}
// Will be used to clean up resources (if any, when we implement it)
// Just mark as DEAD then idle. Scheduler will delete process at next timer interrupt % quantum.
void process_exit()
{
DEBUG("Exiting from process '%s'", current_process->name);
CLEAR_INTERRUPTS;
if (current_process)
{
current_process->status = DEAD;
}
SET_INTERRUPTS;
outb(0x20, 0x20);
for (;;)
{
asm("hlt");
}
}

42
src/sched/process.h Normal file
View File

@@ -0,0 +1,42 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Process definition
* @license GPL-3.0-only
*/
#ifndef PROCESS_H
#define PROCESS_H
#include <stddef.h>
#include "config.h"
#include <stdint.h>
typedef enum
{
READY,
RUNNING,
DEAD
} status_t;
struct process_t
{
size_t pid;
char name[PROCESS_NAME_MAX];
status_t status;
struct cpu_status_t* context;
void* root_page_table; // Process PML4 (should contain kernel PML4 in higher half [256-511]
struct process_t* next;
};
void process_init();
struct process_t* process_create(char* name, void(*function)(void*), void* arg);
void process_add(struct process_t** processes_list, struct process_t* process);
void process_delete(struct process_t** processes_list, struct process_t* process);
struct process_t* process_get_next(struct process_t* process);
void process_switch(uint64_t stack_addr, uint64_t code_addr);
void process_exit();
void process_display_list(struct process_t* processes_list);
#endif

55
src/sched/scheduler.c Normal file
View File

@@ -0,0 +1,55 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Round-robin scheduler
* @license GPL-3.0-only
*/
#include "kernel.h"
#include "process.h"
#include "mem/paging/paging.h"
#include <stdint.h>
#include "io/serial/serial.h"
extern struct process_t* processes_list;
extern struct process_t* current_process;
void scheduler_init()
{
// Choose first process?
current_process = processes_list;
}
struct cpu_status_t* scheduler_schedule(struct cpu_status_t* context)
{
current_process->context = context;
//current_process->status = READY;
for (;;) {
struct process_t* prev_process = current_process;
if (current_process->next != NULL)
{
current_process = current_process->next;
} else
{
current_process = processes_list;
}
if (current_process != NULL && current_process->status == DEAD)
{
process_delete(&prev_process, current_process);
} else
{
current_process->status = RUNNING;
break;
}
}
// Current_process gets wrong context?? (iret_rip points to other stuff than process function; like putpixel() for example)
DEBUG("current_process={pid=%u, name='%s', root_page_table[virt]=%p}", current_process->pid, current_process->name, current_process->root_page_table);
load_cr3(VIRT_TO_PHYS((uint64_t)current_process->root_page_table));
DEBUG("loaded process pml4 into cr3");
return current_process->context;
}

13
src/sched/scheduler.h Normal file
View File

@@ -0,0 +1,13 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Round-robin scheduler
* @license GPL-3.0-only
*/
#ifndef SCHEDULER_H
#define SCHEDULER_H
struct cpu_status_t* scheduler_schedule(struct cpu_status_t* context);
void scheduler_init();
#endif

7
src/sched/task.S Normal file
View File

@@ -0,0 +1,7 @@
; Task (process) switching
bits 64
global switch_to_task
switch_to_task:

32
src/string/string.c Normal file
View File

@@ -0,0 +1,32 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief String manipulation utilities
* @license GPL-3.0-only
*/
#include <stddef.h>
char* strcpy(char *dest, const char *src)
{
char *temp = dest;
while((*dest++ = *src++));
return temp;
}
// https://stackoverflow.com/questions/2488563/strcat-implementation
char *strcat(char *dest, const char *src){
size_t i,j;
for (i = 0; dest[i] != '\0'; i++)
;
for (j = 0; src[j] != '\0'; j++)
dest[i+j] = src[j];
dest[i+j] = '\0';
return dest;
}
// https://stackoverflow.com/questions/14159625/implementation-of-strncpy
void strncpy(char* dst, const char* src, size_t n)
{
size_t i = 0;
while(i++ != n && (*dst++ = *src++));
}

14
src/string/string.h Normal file
View File

@@ -0,0 +1,14 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief String manipulation functions
* @license GPL-3.0-only
*/
#ifndef STRING_H
#define STRING_H
char *strcpy(char *dest, const char *src);
char *strcat(char *dest, const char *src);
void strncpy(char* dst, const char* src, size_t n);
#endif

View File

@@ -1,5 +1,12 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief Programmable Interval Timer init and enabling
* @license GPL-3.0-only
*/
#include <stdint.h>
#include "../io/serial.h"
#include "io/serial/serial.h"
#include <kernel.h>
/*
For now, the timer module will be using the PIC.
@@ -43,7 +50,7 @@ void pic_enable()
// Enabling IRQ0 (unmasking it) but not the others
uint8_t mask = inb(0x21);
mask &= ~(1 << 0); // Set IRQ0 (timer, clear bit 0)
mask &= ~(1 << 1); // Set IRQ1 (PS/2 Keyboard, clear bit 1)
//mask &= ~(1 << 1); // Set IRQ1 (PS/2 Keyboard, clear bit 1)
outb(0x21, mask);
}
@@ -64,6 +71,17 @@ void pit_init()
outb(0x40, (divisor >> 8) & 0xFF);
}
// Wait n ticks
// Given that there's a tick every 1ms, wait n milliseconds
void timer_wait(uint64_t wait_ticks)
{
uint64_t then = ticks + wait_ticks;
while (ticks < then)
{
asm("hlt");
};
}
void timer_init()
{
// Remapping the PIC, because at startup it conflicts with
@@ -72,4 +90,5 @@ void timer_init()
pic_remap();
pic_enable();
pit_init();
DEBUG("PIT initialized");
}

View File

@@ -1,6 +1,13 @@
/*
* @author xamidev <xamidev@riseup.net>
* @brief PIT functions
* @license GPL-3.0-only
*/
#ifndef TIMER_H
#define TIMER_H
void timer_init();
void timer_wait(unsigned int wait_ticks);
#endif

33
symbols.py Normal file
View File

@@ -0,0 +1,33 @@
# Make assembly file from ELF symbols map
# Then link it to kernel so it's aware of symbol names
# then we can use that for the stack trace.
print("Extracting symbols from map file to assembly...")
with open("symbols.map", "r") as f:
lines = f.readlines()
symbols = []
for line in lines:
parts = line.split()
# output is formed like "address name"
symbols.append((parts[0], parts[1]))
with open("symbols.S", "w") as f:
f.write("section .rodata\n")
f.write("global symbol_table\n")
f.write("global symbol_count\n")
f.write("symbol_table:\n")
for i, (addr, name) in enumerate(symbols):
f.write(f" dq 0x{addr}\n")
f.write(f" dq sym_name_{i}\n")
f.write("\nsymbol_count: dq " + str(len(symbols)) + "\n\n")
for i, (addr, name) in enumerate(symbols):
# escaping quotes
safe_name = name.replace('"', '\\"')
f.write(f'sym_name_{i}: db "{safe_name}", 0\n')
print("Done!")

1
test.bin Normal file
View File

@@ -0,0 +1 @@
<EFBFBD>ュ゙<EFBFBD>