Compare commits

..

28 commits

Author SHA1 Message Date
0330ab168e start forth shell 2026-01-06 13:04:05 -05:00
7ec40a1d7e custom linker script for user apps to guarantee that sections are page-aligned 2026-01-05 15:14:35 -05:00
225ea67c64 disable bump allocator in flanterm 2026-01-02 23:13:16 -05:00
c6dac5c489 tweak logging, get rid of old test in init 2026-01-02 23:02:48 -05:00
085ae6ba53 add guard pages to stack; dynamically allocate user stacks; zero non-preserved registers before returning from syscalls 2026-01-02 22:39:57 -05:00
5821f51f02 refactor some kernel stuff, start terminal 2026-01-02 18:00:08 -05:00
8d1785fb06 update readme to reflect debug/release builds 2026-01-01 00:09:28 -05:00
a69dbc3c7a rename src/kernel/panic to src/kernel/debug, add serial logging 2026-01-01 00:05:11 -05:00
527498a491 debug and release modes in make-build.sh 2025-12-31 22:10:26 -05:00
42fe995724 keep kernel panics on failed assertion with NDEBUG 2025-12-31 22:06:18 -05:00
a3575b8997 give each thread a syscall stack instead of the silliness from before 2025-12-31 20:02:59 -05:00
6d9c3f7794 get ide controllers from pci instead of assuming compatibility mode; name pata drives based on order discovered instead of controller 2025-12-31 18:16:05 -05:00
7d90ac7d3d environment variables for processes 2025-12-29 18:27:34 -05:00
285da1dc46 readme: remove tar mention since it is no longer used 2025-12-29 17:49:57 -05:00
4f9bf8afef ipc and thread spawning 2025-12-29 16:55:53 -05:00
02e855c066 kernel: add timers via pit 2025-12-28 22:30:37 -05:00
e698cfdfd1 kernel: fix race condition in input component 2025-12-28 20:27:00 -05:00
e2188aa407 kernel: utility: change memcpy back to rep movsb 2025-12-28 20:15:07 -05:00
35725e0cd0 build: make xorriso and limine quiet(er) 2025-12-28 17:20:29 -05:00
aa54761c77 process: track ownership of pages in a more flexible way 2025-12-28 17:02:25 -05:00
f5ccce54a4 use relative addressing in all assembly files 2025-12-28 14:48:36 -05:00
b539b6302c more user space stuff, including files and mouse packets 2025-12-28 14:08:55 -05:00
32524106e8 process/scheduler: make interface a little cleaner, don't leak continuation info 2025-12-27 22:21:46 -05:00
bb10b27152 ps2: only read one byte per interrupt 2025-12-27 21:10:14 -05:00
2eafff563e move framebuffer_info struct into include/kernel-public 2025-12-27 20:53:45 -05:00
645cc90b4d reading files from cd!! 2025-12-27 18:39:05 -05:00
fc79e09922 identify patapi drives 2025-12-27 13:07:01 -05:00
f409903f55 put entry point in calcite library 2025-12-26 18:04:17 -05:00
90 changed files with 6663 additions and 532 deletions

View file

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

1
disk/calcite/init.rc Normal file
View file

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

BIN
disk/calcite/qwerty.bin Normal file

Binary file not shown.

View file

@ -4,5 +4,4 @@ quiet: yes
/Calcite
protocol: limine
path: boot():/calcite/kernel.elf
module_path: boot():/calcite/initfs.tar
module_cmdline: initfs
cmdline: root-drive=pata0,root-fs=iso9660

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

View file

@ -1,4 +1,4 @@
/* Calcite, src/user-apps/hello/hello.c
/* Calcite, include/calcite/file-streams.h
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
@ -15,21 +15,16 @@
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <calcite/calcite.h>
#pragma once
[[noreturn]]
void _start() {
#include <kernel-public/files.h>
struct framebuffer_info fb_info;
map_framebuffer(&fb_info);
struct file_stream;
for (int y = 0; y < fb_info.fb_height; ++y)
for (int x = 0; x < fb_info.fb_width; ++x) {
uint8_t *pixel = fb_info.fb_base + y * fb_info.fb_pitch + x * 4;
pixel[0] = x * 256 / fb_info.fb_width;
pixel[1] = y * 256 / fb_info.fb_height;
}
enum fs_access_result open_file_stream(const char *path, struct file_stream **file_stream_out);
void close_file_stream(struct file_stream *file_stream);
end_thread();
enum fs_access_result read_file_stream(struct file_stream *file_stream, void *buffer, uint64_t bytes);
}
//returns next byte casted to int. on error or eof, returns -1.
int read_file_stream_byte(struct file_stream *file_stream);

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

29
include/calcite/memory.h Normal file
View file

@ -0,0 +1,29 @@
/* Calcite, include/calcite/memory.h
* 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
* 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>
void *heap_alloc(uint64_t bytes);
//start should be something returned from heap_alloc,
//unless this is called (carefully) from inside memory.c
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

@ -0,0 +1,88 @@
/* Calcite, include/calcite/syscalls.h
* 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
* 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 <kernel-public/framebuffer.h>
#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();
void map_framebuffer(struct framebuffer_info *info_out);
enum fs_access_result open_file(const char *path, file_handle_t *handle_out);
void close_file(file_handle_t handle);
enum fs_access_result get_file_size(file_handle_t handle, uint64_t *bytes_out);
enum fs_access_result read_file(struct read_file_parameter *parameter);
enum fs_access_result read_file_splat(file_handle_t handle, void *buffer, uint64_t start, uint64_t bytes);
void wait_for_mouse_packet(struct mouse_packet *packet_out);
int wait_for_keyboard_packet();
void *map_pages(uint64_t count);
void sleep_ms(uint64_t ms_to_sleep);
//1 on success, 0 on failure.
int start_elf(const char *path, const struct process_start_info *info);
enum ipc_dgram_result ipc_create_dgram_receiver(const char *address, ipc_dgram_receiver_handle_t *handle_out);
enum ipc_dgram_result ipc_create_dgram_sender(const char *address, ipc_dgram_sender_handle_t *handle_out);
//on entry, bytes is maximum accepted packet length.
//on exit, if result was IPR_SUCCESS or IPR_TOO_BIG, bytes is actual packet length.
//actual packet length will always be positive.
enum ipc_dgram_result ipc_receive_dgram(ipc_dgram_receiver_handle_t handle, void *buffer, int *bytes);
//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);
//value_space must be positive at entry.
//looks up the environment variable with this key. if there is no such
//variable, sets value_space to 0. otherwise, sets value_space to the
//length of the value including null terminator (which will be positive),
//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);
//vma should be page-aligned. writable and executable should be 1 or 0.
void change_page_permissions(void *vma, int writable, int executable);

View file

@ -0,0 +1,46 @@
/* 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
#include <stdint.h>
void init_terminal();
void terminal_write(const void *data, int bytes);
void terminal_flush();
enum terminal_key_flags {
TKF_LEFT_SHIFT = 0x20000,
TKF_RIGHT_SHIFT = 0x40000,
TKF_LEFT_CTRL = 0x80000,
TKF_RIGHT_CTRL = 0x100000,
TKF_LEFT_ALT = 0x200000,
TKF_RIGHT_ALT = 0x400000,
TKF_CAPS_LOCK = 0x800000,
TKF_NUM_LOCK = 0x1000000
};
//0x000000ff is printable character, if any, otherwise 0.
// this includes space, newline, and tab, but not delete or backspace.
// control, alt, and make are ignored, but shift, caps lock, and num lock are considered.
//0x0001ff00 is result of syscall_wait_for_keyboard_packet, shifted left.
//0x01fe0000 is flags as above.
int terminal_readkey();
//has null terminator, no newline. returned pointer is invalidated by next call.
const char *terminal_readline();

View file

@ -1,4 +1,4 @@
/* Calcite, src/kernel/panic.h
/* Calcite, include/kernel-public/files.h
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
@ -17,17 +17,24 @@
#pragma once
[[noreturn]] void panic_core(
const char *file, const char *function, int line, const char *message);
#include <stdint.h>
#define panic(message) panic_core(__FILE__, __func__, __LINE__, message);
enum fs_access_result {
FAR_SUCCESS,
FAR_HARDWARE_ERROR,
FAR_FORMAT_ERROR,
FAR_UNKNOWN_FS_TYPE,
FAR_NOT_FOUND,
FAR_OUT_OF_BOUNDS,
FAR_BAD_HANDLE
};
#ifdef NDEBUG
#define assert(condition) ((void)0)
#else
#define assert(condition) \
{ \
if (!(condition)) \
panic("assertion failed: " #condition) \
}
#endif
typedef uint64_t file_handle_t;
struct read_file_parameter {
file_handle_t handle;
void *buffer;
//in bytes
uint64_t start;
uint64_t bytes;
};

View file

@ -1,4 +1,4 @@
/* Calcite, include/calcite/calcite.h
/* Calcite, include/kernel-public/framebuffer.h
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
@ -19,13 +19,9 @@
#include <stdint.h>
[[noreturn]] void end_thread();
struct framebuffer_info {
uint8_t *fb_base;
int fb_width;
int fb_height;
int fb_pitch;
};
void map_framebuffer(struct framebuffer_info *info_out);

View file

@ -0,0 +1,153 @@
/* Calcite, include/kernel-public/input.h
* 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
* 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 mouse_packet {
int x_change;
int y_change;
};
//named after qwerty value of key
enum key_value {
KEY_F9 = 0x01,
KEY_F5 = 0x03,
KEY_F3 = 0x04,
KEY_F1 = 0x05,
KEY_F2 = 0x06,
KEY_F12 = 0x07,
KEY_F10 = 0x09,
KEY_F8 = 0x0a,
KEY_F6 = 0x0b,
KEY_F4 = 0x0c,
KEY_TAB = 0x0d,
KEY_GRAVE = 0x0e,
KEY_LEFT_ALT = 0x11,
KEY_LEFT_SHIFT = 0x12,
KEY_LEFT_CTRL = 0x14,
KEY_Q = 0x15,
KEY_1 = 0x16,
KEY_Z = 0x1a,
KEY_S = 0x1b,
KEY_A = 0x1c,
KEY_W = 0x1d,
KEY_2 = 0x1e,
KEY_C = 0x21,
KEY_X = 0x22,
KEY_D = 0x23,
KEY_E = 0x24,
KEY_4 = 0x25,
KEY_3 = 0x26,
KEY_SPACE = 0x29,
KEY_V = 0x2a,
KEY_F = 0x2b,
KEY_T = 0x2c,
KEY_R = 0x2d,
KEY_5 = 0x2e,
KEY_N = 0x31,
KEY_B = 0x32,
KEY_H = 0x33,
KEY_G = 0x34,
KEY_Y = 0x35,
KEY_6 = 0x36,
KEY_M = 0x3a,
KEY_J = 0x3b,
KEY_U = 0x3c,
KEY_7 = 0x3d,
KEY_8 = 0x3e,
KEY_COMMA = 0x41,
KEY_K = 0x42,
KEY_I = 0x43,
KEY_O = 0x44,
KEY_0 = 0x45,
KEY_9 = 0x46,
KEY_PERIOD = 0x49,
KEY_SLASH = 0x4a,
KEY_L = 0x4b,
KEY_SEMICOLON = 0x4c,
KEY_P = 0x4d,
KEY_MINUS = 0x4e,
KEY_APOSTRAPHE = 0x52,
KEY_OPEN_BRACKET = 0x54,
KEY_EQUALS = 0x55,
KEY_CAPS_LOCK = 0x58,
KEY_RIGHT_SHIFT = 0x59,
KEY_ENTER = 0x5a,
KEY_CLOSE_BRACKET = 0x5b,
KEY_BACKSLASH = 0x5d,
KEY_BACKSPACE = 0x66,
KEY_NUM_1 = 0x69,
KEY_NUM_4 = 0x6b,
KEY_NUM_7 = 0x6c,
KEY_NUM_0 = 0x70,
KEY_NUM_PERIOD = 0x71,
KEY_NUM_2 = 0x72,
KEY_NUM_5 = 0x73,
KEY_NUM_6 = 0x74,
KEY_NUM_8 = 0x75,
KEY_ESCAPE = 0x76,
KEY_NUM_LOCK = 0x77,
KEY_F11 = 0x78,
KEY_NUM_PLUS = 0x79,
KEY_NUM_3 = 0x7a,
KEY_NUM_MINUS = 0x7b,
KEY_NUM_TIMES = 0x7c,
KEY_NUM_9 = 0x7d,
KEY_SCROLL_LOCK = 0x7e,
KEY_F7 = 0x83,
KEY_WEB_SEARCH = 0x90,
KEY_RIGHT_ALT = 0x91,
KEY_RIGHT_CTRL = 0x94,
KEY_MEDIA_PREVIOUS = 0x95,
KEY_WEB_FAVORITES = 0x98,
KEY_LEFT_GUI = 0x9f,
KEY_WEB_REFRESH = 0xa0,
KEY_MEDIA_VOLUME_DOWN = 0xa1,
KEY_MEDIA_MUTE = 0xa3,
KEY_RIGHT_GUI = 0xa7,
KEY_WEB_STOP = 0xa8,
KEY_CALCULATOR = 0xab,
KEY_MENU = 0xaf,
KEY_WEB_FORWARD = 0xb0,
KEY_MEDIA_VOLUME_UP = 0xb2,
KEY_MEDIA_PAUSE = 0xb4,
KEY_ACPI_POWER = 0xb7,
KEY_WEB_BACK = 0xb8,
KEY_WEB_HOME = 0xba,
KEY_MEDIA_STOP = 0xbb,
KEY_ACPI_SLEEP = 0xbf,
KEY_COMPUTER = 0xc0,
KEY_EMAIL = 0xc8,
KEY_NUM_DIVIDE = 0xca,
KEY_MEDIA_NEXT = 0xcd,
KEY_MEDIA_SELECT = 0xd0,
KEY_NUM_ENTER = 0xda,
KEY_ACPI_WAKE = 0xde,
KEY_END = 0xe9,
KEY_LEFT = 0xeb,
KEY_HOME = 0xec,
KEY_INSERT = 0xf0,
KEY_DELETE = 0xf1,
KEY_DOWN = 0xf2,
KEY_RIGHT = 0xf4,
KEY_UP = 0xf5,
KEY_PAGE_DOWN = 0xfa,
KEY_PAGE_UP = 0xfd,
KEY_PRINT_SCREEN = 0xfe,
KEY_PAUSE_BREAK = 0xff,
KEY_FLAG_MAKE = 0x100
};

View file

@ -0,0 +1,38 @@
/* Calcite, include/kernel-public/ipc.h
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
enum ipc_dgram_result {
IPR_SUCCESS,
//on write, packet is bigger than kernel data structure.
//on read, packet is bigger than max requested size.
IPR_TOO_BIG,
IPR_BAD_HANDLE,
//on create receiver, someone is already receiving at that address.
IPR_IN_USE
};
typedef uint64_t ipc_dgram_receiver_handle_t;
typedef uint64_t ipc_dgram_sender_handle_t;

View file

@ -0,0 +1,32 @@
/* Calcite, include/kernel-public/process.h
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
struct process_start_info {
//keys of envvars to copy to new process.
const char **forwared_envvars;
int forwared_envvar_count;
//keys and values to set in new process.
//entry i has key set_envvars[i * 2] and value set_envvars[i * 2 + 1].
//set_envvar_count is number of envvars, not twice that.
const char **set_envvars;
int set_envvar_count;
};

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
@ -19,5 +19,32 @@
enum {
SYSCALL_END_THREAD,
SYSCALL_MAP_FRAMEBUFFER
SYSCALL_MAP_FRAMEBUFFER,
SYSCALL_OPEN_FILE,
SYSCALL_CLOSE_FILE,
SYSCALL_GET_FILE_SIZE,
SYSCALL_READ_FILE,
SYSCALL_WAIT_FOR_MOUSE_PACKET,
SYSCALL_MAP_PAGES,
SYSCALL_SLEEP_MS,
SYSCALL_START_ELF,
SYSCALL_IPC_CREATE_DGRAM_RECEIVER,
SYSCALL_IPC_CREATE_DGRAM_SENDER,
SYSCALL_IPC_RECEIVE_DGRAM,
SYSCALL_IPC_SEND_DGRAM,
SYSCALL_CREATE_THREAD,
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,
SYSCALL_CHANGE_PAGE_PERMISSIONS,
SYSCALL_WAIT_FOR_KEYBOARD_PACKET
};

36
include/silver/image.h Normal file
View file

@ -0,0 +1,36 @@
/* Calcite, include/silver/image.h
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
struct pixel {
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
};
struct image {
int width;
int height;
struct pixel *pixels;
};
void create_image(int width, int height, struct image **image_out);
void destroy_image(struct image *image);

23
include/silver/pam.h Normal file
View file

@ -0,0 +1,23 @@
/* Calcite, include/silver/pam.h
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <silver/image.h>
//returns 0 on failure, 1 on success.
int load_pam(const char *path, struct image **image_out);

View file

@ -1,10 +1,26 @@
#!/bin/sh
COMMON_CC_EXTRA_FLAGS="-O3 -Wall -Wextra"
COMMON_CC_EXTRA_FLAGS="-Wall -Wextra"
COMMON_LD_EXTRA_FLAGS=""
COMMON_CC_FLAGS="-std=c23 -ffreestanding -I include ${COMMON_CC_EXTRA_FLAGS}"
COMMON_LD_FLAGS="${COMMON_LD_EXTRA_FLAGS}"
if [ "$1" = debug ]; then
COMMON_CC_FLAGS="-O0 -ggdb -D CALCITE_DEBUG ${COMMON_CC_FLAGS}"
elif [ "$1" = release ]; then
COMMON_CC_FLAGS="-O3 -D CALCITE_RELEASE ${COMMON_CC_FLAGS}"
COMMON_LD_FLAGS="-s ${COMMON_LD_FLAGS}"
else
echo pass either "debug" or "release" as an argument.
exit
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}"
FLANTERM_CC_FLAGS="-D FLANTERM_FB_DISABLE_BUMP_ALLOC ${USER_CC_FLAGS}"
if [ -e build.ninja ]; then
echo build.ninja already exists.
@ -24,15 +40,19 @@ echo "rule user_cc" >> build.ninja
echo " depfile = \$out.d" >> build.ninja
echo " command = cc -c -MD -MF \$out.d ${USER_CC_FLAGS} \$in -o \$out" >> build.ninja
echo "rule flanterm_cc" >> build.ninja
echo " depfile = \$out.d" >> build.ninja
echo " command = cc -c -MD -MF \$out.d ${FLANTERM_CC_FLAGS} \$in -o \$out" >> build.ninja
echo "rule kernel_ld" >> build.ninja
echo " command = ld -T src/kernel/link.ld \$in -o \$out" >> build.ninja
echo " command = ld ${COMMON_LD_FLAGS} -T src/kernel/link.ld \$in -o \$out" >> build.ninja
echo "rule user_lib_ld" >> build.ninja
echo " command = ld -r \$in -o \$out" >> build.ninja
#eventually maybe a libc will be linked in
echo "rule user_app_ld" >> build.ninja
echo " command = ld \$in -o \$out" >> build.ninja
echo " command = ld ${COMMON_LD_FLAGS} -T src/user-apps/link.ld \$in -o \$out" >> build.ninja
#builds everything in a directory
# $1 - source directory
@ -64,38 +84,47 @@ 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: flanterm_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
done
apps=""
cp_apps=""
app_deps=""
for dir in src/user-apps/*; do
if [ -d $dir ]; then
if [ -e $dir/libraries.txt ]; then
lib_paths=$(cat $dir/libraries.txt | sed -e 's/^.*$/build\/user-libs\/\0\/lib\0.o/')
lib_paths=$(cat $dir/libraries.txt | sed -e 's/^.*$/build\/user-libs\/\0\/lib\0.o/' | tr '\n' ' ')
else
lib_paths=""
fi
app_name=$(echo $dir | sed -e 's/^src\/user-apps\///')
build_all $dir user_cc user_app_ld $app_name.elf $lib_paths
build_dir=$(echo $dir | sed -e 's/^src/build/')
apps="$apps $build_dir/$app_name.elf"
build_all $dir user_cc user_app_ld $app_name.elf "$lib_paths"
build_elf=$(echo $dir | sed -e 's/^src/build/')/$app_name.elf
disk_dir=build/disk/calcite/apps/$app_name
disk_elf=$disk_dir/$app_name.elf
cp_apps=$(echo "$cp_apps" \
"mkdir -p $disk_dir &&" \
"cp $build_elf $disk_elf &&")
app_deps="$app_deps $build_elf"
fi
done
echo "rule initfs" >> build.ninja
echo " command =" \
"rm -rf build/initfs &&" \
"mkdir -p build/initfs/apps &&" \
"cp build/user-apps/hello/hello.elf build/initfs/apps/ &&" \
"cd build/initfs &&" \
"tar cf ../initfs.tar *" >> build.ninja
echo "build build/initfs.tar: initfs $apps" >> build.ninja
echo "rule disk" >> build.ninja
echo " command =" \
"rm -rf build/disk &&" \
@ -105,12 +134,12 @@ echo " command =" \
"cp dependencies/limine/limine-bios.sys build/disk/limine/ &&"\
"mkdir -p build/disk/EFI/BOOT &&" \
"cp dependencies/limine/BOOTX64.EFI build/disk/EFI/BOOT/ &&" \
"mkdir build/disk/calcite &&" \
"mkdir -p build/disk/calcite &&" \
"cp build/kernel/kernel.elf build/disk/calcite/ &&" \
"cp build/initfs.tar build/disk/calcite/ &&" \
"xorriso -as mkisofs -R -r -J -b limine/limine-bios-cd.bin -no-emul-boot -boot-load-size 4 -boot-info-table -hfsplus -apm-block-size 2048 --efi-boot limine/limine-uefi-cd.bin -efi-boot-part --efi-boot-image --protective-msdos-label build/disk -o build/disk.iso &&" \
"dependencies/limine/limine bios-install build/disk.iso" >> build.ninja
$cp_apps \
"xorriso -as mkisofs -R -r -J -b limine/limine-bios-cd.bin -no-emul-boot -boot-load-size 4 -boot-info-table -hfsplus -apm-block-size 2048 --efi-boot limine/limine-uefi-cd.bin -efi-boot-part --efi-boot-image --protective-msdos-label -quiet build/disk -o build/disk.iso &&" \
"dependencies/limine/limine bios-install --quiet build/disk.iso" >> build.ninja
echo "build build/disk.iso: disk | build/kernel/kernel.elf build/initfs.tar" >> build.ninja
echo "build build/disk.iso: disk | build/kernel/kernel.elf $app_deps" >> build.ninja
echo "default build/disk.iso" >> build.ninja

View file

@ -15,17 +15,16 @@ Calcite requires some software to be installed before it can be built:
* curl
* GCC (Any C compiler supporting both C99 and C23 should work.)
* GNU Binutils (Specifically, "ld" is used to link the kernel.)
* GNU tar (Any POSIX tar should be fine.)
* GNU xorriso
* NASM
* Ninja build system
On Debian, it is sufficient to run this command:
apt install binutils curl gcc nasm ninja-build tar xorriso
apt install binutils curl gcc nasm ninja-build xorriso
To build Calcite, run these commands:
sh get-dependencies.sh
sh make-build.sh
sh make-build.sh release
ninja
This will build a disk image at "build/disk.iso" that can be booted with
@ -44,22 +43,30 @@ Then, just run "gdb -x qemu.gdb". Calcite will be booted in QEMU, and GDB will
be attached to it. Use "c" or "continue" in GDB to start execution.
While debugging, it is useful to disable optimizations and enable debugging
information. In "make-build.sh", under COMMON_CC_EXTRA_FLAGS, you can change
"-O3" to "-O0 -ggdb". Then, run these commands to rebuild:
rm -r build
rm build.ninja
sh make-build.sh
information. If you already built Calcite in "release" mode, run:
rm -r build build.ninja
sh make-build.sh debug
ninja
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".

113
src/kernel/debug.c Normal file
View file

@ -0,0 +1,113 @@
/* Calcite, src/kernel/debug.c
* 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
* 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 "serial.h"
#include "debug.h"
#include <stdarg.h>
void log_core(const char *file, const char *function, const char *format, ...) {
int file_length = 0;
while (file[file_length] != 0)
++file_length;
int function_length = 0;
while (function[function_length] != 0)
++function_length;
write_serial_string_n(file, file_length);
write_serial_string_n(" ", 1);
write_serial_string_n(function, function_length);
write_serial_string_n(" -", 2);
for (int i = file_length + function_length + 3; i < 49; ++i)
write_serial_string_n("-", 1);
while (format[0] == ' ') {
write_serial_string_n("-", 1);
++format;
}
write_serial_string_n(" ", 1);
va_list args;
va_start(args, format);
while (1) {
int next_percent = 0;
while (format[next_percent] != '%') {
if (format[next_percent] == 0) {
write_serial_string_n(format, next_percent);
va_end(args);
write_serial_string_n("\r\n", 2);
return;
}
++next_percent;
}
write_serial_string_n(format, next_percent);
switch (format[next_percent + 1]) {
case 's': {
const char *arg = va_arg(args, const char *);
write_serial_string(arg);
break;
}
case 'd': {
int arg = va_arg(args, int);
write_serial_integer(arg);
break;
}
case 'h': {
uint64_t arg1 = va_arg(args, uint64_t);
int arg2 = va_arg(args, int);
write_serial_hex(arg1, arg2);
break;
}
case 'B': {
uint64_t arg = va_arg(args, uint64_t);
if (arg < 10000) {
write_serial_integer(arg);
write_serial_string_n(" B", 2);
}
else if ((arg + 512) / 1024 < 10000) {
write_serial_integer((arg + 512) / 1024);
write_serial_string_n(" kiB", 4);
}
else if ((arg + 512 * 1024) / (1024 * 1024) < 10000) {
write_serial_integer((arg + 512 * 1024) / (1024 * 1024));
write_serial_string_n(" MiB", 4);
}
else if ((arg + 512 * 1024 * 1024) / (1024 * 1024 * 1024) < 10000) {
write_serial_integer((arg + 512 * 1024 * 1024) / (1024 * 1024 * 1024));
write_serial_string_n(" GiB", 4);
}
else {
write_serial_integer((arg + 512 * 1024 * 1024 * 1024ULL) / (1024 * 1024 * 1024 * 1024ULL));
write_serial_string_n(" TiB", 4);
}
break;
}
default:
panic("bad format string")
}
format = &format[next_percent + 2];
}
}

44
src/kernel/debug.h Normal file
View file

@ -0,0 +1,44 @@
/* Calcite, src/kernel/debug.h
* 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
* 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 log_core(const char *file, const char *function, const char *format, ...);
#define log(...) { \
log_core(__FILE__, __func__, __VA_ARGS__); \
}
#ifdef CALCITE_DEBUG
#define debug_log(...) log(__VA_ARGS__)
#elif CALCITE_RELEASE
#define debug_log(...) {}
#else
#error neither CALCITE_DEBUG nor CALCITE_RELEASE defined
#endif
#define panic(message) { \
log_core(__FILE__, __func__, "kernel panic: %s", message); \
while (1) \
__asm__ ("hlt"); \
}
#define assert(condition) \
{ \
if (!(condition)) \
panic("assertion failed: " #condition) \
}

55
src/kernel/drives.c Normal file
View file

@ -0,0 +1,55 @@
/* Calcite, src/kernel/drives.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 "utility.h"
#include "drives.h"
#include "heap.h"
struct drive_info *drive_list;
int drive_list_buffer_length = 0;
int drive_list_count = 0;
#define DRIVE_LIST_INITIAL_LENGTH 16
struct drive_info *add_drive() {
if (drive_list_buffer_length == 0) {
drive_list = heap_alloc(DRIVE_LIST_INITIAL_LENGTH * sizeof(struct drive_info));
drive_list_buffer_length = DRIVE_LIST_INITIAL_LENGTH;
}
else if (drive_list_count == drive_list_buffer_length) {
struct drive_info *new_drive_list =
heap_alloc(2 * drive_list_buffer_length * sizeof(struct drive_info));
memcpy(new_drive_list, drive_list, drive_list_count * sizeof(struct drive_info));
heap_dealloc(drive_list, drive_list_count * sizeof(struct drive_info));
drive_list = new_drive_list;
drive_list_buffer_length *= 2;
}
struct drive_info *to_return = &drive_list[drive_list_count];
++drive_list_count;
return to_return;
}
const struct drive_info *look_up_drive(const char *name) {
for (int i = 0; i < drive_list_count; ++i)
if (strequ(drive_list[i].name, name))
return &drive_list[i];
return 0;
}

46
src/kernel/drives.h Normal file
View file

@ -0,0 +1,46 @@
/* Calcite, src/kernel/drives.h
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
enum drive_access_result {
DAR_SUCCESS,
DAR_HARDWARE_ERROR
};
struct drive_info {
const char *name;
uint64_t block_size;
uint64_t block_count;
const void *driver_info;
//start and count are both in blocks. bounds-checking is caller responsibility.
enum drive_access_result (*read_blocks)(
const struct drive_info *info, void *buffer, uint64_t start, uint64_t count);
};
//add a drive by calling this and writing to the returned pointer.
//the returned pointer might be invalidated by future calls.
struct drive_info *add_drive();
//returned pointer might be invalidated by future calls to add_drive.
//returns 0 if there is no drive with that name.
const struct drive_info *look_up_drive(const char *name);

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,17 +17,23 @@
#include "framebuffer.h"
#include "interrupts.h"
#include "kernel-public/syscall-numbers.h"
#include "ipc-dgram.h"
#include "scheduler.h"
#include "syscalls.h"
#include "process.h"
#include "utility.h"
#include "initfs.h"
#include "drives.h"
#include "paging.h"
#include "panic.h"
#include "serial.h"
#include "debug.h"
#include "input.h"
#include "timer.h"
#include "heap.h"
#include "pci.h"
#include "ps2.h"
#include "fs.h"
#include <kernel-public/syscall-numbers.h>
#include <limine.h>
LIMINE_BASE_REVISION(3)
@ -56,8 +62,8 @@ static volatile struct limine_memmap_request memmap_request = {
.response = 0
};
static volatile struct limine_module_request module_request = {
.id = LIMINE_MODULE_REQUEST,
static volatile struct limine_executable_cmdline_request cmdline_request = {
.id = LIMINE_EXECUTABLE_CMDLINE_REQUEST,
.revision = 0,
.response = 0
};
@ -83,8 +89,8 @@ static void map_kernel_region(
physical_start + i, (uint8_t *)virtual_start + i, writable, executable);
}
static uint8_t *initfs_start;
static uint64_t initfs_length;
#define MAX_CMDLINE_LENGTH 1000
static char cmdline_copy[MAX_CMDLINE_LENGTH + 1];
[[noreturn]] static void with_kernel_page_tables();
@ -94,9 +100,7 @@ static uint64_t initfs_length;
if (fb_request.response == 0 || hhdm_request.response == 0 ||
ka_request.response == 0 || memmap_request.response == 0 ||
module_request.response == 0 ||
fb_request.response->framebuffer_count == 0 ||
module_request.response->module_count == 0)
fb_request.response->framebuffer_count == 0)
die();
struct limine_framebuffer *fb = fb_request.response->framebuffers[0];
@ -106,19 +110,6 @@ static uint64_t initfs_length;
fb->blue_mask_shift != 0 || fb->blue_mask_size != 8)
die();
//find the initfs, and die if we don't have one.
struct limine_file *initfs = 0;
struct limine_module_response *response = module_request.response;
for (uint64_t i = 0; i < response->module_count; ++i)
if (strequ(response->modules[i]->cmdline, "initfs")) {
initfs = response->modules[i];
break;
}
if (!initfs)
die();
//set up page tables. we will mark the regions with bootloader structures as
//usable, so we need to be careful not to allocate any physical pages until
//after we are done using bootloader structures. we map the kernel into our
@ -153,7 +144,8 @@ static uint64_t initfs_length;
uint64_t fb_length = ((fb->height * fb->pitch - 1) / 4096 + 1) * 4096;
fb_physical_base = (uint64_t)fb->address - hhdm_request.response->offset;
fb_base = find_free_kernel_region(fb_length);
map_kernel_region(fb_physical_base, fb_base, fb_length, 1, 0);
map_kernel_region(
fb_physical_base, fb_base, fb_length, 1, 0);
//store rest of framebuffer information
@ -161,17 +153,17 @@ static uint64_t initfs_length;
fb_height = fb->height;
fb_pitch = fb->pitch;
//remap initfs module and store information.
//make a copy of the kernel cmdline
uint64_t initfs_physical_start =
(uint64_t)initfs->address - hhdm_request.response->offset;
const char *original_cmdline = cmdline_request.response->cmdline;
int cmdline_length = 0;
while (original_cmdline[cmdline_length] != 0)
++cmdline_length;
initfs_length = initfs->size;
uint64_t initfs_length_rounded = ((initfs_length - 1) / 4096 + 1) * 4096;
if (cmdline_length > MAX_CMDLINE_LENGTH)
die();
initfs_start = find_free_kernel_region(initfs_length_rounded);
map_kernel_region(
initfs_physical_start, initfs_start, initfs_length_rounded, 0, 0);
memcpy(cmdline_copy, original_cmdline, cmdline_length + 1);
//switch to kernel page tables!
@ -179,47 +171,157 @@ static uint64_t initfs_length;
}
struct cmdline_pair {
const char *key;
const char *value;
};
static const struct cmdline_pair *cmdline_pairs;
static int cmdline_pair_count;
//returns the corresponding value if the key exists, otherwise returns 0.
static const char *cmdline_look_up(const char *key) {
for (int i = 0; i < cmdline_pair_count; ++i)
if (strequ(cmdline_pairs[i].key, key))
return cmdline_pairs[i].value;
return 0;
}
[[noreturn]] static void with_kernel_page_tables() {
//set initfs
init_serial();
set_initfs(initfs_start, initfs_length);
//store cmdline as key-value pairs
if (cmdline_copy[0] == 0)
cmdline_pair_count = 0;
else {
int comma_count = 0;
for (int i = 0; cmdline_copy[i] != 0; ++i)
if (cmdline_copy[i] == ',')
++comma_count;
cmdline_pair_count = comma_count + 1;
struct cmdline_pair *pairs = heap_alloc(cmdline_pair_count * sizeof(struct cmdline_pair));
int key_start = 0;
for (int i = 0; i < cmdline_pair_count; ++i) {
int key_end = key_start;
while (1) {
if (cmdline_copy[key_end] == '=')
break;
if (cmdline_copy[key_end] == ',' || cmdline_copy[key_end] == 0)
die();
++key_end;
}
int value_start = key_end + 1;
int value_end = value_start;
while (1) {
if (cmdline_copy[value_end] == ',' || cmdline_copy[value_end] == 0)
break;
if (cmdline_copy[value_end] == '=')
die();
++value_end;
}
char *key = heap_alloc(key_end - key_start + 1);
memcpy(key, &cmdline_copy[key_start], key_end - key_start);
key[key_end - key_start] = 0;
pairs[i].key = key;
char *value = heap_alloc(value_end - value_start + 1);
memcpy(value, &cmdline_copy[value_start], value_end - value_start);
value[value_end - value_start] = 0;
pairs[i].value = value;
key_start = value_end + 1;
}
cmdline_pairs = pairs;
}
debug_log("command line")
for (int i = 0; i < cmdline_pair_count; ++i)
debug_log(" %s %s", cmdline_pairs[i].key, cmdline_pairs[i].value)
//set up interrupts
init_timer();
init_ps2();
set_irq_handler(0x00, &on_pit_irq);
set_irq_handler(0x01, &on_keyboard_irq);
set_irq_handler(0x0c, &on_mouse_irq);
enable_interrupts();
init_interrupts();
//set up syscalls
init_syscalls();
register_syscall(SYSCALL_END_THREAD, (void *)&syscall_end_thread);
register_syscall(SYSCALL_MAP_FRAMEBUFFER, (void *)&syscall_map_framebuffer);
register_syscall(SYSCALL_OPEN_FILE, (void *)&syscall_open_file);
register_syscall(SYSCALL_CLOSE_FILE, (void *)&syscall_close_file);
register_syscall(SYSCALL_GET_FILE_SIZE, (void *)&syscall_get_file_size);
register_syscall(SYSCALL_READ_FILE, (void *)&syscall_read_file);
register_syscall(SYSCALL_WAIT_FOR_MOUSE_PACKET, (void *)&syscall_wait_for_mouse_packet);
register_syscall(SYSCALL_MAP_PAGES, (void *)&syscall_map_pages);
register_syscall(SYSCALL_SLEEP_MS, (void *)&syscall_sleep_ms);
register_syscall(SYSCALL_START_ELF, (void *)&syscall_start_elf);
register_syscall(SYSCALL_IPC_CREATE_DGRAM_RECEIVER, (void *)&syscall_ipc_create_dgram_receiver);
register_syscall(SYSCALL_IPC_CREATE_DGRAM_SENDER, (void *)&syscall_ipc_create_dgram_sender);
register_syscall(SYSCALL_IPC_RECEIVE_DGRAM, (void *)&syscall_ipc_receive_dgram);
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);
register_syscall(SYSCALL_CHANGE_PAGE_PERMISSIONS, (void *)&syscall_change_page_permissions);
register_syscall(SYSCALL_WAIT_FOR_KEYBOARD_PACKET, (void *)&syscall_wait_for_keyboard_packet);
//load hello and start it
//probe pci devices
probe_pci();
//load root file system
const char *root_drive_name = cmdline_look_up("root-drive");
if (!root_drive_name)
panic("root drive mising from cmdline")
const struct drive_info *root_drive = look_up_drive(root_drive_name);
if (!root_drive)
panic("could not find root drive")
const char *root_fs_type = cmdline_look_up("root-fs");
if (!root_fs_type)
panic("root fs missing from cmdline")
struct fs_info *root_fs = heap_alloc(sizeof(struct fs_info));
if (create_fs_info(root_drive, root_fs, root_fs_type) != FAR_SUCCESS)
panic("could not create root file system info")
set_fs("root", root_fs);
//load init and start it
init_scheduler();
const uint8_t *hello_start;
uint64_t hello_length;
look_up_initfs_file("apps/hello.elf", &hello_start, &hello_length);
struct process *hello = heap_alloc(sizeof(struct process));
uint64_t hello_entry;
create_process(hello);
load_elf(hello, &hello_entry, hello_start, hello_length);
struct thread *hello_thread = heap_alloc(sizeof(struct thread));
create_thread(hello, hello_thread);
create_user_task(
hello->p4_physical_base,
hello_entry,
(uint64_t)hello_thread->stack_top);
running_thread = hello_thread;
if (start_elf("root://calcite/apps/init/init.elf") == 0)
panic("could not start init.elf")
resume_next_continuation();

105
src/kernel/fs.c Normal file
View file

@ -0,0 +1,105 @@
/* Calcite, src/kernel/fs.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 "iso9660.h"
#include "utility.h"
#include "debug.h"
#include "heap.h"
#include "fs.h"
#include <kernel-public/files.h>
enum fs_access_result create_fs_info(
const struct drive_info *drive, struct fs_info *fs_out, const char *fs_type) {
if (strequ(fs_type, "iso9660"))
return create_iso9660_info(drive, fs_out);
return FAR_UNKNOWN_FS_TYPE;
}
struct set_fs {
const char *name;
const struct fs_info *fs;
};
struct set_fs *set_fses;
int set_fses_buffer_length = 0;
int set_fses_count = 0;
#define SET_FSES_INITIAL_LENGTH 16
void set_fs(const char *name, const struct fs_info *fs) {
assert(get_fs(name) == 0)
if (set_fses_buffer_length == 0) {
set_fses = heap_alloc(SET_FSES_INITIAL_LENGTH * sizeof(struct set_fs));
set_fses_buffer_length = SET_FSES_INITIAL_LENGTH;
}
else if (set_fses_count == set_fses_buffer_length) {
struct set_fs *new_set_fses = heap_alloc(2 * set_fses_buffer_length * sizeof(struct set_fs));
memcpy(new_set_fses, set_fses, set_fses_count * sizeof(struct set_fs));
heap_dealloc(set_fses, set_fses_count * sizeof(struct set_fs));
set_fses = new_set_fses;
set_fses_buffer_length *= 2;
}
set_fses[set_fses_count].name = name;
set_fses[set_fses_count].fs = fs;
++set_fses_count;
}
const struct fs_info *get_fs(const char *name) {
for (int i = 0; i < set_fses_count; ++i)
if (strequ(set_fses[i].name, name))
return set_fses[i].fs;
return 0;
}
enum fs_access_result look_up_file(
const char *uri, const struct fs_info **fs_out, void **node_out) {
int colon = 0;
while (1) {
if (uri[colon] == ':')
break;
if (uri[colon] == 0)
panic("bad uri")
++colon;
}
if (uri[colon + 1] != '/' || uri[colon + 2] != '/')
panic("bad uri")
char *uri_copy = heap_alloc(colon + 1);
memcpy(uri_copy, uri, colon);
uri_copy[colon] = 0;
*fs_out = get_fs(uri_copy);
heap_dealloc(uri_copy, colon + 1);
if (!*fs_out)
return FAR_NOT_FOUND;
return (*(*fs_out)->look_up_file)(*fs_out, node_out, &uri[colon + 3]);
}

58
src/kernel/fs.h Normal file
View file

@ -0,0 +1,58 @@
/* Calcite, src/kernel/fs.h
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "drives.h"
#include <kernel-public/files.h>
#include <stdint.h>
struct fs_stat {
uint64_t bytes;
};
struct fs_info {
const struct drive_info *drive;
const void *driver_info;
enum fs_access_result (*free_node)(
const struct fs_info *info, void *node);
enum fs_access_result (*look_up_file)(
const struct fs_info *info, void **node_out, const char *path);
enum fs_access_result (*stat_file)(
const struct fs_info *info, void *node, struct fs_stat *stat_out);
//start is in bytes. bounds-checking is caller responsibility.
enum fs_access_result (*read_file)(
const struct fs_info *info, void *node,
void *buffer, uint64_t start, uint64_t bytes);
};
enum fs_access_result create_fs_info(
const struct drive_info *drive, struct fs_info *fs_out, const char *fs_type);
void set_fs(const char *name, const struct fs_info *fs);
//returns 0 if no fs is set with that name.
const struct fs_info *get_fs(const char *name);
enum fs_access_result look_up_file(const char *uri, const struct fs_info **fs_out, void **node_out);

View file

@ -1,74 +0,0 @@
/* Calcite, src/kernel/initfs.c
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 "initfs.h"
static const uint8_t *initfs_start;
static uint64_t initfs_length;
void set_initfs(const uint8_t *start, uint64_t length) {
initfs_start = start;
initfs_length = length;
}
static uint64_t decode_octal(const char *from) {
uint64_t value = 0;
while (*from != '\0') {
value = value * 8 + *from - '0';
++from;
}
return value;
}
void look_up_initfs_file(
const char *path, const uint8_t **start_out, uint64_t *length_out) {
int path_len = 0;
while (path[path_len] != '\0')
++path_len;
uint64_t offset = 0;
while (1) {
if (offset + 512 > initfs_length) {
*start_out = 0;
return;
}
uint64_t length =
decode_octal((const char *)(initfs_start + offset + 0x7c));
int found_it = 1;
for (int i = 0; i < path_len; ++i)
if (initfs_start[offset + i] != path[i]) {
found_it = 0;
break;
}
if (found_it) {
*start_out = initfs_start + offset + 512;
*length_out = length;
return;
}
offset += 512 + ((length - 1) / 512 + 1) * 512;
}
}

140
src/kernel/input.c Normal file
View file

@ -0,0 +1,140 @@
/* Calcite, src/kernel/input.c
* 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
* 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 "scheduler.h"
#include "process.h"
#include "debug.h"
#include "input.h"
#include "heap.h"
static int is_somebody_waiting_for_mouse_packet = 0;
static struct continuation_info waiting_continuation_mouse;
static struct mouse_packet *resume_by_reporting_to_mouse;
static int total_unreported_x_movement = 0;
static int total_unreported_y_movement = 0;
void add_mouse_movement(int x, int y) {
total_unreported_x_movement += x;
total_unreported_y_movement += y;
if (is_somebody_waiting_for_mouse_packet) {
resume_by_reporting_to_mouse->x_change = total_unreported_x_movement;
resume_by_reporting_to_mouse->y_change = total_unreported_y_movement;
add_to_queue(&ready_continuations, &waiting_continuation_mouse);
total_unreported_x_movement = 0;
total_unreported_y_movement = 0;
is_somebody_waiting_for_mouse_packet = 0;
}
}
void syscall_wait_for_mouse_packet(struct mouse_packet *packet_out) {
assert(running_thread != 0);
//TODO: handle these
if (is_somebody_waiting_for_mouse_packet ||
!is_mapped_writable(
running_thread->process, packet_out, sizeof(struct mouse_packet)))
panic("TODO")
if (total_unreported_x_movement != 0 || total_unreported_y_movement != 0) {
packet_out->x_change = total_unreported_x_movement;
packet_out->y_change = total_unreported_y_movement;
total_unreported_x_movement = 0;
total_unreported_y_movement = 0;
return;
}
is_somebody_waiting_for_mouse_packet = 1;
resume_by_reporting_to_mouse = packet_out;
yield(&waiting_continuation_mouse);
}
static int is_somebody_waiting_for_keyboard_packet = 0;
static struct continuation_info waiting_continuation_keyboard;
static int *resume_by_reporting_to_keyboard;
//in packets
#define INITIAL_KEYBOARD_BUFFER_LENGTH 1024
static int *keyboard_buffer = 0;
static int keyboard_buffer_length = 0;
static int keyboard_buffer_used = 0;
static int keyboard_buffer_read_pointer = 0;
void add_keyboard_packet(int packet) {
if (is_somebody_waiting_for_keyboard_packet) {
*resume_by_reporting_to_keyboard = packet;
add_to_queue(&ready_continuations, &waiting_continuation_keyboard);
is_somebody_waiting_for_keyboard_packet = 0;
}
else if (keyboard_buffer_used < keyboard_buffer_length) {
int write_pointer = (keyboard_buffer_read_pointer + keyboard_buffer_used) % keyboard_buffer_length;
keyboard_buffer[write_pointer] = packet;
++keyboard_buffer_used;
}
else if (keyboard_buffer == 0) {
keyboard_buffer = heap_alloc(INITIAL_KEYBOARD_BUFFER_LENGTH * sizeof(int));
keyboard_buffer_length = INITIAL_KEYBOARD_BUFFER_LENGTH;
keyboard_buffer[0] = packet;
keyboard_buffer_used = 1;
}
else {
int *new_buffer = heap_alloc(2 * keyboard_buffer_length * sizeof(int));
memcpy(
new_buffer, &keyboard_buffer[keyboard_buffer_read_pointer],
keyboard_buffer_length - keyboard_buffer_read_pointer);
memcpy(
&new_buffer[keyboard_buffer_length - keyboard_buffer_read_pointer],
keyboard_buffer, keyboard_buffer_read_pointer);
heap_dealloc(keyboard_buffer, keyboard_buffer_length * sizeof(int));
keyboard_buffer = new_buffer;
keyboard_buffer_length *= 2;
keyboard_buffer_read_pointer = 0;
keyboard_buffer[keyboard_buffer_length / 2] = packet;
++keyboard_buffer_used;
}
}
int syscall_wait_for_keyboard_packet() {
if (keyboard_buffer_used > 0) {
int value = keyboard_buffer[keyboard_buffer_read_pointer];
keyboard_buffer_read_pointer = (keyboard_buffer_read_pointer + 1) % keyboard_buffer_length;
--keyboard_buffer_used;
return value;
}
if (is_somebody_waiting_for_keyboard_packet)
panic("TODO")
is_somebody_waiting_for_keyboard_packet = 1;
int result;
resume_by_reporting_to_keyboard = &result;
yield(&waiting_continuation_keyboard);
return result;
}

28
src/kernel/input.h Normal file
View file

@ -0,0 +1,28 @@
/* Calcite, src/kernel/input.h
* 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
* 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 <kernel-public/input.h>
void add_mouse_movement(int x, int y);
void syscall_wait_for_mouse_packet(struct mouse_packet *packet_out);
void add_keyboard_packet(int packet);
int syscall_wait_for_keyboard_packet();

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
@ -16,6 +16,7 @@
bits 64
default rel
;both defined in interrupts.c
extern isr_exception_c
@ -70,8 +71,28 @@ 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
call isr_exception_c
add rsp, 48
pop r11
pop r10
pop r9
pop r8
add rsp, 8
pop rsi
pop rdi
pop rdx
pop rcx
add rsp, 8
pop rax
add rsp, 16
iretq
isr_irq_common:
push rax
@ -114,8 +135,8 @@ isr_irq_common:
;rdi is pointer to gdt descriptor
;rsi is pointer to idt descriptor
;dx is offset of tss into gdt
global enable_interrupts_asm
enable_interrupts_asm:
global init_interrupts_asm
init_interrupts_asm:
;load tables
@ -147,7 +168,4 @@ enable_interrupts_asm:
mov al, 0x00
out 0xa1, al
;enable interrupts and return
sti
ret

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
@ -22,9 +22,14 @@
// https://osdev.wiki/wiki/Task_State_Segment
#include "interrupts.h"
#include "panic.h"
#include "process.h"
#include "paging.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 +55,73 @@ 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(const 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")
while (1)
__asm__ ("hlt");
}
//referenced in interrupts.asm
void isr_exception_c(struct exception_parameter *parameter) {
(void)parameter;
panic("TODO");
void isr_exception_c(const struct exception_parameter *parameter) {
if (running_thread != 0 &&
parameter->exception_number == 0x0e &&
(parameter->error_code & 0x1) == 0x0 &&
parameter->cr2 >= (uint64_t)running_thread->stack_bottom + 4096 &&
parameter->cr2 < (uint64_t)running_thread->stack_top) {
void *vma = (void *)(parameter->cr2 & 0xfffffffffffff000);
unmap_page_for_process(running_thread->process, vma);
uint64_t pma = take_free_physical_page();
map_page_for_process(
running_thread->process, pma,
vma, 1, 0, 1);
}
else
unhandled_exception(parameter);
}
void (*irq_handlers[16])() = {
@ -185,12 +253,12 @@ static struct idt_descriptor the_idt_descriptor = {
extern void *isrs[48];
//defined in interrupts.asm
void enable_interrupts_asm(
void init_interrupts_asm(
struct gdt_descriptor *gdtr,
struct idt_descriptor *idtr,
uint16_t tssr);
void enable_interrupts() {
void init_interrupts() {
the_tss.ist1 = (uint64_t)interrupt_stack_one + INTERRUPT_STACK_SIZE;
the_tss.ist2 = (uint64_t)interrupt_stack_two + INTERRUPT_STACK_SIZE;
@ -247,6 +315,6 @@ void enable_interrupts() {
the_idt[i].offset_32 = offset >> 32;
}
enable_interrupts_asm(&the_gdt_descriptor, &the_idt_descriptor, 0x08);
init_interrupts_asm(&the_gdt_descriptor, &the_idt_descriptor, 0x08);
}

View file

@ -1,5 +1,5 @@
/* Calcite, src/kernel/interrupts.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,8 +19,8 @@
#include <stdint.h>
//switches to kernel gdt and idt, maps and unmasks irqs, enables interrupts
void enable_interrupts();
//switches to kernel gdt and idt, maps and unmasks irqs
void init_interrupts();
//the handler should have normal C linkage
void set_irq_handler(uint8_t irq_line, void (*handler)());

269
src/kernel/ipc-dgram.c Normal file
View file

@ -0,0 +1,269 @@
/* Calcite, src/kernel/ipc-dgram.c
* 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
* 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 "ipc-dgram.h"
#include "process.h"
#include "scheduler.h"
#include "utility.h"
#include "debug.h"
#include "heap.h"
#include <kernel-public/ipc.h>
struct ipc_dgram_address_book {
const char *address;
struct ipc_dgram_box box;
struct ipc_dgram_address_book *prev;
};
static struct ipc_dgram_address_book *last = 0;
#define INITIAL_SEND_BACKLOG 4
struct ipc_dgram_box *get_ipc_dgram_box(const char *address) {
for (struct ipc_dgram_address_book *book = last; book != 0; book = book->prev)
if (strequ(book->address, address))
return &book->box;
struct ipc_dgram_address_book *new = heap_alloc(sizeof(struct ipc_dgram_address_book));
int address_length = 0;
while (address[address_length] != 0)
++address_length;
char *new_address = heap_alloc(address_length + 1);
memcpy(new_address, address, address_length);
new_address[address_length] = 0;
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;
new->box.buffer_read_available = 0;
new->prev = last;
last = new;
return &new->box;
}
int get_ipc_dgram_size(struct ipc_dgram_box *box) {
if (box->is_someone_waiting_to_receive)
return -1;
while (box->buffer_read_available == 0) {
box->is_someone_waiting_to_receive = 1;
yield(&box->waiting_to_receive);
}
assert(box->buffer_read_available >= 4)
return *(int *)&box->buffer[box->buffer_read_pointer];
}
void receive_ipc_dgram(struct ipc_dgram_box *box, void *buffer) {
assert(box->is_someone_waiting_to_receive == 0)
assert(box->buffer_read_available >= 4)
int size = *(int *)&box->buffer[box->buffer_read_pointer];
box->buffer_read_available -= 4;
box->buffer_read_pointer = (box->buffer_read_pointer + 4) % IPC_DGRAM_BOX_BUFFER_LENGTH;
int size_rounded_up = ((size - 1) / 4 + 1) * 4;
assert(box->buffer_read_available >= size_rounded_up)
int first_block = IPC_DGRAM_BOX_BUFFER_LENGTH - box->buffer_read_pointer;
if (size < first_block)
first_block = size;
memcpy(buffer, &box->buffer[box->buffer_read_pointer], first_block);
if (first_block != size)
memcpy(buffer + first_block, box->buffer, size - first_block);
box->buffer_read_available -= size_rounded_up;
box->buffer_read_pointer = (box->buffer_read_pointer + size_rounded_up) % IPC_DGRAM_BOX_BUFFER_LENGTH;
struct continuation_info ci;
while (take_from_queue(&box->waiting_to_send, &ci))
add_to_queue(&ready_continuations, &ci);
}
enum ipc_dgram_result send_ipc_dgram(struct ipc_dgram_box *box, const void *data, int bytes) {
assert(bytes > 0)
int bytes_round_up = ((bytes - 1) / 4 + 1) * 4;
if (4 + bytes_round_up > IPC_DGRAM_BOX_BUFFER_LENGTH)
return IPR_TOO_BIG;
while (IPC_DGRAM_BOX_BUFFER_LENGTH - box->buffer_read_available < 4 + bytes_round_up)
yield_to_queue(&box->waiting_to_send);
int write_pointer = (box->buffer_read_pointer + box->buffer_read_available) % IPC_DGRAM_BOX_BUFFER_LENGTH;
assert(write_pointer + 4 <= IPC_DGRAM_BOX_BUFFER_LENGTH)
*(int *)&box->buffer[write_pointer] = bytes;
write_pointer = (write_pointer + 4) % IPC_DGRAM_BOX_BUFFER_LENGTH;
int first_block = IPC_DGRAM_BOX_BUFFER_LENGTH - write_pointer;
if (bytes < first_block)
first_block = bytes;
memcpy(&box->buffer[write_pointer], data, first_block);
if (first_block != bytes)
memcpy(box->buffer, data + first_block, bytes - first_block);
box->buffer_read_available += 4 + bytes_round_up;
if (box->is_someone_waiting_to_receive) {
box->is_someone_waiting_to_receive = 0;
add_to_queue(&ready_continuations, &box->waiting_to_receive);
}
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);
}

68
src/kernel/ipc-dgram.h Normal file
View file

@ -0,0 +1,68 @@
/* Calcite, src/kernel/ipc-dgram.h
* 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
* 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 "scheduler.h"
#include <kernel-public/ipc.h>
//must be a multiple of 4
#define IPC_DGRAM_BOX_BUFFER_LENGTH 1024
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;
struct continuation_queue waiting_to_send;
int buffer_read_pointer;
int buffer_read_available;
//packets are of the form:
// int size;
// uint8_t packet[size];
// uint8_t padding[size % 4 == 0 ? 0 : 4 - size % 4];
uint8_t buffer[IPC_DGRAM_BOX_BUFFER_LENGTH];
};
struct ipc_dgram_box *get_ipc_dgram_box(const char *address);
//waits until a packet is available, then returns the size of that packet.
//returns -1 if someone is already waiting.
int get_ipc_dgram_size(struct ipc_dgram_box *box);
//should only be called just after get_ipc_dgram_size
//(i.e. with no intervening continuation resumptions)
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);

382
src/kernel/iso9660.c Normal file
View file

@ -0,0 +1,382 @@
/* Calcite, src/kernel/iso9660.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 "iso9660.h"
#include "kernel-public/files.h"
#include "utility.h"
#include "drives.h"
#include "debug.h"
#include "heap.h"
#include "fs.h"
//relevant sources:
// https://www.ecma-international.org/wp-content/uploads/ECMA-119_2nd_edition_december_1987.pdf
//case-insensitive equality
static bool ciequ(char c1, char c2) {
if (c1 >= 'a' && c1 <= 'z')
c1 &= 0xdf;
if (c2 >= 'a' && c2 <= 'z')
c2 &= 0xdf;
return c1 == c2;
}
struct iso9660_driver_info {
const void *path_table;
//in bytes
int path_table_length;
};
static const struct iso9660_driver_info *get_driver_info(const struct fs_info *info) {
return (const struct iso9660_driver_info *)info->driver_info;
}
struct iso9660_node {
uint32_t first_block;
//in bytes
uint32_t length;
};
static enum fs_access_result free_node_iso9660(const struct fs_info *info, void *node) {
(void)info;
heap_dealloc(node, sizeof(struct iso9660_node));
return FAR_SUCCESS;
}
static uint8_t directory_buffer[2048];
static enum fs_access_result look_up_in_directory(
const struct fs_info *info, void **node_out,
const char *path, int path_length,
uint32_t first_directory_block) {
if (first_directory_block >= info->drive->block_count)
return FAR_FORMAT_ERROR;
switch ((*info->drive->read_blocks)(info->drive, directory_buffer, first_directory_block, 1)) {
case DAR_SUCCESS:
break;
case DAR_HARDWARE_ERROR:
return FAR_HARDWARE_ERROR;
default:
assert(0);
}
int length = *(uint32_t *)&directory_buffer[10];
if (length % 2048 != 0)
return FAR_FORMAT_ERROR;
uint32_t loaded_block = first_directory_block;
int offset_in_block = 0;
int total_offset = 0;
while (1) {
if (total_offset == length)
return FAR_NOT_FOUND;
if (offset_in_block == 2048 || directory_buffer[offset_in_block] == 0) {
++loaded_block;
if (loaded_block >= info->drive->block_count)
return FAR_FORMAT_ERROR;
switch ((*info->drive->read_blocks)(info->drive, directory_buffer, loaded_block, 1)) {
case DAR_SUCCESS:
break;
case DAR_HARDWARE_ERROR:
return FAR_HARDWARE_ERROR;
default:
assert(0);
}
total_offset = total_offset - offset_in_block + 2048;
offset_in_block = 0;
if (total_offset == length)
return FAR_NOT_FOUND;
}
int dr_length = directory_buffer[offset_in_block];
if (offset_in_block + dr_length > 2048)
return FAR_FORMAT_ERROR;
int id_length = directory_buffer[offset_in_block + 32];
if (33 + id_length > dr_length)
return FAR_FORMAT_ERROR;
for (int i = 0; i < id_length; ++i)
if (directory_buffer[offset_in_block + 33 + i] == ';') {
id_length = i;
break;
}
if (id_length != path_length)
goto next_entry;
for (int i = 0; i < id_length; ++i)
if (!ciequ(directory_buffer[offset_in_block + 33 + i], path[i]))
goto next_entry;
struct iso9660_node *node = heap_alloc(sizeof(struct iso9660_node));
node->first_block = *(uint32_t *)&directory_buffer[offset_in_block + 2];
node->length = *(uint32_t *)&directory_buffer[offset_in_block + 10];
*node_out = node;
return FAR_SUCCESS;
next_entry:
offset_in_block += dr_length;
total_offset += dr_length;
}
}
static enum fs_access_result look_up_recursive(
const struct fs_info *info, void **node_out,
const char *path, int parent_path_table_offset,
int parent_directory_number) {
//FIXME: this function is pretty unoptimized
recurse:
int slash_location = 0;
int path_length;
while (1) {
if (path[slash_location] == '/')
break;
if (path[slash_location] == 0) {
path_length = slash_location;
slash_location = -1;
break;
}
++slash_location;
}
const struct iso9660_driver_info *driver_info = get_driver_info(info);
int on_offset = parent_path_table_offset;
int on_number = parent_directory_number;
if (slash_location == -1) {
if (on_offset + 6 > driver_info->path_table_length)
return FAR_NOT_FOUND;
uint32_t first_directory_block = *(uint32_t *)(driver_info->path_table + on_offset + 2);
return look_up_in_directory(info, node_out, path, path_length, first_directory_block);
}
while (1) {
if (on_offset >= driver_info->path_table_length)
return FAR_NOT_FOUND;
int old_id_length = *(uint8_t *)(driver_info->path_table + on_offset);
on_offset += 8 + old_id_length + (old_id_length & 0x01);
++on_number;
if (on_offset + 8 > driver_info->path_table_length)
return FAR_NOT_FOUND;
int this_entry_parent = *(uint16_t *)(driver_info->path_table + on_offset + 6);
if (this_entry_parent != parent_directory_number)
continue;
int this_entry_id_length = *(uint8_t *)(driver_info->path_table + on_offset);
if (this_entry_id_length != slash_location)
continue;
if (on_offset + 8 + this_entry_id_length > driver_info->path_table_length)
return FAR_NOT_FOUND;
char *this_entry_id = (char *)(driver_info->path_table + on_offset + 8);
for (int i = 0; i < slash_location; ++i)
if (!ciequ(this_entry_id[i], path[i]))
goto next_entry;
path = path + slash_location + 1;
parent_path_table_offset = on_offset;
parent_directory_number = on_number;
goto recurse;
next_entry:
;
}
}
static enum fs_access_result look_up_file_iso9660(
const struct fs_info *info, void **node_out, const char *path) {
return look_up_recursive(info, node_out, path, 0, 1);
}
static enum fs_access_result stat_file_iso9660(
const struct fs_info *info, void *node, struct fs_stat *stat_out) {
(void)info;
struct iso9660_node *iso9660_node = (struct iso9660_node *)node;
stat_out->bytes = iso9660_node->length;
return FAR_SUCCESS;
}
static uint8_t read_buffer[2048];
static enum fs_access_result read_file_iso9660(
const struct fs_info *info, void *node, void *buffer, uint64_t start, uint64_t bytes) {
struct iso9660_node *iso9660_node = (struct iso9660_node *)node;
if (iso9660_node->first_block + (start + bytes - 1) / 2048 + 1 > info->drive->block_count)
return FAR_FORMAT_ERROR;
if (start % 2048 != 0) {
switch ((*info->drive->read_blocks)(
info->drive, read_buffer, iso9660_node->first_block + start / 2048, 1)) {
case DAR_SUCCESS:
break;
case DAR_HARDWARE_ERROR:
return FAR_HARDWARE_ERROR;
default:
assert(0);
}
if (start + bytes <= ((start - 1) / 2048 + 1) * 2048) {
memcpy(buffer, read_buffer + (start % 2048), bytes);
return FAR_SUCCESS;
}
int to_copy = 2048 - (start % 2048);
memcpy(buffer, read_buffer + (start % 2048), to_copy);
buffer += to_copy;
start += to_copy;
bytes -= to_copy;
}
switch ((*info->drive->read_blocks)(
info->drive, buffer,
iso9660_node->first_block + start / 2048,
bytes / 2048)) {
case DAR_SUCCESS:
break;
case DAR_HARDWARE_ERROR:
return FAR_HARDWARE_ERROR;
default:
assert(0);
}
buffer += (bytes / 2048) * 2048;
start += (bytes / 2048) * 2048;
bytes %= 2048;
if (bytes > 0) {
switch ((*info->drive->read_blocks)(
info->drive, read_buffer, iso9660_node->first_block + start / 2048, 1)) {
case DAR_SUCCESS:
break;
case DAR_HARDWARE_ERROR:
return FAR_HARDWARE_ERROR;
default:
assert(0);
}
memcpy(buffer, read_buffer + (start % 2048), bytes);
}
return FAR_SUCCESS;
}
static uint8_t vd_buffer[2048];
enum fs_access_result create_iso9660_info(const struct drive_info *drive, struct fs_info *fs_out) {
if (drive->block_size != 2048)
panic("TODO")
int descriptor_number = 0;
while (1) {
if ((uint64_t)(16 + descriptor_number) >= drive->block_count)
return FAR_FORMAT_ERROR;
switch ((*drive->read_blocks)(drive, vd_buffer, 16 + descriptor_number, 1)) {
case DAR_SUCCESS:
break;
case DAR_HARDWARE_ERROR:
return FAR_HARDWARE_ERROR;
default:
assert(0)
}
if (vd_buffer[0] == 0xff)
return FAR_FORMAT_ERROR;
if (vd_buffer[0] == 0x01)
break;
++descriptor_number;
}
uint32_t path_table_length = *(uint32_t *)&vd_buffer[132];
uint32_t path_table_start = *(uint32_t *)&vd_buffer[140];
uint64_t path_table_length_rounded_up = ((path_table_length - 1) / 2048 + 1) * 2048;
void *path_table = heap_alloc(path_table_length_rounded_up);
if (path_table_start >= drive->block_count)
return FAR_FORMAT_ERROR;
switch ((*drive->read_blocks)(
drive, path_table, path_table_start,
path_table_length_rounded_up / 2048)) {
case DAR_SUCCESS:
break;
case DAR_HARDWARE_ERROR:
heap_dealloc(path_table, path_table_length_rounded_up);
return FAR_HARDWARE_ERROR;
default:
assert(0);
}
heap_dealloc(
path_table + path_table_length,
path_table_length_rounded_up - path_table_length);
struct iso9660_driver_info *driver_info = heap_alloc(sizeof(struct iso9660_driver_info));
driver_info->path_table = path_table;
driver_info->path_table_length = path_table_length;
fs_out->drive = drive;
fs_out->driver_info = driver_info;
fs_out->free_node = &free_node_iso9660;
fs_out->look_up_file = &look_up_file_iso9660;
fs_out->stat_file = &stat_file_iso9660;
fs_out->read_file = &read_file_iso9660;
debug_log("created iso9660 file system")
debug_log(" drive name %s", drive->name)
debug_log(" path table start block %d", path_table_start)
debug_log(" path table block count %d", path_table_length_rounded_up / 2048)
return FAR_SUCCESS;
}

22
src/kernel/iso9660.h Normal file
View file

@ -0,0 +1,22 @@
/* Calcite, src/kernel/iso9660.h
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "fs.h"
enum fs_access_result create_iso9660_info(const struct drive_info *drive, struct fs_info *fs_out);

View file

@ -16,6 +16,7 @@
bits 64
default rel
;both defined in paging.c
extern kernel_p4_physical_address

View file

@ -1,5 +1,5 @@
/* Calcite, src/kernel/paging.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,7 +16,7 @@
*/
#include "paging.h"
#include "panic.h"
#include "debug.h"
#define MAX_PHYSICAL_GB 64ULL
@ -104,9 +104,7 @@ void mark_physical_memory_free(uint64_t base, uint64_t length) {
//defined in paging.asm
void invlpg(void *address);
void map_in_kernel_page_table(
uint64_t physical_base, void *virtual_base,
int writable, int executable) {
void map_in_kernel_page_table(uint64_t physical_base, void *virtual_base, int writable, int executable) {
uint64_t virtual_base_u64 = (uint64_t)virtual_base;
assert(virtual_base_u64 >= 0xffffffffc0000000);
@ -122,6 +120,18 @@ void map_in_kernel_page_table(
}
void create_kernel_guard_page(void *virtual_base) {
uint64_t virtual_base_u64 = (uint64_t)virtual_base;
assert(virtual_base_u64 >= 0xffffffffc0000000);
uint64_t p1s_index = (virtual_base_u64 - 0xffffffffc0000000) >> 12;
assert(kernel_p1s[p1s_index] == 0);
kernel_p1s[p1s_index] = 0x2;
}
void unmap_kernel_page(void *virtual_base) {
uint64_t virtual_base_u64 = (uint64_t)virtual_base;
@ -187,3 +197,36 @@ uint64_t take_free_physical_page() {
}
panic("out of physical memory");
}
#define SYSCALL_STACK_BYTES (1 << 15)
void *create_syscall_stack() {
void *vma = find_free_kernel_region(SYSCALL_STACK_BYTES);
for (uint64_t i = 0; i < SYSCALL_STACK_BYTES; i += 4096) {
uint64_t pma = take_free_physical_page();
map_in_kernel_page_table(pma, vma + i * 4096, 1, 0);
}
return vma + SYSCALL_STACK_BYTES;
}
void destroy_syscall_stack(void *stack_top) {
for (uint64_t i = 0; i < SYSCALL_STACK_BYTES; i += 4096)
unmap_and_free_kernel_page(stack_top - SYSCALL_STACK_BYTES + i * 4096);
}
uint64_t count_free_pram() {
uint64_t total = 0;
for (uint64_t i = 0; i < (MAX_PHYSICAL_GB << 15); ++i)
for (int j = 0; j < 8; ++j)
if (physical_map[i] & (1 << j))
total += 4096;
return total;
}
uint64_t count_free_kernel_vram() {
uint64_t total = 0;
for (uint64_t i = 0; i < 512 * 512; ++i)
if (kernel_p1s[i] == 0)
total += 4096;
return total;
}

View file

@ -1,5 +1,5 @@
/* Calcite, src/kernel/paging.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,6 +33,9 @@ void mark_physical_memory_free(uint64_t base, uint64_t length);
void map_in_kernel_page_table(
uint64_t physical_base, void *virtual_base, int writable, int executable);
//virtual base should be page-aligned and within kernel range.
void create_kernel_guard_page(void *virtual_base);
//unmaps one page. base should be page-aligned and within kernel range.
void unmap_kernel_page(void *virtual_base);
@ -48,3 +51,13 @@ uint64_t take_free_physical_page();
//implemented in paging.asm. the continuation should be noreturn.
[[noreturn]] void switch_to_kernel_page_tables(void (*continuation)());
//returns the top
void *create_syscall_stack();
void destroy_syscall_stack(void *stack_top);
//return value in bytes
uint64_t count_free_pram();
//return value in bytes
uint64_t count_free_kernel_vram();

250
src/kernel/pata.c Normal file
View file

@ -0,0 +1,250 @@
/* Calcite, src/kernel/pata.c
* 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
* 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 "utility.h"
#include "drives.h"
#include "debug.h"
#include "heap.h"
#include "pata.h"
#include "pci.h"
//some relevant sources:
// https://www.isdaman.com/alsos/hardware/hdc/pciide.pdf
// ANSI: AT Attachment 8 - ATA/ATAPI Command Set
// Seagate: SCSI Commands Reference Manual
// OSDev Wiki: ATAPI, PCI IDE Controller
enum pata_result {
PR_SUCCESS,
PR_NO_CONTROLLER,
PR_ABORTED
};
enum pata_result wait_pio(uint16_t command_block_base) {
while (1) {
uint8_t status = inb(command_block_base + 7);
//0x00 happens in qemu, 0x7f happens in virtualbox
if (status == 0x00 || status == 0x7f)
return PR_NO_CONTROLLER;
if (status & 0x01) {
uint8_t error = inb(command_block_base + 1);
if (error == 0x04)
return PR_ABORTED;
panic("TODO")
}
if ((status & 0x80) || !(status & 0x08))
continue;
return PR_SUCCESS;
}
}
//buffer should have room for 256 words.
static enum pata_result
pata_identify_packet_device(uint16_t command_block_base, uint8_t device_byte, uint16_t *buffer) {
outb(command_block_base + 6, device_byte);
outb(command_block_base + 7, 0xa1);
enum pata_result result = wait_pio(command_block_base);
if (result != PR_SUCCESS)
return result;
insw(command_block_base, buffer, 256);
return PR_SUCCESS;
}
static enum pata_result patapi_read_capacity(
uint16_t command_block_base, uint8_t device_byte, uint32_t *max_block_out, uint32_t *block_size_out) {
outb(command_block_base + 6, device_byte);
outb(command_block_base + 1, 0);
outb(command_block_base + 3, 0);
outb(command_block_base + 4, 8);
outb(command_block_base + 5, 0);
outb(command_block_base + 7, 0xa0);
enum pata_result result = wait_pio(command_block_base);
if (result != PR_SUCCESS)
return result;
uint8_t cmd[12] = {
0x25, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0 };
outsw(command_block_base, cmd, 6);
result = wait_pio(command_block_base);
if (result != PR_SUCCESS)
return result;
uint32_t actual_response_size =
inb(command_block_base + 4) | (inb(command_block_base + 5) << 8);
if (actual_response_size != 8)
panic("TODO")
uint32_t response[2];
insw(command_block_base, response, 4);
*max_block_out = end_swap_u32(response[0]);
*block_size_out = end_swap_u32(response[1]);
return PR_SUCCESS;
}
static enum pata_result patapi_read(
uint16_t command_block_base, uint8_t device_byte, uint16_t block_size,
uint32_t start_block, uint32_t block_count, void *buffer) {
outb(command_block_base + 6, device_byte);
outb(command_block_base + 1, 0);
outb(command_block_base + 3, 0);
outb(command_block_base + 4, block_size & 0xff);
outb(command_block_base + 5, block_size >> 8);
outb(command_block_base + 7, 0xa0);
enum pata_result result = wait_pio(command_block_base);
if (result != PR_SUCCESS)
return result;
uint8_t cmd[12] = {
0xa8, 0,
start_block >> 24,
(start_block >> 16) & 0xff,
(start_block >> 8) & 0xff,
start_block & 0xff,
block_count >> 24,
(block_count >> 16) & 0xff,
(block_count >> 8) & 0xff,
block_count & 0xff,
0, 0 };
outsw(command_block_base, cmd, 6);
for (uint32_t i = 0; i < block_count; ++i) {
result = wait_pio(command_block_base);
if (result != PR_SUCCESS)
return result;
uint32_t actual_response_size =
inb(command_block_base + 4) | (inb(command_block_base + 5) << 8);
if (actual_response_size != block_size)
panic("TODO")
insw(command_block_base, buffer + i * block_size, block_size / 2);
}
return PR_SUCCESS;
}
static uint16_t ipd_buffer[256];
struct pata_driver_info {
uint16_t command_block_base;
uint8_t device_byte;
};
static enum drive_access_result read_blocks_patapi(
const struct drive_info *drive_info,
void *buffer, uint64_t start, uint64_t count) {
if (count >= (1ULL << 32) || start >= (1ULL << 32))
panic("TODO")
const struct pata_driver_info *driver_info = drive_info->driver_info;
return
patapi_read(
driver_info->command_block_base, driver_info->device_byte,
drive_info->block_size, start, count, buffer) == PR_SUCCESS
? DAR_SUCCESS : DAR_HARDWARE_ERROR;
}
int next_pata_drive_number = 0;
static void probe_pata_drive(uint16_t command_block_base, uint8_t device_byte) {
if (pata_identify_packet_device(command_block_base, device_byte, ipd_buffer) != PR_SUCCESS)
return;
uint32_t max_block, block_size;
if (patapi_read_capacity(
command_block_base, device_byte, &max_block, &block_size) != PR_SUCCESS)
return;
if (next_pata_drive_number > 9)
panic("TODO")
struct drive_info *di = add_drive();
char *name = heap_alloc(6);
memcpy(name, "pata", 4);
name[4] = '0' + next_pata_drive_number++;
name[5] = 0;
di->name = name;
di->block_size = block_size;
di->block_count = max_block + 1;
struct pata_driver_info *driver_info =
heap_alloc(sizeof(struct pata_driver_info));
driver_info->command_block_base = command_block_base;
driver_info->device_byte = device_byte;
di->driver_info = driver_info;
di->read_blocks = &read_blocks_patapi;
debug_log("added pata drive")
debug_log(" drive name %s", di->name)
debug_log(" command block base 0x%h", command_block_base, 4)
debug_log(" device byte 0x%h", device_byte, 2)
debug_log(" block size %d", di->block_size)
debug_log(" block count %d", di->block_count)
debug_log(" total size %B", di->block_count * di->block_size)
}
void probe_pata_drives(uint32_t pci_address_base, uint32_t pci_class_etc) {
if (pci_class_etc & 0x00000100) {
uint32_t bar = read_pci_config(pci_address_base + 0x10);
if ((bar & 0xffff0003) != 0x00000001)
panic("TODO")
probe_pata_drive(bar & 0xfffc, 0x00);
probe_pata_drive(bar & 0xfffc, 0x10);
}
else {
probe_pata_drive(0x01f0, 0x00);
probe_pata_drive(0x01f0, 0x10);
}
if (pci_class_etc & 0x00000400) {
uint32_t bar = read_pci_config(pci_address_base + 0x18);
if ((bar & 0xffff0003) != 0x00000001)
panic("TODO")
probe_pata_drive(bar & 0xfffc, 0x00);
probe_pata_drive(bar & 0xfffc, 0x10);
}
else {
probe_pata_drive(0x0170, 0x00);
probe_pata_drive(0x0170, 0x10);
}
}

View file

@ -1,4 +1,4 @@
/* Calcite, src/kernel/initfs.h
/* Calcite, src/kernel/pata.h
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
@ -19,8 +19,5 @@
#include <stdint.h>
void set_initfs(const uint8_t *start, uint64_t length);
//if the file does not exist, *start_out is set to a null pointer.
void look_up_initfs_file(
const char *path, const uint8_t **start_out, uint64_t *length_out);
//probes for pata drives on this "card" and adds any that exist to drives.h
void probe_pata_drives(uint32_t pci_address_base, uint32_t pci_class_etc);

40
src/kernel/pci.asm Normal file
View file

@ -0,0 +1,40 @@
; Calcite, src/kernel/pci.asm
; Copyright 2025 Benji Dial
;
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful, but
; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
; for more details.
;
; You should have received a copy of the GNU General Public License along
; with this program. If not, see <https://www.gnu.org/licenses/>.
bits 64
default rel
section .text
global read_pci_config
read_pci_config:
mov dx, 0x0cf8
mov eax, edi
out dx, eax
mov dx, 0x0cfc
in eax, dx
ret
global write_pci_config
write_pci_config:
mov dx, 0x0cf8
mov eax, edi
out dx, eax
mov dx, 0x0cfc
mov eax, esi
out dx, eax
ret

61
src/kernel/pci.c Normal file
View file

@ -0,0 +1,61 @@
/* Calcite, src/kernel/pci.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 "debug.h"
#include "pata.h"
#include "pci.h"
#include <stdint.h>
static void probe_bus(uint32_t bus_address_base) {
for (uint32_t device_address_base = bus_address_base;
device_address_base < bus_address_base + 0x00010000;
device_address_base += 0x00000800)
for (uint32_t function_address_base = device_address_base;
function_address_base < device_address_base + 0x00000800;
function_address_base += 0x00000100) {
uint8_t header_type = (read_pci_config(function_address_base | 0x0c) >> 16) & 0xff;
if (header_type == 0xff)
break;
if ((header_type & 0x7f) == 0x01)
//this is a pci-to-pci bridge
probe_bus((read_pci_config(function_address_base | 0x18) >> 8) & 0xff);
else if ((header_type & 0x7f) == 0x00) {
//this is a normal function
uint32_t class_etc = read_pci_config(function_address_base | 0x08);
debug_log("pci device with class %h:%h", class_etc >> 24, 2, class_etc >> 16, 2);
switch (class_etc & 0xffff0000) {
case 0x01010000:
probe_pata_drives(function_address_base, class_etc);
break;
}
}
if (!(header_type & 0x80))
break;
}
}
void probe_pci() {
probe_bus(0x80000000);
}

32
src/kernel/pci.h Normal file
View file

@ -0,0 +1,32 @@
/* Calcite, src/kernel/pci.h
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
//reads one dword from config space.
//address is 0x8000000 | (bus << 16) | (device << 11) | (function << 8) | (bytes into that function's space).
//bytes into that function's space must be dword-aligned.
uint32_t read_pci_config(uint32_t address);
//writes one dword to config space.
//address is 0x8000000 | (bus << 16) | (device << 11) | (function << 8) | (bytes into that function's space).
//bytes into that function's space must be dword-aligned.
void write_pci_config(uint32_t address, uint32_t value);
void probe_pci();

55
src/kernel/process.asm Normal file
View file

@ -0,0 +1,55 @@
; Calcite, src/kernel/process.asm
; Copyright 2025 Benji Dial
;
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful, but
; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
; for more details.
;
; You should have received a copy of the GNU General Public License along
; with this program. If not, see <https://www.gnu.org/licenses/>.
bits 64
default rel
section .bss
resb 16384
temporary_stack:
section .text
;referenced in process.c
global thread_start
thread_start:
mov cr3, rbp
mov rcx, r12
mov r11, 0x200
mov rdi, r13
xor rax, rax
xor rbx, rbx
xor rdx, rdx
xor rsi, rsi
xor rbp, rbp
xor r8, r8
xor r9, r9
xor r10, r10
xor r12, r12
xor r13, r13
xor r14, r14
xor r15, r15
o64 sysret
;referenced in process.c
global with_temporary_stack
with_temporary_stack:
mov rsp, temporary_stack
jmp rdi

File diff suppressed because it is too large Load diff

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,8 +17,51 @@
#pragma once
#include <stdint.h>
#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;
struct process_file_info {
const struct fs_info *fs;
void *node;
struct fs_stat stat;
};
struct process_ipc_dgram_handle_info {
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 {
//0 for unused
int key_length;
int value_length;
char *key;
char *value;
};
//layout used in scheduler.asm
struct process {
uint64_t p4_physical_base;
@ -28,12 +71,25 @@ struct process {
int n_threads;
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;
//0 for missing levels. just bottom p3 of address space.
uint64_t *p2_virtual_bases[512];
uint64_t **p1_virtual_bases[512];
//also just bottom p3. bit set indicates we should free
//the physical page when we clean up this process.
uint8_t **owned_pages_bitmaps[512];
};
//layout used in syscalls.asm and scheduler.asm
struct thread {
struct process *process;
@ -42,6 +98,10 @@ struct thread {
void *stack_bottom;
void *stack_top;
//both page-aligned
void *syscall_stack_bottom;
void *syscall_stack_top;
};
extern struct thread *running_thread;
@ -53,9 +113,13 @@ void create_thread(struct process *process, struct thread *thread_out);
//virtual base must be in bottom p3 and not zero.
void map_page_for_process(
struct process *process, uint64_t physical_base,
void *virtual_base, int writable, int executable);
void *virtual_base, int writable, int executable, int owned);
//physical and virtual bases must be page-aligned.
void create_guard_page_for_process(struct process *process, void *virtual_base);
//virtual base must be page-aligned, in bottom p3, and not zero.
//also frees physical page if owned by process.
void unmap_page_for_process(
struct process *process, void *virtual_base);
@ -64,25 +128,78 @@ void unmap_page_for_process(
void *find_free_process_region(
struct process *process, uint64_t page_count);
//loaded sections are copied to new pages. once storage drivers are written,
//this should probably just take a file handle so it can load and parse the
//header and then load sections directly into user pages.
void load_elf(
//returns 0 on failure, 1 on success.
int load_elf(
struct process *process, uint64_t *entry_out,
const void *elf_start, uint64_t elf_length);
const struct fs_info *fs_info, void *fs_node);
//returns 0 on failure.
//creates a process and a thread in that process, loads the elf into the process,
//and schedules a ready task that sets the running thread to the new thread and
//starts user mode at the elf's entry point.
struct process *start_elf(const char *uri);
int syscall_start_elf(const char *path, const struct process_start_info *info);
void destroy_process(struct process *process);
//must not be called with syscall stack of the thread being destroyed
void destroy_thread(struct thread *thread);
[[noreturn]] void syscall_illegal_args();
//returs 1 if [start, start + length) is writable by process, otherwise 0.
int is_mapped_writable(struct process *process, void *start, uint64_t length);
int is_mapped_writable(struct process *process, const void *start, uint64_t length);
//returs 1 if [start, start + length) is readable by process, otherwise 0.
int is_mapped_readable(struct process *process, const void *start, uint64_t length);
//return 1 if the entire null-terminated string starting at start is readable by process, otherwise 0.
int is_mapped_readable_string(struct process *process, const char *start);
[[noreturn]] void syscall_end_thread();
struct syscall_framebuffer_info {
uint8_t *fb_base;
int fb_width;
int fb_height;
int fb_pitch;
};
void syscall_map_framebuffer(struct framebuffer_info *info_out);
void syscall_map_framebuffer(struct syscall_framebuffer_info *info_out);
enum fs_access_result syscall_open_file(const char *path, file_handle_t *handle_out);
void syscall_close_file(file_handle_t handle);
enum fs_access_result syscall_get_file_size(file_handle_t handle, uint64_t *bytes_out);
enum fs_access_result syscall_read_file(struct read_file_parameter *parameter);
void *syscall_map_pages(uint64_t count);
void syscall_change_page_permissions(void *vma, int writable, int executable);
enum ipc_dgram_result syscall_ipc_create_dgram_receiver(
const char *address, ipc_dgram_receiver_handle_t *handle_out);
enum ipc_dgram_result syscall_ipc_create_dgram_sender(
const char *address, ipc_dgram_sender_handle_t *handle_out);
//on entry, bytes is maximum accepted packet length.
//on exit, if result was IPR_SUCCESS or IPR_TOO_BIG, bytes is actual packet length.
//actual packet length will always be positive.
enum ipc_dgram_result syscall_ipc_receive_dgram(
ipc_dgram_receiver_handle_t handle, void *buffer, int *bytes);
//bytes must be positive.
enum ipc_dgram_result syscall_ipc_send_dgram(
ipc_dgram_sender_handle_t handle, const void *data, int bytes);
//f should not return.
void syscall_create_thread(void (*f)(uint64_t x), uint64_t x);
void set_envvar(struct process *process, const char *key, const char *value);
//0 if unset
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

@ -16,6 +16,7 @@
bits 64
default rel
section .text

View file

@ -1,5 +1,5 @@
/* Calcite, src/kernel/ps2.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
@ -15,26 +15,88 @@
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "framebuffer.h"
#include "panic.h"
#include "input.h"
#include "ps2.h"
#include <kernel-public/input.h>
#include <stdint.h>
//defined in ps2.asm
//returns -1 if no byte available
int read_ps2_byte();
static void process_keyboard_byte(uint8_t byte) {
(void)byte;
//TODO
static uint8_t keyboard_packet[7];
static int keyboard_packet_length = 0;
void on_keyboard_irq() {
int byte = read_ps2_byte();
if (byte == -1)
return;
if (keyboard_packet_length == 0 && byte != 0xe0 && byte != 0xe1 && byte != 0xf0) {
add_keyboard_packet(KEY_FLAG_MAKE | byte);
return;
}
if (keyboard_packet_length == 1 && keyboard_packet[0] == 0xe0 && byte != 0xf0 && byte != 0x12) {
add_keyboard_packet(KEY_FLAG_MAKE | 0x80 | byte);
keyboard_packet_length = 0;
return;
}
if (keyboard_packet_length == 1 && keyboard_packet[0] == 0xf0) {
add_keyboard_packet(byte);
keyboard_packet_length = 0;
return;
}
if (keyboard_packet_length == 2 && keyboard_packet[0] == 0xe0 && keyboard_packet[1] == 0xf0 && byte != 0x7c) {
add_keyboard_packet(0x80 | byte);
keyboard_packet_length = 0;
return;
}
if (keyboard_packet_length == 3 && keyboard_packet[0] == 0xe0 && keyboard_packet[1] == 0x12 &&
keyboard_packet[2] == 0xe0 && byte == 0x7c) {
add_keyboard_packet(KEY_FLAG_MAKE | KEY_PRINT_SCREEN);
keyboard_packet_length = 0;
return;
}
if (keyboard_packet_length == 5 && keyboard_packet[0] == 0xe0 && keyboard_packet[1] == 0xf0 &&
keyboard_packet[2] == 0x7c && keyboard_packet[3] == 0xe0 && keyboard_packet[4] == 0xf0 && byte == 0x12) {
add_keyboard_packet(KEY_PRINT_SCREEN);
keyboard_packet_length = 0;
return;
}
if (keyboard_packet_length == 7 && keyboard_packet[0] == 0xe1 && keyboard_packet[1] == 0x14 &&
keyboard_packet[2] == 0x77 && keyboard_packet[3] == 0xe1 && keyboard_packet[4] == 0xf0 &&
keyboard_packet[5] == 0x14 && keyboard_packet[6] == 0xf0 && byte == 0x77) {
add_keyboard_packet(KEY_FLAG_MAKE | KEY_PAUSE_BREAK);
add_keyboard_packet(KEY_PAUSE_BREAK);
keyboard_packet_length = 0;
return;
}
if (keyboard_packet_length == 7) {
keyboard_packet_length = 0;
return;
}
keyboard_packet[keyboard_packet_length++] = byte;
}
static uint8_t mouse_packet[3];
static int mouse_packet_length = 0;
static int total_x = 0;
static int total_y = 0;
void on_mouse_irq() {
static void process_mouse_byte(uint8_t byte) {
int byte = read_ps2_byte();
if (byte == -1)
return;
mouse_packet[mouse_packet_length] = byte;
if (mouse_packet_length < 2) {
@ -43,47 +105,14 @@ static void process_mouse_byte(uint8_t byte) {
}
mouse_packet_length = 0;
int x = mouse_packet[1];
int x_change = mouse_packet[1];
if (mouse_packet[0] & 0x10)
x -= 256;
x_change -= 256;
int y = mouse_packet[2];
int y_change = mouse_packet[2];
if (mouse_packet[0] & 0x20)
y -= 256;
y_change -= 256;
total_x += x;
total_y -= y;
if (total_x < 0)
total_x = 0;
if (total_x >= fb_width)
total_x = fb_width - 1;
if (total_y < 0)
total_y = 0;
if (total_y >= fb_height)
total_y = fb_height - 1;
fb_base[total_y * fb_pitch + total_x * 4] = 0xff;
fb_base[total_y * fb_pitch + total_x * 4 + 1] = 0xff;
fb_base[total_y * fb_pitch + total_x * 4 + 2] = 0xff;
add_mouse_movement(x_change, y_change);
}
void on_keyboard_irq() {
while (1) {
int byte = read_ps2_byte();
if (byte == -1)
return;
process_keyboard_byte(byte);
}
}
void on_mouse_irq() {
while (1) {
int byte = read_ps2_byte();
if (byte == -1)
return;
process_mouse_byte(byte);
}
}

View file

@ -1,5 +1,5 @@
; Calcite, src/kernel/scheduler.c
; Copyright 2025 Benji Dial
; Calcite, src/kernel/scheduler.asm
; 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,30 +16,11 @@
bits 64
default rel
;referenced in scheduler.c
global user_task_start
user_task_start:
mov cr3, rbx
mov rcx, rbp
mov r11, 0x200
xor rax, rax
xor rbx, rbx
xor rdx, rdx
xor rdi, rdi
xor rsi, rsi
xor rbp, rbp
xor r8, r8
xor r9, r9
xor r10, r10
xor r12, r12
xor r13, r13
xor r14, r14
xor r15, r15
o64 sysret
extern resume_next_continuation
extern running_thread
extern add_to_queue
;referenced in scheduler.c
global resume_continuation
@ -53,5 +34,49 @@ resume_continuation:
mov r13, qword [rdi + 40]
mov r14, qword [rdi + 48]
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
global yield
yield:
mov qword [rdi], .ret
mov qword [rdi + 8], rbx
mov qword [rdi + 16], rbp
mov qword [rdi + 24], rsp
mov qword [rdi + 32], r12
mov qword [rdi + 40], r13
mov qword [rdi + 48], r14
mov qword [rdi + 56], r15
mov qword rsi, qword [running_thread]
mov qword [rdi + 64], rsi
jmp resume_next_continuation
.ret:
ret
global yield_to_queue
yield_to_queue:
mov qword [rsp - 72], .ret
mov qword [rsp - 64], rbx
mov qword [rsp - 56], rbp
mov qword [rsp - 48], rsp
mov qword [rsp - 40], r12
mov qword [rsp - 32], r13
mov qword [rsp - 24], r14
mov qword [rsp - 16], r15
mov rdx, qword [running_thread]
mov qword [rsp - 8], rdx
sub rsp, 72
mov rsi, rsp
call add_to_queue
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
@ -16,103 +16,85 @@
*/
#include "scheduler.h"
#include "process.h"
#include "utility.h"
#include "debug.h"
#include "heap.h"
struct continuation_info {
uint64_t rip;
uint64_t rbx;
uint64_t rbp;
uint64_t rsp;
uint64_t r12;
uint64_t r13;
uint64_t r14;
uint64_t r15;
};
static struct continuation_info **ready_continuations = 0;
static int rc_buffer_length = 0;
static int rc_read_ptr = 0;
static int rc_count = 0;
#define INITIAL_RC_BUFFER_LENGTH 128
struct continuation_queue ready_continuations;
void init_scheduler() {
ready_continuations = heap_alloc(INITIAL_RC_BUFFER_LENGTH * sizeof(void *));
rc_buffer_length = INITIAL_RC_BUFFER_LENGTH;
for (int i = 0; i < INITIAL_RC_BUFFER_LENGTH; ++i)
ready_continuations[i] = 0;
}
static void queue_continuation(struct continuation_info *info) {
if (rc_count == rc_buffer_length) {
struct continuation_info **new_rc_buffer =
heap_alloc(2 * rc_buffer_length * sizeof(void *));
memcpy(
new_rc_buffer,
ready_continuations + rc_read_ptr,
(rc_buffer_length - rc_read_ptr) * sizeof(void *));
memcpy(
new_rc_buffer + rc_buffer_length - rc_read_ptr,
ready_continuations,
rc_read_ptr * sizeof(void *));
heap_dealloc(ready_continuations, rc_buffer_length * sizeof(void *));
new_rc_buffer[rc_buffer_length] = info;
for (int i = rc_buffer_length + 1; i < 2 * rc_buffer_length; ++i)
new_rc_buffer[i] = 0;
ready_continuations = new_rc_buffer;
rc_buffer_length *= 2;
rc_read_ptr = 0;
++rc_count;
}
else {
ready_continuations[(rc_read_ptr + rc_count) % rc_buffer_length] = info;
++rc_count;
}
create_queue(&ready_continuations, 128);
}
//defined in scheduler.asm
void user_task_start();
void create_user_task(
uint64_t cr3, uint64_t rip, uint64_t rsp) {
struct continuation_info *info =
heap_alloc(sizeof(struct continuation_info));
info->rip = (uint64_t)&user_task_start;
info->rsp = (uint64_t)rsp;
info->rbx = cr3;
info->rbp = rip;
queue_continuation(info);
}
//defined in scheduler.asm
[[noreturn]] void resume_continuation(struct continuation_info *info);
[[noreturn]] void resume_continuation(const struct continuation_info *info);
[[noreturn]] void resume_next_continuation() {
while (rc_count == 0)
running_thread = 0;
struct continuation_info ci;
while (!take_from_queue(&ready_continuations, &ci)) {
__asm__ ("sti");
__asm__ ("hlt");
__asm__ ("cli");
}
struct continuation_info *info = ready_continuations[rc_read_ptr];
ready_continuations[rc_read_ptr] = 0;
rc_read_ptr = (rc_read_ptr + 1) % rc_buffer_length;
--rc_count;
resume_continuation(info);
assert(running_thread == 0)
assert(ci.running_thread != 0)
resume_continuation(&ci);
}
void create_queue(struct continuation_queue *queue, int buffer_size) {
queue->cis = heap_alloc(buffer_size * sizeof(struct continuation_info));
queue->next_read_index = 0;
queue->buffer_size = 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) {
struct continuation_info *new_buffer =
heap_alloc(2 * queue->buffer_size * sizeof(struct continuation_info));
memcpy(
new_buffer, &queue->cis[queue->next_read_index],
(queue->buffer_size - queue->next_read_index) * sizeof(struct continuation_info));
memcpy(
&new_buffer[queue->buffer_size - queue->next_read_index],
queue->cis, queue->next_read_index * sizeof(struct continuation_info));
heap_dealloc(queue->cis, queue->buffer_size * sizeof(struct continuation_info));
queue->cis = new_buffer;
queue->buffer_size *= 2;
queue->next_read_index = 0;
}
memcpy(
&queue->cis[(queue->next_read_index + queue->count) % queue->buffer_size],
ci, sizeof(struct continuation_info));
++queue->count;
}
int take_from_queue(struct continuation_queue *queue, struct continuation_info *ci) {
if (queue->count == 0)
return 0;
memcpy(ci, &queue->cis[queue->next_read_index], sizeof(struct continuation_info));
queue->next_read_index = (queue->next_read_index + 1) % queue->buffer_size;
--queue->count;
return 1;
}

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
@ -19,9 +19,46 @@
#include <stdint.h>
struct thread;
//scheduler.asm depends on layout
struct continuation_info {
uint64_t rip;
uint64_t rbx;
uint64_t rbp;
uint64_t rsp;
uint64_t r12;
uint64_t r13;
uint64_t r14;
uint64_t r15;
struct thread *running_thread;
};
void init_scheduler();
void create_user_task(
uint64_t cr3, uint64_t rip, uint64_t rsp);
[[noreturn]] void resume_next_continuation();
struct continuation_queue {
struct continuation_info *cis;
int next_read_index;
int buffer_size;
int count;
};
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);
//if queue is empty, returns 0. otherwise, copies next read to ci, advances read, and returns 1.
int take_from_queue(struct continuation_queue *queue, struct continuation_info *ci);
//saves a continuation that returns from this function to save_current_continuation_to, then resumes
//the next ready continuation (thus doesn't return until save_current_continuation_to is resumed).
void yield(struct continuation_info *save_current_continuation_to);
//same as yield, but instead of saving continuation to a pointer, saves it to the end of the queue.
void yield_to_queue(struct continuation_queue *queue);

73
src/kernel/serial.asm Normal file
View file

@ -0,0 +1,73 @@
; Calcite, src/kernel/serial.asm
; Copyright 2025 Benji Dial
;
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful, but
; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
; for more details.
;
; You should have received a copy of the GNU General Public License along
; with this program. If not, see <https://www.gnu.org/licenses/>.
bits 64
default rel
section .text
global init_serial
init_serial:
mov dx, 0x03f9
mov al, 0x00
out dx, al
mov dl, 0xfb
mov al, 0x80
out dx, al
mov dl, 0xf8
mov al, 0x03
out dx, al
mov dl, 0xf9
mov al, 0x00
out dx, al
mov dl, 0xfb
mov al, 0x03
out dx, al
mov dl, 0xfa
mov al, 0xc7
out dx, al
ret
global write_serial_string_n
write_serial_string_n:
test esi, esi
jz .ret
movzx rcx, esi
.wait_ready:
mov dx, 0x03fd
in al, dx
test al, 0x20
jnz .ready
pause
jmp .wait_ready
.ready:
mov dl, 0xf8
mov al, byte [rdi]
out dx, al
inc rdi
loop .wait_ready
.ret:
ret

54
src/kernel/serial.c Normal file
View file

@ -0,0 +1,54 @@
/* Calcite, src/kernel/serial.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 "serial.h"
#include "debug.h"
void write_serial_string(const char *string) {
int n = 0;
while (string[n] != 0)
++n;
write_serial_string_n(string, n);
}
void write_serial_integer(int integer) {
if (integer == 0) {
write_serial_string_n("0", 1);
return;
}
char buffer[10];
char *ptr = &buffer[9];
while (integer != 0) {
*ptr = '0' + integer % 10;
integer /= 10;
--ptr;
}
write_serial_string_n(ptr + 1, (buffer + 10) - (ptr + 1));
}
void write_serial_hex(uint64_t value, int places) {
assert(places <= 16)
char buffer[16];
for (int i = 0; i < places; ++i)
buffer[i] = "0123456789abcdef"[(value >> (4 * (places - i - 1))) & 0xf];
write_serial_string_n(buffer, places);
}

26
src/kernel/serial.h Normal file
View file

@ -0,0 +1,26 @@
/* Calcite, src/kernel/serial.h
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
void init_serial();
void write_serial_string(const char *string);
void write_serial_string_n(const char *string, int n);
void write_serial_integer(int integer);
void write_serial_hex(uint64_t value, int places);

View file

@ -1,5 +1,5 @@
; Calcite, src/kernel/syscalls.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
@ -16,37 +16,53 @@
bits 64
default rel
;defined in syscalls.c
extern syscall_entry_c
section .bss
;this should have guard pages blah blah blah
resb 16 << 20
syscall_stack_top:
extern running_thread
section .text
;system call number is in rax.
;system call arguments are in rdi, rsi, rdx.
;system call returns a value in rax.
;depends on layout of struct thread from process.h
syscall_entry:
mov qword [syscall_stack_top - 8], rsp
mov rsp, syscall_stack_top - 8
push r11
mov r8, qword [running_thread]
test r8, r8
jz .assert_fail
mov r8, qword [r8 + 32]
mov qword [r8 - 8], rsp
mov rsp, r8
sub rsp, 8
push rcx
push r11
mov rcx, rax
cli
call syscall_entry_c
sti
xor rdi, rdi
xor rsi, rsi
xor rdx, rdx
xor r8, r8
xor r9, r9
xor r10, r10
pop rcx
pop r11
pop rcx
pop rsp
o64 sysret
.assert_fail:
ud2
global init_syscalls
init_syscalls:
mov ecx, 0xc0000080
rdmsr
or al, 0x01

View file

@ -16,7 +16,7 @@
*/
#include "syscalls.h"
#include "panic.h"
#include "debug.h"
#define MAX_SYSCALL_NUMBER 99

37
src/kernel/timer.asm Normal file
View file

@ -0,0 +1,37 @@
; Calcite, src/kernel/timer.asm
; Copyright 2025 Benji Dial
;
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful, but
; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
; for more details.
;
; You should have received a copy of the GNU General Public License along
; with this program. If not, see <https://www.gnu.org/licenses/>.
bits 64
default rel
section .text
global init_timer
init_timer:
;generate an irq every 1193 cycles (~ every millisecond)
mov al, 0x34
out 0x43, al
mov al, 0xa9
out 0x40, al
mov al, 0x04
out 0x40, al
ret

111
src/kernel/timer.c Normal file
View file

@ -0,0 +1,111 @@
/* Calcite, src/kernel/timer.c
* 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
* 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 "scheduler.h"
#include "timer.h"
#include "heap.h"
struct sleeping_list_entry {
struct sleeping_list_entry *next;
uint64_t sleeping_until;//pit irqs since boot
struct continuation_info continuation;
};
//entries are always sorted increasing by sleeping_until
static struct sleeping_list_entry *first_entry = 0;
static struct sleeping_list_entry *last_entry = 0;
//pit irq happens ~ once per millisecond
static uint64_t pit_irqs_since_boot = 0;
static struct sleeping_list_entry *local_sleeping_list_entry_pool = 0;
void on_pit_irq() {
++pit_irqs_since_boot;
while (first_entry != 0 && pit_irqs_since_boot >= first_entry->sleeping_until) {
add_to_queue(&ready_continuations, &first_entry->continuation);
struct sleeping_list_entry *old_first = first_entry;
first_entry = old_first->next;
*(struct sleeping_list_entry **)old_first = local_sleeping_list_entry_pool;
local_sleeping_list_entry_pool = old_first;
if (first_entry == 0)
last_entry = 0;
}
}
//until in pit irqs since boot.
//must be called with interrupts disabled.
//on return, interrupts are enabled.
static void sleep_until(uint64_t until) {
struct sleeping_list_entry *entry;
if (local_sleeping_list_entry_pool != 0) {
entry = local_sleeping_list_entry_pool;
local_sleeping_list_entry_pool = *(struct sleeping_list_entry **)entry;
}
else
entry = heap_alloc(sizeof(struct sleeping_list_entry));
entry->sleeping_until = until;
if (first_entry == 0) {
entry->next = 0;
first_entry = entry;
last_entry = entry;
}
else if (first_entry->sleeping_until >= until) {
entry->next = first_entry;
first_entry = entry;
}
else if (last_entry->sleeping_until <= until) {
entry->next = 0;
last_entry->next = entry;
last_entry = entry;
}
else {
struct sleeping_list_entry *just_before = first_entry;
while (just_before->next->sleeping_until <= until)
just_before = just_before->next;
entry->next = just_before->next;
just_before->next = entry;
}
yield(&entry->continuation);
}
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);
}

28
src/kernel/timer.h Normal file
View file

@ -0,0 +1,28 @@
/* Calcite, src/kernel/timer.h
* 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
* 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>
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

@ -16,18 +16,47 @@
bits 64
default rel
section .text
;global memcpy
;memcpy:
; mov rcx, rdx
; rep movsb
; ret
global memcpy
memcpy:
mov rcx, rdx
rep movsb
ret
;global memzero
;memzero:
; xor al, al
; mov rcx, rsi
; rep stosb
; ret
global memzero
memzero:
xor al, al
mov rcx, rsi
rep stosb
ret
global outb
outb:
mov dx, di
mov al, sil
out dx, al
ret
global inb
inb:
mov dx, di
in al, dx
ret
global outsw
outsw:
mov rcx, rdx
mov dx, di
rep outsw
ret
global insw
insw:
mov rcx, rdx
mov dx, di
mov rdi, rsi
rep insw
ret

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
@ -16,6 +16,7 @@
*/
#include "utility.h"
#include "heap.h"
int strequ(const char *str1, const char *str2) {
while (1) {
@ -28,12 +29,89 @@ int strequ(const char *str1, const char *str2) {
}
}
void memcpy(void *to, const void *from, uint64_t bytes) {
for (uint64_t i = 0; i < bytes; ++i)
*(uint8_t *)(to + i) = *(const uint8_t *)(from + i);
uint32_t end_swap_u32(uint32_t value) {
return
(value >> 24) |
(((value >> 16) & 0xff) << 8) |
(((value >> 8) & 0xff) << 16) |
((value & 0xff) << 24);
}
void memzero(void *start, uint64_t bytes) {
for (uint64_t i = 0; i < bytes; ++i)
*(uint8_t *)(start + i) = 0;
void double_buffer_zero(void **buffer, int *length, uint64_t bytes_per_entry) {
void *new_buffer = heap_alloc(2 * *length * bytes_per_entry);
memcpy(new_buffer, *buffer, *length * bytes_per_entry);
heap_dealloc(*buffer, *length * bytes_per_entry);
memzero(new_buffer + *length * bytes_per_entry, *length * 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
@ -24,3 +24,34 @@ int strequ(const char *str1, const char *str2);
void memcpy(void *to, const void *from, uint64_t bytes);
void memzero(void *start, uint64_t bytes);
//swaps the endianness of the value
uint32_t end_swap_u32(uint32_t value);
//1. allocates a new buffer with 2 * length * bytes_per_entry bytes
//2. copies length * bytes_per_entry bytes from old buffer to new buffer
//3. deallocates old buffer
//4. zeroes rest of new buffer
//5. sets buffer and length to new buffer and twice length
void double_buffer_zero(void **buffer, int *length, uint64_t bytes_per_entry);
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);

75
src/user-apps/init/init.c Normal file
View file

@ -0,0 +1,75 @@
/* Calcite, src/user-apps/init/init.c
* 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
* 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/process.h>
#include <calcite/file-streams.h>
#include <kernel-public/files.h>
#include <kernel-public/ipc.h>
#include <calcite/syscalls.h>
#define MAX_LINE_LENGTH 1023
void main() {
struct file_stream *rc;
if (open_file_stream("root://calcite/init.rc", &rc) != FAR_SUCCESS)
return;
int at_file_end = 0;
while (!at_file_end) {
char line[MAX_LINE_LENGTH + 1];
int line_length = 0;
while (1) {
char ch = read_file_stream_byte(rc);
if (ch == -1) {
at_file_end = 1;
break;
}
if (ch == '\n')
break;
if (line_length == MAX_LINE_LENGTH) {
while (1) {
ch = read_file_stream_byte(rc);
if (ch == -1 || ch == '\n')
break;
}
break;
}
line[line_length++] = ch;
}
if (line_length == 0 || line[0] == '#')
continue;
line[line_length] = 0;
struct process_start_info psi;
psi.forwared_envvar_count = 0;
psi.set_envvar_count = 0;
start_elf(line, &psi);
}
}

39
src/user-apps/link.ld Normal file
View file

@ -0,0 +1,39 @@
OUTPUT_FORMAT(elf64-x86-64)
OUTPUT_ARCH(i386:x86-64)
ENTRY(_start)
PHDRS {
rx PT_LOAD FLAGS(5);
ro PT_LOAD FLAGS(4);
rw PT_LOAD FLAGS(6);
}
SECTIONS {
. = 0x400000;
.text : {
*(.text .text.*)
} : rx
. = ALIGN(4096);
.rodata : {
*(.rodata .rodata.*)
} : ro
. = ALIGN(4096);
.data : {
*(.data .data.*)
} : rw
. = ALIGN(4096);
.bss : {
*(.bss .bss.*)
*(COMMON)
} : rw
}

View file

@ -0,0 +1,218 @@
/* Calcite, src/user-apps/shell/assembler.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 "assembler.h"
#include <calcite/syscalls.h>
#include <calcite/memory.h>
#include <stdint.h>
static uint8_t *function_buffer = 0;
static int function_buffer_length;
static int function_buffer_used;
struct relocation_info {
int offset_into_function;
int from_offset_into_function;
void *to;
struct relocation_info *previous;
};
static struct relocation_info *relocations = 0;
static char *function_name = 0;
static int function_name_length;
struct function_alloc {
void *start;
int bytes;
struct function_alloc *previous;
};
static struct function_alloc *allocs = 0;
#define FUNCTION_BUFFER_INITIAL_SIZE 4096
void start_function(const char *name, int name_length) {
if (function_buffer == 0) {
function_buffer_length = FUNCTION_BUFFER_INITIAL_SIZE;
function_buffer = heap_alloc(function_buffer_length);
}
function_buffer_used = 0;
function_name_length = name_length;
char *name_copy = heap_alloc(name_length);
memcpy(name_copy, name, name_length);
function_name = name_copy;
}
static void destroy_relocations() {
struct relocation_info *r = relocations;
while (r != 0) {
struct relocation_info *p = r->previous;
heap_dealloc(r, sizeof(struct relocation_info));
r = p;
}
relocations = 0;
}
static void destroy_function_allocs(int and_free) {
struct function_alloc *a = allocs;
while (a != 0) {
struct function_alloc *p = a->previous;
if (and_free)
heap_dealloc(a->start, a->bytes);
heap_dealloc(a, sizeof(struct function_alloc));
a = p;
}
allocs = 0;
}
const char *get_function_name() {
return function_name;
}
int get_function_name_length() {
return function_name_length;
}
//must be multiple of 4096.
#define MIN_RX_SPACE (1 << 20)
static void *rx_pointer;
static int rx_space = 0;
void (*end_function())() {
void *function_start = 0;
if (function_buffer_used <= rx_space) {
function_start = rx_pointer;
rx_pointer += function_buffer_used;
rx_space -= function_buffer_used;
}
else {
int to_allocate = ((function_buffer_length - 1) / 4096 + 1) * 4096;
if (to_allocate < MIN_RX_SPACE)
to_allocate = MIN_RX_SPACE;
void *pages = map_pages(to_allocate / 4096);
for (int i = 0; i < to_allocate; i += 4096)
change_page_permissions(pages + i, 1, 1);
function_start = pages;
rx_pointer = pages + function_buffer_used;
rx_space = to_allocate - function_buffer_used;
}
memcpy(function_start, function_buffer, function_buffer_used);
for (struct relocation_info *r = relocations; r != 0; r = r->previous) {
int32_t *at = function_start + r->offset_into_function;
void *from = function_start + r->from_offset_into_function;
*at = r->to - from;
}
destroy_relocations();
destroy_function_allocs(0);
function_name = 0;
return function_start;
}
void abort_function() {
destroy_relocations();
destroy_function_allocs(1);
if (function_name != 0) {
heap_dealloc(function_name, function_name_length);
function_name = 0;
}
}
void *function_data_alloc(int bytes) {
void *start = heap_alloc(bytes);
struct function_alloc *a = heap_alloc(sizeof(struct function_alloc));
a->start = start;
a->bytes = bytes;
a->previous = allocs;
allocs = a;
return start;
}
static void emit_bytes(const uint8_t *bytes, int count) {
if (function_buffer_used + count > function_buffer_length) {
int new_length = function_name_length * 2;
while (function_buffer_used + count > function_buffer_length)
new_length *= 2;
void *new_buffer = heap_alloc(new_length);
memcpy(new_buffer, function_buffer, function_buffer_used);
heap_dealloc(function_buffer, function_buffer_length);
function_buffer = new_buffer;
function_buffer_length = new_length;
}
memcpy(function_buffer + function_buffer_used, bytes, count);
function_buffer_used += count;
}
static struct relocation_info *new_relocation() {
struct relocation_info *r = heap_alloc(sizeof(struct relocation_info));
r->previous = relocations;
relocations = r;
return r;
}
void emit_ret() {
uint8_t byte = 0xc3;
emit_bytes(&byte, 1);
}
void emit_call(void (*to)()) {
struct relocation_info *r = new_relocation();
r->offset_into_function = function_buffer_used + 1;
r->from_offset_into_function = function_buffer_used + 5;
r->to = to;
uint8_t bytes[5];
bytes[0] = 0xe8;
emit_bytes(bytes, 5);
}
void emit_mov_edi(int value) {
uint8_t bytes[5];
bytes[0] = 0xbf;
*(int32_t *)&bytes[1] = value;
emit_bytes(bytes, 5);
}
void emit_mov_esi(int value) {
uint8_t bytes[5];
bytes[0] = 0xbe;
*(int32_t *)&bytes[1] = value;
emit_bytes(bytes, 5);
}
void emit_mov_rdi(uint64_t value) {
uint8_t bytes[10];
bytes[0] = 0x48;
bytes[1] = 0xbf;
*(uint64_t *)&bytes[2] = value;
emit_bytes(bytes, 10);
}

View file

@ -0,0 +1,38 @@
/* Calcite, src/user-apps/shell/assembler.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>
//name is copied
void start_function(const char *name, int name_length);
const char *get_function_name();
int get_function_name_length();
//returns interpret
void (*end_function())();
void abort_function();
void *function_data_alloc(int bytes);
void emit_ret();
void emit_call(void (*to)());
void emit_mov_edi(int value);
void emit_mov_esi(int value);
void emit_mov_rdi(uint64_t value);

View file

@ -0,0 +1,226 @@
/* Calcite, src/user-apps/shell/builtin-words.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/terminal.h>
#include <calcite/memory.h>
#include "builtin-words.h"
#include "assembler.h"
#include "runtime.h"
static void words_interpret() {
print_all_words();
}
static void dup_interpret() {
int x = pop();
push(x);
push(x);
}
static void drop_interpret() {
pop();
}
static void swap_interpret() {
int y = pop();
int x = pop();
push(y);
push(x);
}
static void rot_interpret() {
int z = pop();
int y = pop();
int x = pop();
push(y);
push(z);
push(x);
}
static void plus_interpret() {
int y = pop();
int x = pop();
push(x + y);
}
static void minus_interpret() {
int y = pop();
int x = pop();
push(x - y);
}
static void times_interpret() {
int y = pop();
int x = pop();
push(x * y);
}
static void divide_interpret() {
int y = pop();
int x = pop();
if (y == 0)
error("division by zero");
push(x / y);
}
static void mod_interpret() {
int y = pop();
int x = pop();
if (y == 0)
error("division by zero");
push(x % y);
}
static void print_integer(int x) {
if (x == 0) {
terminal_write("0 ", 2);
return;
}
int is_negative;
if (x < 0) {
x = -x;
is_negative = 1;
}
else
is_negative = 0;
char text[12];
text[11] = ' ';
int text_ptr = 11;
while (x > 0) {
--text_ptr;
text[text_ptr] = '0' + x % 10;
x /= 10;
}
if (is_negative) {
--text_ptr;
text[text_ptr] = '-';
}
terminal_write(&text[text_ptr], 12 - text_ptr);
}
static void dot_s_interpret() {
int *stack_pointer = get_data_stack_pointer();
int *stack_top = get_data_stack_top();
for (int *i = stack_top - 1; i >= stack_pointer; --i)
print_integer(*i);
}
static void dot_interpret() {
print_integer(pop());
}
static void dot_quote_interpret() {
const char *start;
int length;
read_until('"', &start, &length);
terminal_write(start, length);
}
static void dot_quote_compile() {
const char *start;
int length;
read_until('"', &start, &length);
void *address = function_data_alloc(length);
memcpy(address, start, length);
emit_mov_rdi((uint64_t)address);
emit_mov_esi(length);
emit_call((void *)&terminal_write);
}
static void cr_interpret() {
terminal_write("\n", 1);
}
static void colon_interpret() {
const char *name_start;
int name_length;
read_input_word(&name_start, &name_length);
if (name_length == 0)
error("colon at end of input");
start_function(name_start, name_length);
compiling = 1;
}
static void semicolon_compile() {
emit_ret();
const char *function_name = get_function_name();
int function_name_length = get_function_name_length();
void (*interpret)() = end_function();
struct word *word = new_word();
word->name_length = function_name_length;
word->name = function_name;
word->interpret = interpret;
word->compile = COMPILE_EMIT_CALL;
compiling = 0;
}
static void add(const char *name, void (*interpret)(), void (*compile)()) {
int name_length = 0;
while (name[name_length] != 0)
++name_length;
struct word *w = new_word();
w->name_length = name_length;
w->name = name;
w->interpret = interpret;
w->compile = compile;
}
static void add_compile_only(const char *name, void (*compile)()) {
add(name, INTERPRET_ERROR, compile);
}
static void add_interpret_only(const char *name, void (*interpret)()) {
add(name, interpret, COMPILE_ERROR);
}
static void add_emit_call(const char *name, void (*interpret)()) {
add(name, interpret, COMPILE_EMIT_CALL);
}
void add_builtin_words() {
add_emit_call( "words", &words_interpret);
add_emit_call( "dup", &dup_interpret);
add_emit_call( "drop", &drop_interpret);
add_emit_call( "swap", &swap_interpret);
add_emit_call( "rot", &rot_interpret);
add_emit_call( "+", &plus_interpret);
add_emit_call( "-", &minus_interpret);
add_emit_call( "*", &times_interpret);
add_emit_call( "/", &divide_interpret);
add_emit_call( "mod", &mod_interpret);
add_emit_call( ".s", &dot_s_interpret);
add_emit_call( ".", &dot_interpret);
add( ".\"", &dot_quote_interpret, &dot_quote_compile);
add_emit_call( "cr", &cr_interpret);
add_interpret_only(":", &colon_interpret);
add_compile_only( ";", &semicolon_compile);
}

View file

@ -0,0 +1,20 @@
/* Calcite, src/user-apps/shell/builtin-words.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 add_builtin_words();

View file

@ -0,0 +1 @@
calcite

View file

@ -0,0 +1,36 @@
/* Calcite, src/user-apps/shell/main.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 "builtin-words.h"
#include "runtime.h"
#include <calcite/terminal.h>
void main() {
init_terminal();
save_return_stack();
create_data_stack();
add_builtin_words();
terminal_write("words: ", 7);
print_all_words();
terminal_write("\n", 1);
main_loop();
}

View file

@ -0,0 +1,40 @@
; Calcite, src/user-apps/shell/runtime.asm
; 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/>.
bits 64
default rel
section .bss
saved_return_stack:
resq 1
section .text
global save_return_stack
save_return_stack:
mov rax, rsp
and ax, 0xf000
add rax, 0x1000
mov qword [saved_return_stack], rax
ret
global restore_return_stack
restore_return_stack:
mov rsp, qword [saved_return_stack]
push qword 0
jmp rdi

View file

@ -0,0 +1,218 @@
/* Calcite, src/user-apps/shell/runtime.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 "assembler.h"
#include "runtime.h"
#include <calcite/terminal.h>
#include <calcite/memory.h>
static int *data_stack_bottom;
static int *data_stack_top;
static int *data_stack_pointer;
#define INITIAL_DATA_STACK_INTS 1024
void create_data_stack() {
data_stack_bottom = heap_alloc(INITIAL_DATA_STACK_INTS * sizeof(int));
data_stack_top = data_stack_bottom + INITIAL_DATA_STACK_INTS;
data_stack_pointer = data_stack_top;
}
void push(int value) {
if (data_stack_pointer == data_stack_bottom) {
int old_size = data_stack_top - data_stack_bottom;
int new_size = old_size * 2;
int *new_stack = heap_alloc(new_size * sizeof(int));
memcpy(new_stack + old_size, data_stack_bottom, old_size * sizeof(int));
heap_dealloc(data_stack_bottom, old_size * sizeof(int));
data_stack_bottom = new_stack;
data_stack_top = new_stack + new_size;
data_stack_pointer = new_stack + old_size;
}
*--data_stack_pointer = value;
}
int pop() {
if (data_stack_pointer == data_stack_top)
error("stack underflow");
return *(data_stack_pointer++);
}
int *get_data_stack_pointer() {
return data_stack_pointer;
}
int *get_data_stack_top() {
return data_stack_top;
}
//null-terminated
static const char *input_buffer;
static const char *input_pointer;
void set_input(const char *buffer) {
input_buffer = buffer;
input_pointer = buffer;
}
static int is_ws(char ch) {
return ch == ' ';
}
void read_input_word(const char **start_out, int *length_out) {
const char *start = input_pointer;
while (start[0] != 0 && is_ws(start[0]))
++start;
int length = 0;
while (start[length] != 0 && !is_ws(start[length]))
++length;
*start_out = start;
*length_out = length;
if (start[length] == 0)
input_pointer = &start[length];
else
input_pointer = &start[length + 1];
}
void read_until(char end, const char **start_out, int *length_out) {
const char *start = input_pointer;
int length = 0;
while (start[length] != 0 && start[length] != end)
++length;
if (start[length] == 0)
error("unexpected eof");
*start_out = start;
*length_out = length;
input_pointer = &start[length + 1];
}
static const struct word *dictionary = 0;
const struct word *look_up_word(int name_length, const char *name) {
for (const struct word *word = dictionary; word != 0; word = word->previous) {
if (word->name_length == name_length) {
for (int i = 0; i < name_length; ++i)
if (word->name[i] != name[i])
goto next_word;
return word;
}
next_word:
;
}
return 0;
}
struct word *new_word() {
struct word *word = heap_alloc(sizeof(struct word));
word->previous = dictionary;
dictionary = word;
return word;
}
void print_all_words() {
for (const struct word *w = dictionary; w != 0; w = w->previous) {
terminal_write(w->name, w->name_length);
terminal_write(" ", 1);
}
}
[[noreturn]] void error(const char *message) {
terminal_write("*** error: ", 11);
int length = 0;
while (message[length] != 0)
++length;
terminal_write(message, length);
terminal_write("\n", 1);
abort_function();
restore_return_stack(&main_loop);
}
static void interpret_all() {
while (1) {
const char *start;
int length;
read_input_word(&start, &length);
if (length == 0)
return;
const struct word *word = look_up_word(length, start);
if (word != 0) {
if (compiling) {
if (word->compile == COMPILE_ERROR)
error("interpret-only word in compile context");
else if (word->compile == COMPILE_EMIT_CALL)
emit_call(word->interpret);
else
(*word->compile)();
}
else {
if (word->interpret == INTERPRET_ERROR)
error("compile-only word in interpret context");
else
(*word->interpret)();
}
continue;
}
int is_negative;
if (start[0] == '-') {
is_negative = 1;
++start;
--length;
}
else
is_negative = 0;
int abs = 0;
for (int i = 0; i < length; ++i) {
if (start[i] < '0' || start[i] > '9')
error("unknown word");
abs = abs * 10 + start[i] - '0';
}
int x = is_negative ? -abs : abs;
if (compiling) {
emit_mov_edi(x);
emit_call((void *)&push);
}
else
push(x);
}
}
[[noreturn]] void main_loop() {
compiling = 0;
while (1) {
terminal_write("> ", 2);
set_input(terminal_readline());
interpret_all();
}
}
int compiling;

View file

@ -0,0 +1,63 @@
/* Calcite, src/user-apps/shell/runtime.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 save_return_stack();
//and_call should not return.
[[noreturn]] void restore_return_stack(void (*and_call)());
void create_data_stack();
void push(int value);
int pop();
int *get_data_stack_pointer();
int *get_data_stack_top();
//null-terminated. not copied.
void set_input(const char *buffer);
//length_out is set to 0 on eof. skips all whitespace before and up to one character after.
void read_input_word(const char **start_out, int *length_out);
//errors on eof before end. skips end. end not included in length.
void read_until(char end, const char **start_out, int *length_out);
struct word {
int name_length;
const char *name;
void (*interpret)();
void (*compile)();
const struct word *previous;
};
#define INTERPRET_ERROR ((void (*)())-1)
#define COMPILE_ERROR ((void (*)())-1)
#define COMPILE_EMIT_CALL ((void (*)())-2)
//returns 0 if not found. name does not need to be null-terminated.
const struct word *look_up_word(int name_length, const char *name);
struct word *new_word();
void print_all_words();
[[noreturn]] void error(const char *message);
extern int compiling;
[[noreturn]] void main_loop();

View file

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

View file

@ -0,0 +1,196 @@
/* 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/files.h>
#include <kernel-public/input.h>
#include <kernel-public/ipc.h>
#include <calcite/dispatch.h>
#include <calcite/syscalls.h>
#include <calcite/terminal.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 char keytable[2048];
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);
}
}
static int flags = 0;
static int entering_caps_lock = 0;
static int entering_num_lock = 0;
[[noreturn]] static void input_thread(uint64_t) {
while (1) {
int key = wait_for_keyboard_packet();
int shift = flags & (TKF_LEFT_SHIFT | TKF_RIGHT_SHIFT);
int caps = flags & TKF_CAPS_LOCK;
int num = flags & TKF_NUM_LOCK;
char printable = keytable[(key & 0xff) | (shift ? 0x100 : 0x0) | (caps ? 0x200 : 0x0) | (num ? 0x400 : 0x0)];
if (key == KEY_LEFT_SHIFT)
flags &= ~TKF_LEFT_SHIFT;
else if (key == KEY_RIGHT_SHIFT)
flags &= ~TKF_RIGHT_SHIFT;
else if (key == KEY_LEFT_CTRL)
flags &= ~TKF_LEFT_CTRL;
else if (key == KEY_RIGHT_CTRL)
flags &= ~TKF_RIGHT_CTRL;
else if (key == KEY_LEFT_ALT)
flags &= ~TKF_LEFT_ALT;
else if (key == KEY_RIGHT_ALT)
flags &= ~TKF_RIGHT_ALT;
else if (key == KEY_CAPS_LOCK) {
if (!entering_caps_lock)
flags &= ~TKF_CAPS_LOCK;
else
entering_caps_lock = 0;
}
else if (key == KEY_NUM_LOCK) {
if (!entering_num_lock)
flags &= ~TKF_NUM_LOCK;
else
entering_num_lock = 0;
}
else if (key == (KEY_FLAG_MAKE | KEY_LEFT_SHIFT))
flags |= TKF_LEFT_SHIFT;
else if (key == (KEY_FLAG_MAKE | KEY_RIGHT_SHIFT))
flags |= TKF_RIGHT_SHIFT;
else if (key == (KEY_FLAG_MAKE | KEY_LEFT_CTRL))
flags |= TKF_LEFT_CTRL;
else if (key == (KEY_FLAG_MAKE | KEY_RIGHT_CTRL))
flags |= TKF_RIGHT_CTRL;
else if (key == (KEY_FLAG_MAKE | KEY_LEFT_ALT))
flags |= TKF_LEFT_ALT;
else if (key == (KEY_FLAG_MAKE | KEY_RIGHT_ALT))
flags |= TKF_RIGHT_ALT;
else if (key == (KEY_FLAG_MAKE | KEY_CAPS_LOCK)) {
if (!(flags & TKF_CAPS_LOCK)) {
flags |= TKF_CAPS_LOCK;
entering_caps_lock = 1;
}
}
else if (key == (KEY_FLAG_MAKE | KEY_NUM_LOCK)) {
if (!(flags & TKF_NUM_LOCK)) {
flags |= TKF_NUM_LOCK;
entering_num_lock = 1;
}
}
int packet = flags | (key << 8) | printable;
ipc_send_dgram(input_handle, &packet, sizeof(int));
}
}
void main() {
file_handle_t keytable_handle;
if (open_file("root://calcite/qwerty.bin", &keytable_handle) == FAR_SUCCESS) {
struct read_file_parameter param;
param.handle = keytable_handle;
param.buffer = keytable;
param.start = 0;
param.bytes = 2048;
read_file(&param);
close_file(keytable_handle);
}
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

@ -1,4 +1,4 @@
/* Calcite, src/kernel/panic.c
/* Calcite, src/user-libs/calcite/entry.c
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
@ -15,16 +15,11 @@
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
[[noreturn]] void panic_core(
const char *file, const char *function, int line, const char *message) {
#include <calcite/syscalls.h>
//TODO
(void)file;
(void)function;
(void)line;
(void)message;
while (1)
__asm__ ("hlt");
void main();
[[noreturn]] void _start() {
main();
end_thread();
}

View file

@ -0,0 +1,150 @@
/* Calcite, src/user-libs/calcite/file-streams.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 <calcite/file-streams.h>
#include <kernel-public/files.h>
#include <calcite/syscalls.h>
#include <calcite/memory.h>
static uint64_t min(uint64_t a, uint64_t b) {
return a < b ? a : b;
}
//in bytes
#define FS_BUFFER_SIZE 8192
struct file_stream {
file_handle_t handle;
//both in bytes
uint64_t length;
uint64_t pointer;
int is_there_buffer;
//in bytes
uint64_t buffer_start;
uint8_t buffer[FS_BUFFER_SIZE];
};
enum fs_access_result open_file_stream(const char *path, struct file_stream **file_stream_out) {
file_handle_t handle;
enum fs_access_result result = open_file(path, &handle);
if (result != FAR_SUCCESS)
return result;
uint64_t length;
result = get_file_size(handle, &length);
if (result != FAR_SUCCESS) {
close_file(handle);
return result;
}
*file_stream_out = heap_alloc(sizeof(struct file_stream));
(*file_stream_out)->handle = handle;
(*file_stream_out)->length = length;
(*file_stream_out)->pointer = 0;
(*file_stream_out)->is_there_buffer = 0;
return FAR_SUCCESS;
}
void close_file_stream(struct file_stream *file_stream) {
close_file(file_stream->handle);
heap_dealloc(file_stream, sizeof(struct file_stream));
}
enum fs_access_result get_in_buffer(struct file_stream *file_stream, uint64_t pointer) {
uint64_t bstart = (pointer / FS_BUFFER_SIZE) * FS_BUFFER_SIZE;
uint64_t bbytes = min(FS_BUFFER_SIZE, file_stream->length - bstart);
if (file_stream->is_there_buffer && file_stream->buffer_start == bstart)
return FAR_SUCCESS;
enum fs_access_result result =
read_file_splat(file_stream->handle, file_stream->buffer, bstart, bbytes);
if (result == FAR_SUCCESS) {
file_stream->is_there_buffer = 1;
file_stream->buffer_start = bstart;
return FAR_SUCCESS;
}
file_stream->is_there_buffer = 0;
return result;
}
enum fs_access_result read_file_stream(struct file_stream *file_stream, void *buffer, uint64_t bytes) {
if (bytes == 0)
return FAR_SUCCESS;
if (file_stream->pointer + bytes > file_stream->length)
return FAR_OUT_OF_BOUNDS;
enum fs_access_result result = get_in_buffer(file_stream, file_stream->pointer);
if (result != FAR_SUCCESS)
return result;
uint64_t in_buffer = min(bytes, FS_BUFFER_SIZE - (file_stream->pointer - file_stream->buffer_start));
memcpy(
buffer,
file_stream->buffer + file_stream->pointer - file_stream->buffer_start,
in_buffer);
uint64_t pointer = file_stream->pointer + in_buffer;
uint64_t left = bytes - in_buffer;
void *buffer_pointer = buffer + in_buffer;
while (left >= FS_BUFFER_SIZE) {
enum fs_access_result result =
read_file_splat(file_stream->handle, buffer_pointer, pointer, FS_BUFFER_SIZE);
if (result != FAR_SUCCESS)
return result;
pointer += FS_BUFFER_SIZE;
left -= FS_BUFFER_SIZE;
buffer_pointer += FS_BUFFER_SIZE;
}
if (left > 0) {
enum fs_access_result result = get_in_buffer(file_stream, pointer);
if (result != FAR_SUCCESS)
return result;
memcpy(buffer_pointer, file_stream->buffer, left);
}
file_stream->pointer += bytes;
return FAR_SUCCESS;
}
int read_file_stream_byte(struct file_stream *file_stream) {
if (file_stream->pointer == file_stream->length)
return -1;
enum fs_access_result result = get_in_buffer(file_stream, file_stream->pointer);
if (result != FAR_SUCCESS)
return -1;
return file_stream->buffer[file_stream->pointer++ - file_stream->buffer_start];
}

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

@ -0,0 +1,197 @@
/* Calcite, src/user-libs/calcite/memory.c
* 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
* 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/memory.h>
struct block_list_entry {
struct block_list_entry *prev;
struct block_list_entry *next;
};
struct block_list {
struct block_list_entry *first_entry;
struct block_list_entry *last_entry;
};
#define BASE_BLOCK_SIZE 16ULL
#define BLOCK_LEVELS 32
//BASE_BLOCK_SIZE << MIN_MAP_BLOCK_LEVEL must be at least 4096
#define MIN_MAP_BLOCK_LEVEL 16
//block_lists[n] is a list of free blocks of size BASE_BLOCK_SIZE * 2^n bytes
static struct block_list block_lists[BLOCK_LEVELS];
void *heap_alloc(uint64_t bytes) {
bytes = ((bytes - 1) / BASE_BLOCK_SIZE + 1) * BASE_BLOCK_SIZE;
//TODO: handle this case?
if (bytes > (BASE_BLOCK_SIZE << (BLOCK_LEVELS - 1)))
return 0;
int block_n_minimum = 0;
uint64_t block_size_minimum = BASE_BLOCK_SIZE;
while (block_size_minimum < bytes) {
++block_n_minimum;
block_size_minimum *= 2;
}
int block_n = block_n_minimum;
uint64_t block_size = block_size_minimum;
while (1) {
if (block_n == BLOCK_LEVELS) {
block_n = -1;
break;
}
if (block_lists[block_n].first_entry != 0)
break;
++block_n;
block_size *= 2;
}
if (block_n == -1) {
block_n = block_n_minimum > MIN_MAP_BLOCK_LEVEL ? block_n_minimum : MIN_MAP_BLOCK_LEVEL;
block_size = BASE_BLOCK_SIZE << block_n;
void *start = map_pages(block_size / 4096);
if (block_size != bytes)
heap_dealloc(start + bytes, block_size - bytes);
return start;
}
struct block_list_entry *e = block_lists[block_n].first_entry;
if (e->next)
e->next->prev = 0;
else
block_lists[block_n].last_entry = 0;
block_lists[block_n].first_entry = e->next;
void *start = e;
if (block_size != bytes)
heap_dealloc(start + bytes, block_size - bytes);
return start;
}
static void heap_dealloc_aligned(void *start, uint64_t block_n, uint64_t block_size) {
recurse:
if (block_size != BLOCK_LEVELS - 1) {
void *buddy = (void *)((uint64_t)start ^ block_size);
for (struct block_list_entry *e = block_lists[block_n].first_entry; e != 0; e = e->next)
if (e == buddy) {
if (e->next)
e->next->prev = e->prev;
else
block_lists[block_n].last_entry = e->prev;
if (e->prev)
e->prev->next = e->next;
else
block_lists[block_n].first_entry = e->next;
start = (void *)((uint64_t)start & ~block_size);
++block_n;
block_size *= 2;
goto recurse;
}
}
if (block_lists[block_n].first_entry == 0) {
struct block_list_entry *e = start;
e->next = 0;
e->prev = 0;
block_lists[block_n].first_entry = e;
block_lists[block_n].last_entry = e;
return;
}
if (start < (void *)block_lists[block_n].first_entry) {
struct block_list_entry *e = start;
e->next = block_lists[block_n].first_entry;
e->prev = 0;
block_lists[block_n].first_entry->prev = e;
block_lists[block_n].first_entry = e;
return;
}
if (start > (void *)block_lists[block_n].last_entry) {
struct block_list_entry *e = start;
e->next = 0;
e->prev = block_lists[block_n].last_entry;
block_lists[block_n].last_entry->next = e;
block_lists[block_n].last_entry = e;
return;
}
struct block_list_entry *after = block_lists[block_n].first_entry;
while ((void *)after < start)
after = after->next;
struct block_list_entry *before = after->prev;
struct block_list_entry *e = start;
e->next = after;
e->prev = before;
after->prev = e;
before->next = e;
}
void heap_dealloc(void *start, uint64_t bytes) {
bytes = ((bytes - 1) / BASE_BLOCK_SIZE + 1) * BASE_BLOCK_SIZE;
//bytes is guaranteed to be at most BASE_BLOCK_SIZE << (BLOCK_LEVELS - 1)
int block_n = 0;
uint64_t block_size = BASE_BLOCK_SIZE;
while (block_size <= bytes) {
if ((uint64_t)start & block_size) {
heap_dealloc_aligned(start, block_n, block_size);
start += block_size;
bytes -= block_size;
}
++block_n;
block_size *= 2;
}
while (block_n >= 0) {
if (block_size <= bytes) {
heap_dealloc_aligned(start, block_n, block_size);
start += block_size;
bytes -= block_size;
}
--block_n;
block_size /= 2;
}
}
void memcpy(void *to, const void *from, uint64_t bytes) {
uint8_t *to8 = to;
const uint8_t *from8 = from;
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

@ -16,6 +16,7 @@
bits 64
default rel
section .text

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
@ -16,7 +16,11 @@
*/
#include <kernel-public/syscall-numbers.h>
#include <calcite/calcite.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>
//defined in syscalls.asm
uint64_t do_syscall
@ -30,3 +34,122 @@ uint64_t do_syscall
void map_framebuffer(struct framebuffer_info *info_out) {
do_syscall((uint64_t)info_out, 0, 0, SYSCALL_MAP_FRAMEBUFFER);
}
enum fs_access_result open_file(const char *path, file_handle_t *handle_out) {
return do_syscall((uint64_t)path, (uint64_t)handle_out, 1, SYSCALL_OPEN_FILE);
}
void close_file(file_handle_t handle) {
do_syscall(handle, 0, 0, SYSCALL_CLOSE_FILE);
}
enum fs_access_result get_file_size(file_handle_t handle, uint64_t *bytes_out) {
return do_syscall(handle, (uint64_t)bytes_out, 0, SYSCALL_GET_FILE_SIZE);
}
enum fs_access_result read_file(struct read_file_parameter *parameter) {
return do_syscall((uint64_t)parameter, 0, 0, SYSCALL_READ_FILE);
}
enum fs_access_result read_file_splat(file_handle_t handle, void *buffer, uint64_t start, uint64_t bytes) {
struct read_file_parameter parameter = { .handle = handle, .buffer = buffer, .start = start, .bytes = bytes };
return do_syscall((uint64_t)&parameter, 0, 0, SYSCALL_READ_FILE);
}
void wait_for_mouse_packet(struct mouse_packet *packet_out) {
do_syscall((uint64_t)packet_out, 0, 0, SYSCALL_WAIT_FOR_MOUSE_PACKET);
}
void *map_pages(uint64_t count) {
return (void *)do_syscall(count, 0, 0, SYSCALL_MAP_PAGES);
}
void sleep_ms(uint64_t ms_to_sleep) {
do_syscall(ms_to_sleep, 0, 0, SYSCALL_SLEEP_MS);
}
int start_elf(const char *path, const struct process_start_info *info) {
return do_syscall((uint64_t)path, (uint64_t)info, 0, SYSCALL_START_ELF);
}
enum ipc_dgram_result ipc_create_dgram_receiver(const char *address, ipc_dgram_receiver_handle_t *handle_out) {
return do_syscall(
(uint64_t)address, (uint64_t)handle_out, 0, SYSCALL_IPC_CREATE_DGRAM_RECEIVER);
}
enum ipc_dgram_result ipc_create_dgram_sender(const char *address, ipc_dgram_sender_handle_t *handle_out) {
return do_syscall(
(uint64_t)address, (uint64_t)handle_out, 0, SYSCALL_IPC_CREATE_DGRAM_SENDER);
}
enum ipc_dgram_result ipc_receive_dgram(ipc_dgram_receiver_handle_t handle, void *buffer, int *bytes) {
return do_syscall(
handle, (uint64_t)buffer, (uint64_t)bytes, SYSCALL_IPC_RECEIVE_DGRAM);
}
enum ipc_dgram_result ipc_send_dgram(ipc_dgram_sender_handle_t handle, const void *data, int bytes) {
return do_syscall(handle, (uint64_t)data, bytes, SYSCALL_IPC_SEND_DGRAM);
}
void create_thread(void (*f)(uint64_t x), uint64_t x) {
do_syscall((uint64_t)f, x, 0, SYSCALL_CREATE_THREAD);
}
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);
}
void change_page_permissions(void *vma, int writable, int executable) {
do_syscall((uint64_t)vma, writable, executable, SYSCALL_CHANGE_PAGE_PERMISSIONS);
}
int wait_for_keyboard_packet() {
return do_syscall(0, 0, 0, SYSCALL_WAIT_FOR_KEYBOARD_PACKET);
}

View file

@ -0,0 +1,271 @@
/* 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/files.h>
#include <kernel-public/input.h>
#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 uint8_t *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;
uint8_t *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 void *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 void *data, int bytes) {
if (bytes == 0)
return;
const uint8_t *data8 = (const uint8_t *)data;
int last_newline = bytes - 1;
while (last_newline >= 0 && data8[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_flush() {
if (output_buffer_used != 0) {
send_output(output_buffer, output_buffer_used);
output_buffer_used = 0;
}
}
int terminal_readkey() {
if (!set_up_terminal_input)
return -1;
terminal_flush();
int result;
int bytes = sizeof(int);
ipc_receive_dgram(input_handle, &result, &bytes);
//TODO: handle case where terminal misbehaves and sends more than sizeof(int)
return result;
}
static char *readline_buffer = 0;
static int readline_buffer_length = 0;
static int readline_buffer_used = 0;
static int readline_cursor = 0;
static void room_for_one_more() {
if (readline_buffer == 0) {
readline_buffer_length = 1024;
readline_buffer = heap_alloc(readline_buffer_length);
}
else if (readline_buffer_used == readline_buffer_length) {
char *new_buffer = heap_alloc(readline_buffer_length * 2);
memcpy(new_buffer, readline_buffer, readline_buffer_length);
heap_dealloc(readline_buffer, readline_buffer_length);
readline_buffer = new_buffer;
readline_buffer_length *= 2;
}
}
const char *terminal_readline() {
readline_buffer_used = 0;
readline_cursor = 0;
while (1) {
int key_packet = terminal_readkey();
if (!(key_packet & (KEY_FLAG_MAKE << 8)) ||
(key_packet & (TKF_LEFT_CTRL | TKF_RIGHT_CTRL | TKF_LEFT_ALT | TKF_RIGHT_ALT)))
continue;
int key = (key_packet >> 8) & 0xff;
int num_lock = key_packet & TKF_NUM_LOCK;
if (key == KEY_LEFT || (!num_lock && key == KEY_NUM_4)) {
if (readline_cursor != 0) {
--readline_cursor;
terminal_write("\x1b[D", 3);
}
}
else if (key == KEY_RIGHT || (!num_lock && key == KEY_NUM_6)) {
if (readline_cursor != readline_buffer_used) {
++readline_cursor;
terminal_write("\x1b[C", 3);
}
}
else if (key == KEY_BACKSPACE) {
if (readline_cursor != 0) {
for (int i = readline_cursor; i < readline_buffer_used; ++i)
readline_buffer[i - 1] = readline_buffer[i];
--readline_cursor;
--readline_buffer_used;
terminal_write("\x1b[D", 3);
terminal_write(&readline_buffer[readline_cursor], readline_buffer_used - readline_cursor);
terminal_write(" ", 1);
for (int i = readline_cursor; i < readline_buffer_used + 1; ++i)
terminal_write("\x1b[D", 3);
}
}
else if (key == KEY_DELETE || (!num_lock && key == KEY_NUM_PERIOD)) {
if (readline_cursor != readline_buffer_used) {
++readline_cursor;
for (int i = readline_cursor; i < readline_buffer_used; ++i)
readline_buffer[i - 1] = readline_buffer[i];
--readline_cursor;
--readline_buffer_used;
terminal_write(&readline_buffer[readline_cursor], readline_buffer_used - readline_cursor);
terminal_write(" ", 1);
for (int i = readline_cursor; i < readline_buffer_used + 1; ++i)
terminal_write("\x1b[D", 3);
}
}
else if (key == KEY_ENTER || key == KEY_NUM_ENTER) {
room_for_one_more();
readline_buffer[readline_buffer_used] = 0;
for (int i = readline_cursor; i < readline_buffer_used; ++i)
terminal_write("\x1b[C", 3);
terminal_write("\n", 1);
return readline_buffer;
}
else if (key == KEY_TAB)
continue;
else if (key == KEY_HOME || (!num_lock && key == KEY_NUM_7)) {
for (int i = 0; i < readline_cursor; ++i)
terminal_write("\x1b[D", 3);
readline_cursor = 0;
}
else if (key == KEY_END || (!num_lock && key == KEY_NUM_1)) {
for (int i = readline_cursor; i < readline_buffer_used; ++i)
terminal_write("\x1b[C", 3);
readline_cursor = readline_buffer_used;
}
else {
char value = key_packet & 0xff;
if (value != 0) {
room_for_one_more();
for (int i = readline_buffer_used; i > readline_cursor; --i)
readline_buffer[i] = readline_buffer[i - 1];
readline_buffer[readline_cursor] = value;
++readline_buffer_used;
terminal_write(&readline_buffer[readline_cursor], readline_buffer_used - readline_cursor);
++readline_cursor;
for (int i = readline_buffer_used; i > readline_cursor; --i)
terminal_write("\x1b[D", 3);
}
}
}
}

View file

@ -0,0 +1,31 @@
/* Calcite, src/user-libs/silver/image.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 <calcite/memory.h>
#include <silver/image.h>
void create_image(int width, int height, struct image **image_out) {
*image_out = heap_alloc(sizeof(struct image));
(*image_out)->width = width;
(*image_out)->height = height;
(*image_out)->pixels = heap_alloc(width * height * sizeof(struct pixel));
}
void destroy_image(struct image *image) {
heap_dealloc(image->pixels, image->width * image->height * sizeof(struct pixel));
heap_dealloc(image, sizeof(struct image));
}

128
src/user-libs/silver/pam.c Normal file
View file

@ -0,0 +1,128 @@
/* Calcite, src/user-libs/silver/pam.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 <calcite/file-streams.h>
#include <silver/pam.h>
static int starts_with(const char *big_string, const char *little_string) {
while (*little_string) {
if (*big_string != *little_string)
return 0;
++big_string;
++little_string;
}
return 1;
}
static int strequ(const char *str1, const char *str2) {
while (1) {
if (!*str1 && !*str2)
return 1;
if (*str1 != *str2)
return 0;
++str1;
++str2;
}
}
static int parse_uint(const char *str) {
int i = 0;
while (*str)
i = i * 10 + *(str++) - '0';
return i;
}
enum pam_tupltype {
PTT_RGB_ALPHA
};
#define MAX_LINE_LENGTH 1023
int load_pam(const char *path, struct image **image_out) {
struct file_stream *stream;
if (open_file_stream(path, &stream) != FAR_SUCCESS)
return 0;
if (read_file_stream_byte(stream) != 'P' ||
read_file_stream_byte(stream) != '7' ||
read_file_stream_byte(stream) != '\n') {
close_file_stream(stream);
return 0;
}
int width = -1, height = -1, depth = -1, maxval = -1, tupltype = -1;
while (1) {
char line[MAX_LINE_LENGTH + 1];
int line_length = 0;
while (1) {
int c = read_file_stream_byte(stream);
if (c == -1) {
close_file_stream(stream);
return 0;
}
if (c == '\n') {
line[line_length] = 0;
break;
}
if (line_length == MAX_LINE_LENGTH) {
close_file_stream(stream);
return 0;
}
line[line_length++] = c;
}
if (line[0] == '#')
continue;
if (starts_with(line, "WIDTH "))
width = parse_uint(&line[6]);
else if (starts_with(line, "HEIGHT "))
height = parse_uint(&line[7]);
else if (starts_with(line, "DEPTH "))
depth = parse_uint(&line[6]);
else if (starts_with(line, "MAXVAL "))
maxval = parse_uint(&line[7]);
else if (strequ(line, "TUPLTYPE RGB_ALPHA"))
tupltype = PTT_RGB_ALPHA;
else if (strequ(line, "ENDHDR"))
break;
}
if (width == -1 || height == -1 || depth != 4 || maxval != 255 || tupltype != PTT_RGB_ALPHA) {
close_file_stream(stream);
return 0;
}
create_image(width, height, image_out);
if (read_file_stream(
stream,
(*image_out)->pixels,
width * height * 4) != FAR_SUCCESS) {
close_file_stream(stream);
destroy_image(*image_out);
return 0;
}
close_file_stream(stream);
return 1;
}