diff --git a/compile_flags.txt b/compile_flags.txt
index eb64b69..bfe9d86 100644
--- a/compile_flags.txt
+++ b/compile_flags.txt
@@ -3,6 +3,8 @@
-I
dependencies/limine
-I
+dependencies/flanterm/src
+-I
include
-D
CALCITE_DEBUG
diff --git a/disk/calcite/apps/hello/pointer.pam b/disk/calcite/apps/hello/pointer.pam
deleted file mode 100644
index 18bd8ce..0000000
Binary files a/disk/calcite/apps/hello/pointer.pam and /dev/null differ
diff --git a/disk/calcite/init.rc b/disk/calcite/init.rc
index f00bbe3..cfef8bd 100644
--- a/disk/calcite/init.rc
+++ b/disk/calcite/init.rc
@@ -1 +1 @@
-root://calcite/apps/hello/hello.elf
+root://calcite/apps/terminal/terminal.elf
diff --git a/get-dependencies.sh b/get-dependencies.sh
index 5b025bc..da748d5 100644
--- a/get-dependencies.sh
+++ b/get-dependencies.sh
@@ -1,6 +1,7 @@
#!/bin/sh
LIMINE_VERSION="9.3.4"
+FLANTERM_VERSION="2.1.0"
if [ -e dependencies ]; then
echo dependencies directory already exists.
@@ -12,7 +13,10 @@ set -e
mkdir dependencies
cd dependencies
-curl -L https://github.com/limine-bootloader/limine/archive/refs/tags/v${LIMINE_VERSION}-binary.tar.gz | tar xz
-mv limine-${LIMINE_VERSION}-binary limine
+curl -L https://codeberg.org/Limine/Limine/archive/v${LIMINE_VERSION}-binary.tar.gz | tar xz
make -C limine
cd ..
+
+cd dependencies
+curl -L https://codeberg.org/Mintsuki/Flanterm/archive/v${FLANTERM_VERSION}.tar.gz | tar xz
+cd ..
diff --git a/include/calcite/dispatch.h b/include/calcite/dispatch.h
new file mode 100644
index 0000000..a159a0d
--- /dev/null
+++ b/include/calcite/dispatch.h
@@ -0,0 +1,24 @@
+/* Calcite, include/calcite/dispatch.h
+ * Copyright 2026 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
+
+struct dispatch_queue;
+
+struct dispatch_queue *create_dispatch_queue();
+void dispatch(struct dispatch_queue *queue, void (*callback)(void *x), void *x);
+[[noreturn]] void dispatch_loop(struct dispatch_queue *queue);
diff --git a/include/calcite/format.h b/include/calcite/format.h
new file mode 100644
index 0000000..fb11257
--- /dev/null
+++ b/include/calcite/format.h
@@ -0,0 +1,26 @@
+/* Calcite, include/calcite/format.h
+ * Copyright 2026 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
+
+//does not write null terminator. buffer must be at least digit_count bytes long.
+void to_hex(uint64_t value, int digit_count, char *buffer);
+
+//buffer must be at least digit_count bytes long.
+uint64_t from_hex(int digit_count, char *buffer);
diff --git a/include/calcite/memory.h b/include/calcite/memory.h
index 70993bd..7a74414 100644
--- a/include/calcite/memory.h
+++ b/include/calcite/memory.h
@@ -1,5 +1,5 @@
/* Calcite, include/calcite/memory.h
- * Copyright 2025 Benji Dial
+ * Copyright 2025-2026 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
@@ -26,3 +26,4 @@ void *heap_alloc(uint64_t bytes);
void heap_dealloc(void *start, uint64_t bytes);
void memcpy(void *to, const void *from, uint64_t bytes);
+void memset(void *start, uint8_t value, uint64_t length);
diff --git a/include/calcite/syscalls.h b/include/calcite/syscalls.h
index 2f0abc0..3fcdadd 100644
--- a/include/calcite/syscalls.h
+++ b/include/calcite/syscalls.h
@@ -1,5 +1,5 @@
/* Calcite, include/calcite/syscalls.h
- * Copyright 2025 Benji Dial
+ * Copyright 2025-2026 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
@@ -21,6 +21,7 @@
#include
#include
#include
+#include
#include
[[noreturn]] void end_thread();
@@ -55,6 +56,11 @@ enum ipc_dgram_result ipc_receive_dgram(ipc_dgram_receiver_handle_t handle, void
//bytes must be positive.
enum ipc_dgram_result ipc_send_dgram(ipc_dgram_sender_handle_t handle, const void *data, int bytes);
+//returned id is global across processes. only one receiver and one sender can be created per pipe.
+uint64_t ipc_create_private_dgram_pipe();
+enum ipc_dgram_result ipc_create_private_dgram_receiver(uint64_t id, ipc_dgram_receiver_handle_t *handle_out);
+enum ipc_dgram_result ipc_create_private_dgram_sender(uint64_t id, ipc_dgram_sender_handle_t *handle_out);
+
//f should not return
void create_thread(void (*f)(uint64_t x), uint64_t x);
@@ -65,3 +71,14 @@ void create_thread(void (*f)(uint64_t x), uint64_t x);
//and if that is at most what value_space was set to at entry, also
//copies the value including null terminator to value_out.
void get_envvar(const char *key, char *value_out, int *value_space);
+
+mutex_handle_t create_mutex();
+void acquire_mutex(mutex_handle_t handle);
+void release_mutex(mutex_handle_t handle);
+
+event_handle_t create_event();
+void signal_event(event_handle_t handle);
+void wait_event(event_handle_t handle);
+
+uint64_t get_ms_since_boot();
+void sleep_until_ms_since_boot(uint64_t ms);
diff --git a/include/calcite/terminal.h b/include/calcite/terminal.h
new file mode 100644
index 0000000..d6e4986
--- /dev/null
+++ b/include/calcite/terminal.h
@@ -0,0 +1,22 @@
+/* Calcite, include/calcite/terminal.h
+ * Copyright 2026 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
+
+void init_terminal();
+void terminal_write(const char *data, int bytes);
+void terminal_read(char *buffer, int bytes);
diff --git a/include/kernel-public/sync.h b/include/kernel-public/sync.h
new file mode 100644
index 0000000..893b6ca
--- /dev/null
+++ b/include/kernel-public/sync.h
@@ -0,0 +1,23 @@
+/* Calcite, include/kernel-public/sync.h
+ * Copyright 2026 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
+
+typedef uint64_t mutex_handle_t;
+typedef uint64_t event_handle_t;
diff --git a/include/kernel-public/syscall-numbers.h b/include/kernel-public/syscall-numbers.h
index 1c1cc3b..351d804 100644
--- a/include/kernel-public/syscall-numbers.h
+++ b/include/kernel-public/syscall-numbers.h
@@ -1,5 +1,5 @@
/* Calcite, include/kernel-public/syscall-numbers.h
- * Copyright 2025 Benji Dial
+ * Copyright 2025-2026 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
@@ -33,5 +33,16 @@ enum {
SYSCALL_IPC_RECEIVE_DGRAM,
SYSCALL_IPC_SEND_DGRAM,
SYSCALL_CREATE_THREAD,
- SYSCALL_GET_ENVVAR
+ SYSCALL_GET_ENVVAR,
+ SYSCALL_CREATE_MUTEX,
+ SYSCALL_ACQUIRE_MUTEX,
+ SYSCALL_RELEASE_MUTEX,
+ SYSCALL_CREATE_EVENT,
+ SYSCALL_SIGNAL_EVENT,
+ SYSCALL_WAIT_EVENT,
+ SYSCALL_IPC_CREATE_PRIVATE_DGRAM_PIPE,
+ SYSCALL_IPC_CREATE_PRIVATE_DGRAM_RECEIVER,
+ SYSCALL_IPC_CREATE_PRIVATE_DGRAM_SENDER,
+ SYSCALL_GET_MS_SINCE_BOOT,
+ SYSCALL_SLEEP_UNTIL_MS_SINCE_BOOT
};
diff --git a/make-build.sh b/make-build.sh
index 3630d18..126013b 100644
--- a/make-build.sh
+++ b/make-build.sh
@@ -18,7 +18,7 @@ fi
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}"
+USER_CC_FLAGS="-mno-sse -I dependencies/flanterm/src ${COMMON_CC_FLAGS}"
if [ -e build.ninja ]; then
echo build.ninja already exists.
@@ -78,6 +78,15 @@ build_all() {
build_all src/kernel kernel_cc kernel_ld kernel.elf
+objects=""
+for file in flanterm.c flanterm_backends/fb.c; do
+ src="dependencies/flanterm/src/$file"
+ build="build/user-libs/flanterm/$file.o"
+ echo "build $build: user_cc $src" >> build.ninja
+ objects="$objects $build"
+done
+echo "build build/user-libs/flanterm/libflanterm.o: user_lib_ld $objects" >> build.ninja
+
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
diff --git a/qemu.gdb b/qemu.gdb
index 6de1ab4..8ec043a 100644
--- a/qemu.gdb
+++ b/qemu.gdb
@@ -1,6 +1,5 @@
target remote | qemu-system-x86_64 -gdb stdio -cdrom build/disk.iso -boot d
symbol-file build/kernel/kernel.elf
-add-symbol-file build/user-apps/hello/hello.elf
set disassembly-flavor intel
set print asm-demangle on
layout src
diff --git a/readme.txt b/readme.txt
index 5df6c0f..5c3f22f 100644
--- a/readme.txt
+++ b/readme.txt
@@ -48,17 +48,25 @@ information. If you already built Calcite in "release" mode, run:
sh make-build.sh debug
ninja
-If you did not already built Calcite, then you do not need the first command.
+If you did not already build Calcite, then you do not need the first command.
=== License
-Calcite is Copyright 2025 Benji Dial, and licensed under the GNU GPL v3. See
-"license.txt" for the text of the license.
+Calcite is Copyright 2025-2026 Benji Dial, and licensed under the GNU GPL v3.
+See "license.txt" for the text of the license.
-The disk image includes the Limine bootloader. Limine is Copyright 2019-2025
-Mintsuki and contributors, and is released under the BSD 2-Clause license.
-You can find Limine online at .
-After running "sh get-dependencies.sh", you can find the license of Limine at
-"dependencies/limine/LICENSE". Limine also has its own dependencies. In the
-source repository of Limine, you can find a list of these with their licenses
-at "3RDPARTY.md".
+The disk image includes the Limine bootloader, version 9.3.4. Limine is
+Copyright 2019-2025 Mintsuki and contributors, and is released under the
+BSD 2-Clause license. You can find Limine online at
+ .
+After running "sh get-dependencies.sh", you can find the license of Limine
+at "dependencies/limine/LICENSE". Limine also has its own dependencies.
+In the source repository of Limine, you can find a list of these with
+their licenses at "3RDPARTY.md".
+
+The terminal, included in the disk image, is linked with the Flanterm library,
+version 2.1.0. Flanterm is Copyright 2022-2025 minstuki and contributors, and
+is released under the BSD 2-Clause license. You can find Flanterm online at
+ .
+After running "sh get-dependencies.sh", you can find the license of Flanterm
+at "dependencies/flanterm/LICENSE".
diff --git a/src/kernel/debug.h b/src/kernel/debug.h
index 82f80d4..3c531d1 100644
--- a/src/kernel/debug.h
+++ b/src/kernel/debug.h
@@ -1,5 +1,5 @@
/* Calcite, src/kernel/debug.h
- * Copyright 2025 Benji Dial
+ * Copyright 2025-2026 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
@@ -19,12 +19,14 @@
void log_core(const char *file, const char *function, const char *format, ...);
-#ifdef CALCITE_DEBUG
-#define debug_log(...) { \
+#define log(...) { \
log_core(__FILE__, __func__, __VA_ARGS__); \
}
+
+#ifdef CALCITE_DEBUG
+#define debug_log(...) log(__VA_ARGS__)
#elif CALCITE_RELEASE
-#define debug_log(format, ...) {}
+#define debug_log(...) {}
#else
#error neither CALCITE_DEBUG nor CALCITE_RELEASE defined
#endif
diff --git a/src/kernel/entry.c b/src/kernel/entry.c
index b725c2d..7303b55 100644
--- a/src/kernel/entry.c
+++ b/src/kernel/entry.c
@@ -1,5 +1,5 @@
/* Calcite, src/kernel/entry.c
- * Copyright 2025 Benji Dial
+ * Copyright 2025-2026 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
@@ -17,6 +17,7 @@
#include "framebuffer.h"
#include "interrupts.h"
+#include "ipc-dgram.h"
#include "scheduler.h"
#include "syscalls.h"
#include "process.h"
@@ -279,6 +280,17 @@ static const char *cmdline_look_up(const char *key) {
register_syscall(SYSCALL_IPC_SEND_DGRAM, (void *)&syscall_ipc_send_dgram);
register_syscall(SYSCALL_CREATE_THREAD, (void *)&syscall_create_thread);
register_syscall(SYSCALL_GET_ENVVAR, (void *)&syscall_get_envvar);
+ register_syscall(SYSCALL_CREATE_MUTEX, (void *)&syscall_create_mutex);
+ register_syscall(SYSCALL_ACQUIRE_MUTEX, (void *)&syscall_acquire_mutex);
+ register_syscall(SYSCALL_RELEASE_MUTEX, (void *)&syscall_release_mutex);
+ register_syscall(SYSCALL_CREATE_EVENT, (void *)&syscall_create_event);
+ register_syscall(SYSCALL_SIGNAL_EVENT, (void *)&syscall_signal_event);
+ register_syscall(SYSCALL_WAIT_EVENT, (void *)&syscall_wait_event);
+ register_syscall(SYSCALL_IPC_CREATE_PRIVATE_DGRAM_PIPE, (void *)&syscall_ipc_create_private_dgram_pipe);
+ register_syscall(SYSCALL_IPC_CREATE_PRIVATE_DGRAM_RECEIVER, (void *)&syscall_ipc_create_private_dgram_receiver);
+ register_syscall(SYSCALL_IPC_CREATE_PRIVATE_DGRAM_SENDER, (void *)&syscall_ipc_create_private_dgram_sender);
+ register_syscall(SYSCALL_GET_MS_SINCE_BOOT, (void *)&syscall_get_ms_since_boot);
+ register_syscall(SYSCALL_SLEEP_UNTIL_MS_SINCE_BOOT, (void *)&syscall_sleep_until_ms_since_boot);
//probe pci devices
diff --git a/src/kernel/interrupts.asm b/src/kernel/interrupts.asm
index 3dcd723..1fd0f10 100644
--- a/src/kernel/interrupts.asm
+++ b/src/kernel/interrupts.asm
@@ -1,5 +1,5 @@
; Calcite, src/kernel/interrupts.asm
- ; Copyright 2025 Benji Dial
+ ; Copyright 2025-2026 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
@@ -71,6 +71,10 @@ isr_exception_common:
push r13
push r14
push r15
+ mov rax, cr2
+ push rax
+ mov rax, cr3
+ push rax
mov rdi, rsp
jmp isr_exception_c
diff --git a/src/kernel/interrupts.c b/src/kernel/interrupts.c
index c10326f..f1d64a4 100644
--- a/src/kernel/interrupts.c
+++ b/src/kernel/interrupts.c
@@ -1,5 +1,5 @@
/* Calcite, src/kernel/interrupts.c
- * Copyright 2025 Benji Dial
+ * Copyright 2025-2026 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
@@ -24,7 +24,10 @@
#include "interrupts.h"
#include "debug.h"
+//interrupts.asm depends on layout
struct [[gnu::packed]] exception_parameter {
+ uint64_t cr3;
+ uint64_t cr2;
uint64_t r15;
uint64_t r14;
uint64_t r13;
@@ -50,10 +53,60 @@ struct [[gnu::packed]] exception_parameter {
uint64_t ss;
};
+//names are from amd64 manual mentioned above
+const char *exception_names[32] = {
+ "divide-by-zero-error", "debug", "non-maskable-interrupt", "breakpoint", "overflow",
+ "bound-range", "invalid-opcode", "device-not-available", "double-fault", 0,
+ "invalid-tss", "segment-not-present", "stack", "general-protection", "page-fault",
+ 0, "x87 floating-point exception-pending", "alignment-check", "machine-check",
+ "simd floating-point", 0, 0, 0, 0, 0, 0, 0, 0,
+ "hypervisor injection exception", "vmm communication exception", "security exception", 0
+};
+
+[[noreturn]] static void unhandled_exception(struct exception_parameter *parameter) {
+
+ assert(parameter->exception_number < 32 && exception_names[parameter->exception_number] != 0)
+
+ log("unhandled exception")
+ log(" exception name = %s", exception_names[parameter->exception_number])
+ log(" error code = 0x%h", parameter->error_code, 16)
+ log(" cr2 = 0x%h", parameter->cr2, 16)
+ log(" cr3 = 0x%h", parameter->cr3, 16)
+ log(" cs = 0x%h", parameter->cs, 16)
+ log(" rip = 0x%h", parameter->rip, 16)
+ log(" ss = 0x%h", parameter->ss, 16)
+ log(" rsp = 0x%h", parameter->rsp, 16)
+ log(" rflags = 0x%h", parameter->rflags, 16)
+ log(" rax = 0x%h", parameter->rax, 16)
+ log(" rbx = 0x%h", parameter->rbx, 16)
+ log(" rcx = 0x%h", parameter->rcx, 16)
+ log(" rdx = 0x%h", parameter->rdx, 16)
+ log(" rdi = 0x%h", parameter->rdi, 16)
+ log(" rsi = 0x%h", parameter->rsi, 16)
+ log(" rbp = 0x%h", parameter->rbp, 16)
+ log(" r8 = 0x%h", parameter->r8, 16)
+ log(" r9 = 0x%h", parameter->r9, 16)
+ log(" r10 = 0x%h", parameter->r10, 16)
+ log(" r11 = 0x%h", parameter->r11, 16)
+ log(" r12 = 0x%h", parameter->r12, 16)
+ log(" r13 = 0x%h", parameter->r13, 16)
+ log(" r14 = 0x%h", parameter->r14, 16)
+ log(" r15 = 0x%h", parameter->r15, 16)
+ log("halting")
+
+ __asm__ ("cli");
+ while (1)
+ __asm__ ("hlt");
+
+}
+
//referenced in interrupts.asm
void isr_exception_c(struct exception_parameter *parameter) {
- (void)parameter;
- panic("TODO");
+
+ //eventually some exceptions will be handled in here
+
+ unhandled_exception(parameter);
+
}
void (*irq_handlers[16])() = {
diff --git a/src/kernel/ipc-dgram.c b/src/kernel/ipc-dgram.c
index 083a09c..74c64fe 100644
--- a/src/kernel/ipc-dgram.c
+++ b/src/kernel/ipc-dgram.c
@@ -1,5 +1,5 @@
/* Calcite, src/kernel/ipc-dgram.c
- * Copyright 2025 Benji Dial
+ * Copyright 2025-2026 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
@@ -16,6 +16,7 @@
*/
#include "ipc-dgram.h"
+#include "process.h"
#include "scheduler.h"
#include "utility.h"
#include "debug.h"
@@ -51,6 +52,7 @@ struct ipc_dgram_box *get_ipc_dgram_box(const char *address) {
new->address = new_address;
new->box.is_receiver_held = 0;
+ new->box.is_private = 0;
new->box.is_someone_waiting_to_receive = 0;
create_queue(&new->box.waiting_to_send, INITIAL_SEND_BACKLOG);
new->box.buffer_read_pointer = 0;
@@ -145,3 +147,123 @@ enum ipc_dgram_result send_ipc_dgram(struct ipc_dgram_box *box, const void *data
return IPR_SUCCESS;
}
+
+static uint64_t next_private_id = 0;
+
+struct ipc_dgram_private_book {
+ uint64_t id;
+ struct ipc_dgram_box *box;
+ struct ipc_dgram_private_book *prev;
+};
+
+static struct ipc_dgram_private_book *last_without_receiver = 0;
+static struct ipc_dgram_private_book *last_without_sender = 0;
+
+//0 for none with that id
+static struct ipc_dgram_box *take_from_private_book(struct ipc_dgram_private_book **book, uint64_t id) {
+
+ while (*book != 0) {
+
+ if ((*book)->id == id) {
+ struct ipc_dgram_box *box = (*book)->box;
+ struct ipc_dgram_private_book *prev = (*book)->prev;
+ heap_dealloc(*book, sizeof(struct ipc_dgram_private_book));
+ *book = prev;
+ return box;
+ }
+
+ book = &(*book)->prev;
+
+ }
+
+ return 0;
+
+}
+
+uint64_t syscall_ipc_create_private_dgram_pipe() {
+
+ uint64_t id = next_private_id++;
+
+ struct ipc_dgram_box *box = heap_alloc(sizeof(struct ipc_dgram_box));
+ box->is_receiver_held = 0;
+ box->is_private = 1;
+ box->is_someone_waiting_to_receive = 0;
+ create_queue(&box->waiting_to_send, INITIAL_SEND_BACKLOG);
+ box->buffer_read_pointer = 0;
+ box->buffer_read_available = 0;
+
+ struct ipc_dgram_private_book *without_receiver = heap_alloc(sizeof(struct ipc_dgram_private_book));
+ without_receiver->id = id;
+ without_receiver->box = box;
+ without_receiver->prev = last_without_receiver;
+ last_without_receiver = without_receiver;
+
+ struct ipc_dgram_private_book *without_sender = heap_alloc(sizeof(struct ipc_dgram_private_book));
+ without_sender->id = id;
+ without_sender->box = box;
+ without_sender->prev = last_without_sender;
+ last_without_sender = without_sender;
+
+ return id;
+
+}
+
+enum ipc_dgram_result syscall_ipc_create_private_dgram_receiver(
+ uint64_t id, ipc_dgram_receiver_handle_t *handle_out) {
+
+ assert(running_thread != 0)
+
+ if (!is_mapped_writable(
+ running_thread->process, handle_out, sizeof(ipc_dgram_receiver_handle_t)))
+ syscall_illegal_args();
+
+ struct ipc_dgram_box *box = take_from_private_book(&last_without_receiver, id);
+ if (box == 0)
+ return IPR_BAD_HANDLE;
+
+ assert(box->is_receiver_held == 0)
+ assert(box->private_sides < 2)
+
+ box->is_receiver_held = 1;
+ ++box->private_sides;
+
+ struct process_ipc_dgram_handle_info *handle_info =
+ add_handle(&running_thread->process->ipc_dgram_handles, handle_out);
+
+ handle_info->box = box;
+ handle_info->is_receiver = 1;
+ return IPR_SUCCESS;
+
+}
+
+enum ipc_dgram_result syscall_ipc_create_private_dgram_sender(
+ uint64_t id, ipc_dgram_sender_handle_t *handle_out) {
+
+ assert(running_thread != 0)
+
+ if (!is_mapped_writable(
+ running_thread->process, handle_out, sizeof(ipc_dgram_sender_handle_t)))
+ syscall_illegal_args();
+
+ struct ipc_dgram_box *box = take_from_private_book(&last_without_sender, id);
+ if (box == 0)
+ return IPR_BAD_HANDLE;
+
+ assert(box->private_sides < 2)
+
+ ++box->private_sides;
+
+ struct process_ipc_dgram_handle_info *handle_info =
+ add_handle(&running_thread->process->ipc_dgram_handles, handle_out);
+
+ handle_info->box = box;
+ handle_info->is_receiver = 0;
+ return IPR_SUCCESS;
+
+}
+
+void destroy_ipc_dgram_box(struct ipc_dgram_box *box) {
+ assert(box->is_someone_waiting_to_receive == 0)
+ assert(box->waiting_to_send.count == 0)
+ destroy_queue(&box->waiting_to_send);
+}
diff --git a/src/kernel/ipc-dgram.h b/src/kernel/ipc-dgram.h
index d48c735..fd399f8 100644
--- a/src/kernel/ipc-dgram.h
+++ b/src/kernel/ipc-dgram.h
@@ -1,5 +1,5 @@
/* Calcite, src/kernel/ipc-dgram.h
- * Copyright 2025 Benji Dial
+ * Copyright 2025-2026 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
@@ -27,6 +27,8 @@
struct ipc_dgram_box {
int is_receiver_held;
+ int is_private;
+ int private_sides;
int is_someone_waiting_to_receive;
struct continuation_info waiting_to_receive;
@@ -56,3 +58,11 @@ void receive_ipc_dgram(struct ipc_dgram_box *box, void *buffer);
//only returns IPR_SUCCESS or IPR_TOO_BIG
enum ipc_dgram_result send_ipc_dgram(struct ipc_dgram_box *box, const void *data, int bytes);
+
+uint64_t syscall_ipc_create_private_dgram_pipe();
+enum ipc_dgram_result syscall_ipc_create_private_dgram_receiver(
+ uint64_t id, ipc_dgram_receiver_handle_t *handle_out);
+enum ipc_dgram_result syscall_ipc_create_private_dgram_sender(
+ uint64_t id, ipc_dgram_sender_handle_t *handle_out);
+
+void destroy_ipc_dgram_box(struct ipc_dgram_box *box);
diff --git a/src/kernel/process.c b/src/kernel/process.c
index 73a696c..b3096d6 100644
--- a/src/kernel/process.c
+++ b/src/kernel/process.c
@@ -1,5 +1,5 @@
/* Calcite, src/kernel/process.c
- * Copyright 2025 Benji Dial
+ * Copyright 2025-2026 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
@@ -27,14 +27,9 @@
#include
#include
+#include
#include
-struct process_file_info *get_file_info(struct process *process, file_handle_t handle) {
- if (handle > (uint64_t)process->files_buffer_size || process->files[handle].fs == 0)
- return 0;
- return &process->files[handle];
-}
-
void create_process(struct process *process_out) {
process_out->p4_physical_base = take_free_physical_page();
@@ -64,10 +59,14 @@ void create_process(struct process *process_out) {
}
process_out->n_threads = 0;
- process_out->files = 0;
- process_out->ipc_dgram_handles = 0;
process_out->env_pairs = 0;
+ create_handle_list(&process_out->file_handles, sizeof(struct process_file_info));
+ create_handle_list(
+ &process_out->ipc_dgram_handles, sizeof(struct process_ipc_dgram_handle_info));
+ create_handle_list(&process_out->mutex_handles, sizeof(struct process_mutex_info));
+ create_handle_list(&process_out->event_handles, sizeof(struct process_event_info));
+
}
void map_page_for_process(
@@ -455,25 +454,44 @@ int syscall_start_elf(const char *path, const struct process_start_info *info) {
}
+static void destroy_process_file_info(struct process_file_info *info) {
+ (*info->fs->free_node)(info->fs, info->node);
+}
+
+static void destroy_process_ipc_dgram_handle_info(struct process_ipc_dgram_handle_info *info) {
+ if (info->is_receiver == 1)
+ info->box->is_receiver_held = 0;
+ if (info->box->is_private) {
+ if (info->box->private_sides == 1) {
+ destroy_ipc_dgram_box(info->box);
+ heap_dealloc(info->box, sizeof(struct ipc_dgram_box));
+ }
+ else
+ --info->box->private_sides;
+ }
+}
+
+static void destroy_process_mutex_info(struct process_mutex_info *info) {
+ destroy_queue(&info->waiting_to_acquire);
+}
+
+static void destroy_process_event_info(struct process_event_info *info) {
+ destroy_queue(&info->waiting_for_signal);
+}
+
void destroy_process(struct process *process) {
assert(process->n_threads == 0)
- if (process->files) {
- for (int i = 0; i < process->files_buffer_size; ++i)
- if (process->files[i].fs != 0)
- (*process->files[i].fs->free_node)(process->files[i].fs, process->files[i].node);
- heap_dealloc(process->files, process->files_buffer_size * sizeof(struct process_file_info));
- }
+ for_each_handle(&process->file_handles, (void *)&destroy_process_file_info);
+ for_each_handle(&process->ipc_dgram_handles, (void *)&destroy_process_ipc_dgram_handle_info);
+ for_each_handle(&process->mutex_handles, (void *)&destroy_process_mutex_info);
+ for_each_handle(&process->event_handles, (void *)&destroy_process_event_info);
- if (process->ipc_dgram_handles) {
- for (int i = 0; i < process->ipc_dgram_handles_buffer_size; ++i)
- if (process->ipc_dgram_handles[i].box != 0 && process->ipc_dgram_handles[i].is_receiver == 1)
- process->ipc_dgram_handles[i].box->is_receiver_held = 0;
- heap_dealloc(
- process->ipc_dgram_handles,
- process->ipc_dgram_handles_buffer_size * sizeof(struct process_ipc_dgram_handle_info));
- }
+ destroy_handle_list(&process->file_handles);
+ destroy_handle_list(&process->ipc_dgram_handles);
+ destroy_handle_list(&process->mutex_handles);
+ destroy_handle_list(&process->event_handles);
if (process->env_pairs) {
for (int i = 0; i < process->env_pairs_buffer_size; ++i)
@@ -645,40 +663,10 @@ enum fs_access_result syscall_open_file(const char *path, file_handle_t *handle_
return result;
}
- if (running_thread->process->files == 0) {
- running_thread->process->files =
- heap_alloc(INITIAL_FILE_HANDLE_COUNT * sizeof(struct process_file_info));
- running_thread->process->files_buffer_size = INITIAL_FILE_HANDLE_COUNT;
- for (int i = 0; i < INITIAL_FILE_HANDLE_COUNT; ++i)
- running_thread->process->files[i].fs = 0;
- }
-
- for (int i = 0; i < running_thread->process->files_buffer_size; ++i)
- if (running_thread->process->files[i].fs == 0) {
- running_thread->process->files[i].fs = fs;
- running_thread->process->files[i].node = node;
- memcpy(&running_thread->process->files[i].stat, &stat, sizeof(struct fs_stat));
- *handle_out = i;
- return FAR_SUCCESS;
- }
-
- struct process_file_info *old_buffer = running_thread->process->files;
- int old_size = running_thread->process->files_buffer_size;
-
- struct process_file_info *new_buffer = heap_alloc(2 * old_size * sizeof(struct process_file_info));
- memcpy(new_buffer, old_buffer, old_size * sizeof(struct process_file_info));
- heap_dealloc(old_buffer, old_size * sizeof(struct process_file_info));
-
- new_buffer[old_size].fs = fs;
- new_buffer[old_size].node = node;
- memcpy(&new_buffer[old_size].stat, &stat, sizeof(struct fs_stat));
- for (int i = old_size + 1; i < old_size * 2; ++i)
- new_buffer[i].fs = 0;
-
- running_thread->process->files = new_buffer;
- running_thread->process->files_buffer_size *= 2;
-
- *handle_out = old_size;
+ struct process_file_info *info = add_handle(&running_thread->process->file_handles, handle_out);
+ info->fs = fs;
+ info->node = node;
+ memcpy(&info->stat, &stat, sizeof(struct fs_stat));
return FAR_SUCCESS;
}
@@ -687,7 +675,7 @@ void syscall_close_file(file_handle_t handle) {
assert(running_thread != 0)
- struct process_file_info *file = get_file_info(running_thread->process, handle);
+ struct process_file_info *file = look_up_handle(&running_thread->process->file_handles, handle);
if (file != 0) {
(*file->fs->free_node)(file->fs, file->node);
file->fs = 0;
@@ -699,7 +687,7 @@ enum fs_access_result syscall_get_file_size(file_handle_t handle, uint64_t *byte
assert(running_thread != 0)
- struct process_file_info *file = get_file_info(running_thread->process, handle);
+ struct process_file_info *file = look_up_handle(&running_thread->process->file_handles, handle);
if (file == 0)
return FAR_BAD_HANDLE;
@@ -718,7 +706,8 @@ enum fs_access_result syscall_read_file(struct read_file_parameter *parameter) {
running_thread->process, parameter->buffer, parameter->bytes))
syscall_illegal_args();
- struct process_file_info *file = get_file_info(running_thread->process, parameter->handle);
+ struct process_file_info *file =
+ look_up_handle(&running_thread->process->file_handles, parameter->handle);
if (file == 0)
return FAR_BAD_HANDLE;
@@ -747,46 +736,6 @@ void *syscall_map_pages(uint64_t count) {
}
-#define INITIAL_IPC_DGRAM_HANDLE_COUNT 16
-
-static uint64_t get_free_ipc_dgram_handle(struct process *process) {
-
- if (process->ipc_dgram_handles == 0) {
- process->ipc_dgram_handles =
- heap_alloc(INITIAL_IPC_DGRAM_HANDLE_COUNT * sizeof(struct process_ipc_dgram_handle_info));
- process->ipc_dgram_handles_buffer_size = INITIAL_IPC_DGRAM_HANDLE_COUNT;
- for (int i = 0; i < INITIAL_IPC_DGRAM_HANDLE_COUNT; ++i)
- process->ipc_dgram_handles[i].box = 0;
- return 0;
- }
-
- for (int i = 0; i < process->ipc_dgram_handles_buffer_size; ++i)
- if (process->ipc_dgram_handles[i].box == 0)
- return i;
-
- int old_size = process->ipc_dgram_handles_buffer_size;
- int new_size = old_size * 2;
- struct process_ipc_dgram_handle_info *new_buffer =
- heap_alloc(new_size * sizeof(struct process_ipc_dgram_handle_info));
-
- memcpy(
- new_buffer, process->ipc_dgram_handles,
- old_size * sizeof(struct process_ipc_dgram_handle_info));
-
- heap_dealloc(
- process->ipc_dgram_handles,
- old_size * sizeof(struct process_ipc_dgram_handle_info));
-
- process->ipc_dgram_handles = new_buffer;
- process->ipc_dgram_handles_buffer_size = new_size;
-
- for (int i = old_size; i < new_size; ++i)
- new_buffer[i].box = 0;
-
- return old_size;
-
-}
-
enum ipc_dgram_result syscall_ipc_create_dgram_receiver(
const char *address, ipc_dgram_receiver_handle_t *handle_out) {
@@ -803,8 +752,8 @@ enum ipc_dgram_result syscall_ipc_create_dgram_receiver(
box->is_receiver_held = 1;
- *handle_out = get_free_ipc_dgram_handle(running_thread->process);
- struct process_ipc_dgram_handle_info *info = &running_thread->process->ipc_dgram_handles[*handle_out];
+ struct process_ipc_dgram_handle_info *info =
+ add_handle(&running_thread->process->ipc_dgram_handles, handle_out);
info->box = box;
info->is_receiver = 1;
return IPR_SUCCESS;
@@ -823,8 +772,8 @@ enum ipc_dgram_result syscall_ipc_create_dgram_sender(
struct ipc_dgram_box *box = get_ipc_dgram_box(address);
- *handle_out = get_free_ipc_dgram_handle(running_thread->process);
- struct process_ipc_dgram_handle_info *info = &running_thread->process->ipc_dgram_handles[*handle_out];
+ struct process_ipc_dgram_handle_info *info =
+ add_handle(&running_thread->process->ipc_dgram_handles, handle_out);
info->box = box;
info->is_receiver = 0;
return IPR_SUCCESS;
@@ -836,18 +785,17 @@ enum ipc_dgram_result syscall_ipc_receive_dgram(
assert(running_thread != 0)
- if (!is_mapped_writable(running_thread->process, bytes, sizeof(uint64_t)) ||
+ if (!is_mapped_writable(running_thread->process, bytes, sizeof(int)) ||
!is_mapped_writable(running_thread->process, buffer, *bytes))
syscall_illegal_args();
- if (running_thread->process->ipc_dgram_handles == 0 ||
- handle >= (uint64_t)running_thread->process->ipc_dgram_handles_buffer_size ||
- running_thread->process->ipc_dgram_handles[handle].box == 0 ||
- running_thread->process->ipc_dgram_handles[handle].is_receiver != 1)
+ struct process_ipc_dgram_handle_info *info =
+ look_up_handle(&running_thread->process->ipc_dgram_handles, handle);
+
+ if (info == 0 || info->is_receiver != 1)
return IPR_BAD_HANDLE;
- struct ipc_dgram_box *box = running_thread->process->ipc_dgram_handles[handle].box;
- int actual_bytes = get_ipc_dgram_size(box);
+ int actual_bytes = get_ipc_dgram_size(info->box);
assert(actual_bytes > 0);
if (actual_bytes > *bytes) {
@@ -855,7 +803,7 @@ enum ipc_dgram_result syscall_ipc_receive_dgram(
return IPR_TOO_BIG;
}
- receive_ipc_dgram(box, buffer);
+ receive_ipc_dgram(info->box, buffer);
*bytes = actual_bytes;
return IPR_SUCCESS;
@@ -869,14 +817,13 @@ enum ipc_dgram_result syscall_ipc_send_dgram(
if (bytes <= 0 || !is_mapped_readable(running_thread->process, data, bytes))
syscall_illegal_args();
- if (running_thread->process->ipc_dgram_handles == 0 ||
- handle >= (uint64_t)running_thread->process->ipc_dgram_handles_buffer_size ||
- running_thread->process->ipc_dgram_handles[handle].box == 0 ||
- running_thread->process->ipc_dgram_handles[handle].is_receiver != 0)
+ struct process_ipc_dgram_handle_info *info =
+ look_up_handle(&running_thread->process->ipc_dgram_handles, handle);
+
+ if (info == 0 || info->is_receiver != 0)
return IPR_BAD_HANDLE;
- struct ipc_dgram_box *box = running_thread->process->ipc_dgram_handles[handle].box;
- return send_ipc_dgram(box, data, bytes);
+ return send_ipc_dgram(info->box, data, bytes);
}
@@ -989,3 +936,105 @@ void syscall_get_envvar(const char *key, char *value_out, int *value_space) {
memcpy(value_out, pair->value, pair->value_length + 1);
}
+
+mutex_handle_t syscall_create_mutex() {
+
+ assert(running_thread != 0)
+
+ mutex_handle_t handle;
+ struct process_mutex_info *info =
+ add_handle(&running_thread->process->mutex_handles, &handle);
+
+ info->is_acquired = 0;
+ create_queue(&info->waiting_to_acquire, 4);
+
+ return handle;
+
+}
+
+void syscall_acquire_mutex(mutex_handle_t handle) {
+
+ assert(running_thread != 0)
+
+ struct process_mutex_info *info = look_up_handle(&running_thread->process->mutex_handles, handle);
+ if (info == 0)
+ syscall_illegal_args();
+
+ while (info->is_acquired == 1)
+ yield_to_queue(&info->waiting_to_acquire);
+
+ info->is_acquired = 1;
+
+}
+
+void syscall_release_mutex(mutex_handle_t handle) {
+
+ assert(running_thread != 0)
+
+ struct process_mutex_info *info = look_up_handle(&running_thread->process->mutex_handles, handle);
+ if (info == 0 || info->is_acquired == 0)
+ syscall_illegal_args();
+
+ info->is_acquired = 0;
+
+ struct continuation_info ci;
+ if (take_from_queue(&info->waiting_to_acquire, &ci))
+ add_to_queue(&ready_continuations, &ci);
+
+}
+
+event_handle_t syscall_create_event() {
+
+ assert(running_thread != 0)
+
+ event_handle_t handle;
+ struct process_event_info *info =
+ add_handle(&running_thread->process->event_handles, &handle);
+
+ info->is_signaled = 0;
+ create_queue(&info->waiting_for_signal, 4);
+
+ return handle;
+
+}
+
+void syscall_signal_event(event_handle_t handle) {
+
+ assert(running_thread != 0)
+
+ struct process_event_info *info = look_up_handle(&running_thread->process->event_handles, handle);
+ if (info == 0)
+ syscall_illegal_args();
+
+ if (info->is_signaled == 1)
+ return;
+
+ int resumed_any = 0;
+
+ struct continuation_info ci;
+ while (take_from_queue(&info->waiting_for_signal, &ci)) {
+ add_to_queue(&ready_continuations, &ci);
+ resumed_any = 1;
+ }
+
+ if (resumed_any == 0)
+ info->is_signaled = 1;
+
+}
+
+void syscall_wait_event(event_handle_t handle) {
+
+ assert(running_thread != 0)
+
+ struct process_event_info *info = look_up_handle(&running_thread->process->event_handles, handle);
+ if (info == 0)
+ syscall_illegal_args();
+
+ if (info->is_signaled == 1) {
+ info->is_signaled = 0;
+ return;
+ }
+
+ yield_to_queue(&info->waiting_for_signal);
+
+}
diff --git a/src/kernel/process.h b/src/kernel/process.h
index 727390e..8915003 100644
--- a/src/kernel/process.h
+++ b/src/kernel/process.h
@@ -1,5 +1,5 @@
/* Calcite, src/kernel/process.h
- * Copyright 2025 Benji Dial
+ * Copyright 2025-2026 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
@@ -17,11 +17,14 @@
#pragma once
+#include "scheduler.h"
+#include "utility.h"
#include "fs.h"
#include
#include
#include
+#include
#include
struct ipc_dgram_box;
@@ -33,12 +36,21 @@ struct process_file_info {
};
struct process_ipc_dgram_handle_info {
- //0 if this handle number is not used.
struct ipc_dgram_box *box;
//1 if this is a receiver handle, 0 if this is a sender handle.
int is_receiver;
};
+struct process_mutex_info {
+ int is_acquired;
+ struct continuation_queue waiting_to_acquire;
+};
+
+struct process_event_info {
+ int is_signaled;
+ struct continuation_queue waiting_for_signal;
+};
+
//lengths don't include null terminators, buffers do.
//key and value should not be empty.
struct process_env_pair {
@@ -49,6 +61,7 @@ struct process_env_pair {
char *value;
};
+//layout used in scheduler.asm
struct process {
uint64_t p4_physical_base;
@@ -58,13 +71,10 @@ struct process {
int n_threads;
- //handles are indices into this.
- //0 for fs means unused handle.
- struct process_file_info *files;
- int files_buffer_size;
-
- struct process_ipc_dgram_handle_info *ipc_dgram_handles;
- int ipc_dgram_handles_buffer_size;
+ struct handle_list file_handles;
+ struct handle_list ipc_dgram_handles;
+ struct handle_list mutex_handles;
+ struct handle_list event_handles;
struct process_env_pair *env_pairs;
int env_pairs_buffer_size;
@@ -79,11 +89,7 @@ struct process {
};
-//returns 0 if that handle is not used.
-//return value might be invalidated by future allocation of file handles for this process.
-struct process_file_info *get_file_info(struct process *process, file_handle_t handle);
-
-//layout used in syscalls.asm
+//layout used in syscalls.asm and scheduler.asm
struct thread {
struct process *process;
@@ -184,3 +190,11 @@ struct process_env_pair *get_envvar(struct process *process, const char *key);
//see comment in include/calcite/syscalls.h
void syscall_get_envvar(const char *key, char *value_out, int *value_space);
+
+mutex_handle_t syscall_create_mutex();
+void syscall_acquire_mutex(mutex_handle_t handle);
+void syscall_release_mutex(mutex_handle_t handle);
+
+event_handle_t syscall_create_event();
+void syscall_signal_event(event_handle_t handle);
+void syscall_wait_event(event_handle_t handle);
diff --git a/src/kernel/scheduler.asm b/src/kernel/scheduler.asm
index 0937cce..c6ae2b1 100644
--- a/src/kernel/scheduler.asm
+++ b/src/kernel/scheduler.asm
@@ -1,5 +1,5 @@
; Calcite, src/kernel/scheduler.asm
- ; Copyright 2025 Benji Dial
+ ; Copyright 2025-2026 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
@@ -36,6 +36,9 @@ resume_continuation:
mov r15, qword [rdi + 56]
mov rdi, qword [rdi + 64]
mov qword [running_thread], rdi
+ mov rdi, qword [rdi]
+ mov rdi, qword [rdi]
+ mov cr3, rdi
jmp rax
@@ -93,7 +96,7 @@ yield_to_queue:
sub rsp, 72
mov rsi, rsp
call add_to_queue
- jmp resume_continuation
+ jmp resume_next_continuation
.ret:
ret
diff --git a/src/kernel/scheduler.c b/src/kernel/scheduler.c
index ccaa8a6..68aaaa6 100644
--- a/src/kernel/scheduler.c
+++ b/src/kernel/scheduler.c
@@ -1,5 +1,5 @@
/* Calcite, src/kernel/scheduler.c
- * Copyright 2025 Benji Dial
+ * Copyright 2025-2026 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
@@ -51,6 +51,11 @@ void create_queue(struct continuation_queue *queue, int buffer_size) {
queue->count = 0;
}
+void destroy_queue(struct continuation_queue *queue) {
+ assert(queue->count == 0)
+ heap_dealloc(queue->cis, queue->buffer_size * sizeof(struct continuation_info));
+}
+
void add_to_queue(struct continuation_queue *queue, struct continuation_info *ci) {
if (queue->count == queue->buffer_size) {
diff --git a/src/kernel/scheduler.h b/src/kernel/scheduler.h
index 7d1a866..2475f98 100644
--- a/src/kernel/scheduler.h
+++ b/src/kernel/scheduler.h
@@ -1,5 +1,5 @@
/* Calcite, src/kernel/scheduler.h
- * Copyright 2025 Benji Dial
+ * Copyright 2025-2026 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
@@ -17,10 +17,11 @@
#pragma once
-#include "process.h"
-
#include
+struct thread;
+
+//scheduler.asm depends on layout
struct continuation_info {
uint64_t rip;
uint64_t rbx;
@@ -47,6 +48,7 @@ struct continuation_queue {
extern struct continuation_queue ready_continuations;
void create_queue(struct continuation_queue *queue, int buffer_size);
+void destroy_queue(struct continuation_queue *queue);
//ci is copied
void add_to_queue(struct continuation_queue *queue, struct continuation_info *ci);
diff --git a/src/kernel/timer.c b/src/kernel/timer.c
index 405da78..31a792f 100644
--- a/src/kernel/timer.c
+++ b/src/kernel/timer.c
@@ -1,5 +1,5 @@
/* Calcite, src/kernel/timer.c
- * Copyright 2025 Benji Dial
+ * Copyright 2025-2026 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
@@ -91,3 +91,11 @@ static void sleep_until(uint64_t until) {
void syscall_sleep_ms(uint64_t ms_to_sleep) {
sleep_until(pit_irqs_since_boot + ms_to_sleep);
}
+
+uint64_t syscall_get_ms_since_boot() {
+ return pit_irqs_since_boot;
+}
+
+void syscall_sleep_until_ms_since_boot(uint64_t ms) {
+ sleep_until(ms);
+}
diff --git a/src/kernel/timer.h b/src/kernel/timer.h
index 9e53a09..c4466e2 100644
--- a/src/kernel/timer.h
+++ b/src/kernel/timer.h
@@ -1,5 +1,5 @@
/* Calcite, src/kernel/timer.h
- * Copyright 2025 Benji Dial
+ * Copyright 2025-2026 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
@@ -24,3 +24,5 @@ void init_timer();
void on_pit_irq();
void syscall_sleep_ms(uint64_t ms_to_sleep);
+uint64_t syscall_get_ms_since_boot();
+void syscall_sleep_until_ms_since_boot(uint64_t ms);
diff --git a/src/kernel/utility.c b/src/kernel/utility.c
index 2058505..ed5245e 100644
--- a/src/kernel/utility.c
+++ b/src/kernel/utility.c
@@ -1,5 +1,5 @@
/* Calcite, src/kernel/utility.c
- * Copyright 2025 Benji Dial
+ * Copyright 2025-2026 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
@@ -45,3 +45,73 @@ void double_buffer_zero(void **buffer, int *length, uint64_t bytes_per_entry) {
*buffer = new_buffer;
*length *= 2;
}
+
+void create_handle_list(struct handle_list *list, uint64_t entry_size) {
+ list->entry_size = entry_size;
+ list->buffer = 0;
+ list->buffer_used_bitmap = 0;
+ list->buffer_size = 0;
+}
+
+//must be multiple of 8
+#define HANDLE_LIST_INITIAL_SIZE 16
+
+void *add_handle(struct handle_list *list, uint64_t *handle_out) {
+
+ if (list->buffer == 0) {
+ list->buffer = heap_alloc(HANDLE_LIST_INITIAL_SIZE * list->entry_size);
+ list->buffer_used_bitmap = heap_alloc(HANDLE_LIST_INITIAL_SIZE / 8);
+ list->buffer_size = HANDLE_LIST_INITIAL_SIZE;
+ list->buffer_used_bitmap[0] = 1;
+ memzero(list->buffer_used_bitmap + 1, HANDLE_LIST_INITIAL_SIZE / 8 - 1);
+ *handle_out = 0;
+ return list->buffer;
+ }
+
+ for (uint64_t i = 0; i < list->buffer_size; ++i)
+ if ((list->buffer_used_bitmap[i / 8] & (1 << (i % 8))) == 0) {
+ list->buffer_used_bitmap[i / 8] |= (1 << (i % 8));
+ *handle_out = i;
+ return list->buffer + i * list->entry_size;
+ }
+
+ void *new_buffer = heap_alloc(2 * list->buffer_size * list->entry_size);
+ uint8_t *new_used_bitmap = heap_alloc(list->buffer_size / 4);
+
+ memcpy(new_buffer, list->buffer, list->buffer_size * list->entry_size);
+ memcpy(new_used_bitmap, list->buffer_used_bitmap, list->buffer_size / 8);
+
+ heap_dealloc(list->buffer, list->buffer_size * list->entry_size);
+ heap_dealloc(list->buffer_used_bitmap, list->buffer_size / 8);
+
+ new_used_bitmap[list->buffer_size / 8] = 1;
+ memzero(new_used_bitmap + list->buffer_size / 8 + 1, list->buffer_size / 8 - 1);
+
+ list->buffer = new_buffer;
+ list->buffer_used_bitmap = new_used_bitmap;
+ list->buffer_size *= 2;
+
+ *handle_out = list->buffer_size / 2;
+ return list->buffer + (list->buffer_size / 2) * list->entry_size;
+
+}
+
+void *look_up_handle(struct handle_list *list, uint64_t handle) {
+ if (list->buffer == 0 || handle >= list->buffer_size ||
+ (list->buffer_used_bitmap[handle / 8] & (1 << (handle % 8))) == 0)
+ return 0;
+ return list->buffer + handle * list->entry_size;
+}
+
+void for_each_handle(struct handle_list *list, void (*callback)(void *object)) {
+ for (uint64_t i = 0; i < list->buffer_size; ++i)
+ if ((list->buffer_used_bitmap[i / 8] & (1 << (i % 8))) != 0)
+ (*callback)(list->buffer + i * list->entry_size);
+}
+
+void destroy_handle_list(struct handle_list *list) {
+ if (list->buffer != 0) {
+ heap_dealloc(list->buffer, list->buffer_size * list->entry_size);
+ heap_dealloc(list->buffer_used_bitmap, list->buffer_size / 8);
+ }
+}
diff --git a/src/kernel/utility.h b/src/kernel/utility.h
index 6bde244..e9f35b8 100644
--- a/src/kernel/utility.h
+++ b/src/kernel/utility.h
@@ -1,5 +1,5 @@
/* Calcite, src/kernel/utility.h
- * Copyright 2025 Benji Dial
+ * Copyright 2025-2026 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
@@ -39,3 +39,19 @@ void outb(uint16_t port, uint8_t byte);
uint8_t inb(uint16_t port);
void outsw(uint16_t port, const void *buffer, uint64_t words);
void insw(uint16_t port, void *buffer, uint64_t words);
+
+struct handle_list {
+ uint64_t entry_size;
+ void *buffer;
+ uint8_t *buffer_used_bitmap;
+ //in entries, always a multiple of 8
+ uint64_t buffer_size;
+};
+
+void create_handle_list(struct handle_list *list, uint64_t entry_size);
+//add to list by calling this and writing into returned object
+void *add_handle(struct handle_list *list, uint64_t *handle_out);
+//zero if not in list
+void *look_up_handle(struct handle_list *list, uint64_t handle);
+void for_each_handle(struct handle_list *list, void (*callback)(void *object));
+void destroy_handle_list(struct handle_list *list);
diff --git a/src/user-apps/hello/hello.c b/src/user-apps/hello/hello.c
deleted file mode 100644
index 9922874..0000000
--- a/src/user-apps/hello/hello.c
+++ /dev/null
@@ -1,151 +0,0 @@
-/* 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
-#include
-#include
-
-static struct framebuffer_info fb_info;
-static struct image *fbb;
-
-static struct image *cursor_image;
-
-static void draw_bg(int startx, int starty, int width, int height) {
- if (startx + width > fbb->width)
- width = fbb->width - startx;
- if (starty + height > fbb->height)
- height = fbb->height - starty;
- for (int y = starty; y < starty + height; ++y)
- for (int x = startx; x < startx + width; ++x) {
- struct pixel *pixel = &fbb->pixels[y * fbb->width + x];
- pixel->r = 0;
- pixel->g = y * 256 / fb_info.fb_height;
- pixel->b = x * 256 / fb_info.fb_width;
- }
-}
-
-static void draw_cursor(int x, int y) {
- for (int yy = 0; yy < cursor_image->height && y + yy < fbb->height; ++yy)
- for (int xx = 0; xx < cursor_image->width && x + xx < fbb->width; ++xx) {
- struct pixel *fb_pixel = &fbb->pixels[(y + yy) * fbb->width + (x + xx)];
- struct pixel *ci_pixel = &cursor_image->pixels[yy * cursor_image->width + xx];
- fb_pixel->r = (fb_pixel->r * (255 - ci_pixel->a) + ci_pixel->r * ci_pixel->a) / 255;
- fb_pixel->g = (fb_pixel->g * (255 - ci_pixel->a) + ci_pixel->g * ci_pixel->a) / 255;
- fb_pixel->b = (fb_pixel->b * (255 - ci_pixel->a) + ci_pixel->b * ci_pixel->a) / 255;
- }
-}
-
-static void copy_frame() {
- for (int y = 0; y < fbb->height; ++y)
- for (int x = 0; x < fbb->width; ++x) {
- uint8_t *fb_pixel = &fb_info.fb_base[y * fb_info.fb_pitch + x * 4];
- struct pixel *fbb_pixel = &fbb->pixels[y * fbb->width + x];
- fb_pixel[0] = fbb_pixel->b;
- fb_pixel[1] = fbb_pixel->g;
- fb_pixel[2] = fbb_pixel->r;
- }
-}
-
-[[noreturn]] static void panic() {
- for (int y = 0; y < fbb->height; ++y)
- for (int x = 0; x < fbb->width; ++x) {
- struct pixel *pixel = &fbb->pixels[y * fbb->width + x];
- pixel->r = (x + y) * 256 / (fbb->height + fbb->width);
- pixel->g = 0;
- pixel->b = 0;
- }
- copy_frame();
- end_thread();
-}
-
-static int strequ(const char *str1, const char *str2) {
- while (1) {
- if (*str1 == 0 && *str2 == 0)
- return 1;
- if (*str1 != *str2)
- return 0;
- ++str1;
- ++str2;
- }
-}
-
-static int memequ(const char *str1, const char *str2, int length) {
- for (int i = 0; i < length; ++i)
- if (str1[i] != str2[i])
- return 0;
- return 1;
-}
-
-void main() {
-
- map_framebuffer(&fb_info);
- create_image(fb_info.fb_width, fb_info.fb_height, &fbb);
-
- char buffer[100];
-
- int bytes = 100;
- get_envvar("hello", buffer, &bytes);
- if (bytes != 6 || !strequ(buffer, "world"))
- panic();
-
- ipc_dgram_receiver_handle_t receiver;
- if (ipc_create_dgram_receiver("calcite-test", &receiver) != IPR_SUCCESS)
- panic();
-
- bytes = 100;
- if (ipc_receive_dgram(receiver, buffer, &bytes) != IPR_SUCCESS ||
- bytes != 5 || !memequ(buffer, "hello", 5))
- panic();
-
- if (!load_pam("root://calcite/apps/hello/pointer.pam", &cursor_image))
- panic();
-
- int cursor_x = fb_info.fb_width / 2;
- int cursor_y = fb_info.fb_height / 2;
-
- draw_bg(0, 0, fbb->width, fbb->height);
- draw_cursor(cursor_x, cursor_y);
- copy_frame();
-
- while (1) {
-
- sleep_ms(10);
-
- struct mouse_packet packet;
- wait_for_mouse_packet(&packet);
-
- draw_bg(cursor_x, cursor_y, cursor_image->width, cursor_image->height);
-
- cursor_x += packet.x_change;
- cursor_y -= packet.y_change;
-
- if (cursor_x < 0)
- cursor_x = 0;
- if (cursor_x >= fbb->width)
- cursor_x = fbb->width - 1;
-
- if (cursor_y < 0)
- cursor_y = 0;
- if (cursor_y >= fbb->height)
- cursor_y = fbb->height - 1;
-
- draw_cursor(cursor_x, cursor_y);
- copy_frame();
-
- }
-
-}
diff --git a/src/user-apps/hello/libraries.txt b/src/user-apps/shell/libraries.txt
similarity index 53%
rename from src/user-apps/hello/libraries.txt
rename to src/user-apps/shell/libraries.txt
index 8412fe5..8503661 100644
--- a/src/user-apps/hello/libraries.txt
+++ b/src/user-apps/shell/libraries.txt
@@ -1,2 +1 @@
calcite
-silver
diff --git a/src/user-apps/shell/shell.c b/src/user-apps/shell/shell.c
new file mode 100644
index 0000000..fd3f3db
--- /dev/null
+++ b/src/user-apps/shell/shell.c
@@ -0,0 +1,26 @@
+/* Calcite, src/user-apps/shell/shell.c
+ * Copyright 2026 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
+
+void main() {
+ init_terminal();
+ terminal_write("hello\n", 6);
+ while (1)
+ sleep_ms(1000000000);
+}
diff --git a/src/user-apps/terminal/libraries.txt b/src/user-apps/terminal/libraries.txt
new file mode 100644
index 0000000..12c28c2
--- /dev/null
+++ b/src/user-apps/terminal/libraries.txt
@@ -0,0 +1,2 @@
+calcite
+flanterm
diff --git a/src/user-apps/terminal/terminal.c b/src/user-apps/terminal/terminal.c
new file mode 100644
index 0000000..84f168a
--- /dev/null
+++ b/src/user-apps/terminal/terminal.c
@@ -0,0 +1,120 @@
+/* Calcite, src/user-apps/terminal/terminal.c
+ * Copyright 2026 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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+static struct flanterm_context *flanterm_context;
+
+static ipc_dgram_receiver_handle_t input_handle;
+static ipc_dgram_receiver_handle_t output_handle;
+
+static struct dispatch_queue *queue;
+
+struct got_output_param {
+ int dgram_bytes;
+ void *dgram;
+};
+
+static void got_output(void *x) {
+ struct got_output_param *param = x;
+ flanterm_write(flanterm_context, param->dgram, param->dgram_bytes);
+ heap_dealloc(param->dgram, param->dgram_bytes);
+ heap_dealloc(param, sizeof(struct got_output_param));
+}
+
+[[noreturn]] static void output_thread(uint64_t) {
+
+ while (1) {
+
+ int dgram_bytes = 0;
+ ipc_receive_dgram(output_handle, 0, &dgram_bytes);
+
+ void *dgram = heap_alloc(dgram_bytes);
+ ipc_receive_dgram(output_handle, dgram, &dgram_bytes);
+
+ struct got_output_param *param = heap_alloc(sizeof(struct got_output_param));
+ param->dgram_bytes = dgram_bytes;
+ param->dgram = dgram;
+
+ dispatch(queue, &got_output, param);
+
+ }
+
+}
+
+[[noreturn]] static void input_thread(uint64_t) {
+
+ //TODO
+ while (1)
+ sleep_ms(1000000000);
+
+}
+
+void main() {
+
+ uint64_t output_pipe = ipc_create_private_dgram_pipe();
+ uint64_t input_pipe = ipc_create_private_dgram_pipe();
+
+ ipc_create_private_dgram_receiver(output_pipe, &output_handle);
+ ipc_create_private_dgram_sender(input_pipe, &input_handle);
+
+ struct framebuffer_info fb;
+ map_framebuffer(&fb);
+
+ flanterm_context =
+ flanterm_fb_init(
+ &heap_alloc, &heap_dealloc, (void *)fb.fb_base, fb.fb_width,
+ fb.fb_height, fb.fb_pitch, 8, 16, 8,
+ 8, 8, 0, 0, 0,
+ 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 0, 0, 0);
+
+ queue = create_dispatch_queue();
+
+ create_thread(&output_thread, 0);
+ create_thread(&input_thread, 0);
+
+ char input_pipe_hex[17];
+ to_hex(input_pipe, 16, input_pipe_hex);
+ input_pipe_hex[16] = 0;
+
+ char output_pipe_hex[17];
+ to_hex(output_pipe, 16, output_pipe_hex);
+ output_pipe_hex[16] = 0;
+
+ const char *envvars[4] = {
+ "TERMINAL_INPUT_PIPE_ID", input_pipe_hex,
+ "TERMINAL_OUTPUT_PIPE_ID", output_pipe_hex };
+
+ struct process_start_info psi;
+ psi.forwared_envvar_count = 0;
+ psi.set_envvar_count = 2;
+ psi.set_envvars = envvars;
+
+ start_elf("root://calcite/apps/shell/shell.elf", &psi);
+
+ dispatch_loop(queue);
+
+}
diff --git a/src/user-libs/calcite/dispatch.c b/src/user-libs/calcite/dispatch.c
new file mode 100644
index 0000000..516f1a1
--- /dev/null
+++ b/src/user-libs/calcite/dispatch.c
@@ -0,0 +1,87 @@
+/* Calcite, src/user-libs/calcite/dispatch.c
+ * Copyright 2026 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
+#include
+
+struct dispatch_callback {
+ void (*callback)(void *x);
+ void *x;
+ struct dispatch_callback *next;
+};
+
+struct dispatch_queue {
+ struct dispatch_callback *start;
+ struct dispatch_callback *end;
+ mutex_handle_t mutex;
+ event_handle_t event;
+};
+
+struct dispatch_queue *create_dispatch_queue() {
+ struct dispatch_queue *queue = heap_alloc(sizeof(struct dispatch_queue));
+ queue->start = 0;
+ queue->end = 0;
+ queue->mutex = create_mutex();
+ queue->event = create_event();
+ return queue;
+}
+
+void dispatch(struct dispatch_queue *queue, void (*callback)(void *x), void *x) {
+
+ struct dispatch_callback *new = heap_alloc(sizeof(struct dispatch_callback));
+ new->callback = callback;
+ new->x = x;
+ new->next = 0;
+
+ acquire_mutex(queue->mutex);
+
+ if (queue->start == 0)
+ queue->start = new;
+ else
+ queue->end->next = new;
+
+ queue->end = new;
+
+ release_mutex(queue->mutex);
+ signal_event(queue->event);
+
+}
+
+[[noreturn]] void dispatch_loop(struct dispatch_queue *queue) {
+
+ while (1) {
+
+ wait_event(queue->event);
+ acquire_mutex(queue->mutex);
+
+ struct dispatch_callback *start = queue->start;
+ queue->start = 0;
+ queue->end = 0;
+
+ release_mutex(queue->mutex);
+
+ while (start != 0) {
+ (*start->callback)(start->x);
+ struct dispatch_callback *next = start->next;
+ heap_dealloc(start, sizeof(struct dispatch_callback));
+ start = next;
+ }
+
+ }
+
+}
diff --git a/src/user-libs/calcite/format.c b/src/user-libs/calcite/format.c
new file mode 100644
index 0000000..650e9e9
--- /dev/null
+++ b/src/user-libs/calcite/format.c
@@ -0,0 +1,42 @@
+/* Calcite, src/user-libs/calcite/format.c
+ * Copyright 2026 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
+
+void to_hex(uint64_t value, int digit_count, char *buffer) {
+ for (int i = 0; i < digit_count; ++i) {
+ char *digit = &buffer[digit_count - 1 - i];
+ uint8_t place_value = (value >> (i * 4)) & 0xf;
+ if (place_value < 10)
+ *digit = '0' + place_value;
+ else
+ *digit = 'a' - 10 + place_value;
+ }
+}
+
+uint64_t from_hex(int digit_count, char *buffer) {
+ uint64_t value = 0;
+ for (int i = 0; i < digit_count; ++i) {
+ uint64_t place_value = 0;
+ if (buffer[i] >= '0' && buffer[i] <= '9')
+ place_value = buffer[i] - '0';
+ else if (buffer[i] >= 'a' && buffer[i] <= 'f')
+ place_value = buffer[i] - 'a' + 10;
+ value |= place_value << ((digit_count - 1 - i) * 4);
+ }
+ return value;
+}
diff --git a/src/user-libs/calcite/memory.c b/src/user-libs/calcite/memory.c
index 06f5c8f..766bbf5 100644
--- a/src/user-libs/calcite/memory.c
+++ b/src/user-libs/calcite/memory.c
@@ -1,5 +1,5 @@
/* Calcite, src/user-libs/calcite/memory.c
- * Copyright 2025 Benji Dial
+ * Copyright 2025-2026 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
@@ -189,3 +189,9 @@ void memcpy(void *to, const void *from, uint64_t bytes) {
for (uint64_t i = 0; i < bytes; ++i)
to8[i] = from8[i];
}
+
+void memset(void *start, uint8_t value, uint64_t length) {
+ uint8_t *start8 = start;
+ for (uint64_t i = 0; i < length; ++i)
+ start8[i] = value;
+}
diff --git a/src/user-libs/calcite/syscalls.c b/src/user-libs/calcite/syscalls.c
index 73c3c79..215218a 100644
--- a/src/user-libs/calcite/syscalls.c
+++ b/src/user-libs/calcite/syscalls.c
@@ -1,5 +1,5 @@
/* Calcite, src/user-libs/calcite/syscalls.c
- * Copyright 2025 Benji Dial
+ * Copyright 2025-2026 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
@@ -18,6 +18,7 @@
#include
#include
#include
+#include
#include
#include
@@ -98,3 +99,49 @@ void get_envvar(const char *key, char *value_out, int *value_space) {
do_syscall(
(uint64_t)key, (uint64_t)value_out, (uint64_t)value_space, SYSCALL_GET_ENVVAR);
}
+
+mutex_handle_t create_mutex() {
+ return do_syscall(0, 0, 0, SYSCALL_CREATE_MUTEX);
+}
+
+void acquire_mutex(mutex_handle_t handle) {
+ do_syscall(handle, 0, 0, SYSCALL_ACQUIRE_MUTEX);
+}
+
+void release_mutex(mutex_handle_t handle) {
+ do_syscall(handle, 0, 0, SYSCALL_RELEASE_MUTEX);
+}
+
+event_handle_t create_event() {
+ return do_syscall(0, 0, 0, SYSCALL_CREATE_EVENT);
+}
+
+void signal_event(event_handle_t handle) {
+ do_syscall(handle, 0, 0, SYSCALL_SIGNAL_EVENT);
+}
+
+void wait_event(event_handle_t handle) {
+ do_syscall(handle, 0, 0, SYSCALL_WAIT_EVENT);
+}
+
+uint64_t ipc_create_private_dgram_pipe() {
+ return do_syscall(0, 0, 0, SYSCALL_IPC_CREATE_PRIVATE_DGRAM_PIPE);
+}
+
+enum ipc_dgram_result ipc_create_private_dgram_receiver(uint64_t id, ipc_dgram_receiver_handle_t *handle_out) {
+ return do_syscall(
+ id, (uint64_t)handle_out, 0, SYSCALL_IPC_CREATE_PRIVATE_DGRAM_RECEIVER);
+}
+
+enum ipc_dgram_result ipc_create_private_dgram_sender(uint64_t id, ipc_dgram_sender_handle_t *handle_out) {
+ return do_syscall(
+ id, (uint64_t)handle_out, 0, SYSCALL_IPC_CREATE_PRIVATE_DGRAM_SENDER);
+}
+
+uint64_t get_ms_since_boot() {
+ return do_syscall(0, 0, 0, SYSCALL_GET_MS_SINCE_BOOT);
+}
+
+void sleep_until_ms_since_boot(uint64_t ms) {
+ do_syscall(ms, 0, 0, SYSCALL_SLEEP_UNTIL_MS_SINCE_BOOT);
+}
diff --git a/src/user-libs/calcite/terminal.c b/src/user-libs/calcite/terminal.c
new file mode 100644
index 0000000..6f23e52
--- /dev/null
+++ b/src/user-libs/calcite/terminal.c
@@ -0,0 +1,132 @@
+/* Calcite, src/user-libs/calcite/terminal.c
+ * Copyright 2026 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
+#include
+#include
+#include
+
+static ipc_dgram_receiver_handle_t input_handle;
+static ipc_dgram_sender_handle_t output_handle;
+
+static int set_up_terminal_input = 0;
+static int set_up_terminal_output = 0;
+
+static char *output_buffer;
+static int output_buffer_size;
+static int output_buffer_used;
+
+static void output_buffer_size_at_least(int bytes) {
+
+ if (output_buffer_size >= bytes)
+ return;
+
+ int new_size = output_buffer_size * 2;
+ while (new_size < bytes)
+ new_size *= 2;
+
+ char *new_buffer = heap_alloc(new_size);
+ memcpy(new_buffer, output_buffer, output_buffer_used);
+ heap_dealloc(output_buffer, output_buffer_size);
+ output_buffer = new_buffer;
+ output_buffer_size = new_size;
+
+}
+
+#define MAX_PACKET_LENGTH 500
+
+static void send_output(const char *buffer, int bytes) {
+
+ if (set_up_terminal_output == 0)
+ return;
+
+ while (bytes >= MAX_PACKET_LENGTH) {
+ ipc_send_dgram(output_handle, buffer, MAX_PACKET_LENGTH);
+ buffer += MAX_PACKET_LENGTH;
+ bytes -= MAX_PACKET_LENGTH;
+ }
+
+ if (bytes > 0)
+ ipc_send_dgram(output_handle, buffer, bytes);
+
+}
+
+void init_terminal() {
+
+ output_buffer_size = 512;
+ output_buffer_used = 0;
+ output_buffer = heap_alloc(output_buffer_size);
+
+ char hex[17];
+ int bytes = 17;
+ get_envvar("TERMINAL_INPUT_PIPE_ID", hex, &bytes);
+
+ if (bytes == 17 &&
+ ipc_create_private_dgram_receiver(
+ from_hex(16, hex),
+ &input_handle) == IPR_SUCCESS)
+ set_up_terminal_input = 1;
+
+ bytes = 17;
+ get_envvar("TERMINAL_OUTPUT_PIPE_ID", hex, &bytes);
+
+ if (bytes == 17 &&
+ ipc_create_private_dgram_sender(
+ from_hex(16, hex),
+ &output_handle) == IPR_SUCCESS)
+ set_up_terminal_output = 1;
+
+}
+
+void terminal_write(const char *data, int bytes) {
+
+ if (bytes == 0)
+ return;
+
+ int last_newline = bytes - 1;
+ while (last_newline >= 0 && data[last_newline] != '\n')
+ --last_newline;
+
+ if (last_newline == -1) {
+ output_buffer_size_at_least(output_buffer_used + bytes);
+ memcpy(&output_buffer[output_buffer_used], data, bytes);
+ output_buffer_used += bytes;
+ }
+
+ else {
+ if (output_buffer_used != 0) {
+ send_output(output_buffer, output_buffer_used);
+ output_buffer_used = 0;
+ }
+ send_output(data, last_newline + 1);
+ if (last_newline != bytes - 1) {
+ output_buffer_size_at_least(bytes - last_newline - 1);
+ memcpy(output_buffer, &data[last_newline + 1], bytes - last_newline - 1);
+ output_buffer_used = bytes - last_newline - 1;
+ }
+ }
+
+}
+
+void terminal_read(char *buffer, int bytes) {
+ //TODO
+ (void)buffer;
+ (void)bytes;
+ while (1)
+ sleep_ms(1000000000);
+}