refactor some kernel stuff, start terminal

This commit is contained in:
Benji Dial 2026-01-02 18:00:08 -05:00
parent 8d1785fb06
commit 5821f51f02
39 changed files with 1154 additions and 326 deletions

View file

@ -3,6 +3,8 @@
-I
dependencies/limine
-I
dependencies/flanterm/src
-I
include
-D
CALCITE_DEBUG

Binary file not shown.

View file

@ -1 +1 @@
root://calcite/apps/hello/hello.elf
root://calcite/apps/terminal/terminal.elf

View file

@ -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 ..

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
#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);

26
include/calcite/format.h Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
//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);

View file

@ -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);

View file

@ -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 <kernel-public/process.h>
#include <kernel-public/files.h>
#include <kernel-public/input.h>
#include <kernel-public/sync.h>
#include <kernel-public/ipc.h>
[[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);

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
#pragma once
void init_terminal();
void terminal_write(const char *data, int bytes);
void terminal_read(char *buffer, int bytes);

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
typedef uint64_t mutex_handle_t;
typedef uint64_t event_handle_t;

View file

@ -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
};

View file

@ -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

View file

@ -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

View file

@ -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 <https://github.com/limine-bootloader/limine>.
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
<https://codeberg.org/Limine/Limine>.
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
<https://codeberg.org/Mintsuki/Flanterm>.
After running "sh get-dependencies.sh", you can find the license of Flanterm
at "dependencies/flanterm/LICENSE".

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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])() = {

View file

@ -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);
}

View file

@ -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);

View file

@ -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 <kernel-public/process.h>
#include <kernel-public/files.h>
#include <kernel-public/sync.h>
#include <kernel-public/ipc.h>
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);
}

View file

@ -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 <kernel-public/framebuffer.h>
#include <kernel-public/process.h>
#include <kernel-public/files.h>
#include <kernel-public/sync.h>
#include <kernel-public/ipc.h>
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);

View file

@ -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

View file

@ -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) {

View file

@ -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 <stdint.h>
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);

View file

@ -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);
}

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
#include <kernel-public/ipc.h>
#include <calcite/syscalls.h>
#include <silver/pam.h>
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();
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
#include <calcite/syscalls.h>
#include <calcite/terminal.h>
void main() {
init_terminal();
terminal_write("hello\n", 6);
while (1)
sleep_ms(1000000000);
}

View file

@ -0,0 +1,2 @@
calcite
flanterm

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
#include <kernel-public/framebuffer.h>
#include <kernel-public/process.h>
#include <flanterm_backends/fb.h>
#include <kernel-public/ipc.h>
#include <calcite/dispatch.h>
#include <calcite/syscalls.h>
#include <calcite/format.h>
#include <calcite/memory.h>
#include <flanterm.h>
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);
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
#include <kernel-public/sync.h>
#include <calcite/syscalls.h>
#include <calcite/memory.h>
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;
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
#include <calcite/format.h>
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;
}

View file

@ -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;
}

View file

@ -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 <kernel-public/syscall-numbers.h>
#include <kernel-public/process.h>
#include <kernel-public/files.h>
#include <kernel-public/sync.h>
#include <kernel-public/ipc.h>
#include <calcite/syscalls.h>
@ -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);
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
#include <kernel-public/ipc.h>
#include <calcite/syscalls.h>
#include <calcite/terminal.h>
#include <calcite/format.h>
#include <calcite/memory.h>
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);
}