commit - /dev/null
commit + 9092621dc532e4d49c03e74bf3fda9f9903a3737
blob - /dev/null
blob + 20d4bcef726a4cbf96ffbb605738e9ba4a899377 (mode 644)
--- /dev/null
+++ LICENSE
+Copyright (c) 2026 Onana Onana Xavier Manuel <xavier@onana.net>
+
+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
+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
+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
+[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
+# 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
+rm *.bin *.o
blob - /dev/null
blob + cc371f1cc646496bc730b8e453dd559b603da160 (mode 644)
--- /dev/null
+++ io.h
+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
+; 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
+#include <stdint.h>
+
+#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
+#include <stdint.h>
+
+#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
+/*
+ * 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*)
+ }
+}