Commit Diff


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 <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
@@ -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 <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
@@ -0,0 +1,51 @@
+#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
@@ -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*)
+	}
+}