diff --git a/compile_flags.txt b/compile_flags.txt
index 18399a5..fce5308 100644
--- a/compile_flags.txt
+++ b/compile_flags.txt
@@ -1,5 +1,6 @@
-std=c23
+-ffreestanding
-I
dependencies/limine
-I
-kernel/include
+include
diff --git a/include/calcite/calcite.h b/include/calcite/calcite.h
new file mode 100644
index 0000000..7f9b4d7
--- /dev/null
+++ b/include/calcite/calcite.h
@@ -0,0 +1,31 @@
+/* Calcite, include/calcite/calcite.h
+ * Copyright 2025 Benji Dial
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#pragma once
+
+#include
+
+[[noreturn]] void end_thread();
+
+struct framebuffer_info {
+ uint8_t *fb_base;
+ int fb_width;
+ int fb_height;
+ int fb_pitch;
+};
+
+void map_framebuffer(struct framebuffer_info *info_out);
diff --git a/include/kernel-public/syscall-numbers.h b/include/kernel-public/syscall-numbers.h
new file mode 100644
index 0000000..05f7a6e
--- /dev/null
+++ b/include/kernel-public/syscall-numbers.h
@@ -0,0 +1,23 @@
+/* Calcite, include/kernel-public/syscall-numbers.h
+ * Copyright 2025 Benji Dial
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#pragma once
+
+enum {
+ SYSCALL_END_THREAD,
+ SYSCALL_MAP_FRAMEBUFFER
+};
diff --git a/make-build.sh b/make-build.sh
index 4bffb5f..17a1e28 100644
--- a/make-build.sh
+++ b/make-build.sh
@@ -1,8 +1,10 @@
#!/bin/sh
COMMON_CC_EXTRA_FLAGS="-O3 -Wall -Wextra"
-COMMON_CC_FLAGS="-std=c23 ${COMMON_CC_EXTRA_FLAGS}"
-KERNEL_CC_FLAGS="-I dependencies/limine -I kernel/include -mno-sse -ffreestanding ${COMMON_CC_FLAGS}"
+COMMON_CC_FLAGS="-std=c23 -ffreestanding -I include ${COMMON_CC_EXTRA_FLAGS}"
+KERNEL_CC_FLAGS="-mno-sse -I dependencies/limine ${COMMON_CC_FLAGS}"
+#in the future user code will be allowed to use sse
+USER_CC_FLAGS="-mno-sse ${COMMON_CC_FLAGS}"
if [ -e build.ninja ]; then
echo build.ninja already exists.
@@ -18,34 +20,81 @@ echo "rule kernel_cc" >> build.ninja
echo " depfile = \$out.d" >> build.ninja
echo " command = cc -c -MD -MF \$out.d ${KERNEL_CC_FLAGS} \$in -o \$out" >> build.ninja
+echo "rule user_cc" >> build.ninja
+echo " depfile = \$out.d" >> build.ninja
+echo " command = cc -c -MD -MF \$out.d ${USER_CC_FLAGS} \$in -o \$out" >> build.ninja
+
echo "rule kernel_ld" >> build.ninja
-echo " command = ld -T kernel/link.ld \$in -o \$out" >> build.ninja
+echo " command = ld -T src/kernel/link.ld \$in -o \$out" >> build.ninja
-ALL_KERNEL_OBJECTS=""
+echo "rule user_lib_ld" >> build.ninja
+echo " command = ld -r \$in -o \$out" >> build.ninja
-for f in kernel/src/*.asm; do
- f=$(echo $f | sed -e 's/^kernel\/src\///')
- echo "build build/kernel/$f.o: nasm kernel/src/$f" >> build.ninja
- ALL_KERNEL_OBJECTS="${ALL_KERNEL_OBJECTS} build/kernel/$f.o"
+#eventually maybe a libc will be linked in
+echo "rule user_app_ld" >> build.ninja
+echo " command = ld \$in -o \$out" >> build.ninja
+
+#builds everything in a directory
+# $1 - source directory
+# $2 - cc rule
+# $3 - ld rule
+# $4 - linked object name
+# $5 - extra objects to link in
+build_all() {
+
+ sources="$1/*.asm $1/*.c"
+ objects="$5"
+
+ for src in $sources; do
+ if echo $src | grep -q -v '*'; then
+ build=$(echo $src | sed -e 's/^src/build/').o
+ if echo $src | grep -q '.c$'; then
+ echo "build $build: $2 $src" >> build.ninja
+ else
+ echo "build $build: nasm $src" >> build.ninja
+ fi
+ objects="$objects $build"
+ fi
+ done
+
+ build_dir=$(echo $1 | sed -e 's/^src/build/')
+ echo "build $build_dir/$4: $3 $objects" >> build.ninja
+
+}
+
+build_all src/kernel kernel_cc kernel_ld kernel.elf
+
+for dir in src/user-libs/*; do
+ lib_name=$(echo $dir | sed -e 's/^src\/user-libs\///')
+ build_all $dir user_cc user_lib_ld lib$lib_name.o
done
-for f in kernel/src/*.c; do
- f=$(echo $f | sed -e 's/^kernel\/src\///')
- echo "build build/kernel/$f.o: kernel_cc kernel/src/$f" >> build.ninja
- ALL_KERNEL_OBJECTS="${ALL_KERNEL_OBJECTS} build/kernel/$f.o"
-done
+apps=""
-echo "build build/kernel/kernel.elf: kernel_ld ${ALL_KERNEL_OBJECTS}" >> build.ninja
+for dir in src/user-apps/*; do
+
+ if [ -e $dir/libraries.txt ]; then
+ lib_paths=$(cat $dir/libraries.txt | sed -e 's/^.*$/build\/user-libs\/\0\/lib\0.o/')
+ else
+ lib_paths=""
+ fi
+
+ app_name=$(echo $dir | sed -e 's/^src\/user-apps\///')
+ build_all $dir user_cc user_app_ld $app_name.elf $lib_paths
+ build_dir=$(echo $dir | sed -e 's/^src/build/')
+ apps="$apps $build_dir/$app_name.elf"
+
+done
echo "rule initfs" >> build.ninja
echo " command =" \
"rm -rf build/initfs &&" \
- "mkdir -p build/initfs/resx &&" \
- "echo Hello! > build/initfs/resx/hello.txt &&" \
+ "mkdir -p build/initfs/apps &&" \
+ "cp build/user-apps/hello/hello.elf build/initfs/apps/ &&" \
"cd build/initfs &&" \
"tar cf ../initfs.tar *" >> build.ninja
-echo "build build/initfs.tar: initfs" >> build.ninja
+echo "build build/initfs.tar: initfs $apps" >> build.ninja
echo "rule disk" >> build.ninja
echo " command =" \
diff --git a/kernel/src/entry.c b/src/kernel/entry.c
similarity index 80%
rename from kernel/src/entry.c
rename to src/kernel/entry.c
index 952dba9..51fd941 100644
--- a/kernel/src/entry.c
+++ b/src/kernel/entry.c
@@ -1,28 +1,34 @@
-/* Calcite, kernel/src/entry.c
+/* Calcite, src/kernel/entry.c
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or (at
- * your option) any later version.
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
*
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
*/
-#include
-#include
-#include
-#include
+#include "framebuffer.h"
+#include "interrupts.h"
+#include "kernel-public/syscall-numbers.h"
+#include "scheduler.h"
+#include "syscalls.h"
+#include "process.h"
+#include "utility.h"
+#include "initfs.h"
+#include "paging.h"
+#include "panic.h"
+#include "heap.h"
+#include "ps2.h"
+
#include
-#include
-#include
-#include
LIMINE_BASE_REVISION(3)
@@ -145,10 +151,9 @@ static uint64_t initfs_length;
//we round up to a multiple of a page.
uint64_t fb_length = ((fb->height * fb->pitch - 1) / 4096 + 1) * 4096;
+ fb_physical_base = (uint64_t)fb->address - hhdm_request.response->offset;
fb_base = find_free_kernel_region(fb_length);
- map_kernel_region(
- (uint64_t)fb->address - hhdm_request.response->offset,
- fb_base, fb_length, 1, 0);
+ map_kernel_region(fb_physical_base, fb_base, fb_length, 1, 0);
//store rest of framebuffer information
@@ -187,9 +192,35 @@ static uint64_t initfs_length;
set_irq_handler(0x0c, &on_mouse_irq);
enable_interrupts();
- //halt repeatedly
+ //set up syscalls
- while (1)
- __asm__ ("hlt");
+ init_syscalls();
+ register_syscall(SYSCALL_END_THREAD, (void *)&syscall_end_thread);
+ register_syscall(SYSCALL_MAP_FRAMEBUFFER, (void *)&syscall_map_framebuffer);
+
+ //load hello and start it
+
+ init_scheduler();
+
+ const uint8_t *hello_start;
+ uint64_t hello_length;
+ look_up_initfs_file("apps/hello.elf", &hello_start, &hello_length);
+
+ struct process *hello = heap_alloc(sizeof(struct process));
+ uint64_t hello_entry;
+ create_process(hello);
+ load_elf(hello, &hello_entry, hello_start, hello_length);
+
+ struct thread *hello_thread = heap_alloc(sizeof(struct thread));
+ create_thread(hello, hello_thread);
+
+ create_user_task(
+ hello->p4_physical_base,
+ hello_entry,
+ (uint64_t)hello_thread->stack_top);
+
+ running_thread = hello_thread;
+
+ resume_next_continuation();
}
diff --git a/kernel/src/framebuffer.c b/src/kernel/framebuffer.c
similarity index 70%
rename from kernel/src/framebuffer.c
rename to src/kernel/framebuffer.c
index e261248..a9e545d 100644
--- a/kernel/src/framebuffer.c
+++ b/src/kernel/framebuffer.c
@@ -1,22 +1,23 @@
-/* Calcite, kernel/src/framebuffer.c
+/* Calcite, src/kernel/framebuffer.c
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or (at
- * your option) any later version.
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
*
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
*/
-#include
+#include "framebuffer.h"
+uint64_t fb_physical_base;
uint8_t *fb_base;
int fb_width;
int fb_height;
diff --git a/kernel/include/framebuffer.h b/src/kernel/framebuffer.h
similarity index 72%
rename from kernel/include/framebuffer.h
rename to src/kernel/framebuffer.h
index dc26553..71bdb57 100644
--- a/kernel/include/framebuffer.h
+++ b/src/kernel/framebuffer.h
@@ -1,22 +1,25 @@
-/* Calcite, kernel/include/framebuffer.h
+/* Calcite, src/kernel/framebuffer.h
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or (at
- * your option) any later version.
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
*
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
*/
+#pragma once
+
#include
+extern uint64_t fb_physical_base;
extern uint8_t *fb_base;
extern int fb_width;
extern int fb_height;
diff --git a/src/kernel/heap.c b/src/kernel/heap.c
new file mode 100644
index 0000000..4a9c1f9
--- /dev/null
+++ b/src/kernel/heap.c
@@ -0,0 +1,104 @@
+/* Calcite, src/kernel/heap.c
+ * Copyright 2025 Benji Dial
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#include "paging.h"
+#include "heap.h"
+
+struct dealloc_record {
+ //0 for unused
+ uint64_t length;
+ void *start;
+};
+
+struct dealloc_record_page {
+ struct dealloc_record records[255];
+ struct dealloc_record_page *prev_page;
+};
+
+static struct dealloc_record_page *last_page;
+
+static void *map_pages(uint64_t page_count) {
+
+ void *vma = find_free_kernel_region(page_count * 4096);
+
+ for (uint64_t i = 0; i < page_count; ++i) {
+ uint64_t pma = take_free_physical_page();
+ map_in_kernel_page_table(pma, vma + i * 4096, 1, 0);
+ }
+
+ return vma;
+
+}
+
+static struct dealloc_record *get_unused_record() {
+
+ for (struct dealloc_record_page *page = last_page;
+ page != 0; page = page->prev_page)
+ for (int i = 0; i < 255; ++i)
+ if (page->records[i].length == 0)
+ return &page->records[i];
+
+ struct dealloc_record_page *page = map_pages(1);
+ page->prev_page = last_page;
+ last_page = page;
+
+ page->records[0].start = (void *)page + 4096 - 8;
+ page->records[0].length = 8;
+
+ for (int i = 2; i < 255; ++i)
+ page->records[i].length = 0;
+
+ return &page->records[1];
+
+}
+
+void *heap_alloc(uint64_t length) {
+
+ for (struct dealloc_record_page *page = last_page;
+ page != 0; page = page->prev_page)
+ for (int i = 0; i < 255; ++i)
+ if (page->records[i].length == length) {
+ page->records[i].length = 0;
+ return page->records[i].start;
+ }
+
+ for (struct dealloc_record_page *page = last_page;
+ page != 0; page = page->prev_page)
+ for (int i = 0; i < 255; ++i)
+ if (page->records[i].length > length) {
+ page->records[i].length -= length;
+ return page->records[i].start + page->records[i].length;
+ }
+
+ uint64_t pages = (length - 1) / 4096 + 1;
+ void *vma = map_pages(pages);
+
+ if (pages * 4096 != length) {
+ struct dealloc_record *record = get_unused_record();
+ record->start = vma + length;
+ record->length = pages * 4096 - length;
+ }
+
+ return vma;
+
+}
+
+void heap_dealloc(void *start, uint64_t length) {
+ struct dealloc_record *record = get_unused_record();
+ record->start = start;
+ record->length = length;
+}
diff --git a/src/kernel/heap.h b/src/kernel/heap.h
new file mode 100644
index 0000000..a999f49
--- /dev/null
+++ b/src/kernel/heap.h
@@ -0,0 +1,23 @@
+/* Calcite, src/kernel/heap.h
+ * Copyright 2025 Benji Dial
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#pragma once
+
+#include
+
+void *heap_alloc(uint64_t length);
+void heap_dealloc(void *start, uint64_t length);
diff --git a/kernel/src/initfs.c b/src/kernel/initfs.c
similarity index 88%
rename from kernel/src/initfs.c
rename to src/kernel/initfs.c
index a176b3e..9cf3fed 100644
--- a/kernel/src/initfs.c
+++ b/src/kernel/initfs.c
@@ -1,21 +1,21 @@
-/* Calcite, kernel/src/initfs.c
+/* Calcite, src/kernel/initfs.c
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or (at
- * your option) any later version.
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
*
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
*/
-#include
+#include "initfs.h"
static const uint8_t *initfs_start;
static uint64_t initfs_length;
diff --git a/kernel/include/initfs.h b/src/kernel/initfs.h
similarity index 96%
rename from kernel/include/initfs.h
rename to src/kernel/initfs.h
index 3a1a4d3..e03f7fa 100644
--- a/kernel/include/initfs.h
+++ b/src/kernel/initfs.h
@@ -1,4 +1,4 @@
-/* Calcite, kernel/include/initfs.h
+/* Calcite, src/kernel/initfs.h
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
diff --git a/kernel/src/interrupts.asm b/src/kernel/interrupts.asm
similarity index 92%
rename from kernel/src/interrupts.asm
rename to src/kernel/interrupts.asm
index 4740556..8ea354e 100644
--- a/kernel/src/interrupts.asm
+++ b/src/kernel/interrupts.asm
@@ -1,18 +1,18 @@
- ; Calcite, kernel/src/interrupts.asm
+ ; Calcite, src/kernel/interrupts.asm
; Copyright 2025 Benji Dial
;
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
- ; the Free Software Foundation, either version 3 of the License, or (at
- ; your option) any later version.
+ ; the Free Software Foundation, either version 3 of the License, or
+ ; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful, but
; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- ; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- ; more details.
+ ; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ ; for more details.
;
- ; You should have received a copy of the GNU General Public License along with
- ; this program. If not, see .
+ ; You should have received a copy of the GNU General Public License along
+ ; with this program. If not, see .
bits 64
diff --git a/kernel/src/interrupts.c b/src/kernel/interrupts.c
similarity index 91%
rename from kernel/src/interrupts.c
rename to src/kernel/interrupts.c
index 94575aa..9792863 100644
--- a/kernel/src/interrupts.c
+++ b/src/kernel/interrupts.c
@@ -1,4 +1,4 @@
-/* Calcite, kernel/src/interrupts.c
+/* Calcite, src/kernel/interrupts.c
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
@@ -21,8 +21,8 @@
// https://osdev.wiki/wiki/Interrupt_Descriptor_Table
// https://osdev.wiki/wiki/Task_State_Segment
-#include
-#include
+#include "interrupts.h"
+#include "panic.h"
struct [[gnu::packed]] exception_parameter {
uint64_t r15;
@@ -212,6 +212,18 @@ void enable_interrupts() {
tss_entry->base_24 = (tss_base >> 24) & 0xff;
tss_entry->base_32 = tss_base >> 32;
+ struct gdt_entry *user_data_entry = &the_gdt[3];
+ user_data_entry->access_byte =
+ GDT_AB_CODE_DATA_ACCESSED | GDT_AB_DATA_WRITABLE |
+ GDT_AB_TYPE_DATA | GDT_AB_DPL_THREE | GDT_AB_PRESENT;
+ user_data_entry->flags = GDT_FLAG_LONG;
+
+ struct gdt_entry *user_code_entry = &the_gdt[4];
+ user_code_entry->access_byte =
+ GDT_AB_CODE_DATA_ACCESSED | GDT_AB_TYPE_CODE |
+ GDT_AB_DPL_THREE | GDT_AB_PRESENT;
+ user_code_entry->flags = GDT_FLAG_LONG;
+
struct gdt_entry *kernel_code_entry = &the_gdt[5];
kernel_code_entry->access_byte =
GDT_AB_CODE_DATA_ACCESSED | GDT_AB_TYPE_CODE |
diff --git a/kernel/include/interrupts.h b/src/kernel/interrupts.h
similarity index 95%
rename from kernel/include/interrupts.h
rename to src/kernel/interrupts.h
index d5b075e..b10bce4 100644
--- a/kernel/include/interrupts.h
+++ b/src/kernel/interrupts.h
@@ -1,4 +1,4 @@
-/* Calcite, kernel/include/interrupts.h
+/* Calcite, src/kernel/interrupts.h
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
diff --git a/kernel/link.ld b/src/kernel/link.ld
similarity index 100%
rename from kernel/link.ld
rename to src/kernel/link.ld
diff --git a/kernel/src/paging.asm b/src/kernel/paging.asm
similarity index 80%
rename from kernel/src/paging.asm
rename to src/kernel/paging.asm
index b22f94e..1539875 100644
--- a/kernel/src/paging.asm
+++ b/src/kernel/paging.asm
@@ -1,18 +1,18 @@
- ; Calcite, kernel/src/paging.asm
+ ; Calcite, src/kernel/paging.asm
; Copyright 2025 Benji Dial
;
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
- ; the Free Software Foundation, either version 3 of the License, or (at
- ; your option) any later version.
+ ; the Free Software Foundation, either version 3 of the License, or
+ ; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful, but
; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- ; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- ; more details.
+ ; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ ; for more details.
;
- ; You should have received a copy of the GNU General Public License along with
- ; this program. If not, see .
+ ; You should have received a copy of the GNU General Public License along
+ ; with this program. If not, see .
bits 64
@@ -25,6 +25,12 @@ init_stack_length equ 16384
section .text
+;referenced in paging.c
+extern invlpg
+invlpg:
+ invlpg byte [rdi]
+ ret
+
extern switch_to_kernel_page_tables
switch_to_kernel_page_tables:
diff --git a/kernel/src/paging.c b/src/kernel/paging.c
similarity index 72%
rename from kernel/src/paging.c
rename to src/kernel/paging.c
index f03bff4..ec664df 100644
--- a/kernel/src/paging.c
+++ b/src/kernel/paging.c
@@ -1,22 +1,22 @@
-/* Calcite, kernel/src/paging.c
+/* Calcite, src/kernel/paging.c
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or (at
- * your option) any later version.
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
*
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
*/
-#include
-#include
+#include "paging.h"
+#include "panic.h"
#define MAX_PHYSICAL_GB 64ULL
@@ -38,6 +38,8 @@ static uint8_t physical_map[MAX_PHYSICAL_GB << 15];
//referenced from paging.asm
uint64_t kernel_p4_physical_address;
+uint64_t kernel_p3_physical_address;
+
alignas(4096) static uint64_t kernel_p4[512];
alignas(4096) static uint64_t kernel_p3[512];
alignas(4096) static uint64_t kernel_p2[512];
@@ -55,6 +57,7 @@ void init_paging(uint64_t kernel_physical_base, void *kernel_virtual_base) {
(uint64_t)kernel_virtual_base - kernel_physical_base;
kernel_p4_physical_address = (uint64_t)kernel_p4 - kernel_load_offset;
+ kernel_p3_physical_address = (uint64_t)kernel_p3 - kernel_load_offset;
kernel_p4[511] = ((uint64_t)kernel_p3 - kernel_load_offset) | 0x3;
for (int i = 0; i < 511; ++i)
@@ -98,6 +101,9 @@ void mark_physical_memory_free(uint64_t base, uint64_t length) {
}
+//defined in paging.asm
+void invlpg(void *address);
+
void map_in_kernel_page_table(
uint64_t physical_base, void *virtual_base,
int writable, int executable) {
@@ -112,6 +118,39 @@ void map_in_kernel_page_table(
physical_base | (writable ? 0x3 : 0x1) |
(executable ? 0 : 0x8000000000000000);
+ invlpg(virtual_base);
+
+}
+
+void unmap_kernel_page(void *virtual_base) {
+
+ uint64_t virtual_base_u64 = (uint64_t)virtual_base;
+ assert(virtual_base_u64 >= 0xffffffffc0000000);
+
+ uint64_t p1s_index = (virtual_base_u64 - 0xffffffffc0000000) >> 12;
+ assert(kernel_p1s[p1s_index] != 0);
+
+ kernel_p1s[p1s_index] = 0;
+
+ invlpg(virtual_base);
+
+}
+
+void unmap_and_free_kernel_page(void *virtual_base) {
+
+ uint64_t virtual_base_u64 = (uint64_t)virtual_base;
+ assert(virtual_base_u64 >= 0xffffffffc0000000);
+
+ uint64_t p1s_index = (virtual_base_u64 - 0xffffffffc0000000) >> 12;
+ assert(kernel_p1s[p1s_index] != 0);
+
+ uint64_t pma = kernel_p1s[p1s_index] & 0x7ffffffffffff000;
+ mark_physical_memory_free(pma, 4096);
+
+ kernel_p1s[p1s_index] = 0;
+
+ invlpg(virtual_base);
+
}
void *find_free_kernel_region(uint64_t length) {
@@ -127,9 +166,7 @@ void *find_free_kernel_region(uint64_t length) {
if (run_length == length)
return (void *)(run_start * 4096 + 0xffffffffc0000000);
if (next == 512 * 512)
- //die. TODO: handle this nicer.
- while (1)
- __asm__ ("hlt");
+ panic("out of kernel virtual memory");
if (kernel_p1s[next] == 0)
++run_length;
else {
@@ -140,3 +177,13 @@ void *find_free_kernel_region(uint64_t length) {
}
}
+
+uint64_t take_free_physical_page() {
+ for (uint64_t i = 0; i < (MAX_PHYSICAL_GB << 15); ++i)
+ for (int j = 0; j < 8; ++j)
+ if (physical_map[i] & (1 << j)) {
+ physical_map[i] &= ~(1 << j);
+ return (i << 15) + (j << 12);
+ }
+ panic("out of physical memory");
+}
diff --git a/kernel/include/paging.h b/src/kernel/paging.h
similarity index 72%
rename from kernel/include/paging.h
rename to src/kernel/paging.h
index 9f93ebe..a572f6b 100644
--- a/kernel/include/paging.h
+++ b/src/kernel/paging.h
@@ -1,24 +1,26 @@
-/* Calcite, kernel/include/paging.h
+/* Calcite, src/kernel/paging.h
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or (at
- * your option) any later version.
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
*
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
*/
#pragma once
#include
+extern uint64_t kernel_p3_physical_address;
+
//kernel physical and virtual bases are passed so that we can compute the
//physical addresses of the kernel's statically allocated paging structures.
void init_paging(uint64_t kernel_physical_base, void *kernel_virtual_base);
@@ -31,9 +33,18 @@ void mark_physical_memory_free(uint64_t base, uint64_t length);
void map_in_kernel_page_table(
uint64_t physical_base, void *virtual_base, int writable, int executable);
+//unmaps one page. base should be page-aligned and within kernel range.
+void unmap_kernel_page(void *virtual_base);
+
+//unmaps one page, and frees the corresponding physical page.
+//base should be page-aligned and within kernel range.
+void unmap_and_free_kernel_page(void *virtual_base);
+
//returns a region of contiguous pages in kernel virtual memory where nothing
//is mapped. length should be page-aligned.
void *find_free_kernel_region(uint64_t length);
+uint64_t take_free_physical_page();
+
//implemented in paging.asm. the continuation should be noreturn.
[[noreturn]] void switch_to_kernel_page_tables(void (*continuation)());
diff --git a/kernel/src/panic.c b/src/kernel/panic.c
similarity index 96%
rename from kernel/src/panic.c
rename to src/kernel/panic.c
index ad9f0e6..74844dd 100644
--- a/kernel/src/panic.c
+++ b/src/kernel/panic.c
@@ -1,4 +1,4 @@
-/* Calcite, kernel/src/panic.c
+/* Calcite, src/kernel/panic.c
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
diff --git a/kernel/include/panic.h b/src/kernel/panic.h
similarity index 95%
rename from kernel/include/panic.h
rename to src/kernel/panic.h
index 8555ab3..473415b 100644
--- a/kernel/include/panic.h
+++ b/src/kernel/panic.h
@@ -1,4 +1,4 @@
-/* Calcite, kernel/include/panic.h
+/* Calcite, src/kernel/panic.h
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
@@ -15,6 +15,8 @@
* with this program. If not, see .
*/
+#pragma once
+
[[noreturn]] void panic_core(
const char *file, const char *function, int line, const char *message);
diff --git a/src/kernel/process.c b/src/kernel/process.c
new file mode 100644
index 0000000..c149cdd
--- /dev/null
+++ b/src/kernel/process.c
@@ -0,0 +1,357 @@
+/* Calcite, src/kernel/process.c
+ * Copyright 2025 Benji Dial
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#include "framebuffer.h"
+#include "scheduler.h"
+#include "process.h"
+#include "utility.h"
+#include "paging.h"
+#include "panic.h"
+#include "heap.h"
+
+void create_process(struct process *process_out) {
+
+ process_out->p4_physical_base = take_free_physical_page();
+ process_out->p4_virtual_base = find_free_kernel_region(4096);
+ map_in_kernel_page_table(
+ process_out->p4_physical_base, process_out->p4_virtual_base, 1, 0);
+
+ process_out->p3_physical_base = take_free_physical_page();
+ process_out->p3_virtual_base = find_free_kernel_region(4096);
+ map_in_kernel_page_table(
+ process_out->p3_physical_base, process_out->p3_virtual_base, 1, 0);
+
+ process_out->p4_virtual_base[0] = process_out->p3_physical_base | 0x7;
+ process_out->p4_virtual_base[511] = kernel_p3_physical_address | 0x3;
+ for (int i = 1; i < 511; ++i)
+ process_out->p4_virtual_base[i] = 0;
+
+ for (int i = 0; i < 512; ++i) {
+ process_out->p3_virtual_base[i] = 0;
+ process_out->p2_virtual_bases[i] = 0;
+ process_out->p1_virtual_bases[i] = 0;
+ }
+
+process_out->n_threads = 0;
+
+}
+
+void map_page_for_process(
+ struct process *process, uint64_t physical_base,
+ void *virtual_base, int writable, int executable) {
+
+ assert(physical_base % 4096 == 0)
+
+ uint64_t vma = (uint64_t)virtual_base;
+ assert(vma % 4096 == 0)
+ assert(vma != 0)
+ assert(vma < 0x0000008000000000)
+
+ int p1i = (vma >> 12) & 0x1ff;
+ int p2i = (vma >> 21) & 0x1ff;
+ int p3i = (vma >> 30) & 0x1ff;
+
+ if (process->p3_virtual_base[p3i] == 0) {
+ uint64_t p2_pma = take_free_physical_page();
+ uint64_t *p2_vma = find_free_kernel_region(4096);
+ map_in_kernel_page_table(p2_pma, p2_vma, 1, 0);
+ process->p3_virtual_base[p3i] = p2_pma | 0x7;
+ process->p2_virtual_bases[p3i] = p2_vma;
+ process->p1_virtual_bases[p3i] = heap_alloc(4096);
+ for (int i = 0; i < 512; ++i) {
+ p2_vma[i] = 0;
+ process->p1_virtual_bases[p3i][i] = 0;
+ }
+ }
+
+ if (process->p2_virtual_bases[p3i][p2i] == 0) {
+ uint64_t p1_pma = take_free_physical_page();
+ uint64_t *p1_vma = find_free_kernel_region(4096);
+ map_in_kernel_page_table(p1_pma, p1_vma, 1, 0);
+ process->p2_virtual_bases[p3i][p2i] = p1_pma | 0x7;
+ process->p1_virtual_bases[p3i][p2i] = p1_vma;
+ for (int i = 0; i < 512; ++i)
+ p1_vma[i] = 0;
+ }
+
+ assert(process->p1_virtual_bases[p3i][p2i][p1i] == 0)
+
+ process->p1_virtual_bases[p3i][p2i][p1i] =
+ physical_base | 0x5 | (writable ? 0x2 : 0x0) |
+ (executable ? 0 : 0x8000000000000000);
+
+}
+
+void unmap_page_for_process(
+ struct process *process, void *virtual_base) {
+
+ uint64_t vma = (uint64_t)virtual_base;
+ assert(vma % 4096 == 0)
+ assert(vma != 0)
+ assert(vma < 0x0000008000000000)
+
+ int p1i = (vma >> 12) & 0x1ff;
+ int p2i = (vma >> 21) & 0x1ff;
+ int p3i = (vma >> 30) & 0x1ff;
+
+ assert(
+ process->p1_virtual_bases[p3i] &&
+ process->p1_virtual_bases[p3i][p2i] &&
+ process->p1_virtual_bases[p3i][p2i][p1i])
+
+ process->p1_virtual_bases[p3i][p2i][p1i] = 0;
+
+}
+
+int is_mapped_writable(struct process *process, void *start, uint64_t length) {
+
+ uint64_t vma_start = (uint64_t)start;
+ uint64_t vma_end = vma_start + length;
+ vma_start = (vma_start / 4096) * 4096;
+ vma_end = ((vma_end - 1) / 4096 + 1) * 4096;
+
+ for (uint64_t vma = vma_start; vma < vma_end; vma += 4096) {
+
+ if (vma == 0 || vma >= 0x0000008000000000)
+ return 0;
+
+ int p1i = (vma >> 12) & 0x1ff;
+ int p2i = (vma >> 21) & 0x1ff;
+ int p3i = (vma >> 30) & 0x1ff;
+
+ if (!process->p1_virtual_bases[p3i] ||
+ !process->p1_virtual_bases[p3i][p2i] ||
+ !process->p1_virtual_bases[p3i][p2i][p1i] ||
+ !(process->p1_virtual_bases[p3i][p2i][p1i] & 0x2))
+ return 0;
+
+ }
+
+ return 1;
+
+}
+
+void *find_free_process_region(
+ struct process *process, uint64_t page_count) {
+
+ uint64_t start = 512 * 512 * 4096;
+ uint64_t length = 0;
+
+ while (1) {
+
+ if (length >= page_count * 4096)
+ break;
+
+ uint64_t vma = start + length;
+ if (vma >= 0x0000008000000000)
+ goto no_mem;
+
+ int p1i = (vma >> 12) & 0x1ff;
+ int p2i = (vma >> 21) & 0x1ff;
+ int p3i = (vma >> 30) & 0x1ff;
+
+ if (p2i == 0 && p1i == 0 && process->p2_virtual_bases[p3i] == 0) {
+ length += 512 * 512 * 4096;
+ continue;
+ }
+
+ if (p1i == 0 && process->p1_virtual_bases[p3i][p2i] == 0) {
+ length += 512 * 4096;
+ continue;
+ }
+
+ if (process->p1_virtual_bases[p3i][p2i][p1i] == 0) {
+ length += 4096;
+ continue;
+ }
+
+ start = start + length + 4096;
+ length = 0;
+
+ }
+
+ return (void *)start;
+
+no_mem:
+ panic("process out of virtual memory")
+
+}
+
+void load_elf(
+ struct process *process, uint64_t *entry_out,
+ const void *elf_start, uint64_t elf_length) {
+
+ if (elf_length < 58)
+ panic("malformed elf")
+
+ *entry_out = *(uint64_t *)(elf_start + 24);
+
+ uint64_t phead_start = *(uint64_t *)(elf_start + 32);
+ uint16_t phead_entry_size = *(uint16_t *)(elf_start + 54);
+ uint16_t phead_entry_count = *(uint16_t *)(elf_start + 56);
+
+ if (elf_length < phead_start + phead_entry_count * phead_entry_size)
+ panic("malformed elf")
+
+ for (uint16_t i = 0; i < phead_entry_count; ++i) {
+
+ uint64_t *entry =
+ (uint64_t *)(elf_start + phead_start + i * phead_entry_size);
+
+ if ((entry[0] & 0xffffffff) != 1)
+ continue;
+
+ int executable = (entry[0] >> 32) & 0x1;
+ int writable = (entry[0] >> 33) & 0x1;
+
+ uint64_t file_start = entry[1];
+ void *virtual_start = (void *)entry[2];
+ uint64_t file_length = entry[4];
+ uint64_t virtual_length = ((entry[5] - 1) / 4096 + 1) * 4096;
+
+ if (elf_length < file_start + file_length)
+ panic("malformed elf")
+ if (file_length > virtual_length)
+ panic("malformed elf")
+
+ if ((uint64_t)virtual_start % 4096 != 0 || virtual_length % 4096 != 0)
+ panic("unaligned elf")
+
+ for (uint64_t i = 0; i < virtual_length; i += 4096) {
+
+ uint64_t pma = take_free_physical_page();
+ map_page_for_process(
+ process, pma, virtual_start + i, writable, executable);
+
+ void *kvma = find_free_kernel_region(4096);
+ map_in_kernel_page_table(pma, kvma, 1, 0);
+
+ if (i + 4096 <= file_length)
+ memcpy(kvma, elf_start + file_start + i, 4096);
+ else if (i >= file_length)
+ memzero(kvma, 4096);
+ else {
+ memcpy(kvma, elf_start + file_start + i, file_length & 0xfff);
+ memzero(kvma + (file_length & 0xfff), 4096 - (file_length & 0xfff));
+ }
+
+ unmap_kernel_page(kvma);
+
+ }
+
+ }
+
+}
+
+void destroy_process(struct process *process) {
+ for (int p3i = 0; p3i < 512; ++p3i)
+ if (process->p3_virtual_base[p3i]) {
+ for (int p2i = 0; p2i < 512; ++p2i)
+ if (process->p2_virtual_bases[p3i][p2i]) {
+ for (int p1i = 0; p1i < 512; ++p1i)
+ mark_physical_memory_free(
+ process->p1_virtual_bases[p3i][p2i][p1i] & 0x7ffffffffffff000,
+ 4096);
+ unmap_and_free_kernel_page(process->p1_virtual_bases[p3i][p2i]);
+ }
+ unmap_and_free_kernel_page(process->p2_virtual_bases[p3i]);
+ heap_dealloc(process->p1_virtual_bases[p3i], 4096);
+ }
+ unmap_and_free_kernel_page(process->p3_virtual_base);
+ unmap_and_free_kernel_page(process->p4_virtual_base);
+ heap_dealloc(process, sizeof(struct process));
+}
+
+#define INITIAL_STACK_SIZE (16 << 20)
+
+void create_thread(struct process *process, struct thread *thread_out) {
+
+ //TODO: allocate stack as needed on page faults, have guard pages, etc.
+
+ void *stack_bottom_vma =
+ find_free_process_region(process, INITIAL_STACK_SIZE / 4096);
+
+ for (int i = 0; i < INITIAL_STACK_SIZE / 4096; ++i) {
+
+ uint64_t pma = take_free_physical_page();
+ map_page_for_process(process, pma, stack_bottom_vma + i * 4096, 1, 0);
+
+ void *kvma = find_free_kernel_region(4096);
+ map_in_kernel_page_table(pma, kvma, 1, 0);
+ memzero(kvma, 4096);
+ unmap_kernel_page(kvma);
+
+ }
+
+ thread_out->process = process;
+ thread_out->stack_bottom = stack_bottom_vma;
+ thread_out->stack_top = stack_bottom_vma + INITIAL_STACK_SIZE;
+
+ ++process->n_threads;
+
+}
+
+struct thread *running_thread = 0;
+
+[[noreturn]] void syscall_end_thread() {
+
+ assert(running_thread != 0)
+ assert(running_thread->process->n_threads >= 1)
+
+ if (running_thread->process->n_threads == 1)
+ destroy_process(running_thread->process);
+
+ else {
+
+ --running_thread->process->n_threads;
+
+ for (void *p = running_thread->stack_bottom;
+ p < running_thread->stack_top; p += 4096)
+ unmap_page_for_process(running_thread->process, p);
+
+ }
+
+ heap_dealloc(running_thread, sizeof(struct thread));
+
+ running_thread = 0;
+ resume_next_continuation();
+
+}
+
+void syscall_map_framebuffer(struct syscall_framebuffer_info *info_out) {
+
+ if (!is_mapped_writable(
+ running_thread->process, info_out,
+ sizeof(struct syscall_framebuffer_info)))
+ panic("bad syscall");
+
+ uint64_t pages_needed = (fb_pitch * fb_height - 1) / 4096 + 1;
+
+ void *base = find_free_process_region(running_thread->process, pages_needed);
+
+ for (uint64_t i = 0; i < pages_needed; ++i)
+ map_page_for_process(
+ running_thread->process,
+ fb_physical_base + i * 4096,
+ base + i * 4096, 1, 0);
+
+ info_out->fb_base = base;
+ info_out->fb_pitch = fb_pitch;
+ info_out->fb_height = fb_height;
+ info_out->fb_width = fb_width;
+
+}
diff --git a/src/kernel/process.h b/src/kernel/process.h
new file mode 100644
index 0000000..a623429
--- /dev/null
+++ b/src/kernel/process.h
@@ -0,0 +1,88 @@
+/* Calcite, src/kernel/process.h
+ * Copyright 2025 Benji Dial
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#pragma once
+
+#include
+
+struct process {
+
+ uint64_t p4_physical_base;
+ uint64_t *p4_virtual_base;
+ uint64_t p3_physical_base;
+ uint64_t *p3_virtual_base;
+
+ int n_threads;
+
+ //0 for missing levels. just bottom p3 of address space.
+ uint64_t *p2_virtual_bases[512];
+ uint64_t **p1_virtual_bases[512];
+
+};
+
+struct thread {
+
+ struct process *process;
+
+ //both page-aligned
+ void *stack_bottom;
+ void *stack_top;
+
+};
+
+extern struct thread *running_thread;
+
+void create_process(struct process *process_out);
+void create_thread(struct process *process, struct thread *thread_out);
+
+//physical and virtual bases must be page-aligned.
+//virtual base must be in bottom p3 and not zero.
+void map_page_for_process(
+ struct process *process, uint64_t physical_base,
+ void *virtual_base, int writable, int executable);
+
+//virtual base must be page-aligned, in bottom p3, and not zero.
+void unmap_page_for_process(
+ struct process *process, void *virtual_base);
+
+//finds a free memory region in the bottom p3
+//of the address space, and not the bottom p2.
+void *find_free_process_region(
+ struct process *process, uint64_t page_count);
+
+//loaded sections are copied to new pages. once storage drivers are written,
+//this should probably just take a file handle so it can load and parse the
+//header and then load sections directly into user pages.
+void load_elf(
+ struct process *process, uint64_t *entry_out,
+ const void *elf_start, uint64_t elf_length);
+
+void destroy_process(struct process *process);
+
+//returs 1 if [start, start + length) is writable by process, otherwise 0.
+int is_mapped_writable(struct process *process, void *start, uint64_t length);
+
+[[noreturn]] void syscall_end_thread();
+
+struct syscall_framebuffer_info {
+ uint8_t *fb_base;
+ int fb_width;
+ int fb_height;
+ int fb_pitch;
+};
+
+void syscall_map_framebuffer(struct syscall_framebuffer_info *info_out);
diff --git a/kernel/src/ps2.asm b/src/kernel/ps2.asm
similarity index 85%
rename from kernel/src/ps2.asm
rename to src/kernel/ps2.asm
index b91ec81..21b0aad 100644
--- a/kernel/src/ps2.asm
+++ b/src/kernel/ps2.asm
@@ -1,18 +1,18 @@
- ; Calcite, kernel/src/ps2.asm
+ ; Calcite, src/kernel/ps2.asm
; Copyright 2025 Benji Dial
;
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
- ; the Free Software Foundation, either version 3 of the License, or (at
- ; your option) any later version.
+ ; the Free Software Foundation, either version 3 of the License, or
+ ; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful, but
; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- ; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- ; more details.
+ ; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ ; for more details.
;
- ; You should have received a copy of the GNU General Public License along with
- ; this program. If not, see .
+ ; You should have received a copy of the GNU General Public License along
+ ; with this program. If not, see .
bits 64
diff --git a/kernel/src/ps2.c b/src/kernel/ps2.c
similarity index 88%
rename from kernel/src/ps2.c
rename to src/kernel/ps2.c
index 6da5884..54506af 100644
--- a/kernel/src/ps2.c
+++ b/src/kernel/ps2.c
@@ -1,23 +1,23 @@
-/* Calcite, kernel/src/ps2.c
+/* Calcite, src/kernel/ps2.c
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or (at
- * your option) any later version.
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
*
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
*/
-#include
-#include
-#include
+#include "framebuffer.h"
+#include "panic.h"
+#include "ps2.h"
//defined in ps2.asm
//returns -1 if no byte available
diff --git a/kernel/include/ps2.h b/src/kernel/ps2.h
similarity index 95%
rename from kernel/include/ps2.h
rename to src/kernel/ps2.h
index 53364fb..235caf2 100644
--- a/kernel/include/ps2.h
+++ b/src/kernel/ps2.h
@@ -1,4 +1,4 @@
-/* Calcite, kernel/include/ps2.h
+/* Calcite, src/kernel/ps2.h
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
diff --git a/src/kernel/scheduler.asm b/src/kernel/scheduler.asm
new file mode 100644
index 0000000..ec2d14d
--- /dev/null
+++ b/src/kernel/scheduler.asm
@@ -0,0 +1,57 @@
+ ; Calcite, src/kernel/scheduler.c
+ ; Copyright 2025 Benji Dial
+ ;
+ ; This program is free software: you can redistribute it and/or modify
+ ; it under the terms of the GNU General Public License as published by
+ ; the Free Software Foundation, either version 3 of the License, or
+ ; (at your option) any later version.
+ ;
+ ; This program is distributed in the hope that it will be useful, but
+ ; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ ; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ ; for more details.
+ ;
+ ; You should have received a copy of the GNU General Public License along
+ ; with this program. If not, see .
+
+
+bits 64
+
+;referenced in scheduler.c
+global user_task_start
+user_task_start:
+
+ mov cr3, rbx
+ mov rcx, rbp
+ mov r11, 0x200
+
+ xor rax, rax
+ xor rbx, rbx
+ xor rdx, rdx
+ xor rdi, rdi
+ xor rsi, rsi
+ xor rbp, rbp
+ xor r8, r8
+ xor r9, r9
+ xor r10, r10
+ xor r12, r12
+ xor r13, r13
+ xor r14, r14
+ xor r15, r15
+
+ o64 sysret
+
+;referenced in scheduler.c
+global resume_continuation
+resume_continuation:
+
+ mov rax, qword [rdi]
+ mov rbx, qword [rdi + 8]
+ mov rbp, qword [rdi + 16]
+ mov rsp, qword [rdi + 24]
+ mov r12, qword [rdi + 32]
+ mov r13, qword [rdi + 40]
+ mov r14, qword [rdi + 48]
+ mov r15, qword [rdi + 56]
+
+ jmp rax
diff --git a/src/kernel/scheduler.c b/src/kernel/scheduler.c
new file mode 100644
index 0000000..f3d4595
--- /dev/null
+++ b/src/kernel/scheduler.c
@@ -0,0 +1,118 @@
+/* Calcite, src/kernel/scheduler.c
+ * Copyright 2025 Benji Dial
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#include "scheduler.h"
+#include "utility.h"
+#include "heap.h"
+
+struct continuation_info {
+ uint64_t rip;
+ uint64_t rbx;
+ uint64_t rbp;
+ uint64_t rsp;
+ uint64_t r12;
+ uint64_t r13;
+ uint64_t r14;
+ uint64_t r15;
+};
+
+static struct continuation_info **ready_continuations = 0;
+static int rc_buffer_length = 0;
+static int rc_read_ptr = 0;
+static int rc_count = 0;
+
+#define INITIAL_RC_BUFFER_LENGTH 128
+
+void init_scheduler() {
+ ready_continuations = heap_alloc(INITIAL_RC_BUFFER_LENGTH * sizeof(void *));
+ rc_buffer_length = INITIAL_RC_BUFFER_LENGTH;
+ for (int i = 0; i < INITIAL_RC_BUFFER_LENGTH; ++i)
+ ready_continuations[i] = 0;
+}
+
+static void queue_continuation(struct continuation_info *info) {
+
+ if (rc_count == rc_buffer_length) {
+
+ struct continuation_info **new_rc_buffer =
+ heap_alloc(2 * rc_buffer_length * sizeof(void *));
+
+ memcpy(
+ new_rc_buffer,
+ ready_continuations + rc_read_ptr,
+ (rc_buffer_length - rc_read_ptr) * sizeof(void *));
+
+ memcpy(
+ new_rc_buffer + rc_buffer_length - rc_read_ptr,
+ ready_continuations,
+ rc_read_ptr * sizeof(void *));
+
+ heap_dealloc(ready_continuations, rc_buffer_length * sizeof(void *));
+
+ new_rc_buffer[rc_buffer_length] = info;
+ for (int i = rc_buffer_length + 1; i < 2 * rc_buffer_length; ++i)
+ new_rc_buffer[i] = 0;
+
+ ready_continuations = new_rc_buffer;
+ rc_buffer_length *= 2;
+ rc_read_ptr = 0;
+ ++rc_count;
+
+ }
+
+ else {
+ ready_continuations[(rc_read_ptr + rc_count) % rc_buffer_length] = info;
+ ++rc_count;
+ }
+
+}
+
+//defined in scheduler.asm
+void user_task_start();
+
+void create_user_task(
+ uint64_t cr3, uint64_t rip, uint64_t rsp) {
+
+ struct continuation_info *info =
+ heap_alloc(sizeof(struct continuation_info));
+
+ info->rip = (uint64_t)&user_task_start;
+ info->rsp = (uint64_t)rsp;
+ info->rbx = cr3;
+ info->rbp = rip;
+
+ queue_continuation(info);
+
+}
+
+//defined in scheduler.asm
+[[noreturn]] void resume_continuation(struct continuation_info *info);
+
+[[noreturn]] void resume_next_continuation() {
+
+ while (rc_count == 0)
+ __asm__ ("hlt");
+
+ struct continuation_info *info = ready_continuations[rc_read_ptr];
+
+ ready_continuations[rc_read_ptr] = 0;
+ rc_read_ptr = (rc_read_ptr + 1) % rc_buffer_length;
+ --rc_count;
+
+ resume_continuation(info);
+
+}
diff --git a/src/kernel/scheduler.h b/src/kernel/scheduler.h
new file mode 100644
index 0000000..cf503f3
--- /dev/null
+++ b/src/kernel/scheduler.h
@@ -0,0 +1,27 @@
+/* Calcite, src/kernel/scheduler.h
+ * Copyright 2025 Benji Dial
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#pragma once
+
+#include
+
+void init_scheduler();
+
+void create_user_task(
+ uint64_t cr3, uint64_t rip, uint64_t rsp);
+
+[[noreturn]] void resume_next_continuation();
diff --git a/src/kernel/syscalls.asm b/src/kernel/syscalls.asm
new file mode 100644
index 0000000..de55a1d
--- /dev/null
+++ b/src/kernel/syscalls.asm
@@ -0,0 +1,71 @@
+ ; Calcite, src/kernel/syscalls.asm
+ ; Copyright 2025 Benji Dial
+ ;
+ ; This program is free software: you can redistribute it and/or modify
+ ; it under the terms of the GNU General Public License as published by
+ ; the Free Software Foundation, either version 3 of the License, or
+ ; (at your option) any later version.
+ ;
+ ; This program is distributed in the hope that it will be useful, but
+ ; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ ; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ ; for more details.
+ ;
+ ; You should have received a copy of the GNU General Public License along
+ ; with this program. If not, see .
+
+
+bits 64
+
+extern syscall_entry_c
+
+section .bss
+
+;this should have guard pages blah blah blah
+ resb 16 << 20
+syscall_stack_top:
+
+section .text
+
+;system call number is in rax.
+;system call arguments are in rdi, rsi, rdx.
+;system call returns a value in rax.
+syscall_entry:
+ mov qword [syscall_stack_top - 8], rsp
+ mov rsp, syscall_stack_top - 8
+ push r11
+ push rcx
+
+ mov rcx, rax
+ call syscall_entry_c
+
+ pop rcx
+ pop r11
+ pop rsp
+ o64 sysret
+
+global init_syscalls
+init_syscalls:
+
+ mov ecx, 0xc0000080
+ rdmsr
+ or al, 0x01
+ wrmsr
+
+ mov edx, 0x00130028
+ xor eax, eax
+ mov ecx, 0xc0000081
+ wrmsr
+
+ mov rdx, syscall_entry
+ mov eax, edx
+ shr rdx, 32
+ mov ecx, 0xc0000082
+ wrmsr
+
+ xor edx, edx
+ xor eax, eax
+ mov ecx, 0xc0000084
+ wrmsr
+
+ ret
diff --git a/src/kernel/syscalls.c b/src/kernel/syscalls.c
new file mode 100644
index 0000000..18e6124
--- /dev/null
+++ b/src/kernel/syscalls.c
@@ -0,0 +1,46 @@
+/* Calcite, src/kernel/syscalls.c
+ * Copyright 2025 Benji Dial
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#include "syscalls.h"
+#include "panic.h"
+
+#define MAX_SYSCALL_NUMBER 99
+
+static uint64_t (*syscall_handlers[MAX_SYSCALL_NUMBER])(
+ uint64_t, uint64_t, uint64_t);
+
+void register_syscall(
+ int number,
+ uint64_t (*handler)(uint64_t, uint64_t, uint64_t)) {
+
+ assert(number >= 0 && number <= MAX_SYSCALL_NUMBER)
+ assert(syscall_handlers[number] == 0)
+
+ syscall_handlers[number] = handler;
+
+}
+
+//referenced in syscalls.asm
+uint64_t syscall_entry_c(
+ uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t number) {
+
+ if (number > MAX_SYSCALL_NUMBER || syscall_handlers[number] == 0)
+ panic("TODO: bad syscall");
+
+ return (*syscall_handlers[number])(arg1, arg2, arg3);
+
+}
diff --git a/src/kernel/syscalls.h b/src/kernel/syscalls.h
new file mode 100644
index 0000000..3171a7a
--- /dev/null
+++ b/src/kernel/syscalls.h
@@ -0,0 +1,27 @@
+/* Calcite, src/kernel/syscalls.h
+ * Copyright 2025 Benji Dial
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#pragma once
+
+#include
+#include
+
+void init_syscalls();
+
+void register_syscall(
+ int number,
+ uint64_t (*handler)(uint64_t arg1, uint64_t arg2, uint64_t arg3));
diff --git a/src/kernel/utility.asm b/src/kernel/utility.asm
new file mode 100644
index 0000000..4922388
--- /dev/null
+++ b/src/kernel/utility.asm
@@ -0,0 +1,33 @@
+ ; Calcite, src/kernel/utility.asm
+ ; Copyright 2025 Benji Dial
+ ;
+ ; This program is free software: you can redistribute it and/or modify
+ ; it under the terms of the GNU General Public License as published by
+ ; the Free Software Foundation, either version 3 of the License, or
+ ; (at your option) any later version.
+ ;
+ ; This program is distributed in the hope that it will be useful, but
+ ; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ ; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ ; for more details.
+ ;
+ ; You should have received a copy of the GNU General Public License along
+ ; with this program. If not, see .
+
+
+bits 64
+
+section .text
+
+;global memcpy
+;memcpy:
+; mov rcx, rdx
+; rep movsb
+; ret
+
+;global memzero
+;memzero:
+; xor al, al
+; mov rcx, rsi
+; rep stosb
+; ret
diff --git a/kernel/src/utility.c b/src/kernel/utility.c
similarity index 59%
rename from kernel/src/utility.c
rename to src/kernel/utility.c
index a46a357..742ac5d 100644
--- a/kernel/src/utility.c
+++ b/src/kernel/utility.c
@@ -1,21 +1,21 @@
-/* Calcite, kernel/src/utility.c
+/* Calcite, src/kernel/utility.c
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or (at
- * your option) any later version.
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
*
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
*/
-#include
+#include "utility.h"
int strequ(const char *str1, const char *str2) {
while (1) {
@@ -27,3 +27,13 @@ int strequ(const char *str1, const char *str2) {
++str2;
}
}
+
+void memcpy(void *to, const void *from, uint64_t bytes) {
+ for (uint64_t i = 0; i < bytes; ++i)
+ *(uint8_t *)(to + i) = *(const uint8_t *)(from + i);
+}
+
+void memzero(void *start, uint64_t bytes) {
+ for (uint64_t i = 0; i < bytes; ++i)
+ *(uint8_t *)(start + i) = 0;
+}
diff --git a/kernel/include/utility.h b/src/kernel/utility.h
similarity index 83%
rename from kernel/include/utility.h
rename to src/kernel/utility.h
index 19d3995..cc679a6 100644
--- a/kernel/include/utility.h
+++ b/src/kernel/utility.h
@@ -1,4 +1,4 @@
-/* Calcite, kernel/include/utility.h
+/* Calcite, src/kernel/utility.h
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
@@ -17,5 +17,10 @@
#pragma once
+#include
+
//returns 1 if equal, 0 if not.
int strequ(const char *str1, const char *str2);
+
+void memcpy(void *to, const void *from, uint64_t bytes);
+void memzero(void *start, uint64_t bytes);
diff --git a/src/user-apps/hello/hello.c b/src/user-apps/hello/hello.c
new file mode 100644
index 0000000..20ea16a
--- /dev/null
+++ b/src/user-apps/hello/hello.c
@@ -0,0 +1,35 @@
+/* Calcite, src/user-apps/hello/hello.c
+ * Copyright 2025 Benji Dial
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#include
+
+[[noreturn]]
+void _start() {
+
+ struct framebuffer_info fb_info;
+ map_framebuffer(&fb_info);
+
+ for (int y = 0; y < fb_info.fb_height; ++y)
+ for (int x = 0; x < fb_info.fb_width; ++x) {
+ uint8_t *pixel = fb_info.fb_base + y * fb_info.fb_pitch + x * 4;
+ pixel[0] = x * 256 / fb_info.fb_width;
+ pixel[1] = y * 256 / fb_info.fb_height;
+ }
+
+ end_thread();
+
+}
diff --git a/src/user-apps/hello/libraries.txt b/src/user-apps/hello/libraries.txt
new file mode 100644
index 0000000..8503661
--- /dev/null
+++ b/src/user-apps/hello/libraries.txt
@@ -0,0 +1 @@
+calcite
diff --git a/src/user-libs/calcite/syscalls.asm b/src/user-libs/calcite/syscalls.asm
new file mode 100644
index 0000000..4ded113
--- /dev/null
+++ b/src/user-libs/calcite/syscalls.asm
@@ -0,0 +1,27 @@
+ ; Calcite, src/user-libs/calcite/syscalls.asm
+ ; Copyright 2025 Benji Dial
+ ;
+ ; This program is free software: you can redistribute it and/or modify
+ ; it under the terms of the GNU General Public License as published by
+ ; the Free Software Foundation, either version 3 of the License, or
+ ; (at your option) any later version.
+ ;
+ ; This program is distributed in the hope that it will be useful, but
+ ; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ ; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ ; for more details.
+ ;
+ ; You should have received a copy of the GNU General Public License along
+ ; with this program. If not, see .
+
+
+bits 64
+
+section .text
+
+;referenced in syscalls.c
+global do_syscall
+do_syscall:
+ mov rax, rcx
+ syscall
+ ret
diff --git a/src/user-libs/calcite/syscalls.c b/src/user-libs/calcite/syscalls.c
new file mode 100644
index 0000000..d1584ce
--- /dev/null
+++ b/src/user-libs/calcite/syscalls.c
@@ -0,0 +1,32 @@
+/* Calcite, src/user-libs/calcite/syscalls.c
+ * Copyright 2025 Benji Dial
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#include
+#include
+
+//defined in syscalls.asm
+uint64_t do_syscall
+ (uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t number);
+
+[[noreturn]] void end_thread() {
+ do_syscall(0, 0, 0, SYSCALL_END_THREAD);
+ __builtin_unreachable();
+}
+
+void map_framebuffer(struct framebuffer_info *info_out) {
+ do_syscall((uint64_t)info_out, 0, 0, SYSCALL_MAP_FRAMEBUFFER);
+}