commit 9092621dc532e4d49c03e74bf3fda9f9903a3737 from: Onana Onana Xavier Manuel date: Sun Aug 16 23:38:03 2026 UTC Initial commit commit - /dev/null commit + 9092621dc532e4d49c03e74bf3fda9f9903a3737 blob - /dev/null blob + 20d4bcef726a4cbf96ffbb605738e9ba4a899377 (mode 644) --- /dev/null +++ LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2026 Onana Onana Xavier Manuel + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED AS IS AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. blob - /dev/null blob + 97a88222a424258fe5328fce7be28f862929ad1f (mode 644) --- /dev/null +++ NOBS @@ -0,0 +1,29 @@ +The kernel is meant to run in long mode x86 CPU. When the bootloader +starts the kernel, the kernel can access stored data to get information +about the system. The information is mapped this way: + +0x6000: Kernel size +0x6004: E280 count +0x6008: E280 blocks start address + +Once the kerenl is loaded, theses values are assigned to a kinfo struct +to be able to find the real unused memory (remove the kernel address +space if needed). These are then place in memory blocks that can be used +for memory allocation. + +The kernel is meant to be a microkernel following the original L4 papers. +Meaning certain processes like memory manager, scheduler and supervisor +will be placed in user space. + +Speaking about processes, the idea for this kernel is to have 2 types of +processes: IMMORTAL and MORTAL. IMMORTAL processes are essential processes +that the operating system need such as the memory manager and the scheduler. +These processes upon dying will trigger the supervisor which will call a +RESURECT function. MORTAL processes are not granted that privillege. This +makes the supervisor only have to manager a very small quantity of +processes. + +There is also an additional process called the watchdog which only role +is to send a resurect call on the supervisor in case of failure. The +watchdog itself is MORTAL(ironic) meaning that in case of failure the +the supervisor has no way of resurecting essentialy becoming MORTAL. blob - /dev/null blob + cb5c71e3d752cc5dee1be4db28af52fd28ba18fe (mode 644) --- /dev/null +++ README @@ -0,0 +1,21 @@ +49 is my attempt at making an operating system which +combines L4 microkernel architecture with and ideas +from Plan9. + +The end goal is to have an operating system that does +not use ELF, nor follow POSIX nor SysV ABI and that +is able to compile itself with a custom C dialect. + +Features: + +- Buggy + +- Unsafe + +- Incomplete + +- POSIX unfriendly + +- No ELF + +- and more blob - /dev/null blob + 5ca021cb038dda4abebede76f416828eb88546f4 (mode 644) --- /dev/null +++ boot.s @@ -0,0 +1,297 @@ +[bits 16] +[org 0x7c00] + +start: + xor ax, ax + mov ds, ax + mov es, ax + mov ss, ax + mov sp, 0x7c00 ; not overwritting bootloader + + ; LBA check + mov ah, 0x41 + mov bx, 0x55aa + mov [boot_drive], dl + int 0x13 + + jc giveup + call load_kernel + mov DWORD [KERNEL_SIZE_ADDR], KERNEL_SECTORS * 512 ; Passing the size + + ; Detect upper memory + xor ebx, ebx + mov edx, 0x534d4150 + mov eax, 0xe820 + mov ecx, 24 + mov di, E280_START + mov [E280_PADDR], DWORD E280_START ; Store the pointer address (can't do QWORD :/) + int 0x15 + jc giveup + call e280lp + + ; Check if enabled otherwise do + cli ; disable interrupts + in al, 0x92 + test al, 2 + jnz done + + or al, 2 + and al, 0xfe + out 0x92, al + +done: + jmp init_pm + +e280lp: + cmp ebx, 0 + jz e280_done + inc DWORD [E280_COUNT] + add di, 0x18 ; Normalized to 24 bytes + mov eax, 0xe820 + mov ecx, 24 + mov edx, 0x534d4150 + int 0x15 + jc giveup + jmp e280lp +e280_done: + ret + + +KERNEL_TMP equ 0x8000 +%ifndef KERNEL_SECTORS + KERNEL_SECTORS equ 1 +%endif + +KERNEL_ADDRESS equ 0x100000 +MEMORY_ADDRESS equ 0x6000 +KERNEL_SIZE_ADDR equ MEMORY_ADDRESS +E280_COUNT equ KERNEL_SIZE_ADDR + 4 ; Count is stored as a 2 bytes +E280_PADDR equ E280_COUNT + 4 ; Silly alignement +E280_START equ E280_PADDR + 4 + + +; disk address partition +dap: + db 0x10 ; packet size + db 0x00 ; always 0 + dw KERNEL_SECTORS ; sectors keep under 127 just in case + dw KERNEL_TMP ; dst + dw 0x0000 ; segment of dst + dq 1 ; Sector where it starts (right after bootloader on disk) + +load_kernel: + mov si, dap + mov dl, [boot_drive] + mov ah, 0x42 + int 0x13 + jc giveup + ret + +; Loading GDT (32-bit) +init_pm: + lgdt [gdt_desc] + mov eax, cr0 + or al, 1 + mov cr0, eax ; now in 32-bit + jmp CODE_SEG:mode32 + +giveup: + mov si, failmsg + call print16 + jmp $ + +print16: + lodsb ; loads byte at SI register in AL register + or al, al + jz .done ; exit + mov ah, 0x0e ; tty mode + mov bh, 0 + int 0x10 + jmp print16 +.done: + ret + +gdt_start: + dq 0 ; null descriptor + code_descriptor: + dw 0xffff ; 24-bit from limit + dw 0 ; 16-bit from base + db 0 ; 8-bit from base + db 0b10011010 ; access-byte + db 0b11001111 ; other with limit + db 0 ; last 8-bit of base + + data_descriptor: + dw 0xffff + dw 0 + db 0 + db 0b10010010 + db 0b11001111 + db 0 + +gdt_end: + +gdt_desc: + dw gdt_end - gdt_start - 1 + dd gdt_start + +boot_drive: db 0 + +CODE_SEG equ code_descriptor - gdt_start +DATA_SEG equ data_descriptor - gdt_start + +; ------------------------------------------------------------ +; Console messages (Real Mode) +; ------------------------------------------------------------ +failmsg: db 'Failed!', 13, 10, 0 + +; ------------------------------------------------------------ +; 32 bit mode - preparing for long mode +; ------------------------------------------------------------ +CPUID_EXTENSIONS equ 0x80000000 +CPUID_EXT_FEATURES equ 0x80000001 +VIDEO_MEMORY_PM equ 0xb8000 +CR0_PAGING equ 1 << 31 +PML4T_ADDR equ 0x1000 +SIZEOF_PAGE_TABLE equ 4096 +PDPT_ADDR equ 0x2000 +PDT_ADDR equ 0x3000 +PT_ADDR equ 0x4000 + +PT_ADDR_MASK equ 0xffffffffff000 +PT_PRESENT equ 1 +PT_READABLE equ 2 + +ENTRIES_PER_PT equ 512 +SIZEOF_PT_ENTRY equ 8 +PAGE_SIZE equ 0x1000 +CR4_PAE_ENABLE equ 1 << 5 +EFER_MSR equ 0xC0000080 +EFER_LM_ENABLE equ 1 << 8 +CR0_PM_ENABLE equ 1 << 0 +CR0_PG_ENABLE equ 1 << 31 + +[bits 32] +mode32: + ; Copy Kernel + mov esi, KERNEL_TMP + mov edi, KERNEL_ADDRESS + mov ecx, KERNEL_SECTORS * 512 / 4 + rep movsd + + ; Check support long mode + mov eax, CPUID_EXTENSIONS + cpuid + cmp eax, CPUID_EXT_FEATURES + jb giveup_pm + call disablepaging_pm + + ; clearing tables + mov edi, PML4T_ADDR + mov cr3, edi ; tells CPU where page table are + + xor eax, eax + mov ecx, SIZEOF_PAGE_TABLE + rep stosd + + mov edi, cr3 + + ; Link first entries + mov DWORD [edi], PDPT_ADDR & PT_ADDR_MASK | PT_PRESENT | PT_READABLE + + mov edi, PDPT_ADDR + mov DWORD [edi], PDT_ADDR & PT_ADDR_MASK | PT_PRESENT | PT_READABLE + + mov edi, PDT_ADDR + mov DWORD [edi], PT_ADDR & PT_ADDR_MASK | PT_PRESENT | PT_READABLE + + mov edi, PT_ADDR + mov ebx, PT_PRESENT | PT_READABLE + mov ecx, ENTRIES_PER_PT + +.SetEntry: + mov DWORD [edi], ebx + add ebx, PAGE_SIZE + add edi, SIZEOF_PT_ENTRY + loop .SetEntry + + mov eax, cr4 + or eax, CR4_PAE_ENABLE + mov cr4, eax + + ; Setting LM bit + mov ecx, EFER_MSR + rdmsr + or eax, EFER_LM_ENABLE + wrmsr + + mov eax, cr0 + or eax, CR0_PG_ENABLE | CR0_PM_ENABLE + mov cr0, eax + + ; GDT + lgdt [gdt_desc_lm] + jmp CODE_SEG_LM:mode64 + +gdt_start_lm: + dq 0 + code_descriptor_lm: + dw 0xffff ; 24-bit from limit + dw 0 + db 0 + db 0b10011010 ; access byte + db 0b10100011 ; flags + db 0 + data_descriptor_lm: + dw 0xffff + dw 0 + db 0 + db 0b10010010 + db 0b11000011 + db 0 + +gdt_end_lm: + +gdt_desc_lm: + dw gdt_end_lm - gdt_start_lm - 1 + dq gdt_start_lm + +disablepaging_pm: + mov eax, cr0 + and eax, ~CR0_PAGING + mov cr0, eax + ret + +CODE_SEG_LM equ code_descriptor_lm - gdt_start_lm +DATA_SEG_LM equ data_descriptor_lm - gdt_start_lm +; ------------------------------------------------------------ +; This will only output a ! in failure +; ------------------------------------------------------------ +giveup_pm: + mov ebx, VIDEO_MEMORY_PM + mov al, '!' + mov ah, 0x0f + mov [ebx], ax + jmp $ + +[bits 64] +mode64: + cli + + xor ax, ax + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + + ; load the kernel + mov rax, KERNEL_ADDRESS + mov rdi, MEMORY_ADDRESS + jmp rax + +; ------------------------------------------------------------ +; `signed bootloader` +; ------------------------------------------------------------ +times 510-($-$$) db 0 +dw 0xaa55 blob - /dev/null blob + c433fa544fdb5cceb249294f7f24c25721dee23f (mode 755) --- /dev/null +++ build.sh @@ -0,0 +1,29 @@ +# We generate flat binary for the kernel and bootloader then +# combine them. +# +# The order in which ld links them is important as kmain section must +# be before anything + +OS_NAME=49.bin +CFLAGS='-m64 -fno-pic -fno-pie -ffreestanding -nostdlib -O0 -fno-stack-protector -mno-red-zone -fcf-protection=none' + +nasm -f elf64 -o io.o io.s +cc -m64 $CFLAGS -o kmain.o -c kmain.c +cc -m64 $CFLAGS -o kprint.o -c kprint.c +ld -T linker.ld --no-pie --oformat binary -o kernel.bin kmain.o io.o kprint.o + +# Calculating the size of the kernel to truncate +KSIZE=$(wc -c kernel.bin | awk '{print $1}') +SSIZE=512 # Sectors size + +echo "kernel size is currently $KSIZE bytes" + +KSIZE=$(( ((KSIZE + SSIZE - 1) / SSIZE) * SSIZE)) +SECTORS=$(( KSIZE / SSIZE )) +BOOTIMGSIZE=$((KSIZE + 512)) + +echo "kernel size is now $KSIZE bytes with $SECTORS sector(s)" +echo "added with the boot sector it is $BOOTIMGSIZE bytes and $((SECTORS + 1)) sectors" + +nasm -D KERNEL_SECTORS=$SECTORS boot.s -o boot.bin +cat boot.bin kernel.bin /dev/zero | dd of=$OS_NAME bs=$BOOTIMGSIZE count=1 blob - /dev/null blob + f9b5f8adef4ad85012a80f8661da211c35878ed3 (mode 755) --- /dev/null +++ cleanup.sh @@ -0,0 +1 @@ +rm *.bin *.o blob - /dev/null blob + cc371f1cc646496bc730b8e453dd559b603da160 (mode 644) --- /dev/null +++ io.h @@ -0,0 +1,2 @@ +uint8_t inb(uint16_t port); +void outb(uint16_t port, uint8_t value); blob - /dev/null blob + 562c0bf8b9f7d85ce755c79e60d5af151dd67e27 (mode 644) --- /dev/null +++ io.s @@ -0,0 +1,17 @@ +; Input/Output + +[bits 64] + +global inb +global outb + +inb: + mov dx, di + in al, dx + ret + +outb: + mov dx, di + mov al, sil + out dx, al + ret blob - /dev/null blob + b0aabcd74046d52232af114d0b69d17b493c7391 (mode 644) --- /dev/null +++ kmain.c @@ -0,0 +1,86 @@ +#include + +#define MAX_MEM_BLOCKS 64 + +struct memory_block { + uint64_t base; + uint64_t length; +}; + +struct memory_map { + uint32_t count; + struct memory_block blocks[MAX_MEM_BLOCKS]; +}; + +/* + * This is x86 specific as for now, but a better + * abstraction will be added later + */ +struct e280h { + uint64_t base; + uint64_t length; + uint32_t type; + uint32_t acpi_attr; +}; + +struct kinfo { + uint32_t kernel_size; + uint32_t e280_count; + struct e280h *e280_headers; +}; + +void kprint(const char *s); + +struct memory_map ram; + +void +kmain(struct kinfo *kinfo) +{ + kprint("Only 10 remote holes in a heck of a long time!"); + + uint32_t kernel_start = 0x100000; + uint32_t kernel_end = kernel_start + kinfo->kernel_size; + + /* + * We are fetching the memory blocks to place in + * RAM + */ + for (int i = 0; i < kinfo->e280_count; i++) { + struct e280h *hdr = &kinfo->e280_headers[i]; + + if (hdr->type != 1) + continue; + + /* Just in case */ + if (ram.count >= MAX_MEM_BLOCKS) + continue; + + if (kernel_end > hdr->base && kernel_start < hdr->base + hdr->length) { + if (kernel_start > hdr->base) { + ram.blocks[ram.count].base = hdr->base; + ram.blocks[ram.count].length = (kernel_start - hdr->base); + ram.count++; + } + + if (ram.count >= MAX_MEM_BLOCKS) + continue; + + if (kernel_end < (hdr->base + hdr->length)) { + ram.blocks[ram.count].base = kernel_end; + ram.blocks[ram.count].length = ((hdr->base + hdr->length) - kernel_end); + ram.count++; + } + } else { + ram.blocks[ram.count].base = hdr->base; + ram.blocks[ram.count].length = hdr->length; + ram.count++; + } + + } + + if (ram.count > 1) { + kprint("More than 1 block of memory"); + } +repeat: + goto repeat; +} blob - /dev/null blob + 7d22705df5f2d333be85e90f4a52e60fd222f4d2 (mode 644) --- /dev/null +++ kprint.c @@ -0,0 +1,51 @@ +#include + +#include "io.h" + +/* + * VGA memory starts at 0xA0000 and end at 0xBFFFF. + * + * Text mode begins at 0xB8000 + */ + +#define VGA_W 80 +#define VGA_H 25 + +static volatile uint16_t * const vga = (uint16_t *)0xb8000; + +uint16_t +get_cursor() +{ + uint16_t pos = 0; + + outb(0x3D4, 0x0E); + pos |= ((uint16_t)inb(0x3D5)) << 8; + + outb(0x3D4, 0x0F); + pos |= inb(0x3D5); + + return pos; +} + +void +advance_cursor(uint16_t dt) +{ + uint16_t pos = get_cursor() + dt; + outb(0x3D4, 0x0E); + outb(0x3D5, (uint8_t)(pos >> 8)); + outb(0x3D4, 0x0F); + outb(0x3D5, (uint8_t)(pos & 0xFF)); +} + +void +kprint(const char *s) +{ + uint16_t pos = get_cursor(); + + uint16_t i = 0; + while (*s) { + vga[pos + i++] = (0x1f << 8) | *s++; + } + + advance_cursor(i); +} blob - /dev/null blob + 9bcf49d356fc3f7ccdacf05b6c842f67dc0633b9 (mode 644) --- /dev/null +++ linker.ld @@ -0,0 +1,21 @@ +/* + * This is a flat binary. The importance is that kmain is the + * first text entry. + */ +ENTRY(_start) +SECTIONS +{ + . = 0x100000; + + .text : { *(.text) } + .rodata : { *(.rodata*) } + .data : { *(.data) } + .bss : { *(.bss) } + + /DISCARD/ : + { + *(.note*) + *(.comment*) + *(.eh_frame*) + } +}