Compare commits

..

No commits in common. "reboot" and "alpha" have entirely different histories.

105 changed files with 8769 additions and 863 deletions

View file

@ -1,3 +0,0 @@
#!/bin/sh
rm -rf build build.ninja

View file

@ -1,3 +0,0 @@
#!/bin/sh
rm -rf dependencies

View file

@ -1,3 +1,10 @@
-std=c++17
-std=c23
-ffreestanding
-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

@ -1,6 +1,7 @@
timeout: 0
quiet: yes
quiet: yes
/Calcite
protocol: limine
path: boot():/calcite/core.elf
path: boot():/calcite/kernel.elf
cmdline: root-drive=pata0,root-fs=iso9660

23
get-dependencies.sh Executable file → Normal file
View file

@ -1,25 +1,22 @@
#!/bin/sh
LIMINE_VERSION="12.5.2"
LIMINE_PROTOCOL_COMMIT="630686a3dd3ce40f9e510a7dd9fea6b4c60d952e"
LIMINE_VERSION="9.3.4"
FLANTERM_VERSION="2.1.0"
if [ -e dependencies ]; then
echo The dependencies directory already exists.
echo Use clean-dependencies.sh to remove it.
echo dependencies directory already exists.
exit
fi
set -e
mkdir dependencies
cd dependencies
curl -L https://github.com/Limine-Bootloader/Limine/releases/download/v12.5.2/limine-binary.tar.gz | tar xz
make -C limine-binary
curl -L https://github.com/Limine-Bootloader/limine-protocol/archive/${LIMINE_PROTOCOL_COMMIT}.zip -o limine-protocol.zip
unzip limine-protocol.zip
rm limine-protocol.zip
mv limine-protocol-${LIMINE_PROTOCOL_COMMIT} limine-protocol
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

@ -1,30 +0,0 @@
/* include/calcite/assert.hxx
* Runtime assertions and panics.
*
* Copyright 2026 Benji Dial
* This file is part of the Calcite Operating System.
*
* Calcite 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, version 3 only.
*
* Calcite 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
* Calcite. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#define assert(cond) { \
if (cond) {} \
else ::calcite::assert_fail(); \
}
namespace calcite {
[[noreturn]] void assert_fail();
}

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

@ -0,0 +1,30 @@
/* Calcite, include/calcite/file-streams.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 <kernel-public/files.h>
struct file_stream;
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);
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

@ -1,86 +0,0 @@
/* include/calcite/result.hxx
* Result type returned from functions across Calcite.
*
* Copyright 2026 Benji Dial
* This file is part of the Calcite Operating System.
*
* Calcite 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, version 3 only.
*
* Calcite 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
* Calcite. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <calcite/assert.hxx>
#include <cstdint>
#include <utility>
#include <new>
namespace calcite {
enum class result_code {
good,
out_of_memory,
not_found
};
class empty {};
template <class type>
class [[nodiscard]] result {
private:
result_code code;
uint8_t buffer[sizeof(type)];
public:
result(result_code code) : code(code) {
assert(code != result_code::good);
}
template <class ...param_types>
result(param_types &&...params) : code(result_code::good) {
new ((type *)buffer) type(std::forward<param_types>(params)...);
}
~result() {
if (code != result_code::good)
((type *)buffer)->~type();
}
result(result &&other) = delete;
result(const result &other) = delete;
result &operator =(result &&other) = delete;
result &operator =(const result &other) = delete;
type &unwrap() {
assert(code == result_code::good);
return *(type *)buffer;
}
const type &unwrap() const {
assert(code == result_code::good);
return *(const type *)buffer;
}
bool is_error() const {
return code != result_code::good;
}
result_code get_error() const {
assert(code != result_code::good);
return code;
}
};
}

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

@ -0,0 +1,40 @@
/* Calcite, include/kernel-public/files.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 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
};
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

@ -0,0 +1,27 @@
/* Calcite, include/kernel-public/framebuffer.h
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 framebuffer_info {
uint8_t *fb_base;
int fb_width;
int fb_height;
int fb_pitch;
};

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

@ -0,0 +1,50 @@
/* Calcite, include/kernel-public/syscall-numbers.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
enum {
SYSCALL_END_THREAD,
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);

247
make-build.sh Executable file → Normal file
View file

@ -1,148 +1,145 @@
#!/bin/sh
if [ -e build.ninja ]; then
echo The build.ninja file already exists.
echo Remove it with clean-build.sh.
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
if [ "$#" -eq 0 ]; then
echo This script must be run with at least one flag. The recognized flags are:
echo
echo " -- Do nothing, but silence this message."
echo
echo " --cxx-flags FLAGS Add FLAGS to the flags sent to the C++ compiler. The flags"
echo " should be quoted, so that they are all read by this script"
echo " as one argument."
echo
echo " --debug Enable debugging symbols."
echo
echo " --ld-flags FLAGS Add FLAGS to the flags sent to ld. The flags should be"
echo " quoted, so that they are all read by this script as one"
echo " argument."
echo
echo " --no-optimize Disable compiler optimizations. This might help with using"
echo " the debugger. A bug could only appear when optimizations"
echo " are enabled, so it might be necessary to run the debugger"
echo " without this flag."
echo
echo These options can be combined.
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 -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.
exit
fi
set -e
CXX_FLAGS="-std=c++17 -ffreestanding -fno-exceptions -fno-rtti -mno-sse -I include -Wall -Wextra"
LD_FLAGS="-L build/libraries"
echo "rule nasm" >> build.ninja
echo " command = nasm -f elf64 \$in -o \$out" >> build.ninja
OPTIMIZE_CXX_FLAGS="-O2"
DEBUG_CXX_FLAGS=""
NO_DEBUG_LD_FLAGS="-s"
EXTRA_CXX_FLAGS=""
EXTRA_LD_FLAGS=""
echo "rule kernel_cc" >> build.ninja
echo " depfile = \$out.d" >> build.ninja
echo " command = cc -c -MD -MF \$out.d ${KERNEL_CC_FLAGS} \$in -o \$out" >> build.ninja
while [ "$#" -gt 0 ]; do
case "$1" in
--cxx-flags)
shift
EXTRA_CXX_FLAGS="$EXTRA_CXX_FLAGS $1"
;;
--debug)
DEBUG_CXX_FLAGS="-ggdb"
NO_DEBUG_LD_FLAGS=""
;;
--ld-flags)
shift
EXTRA_LD_FLAGS="$EXTRA_LD_FLAGS $1"
;;
--no-optimize)
OPTIMIZE_CXX_FLAGS=""
;;
--)
;;
*)
echo Unknown argument.
exit
esac
shift
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 ${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 ${COMMON_LD_FLAGS} -T src/user-apps/link.ld \$in -o \$out" >> build.ninja
#builds everything in a directory
# $1 - source directory
# $2 - cc rule
# $3 - ld rule
# $4 - linked object name
# $5 - extra objects to link in
build_all() {
sources="$1/*.asm $1/*.c"
objects="$5"
for src in $sources; do
if echo $src | grep -q -v '*'; then
build=$(echo $src | sed -e 's/^src/build/').o
if echo $src | grep -q '.c$'; then
echo "build $build: $2 $src" >> build.ninja
else
echo "build $build: nasm $src" >> build.ninja
fi
objects="$objects $build"
fi
done
build_dir=$(echo $1 | sed -e 's/^src/build/')
echo "build $build_dir/$4: $3 $objects" >> build.ninja
}
build_all src/kernel kernel_cc kernel_ld kernel.elf
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
CXX_FLAGS="$CXX_FLAGS $OPTIMIZE_CXX_FLAGS $DEBUG_CXX_FLAGS $EXTRA_CXX_FLAGS"
LD_FLAGS="$LD_FLAGS $NO_DEBUG_LD_FLAGS $EXTRA_LD_FLAGS"
cp_apps=""
app_deps=""
rules() {
echo "rule asm"
echo " command = nasm -f elf64 \$in -o \$out"
echo "rule cxx"
echo " depfile = \$out.d"
echo " command = c++ -c -MD -MF \$out.d $CXX_FLAGS \$in -o \$out"
echo "rule core_ld"
echo " depfile = \$out.d"
echo " command = ld $LD_FLAGS -T src/core/link.ld \$in -o \$out"
echo "rule service_ld"
echo " depfile = \$out.d"
echo " command = ld $LD_FLAGS -T src/service/link.ld \$in -o \$out"
echo "rule disk"
echo " command =" \
"rm -rf build/disk &&" \
"cp -r disk build/disk &&" \
"mkdir -p build/disk/EFI/BOOT &&" \
"mkdir -p build/disk/limine &&" \
"mkdir -p build/disk/calcite/service &&" \
"cp dependencies/limine-binary/BOOTX64.EFI build/disk/EFI/BOOT/ &&" \
"cp dependencies/limine-binary/limine-uefi-cd.bin build/disk/limine/ &&" \
"cp dependencies/limine-binary/limine-bios-cd.bin build/disk/limine/ &&" \
"cp dependencies/limine-binary/limine-bios.sys build/disk/limine/ &&" \
"cp build/core.elf build/disk/calcite/ &&" \
"cp build/service/*.elf build/disk/calcite/service/ &&" \
"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-binary/limine bios-install build/disk.iso"
}
for dir in src/user-apps/*; do
if [ -d $dir ]; then
build_core() {
echo "build build/core/core.cxx.o: cxx src/core/core.cxx"
echo "build build/core/core.asm.o: asm src/core/core.asm"
echo "build build/core.elf: core_ld build/core/core.cxx.o build/core/core.asm.o"
}
if [ -e $dir/libraries.txt ]; then
lib_paths=$(cat $dir/libraries.txt | sed -e 's/^.*$/build\/user-libs\/\0\/lib\0.o/' | tr '\n' ' ')
else
lib_paths=""
fi
build_service() {
SERVICE="$1"
OBJECTS=""
for SOURCE in $(find "src/service/$SERVICE" -regex '.*\.cxx'); do
OBJECT="$(echo $SOURCE | sed 's/src/build/').o"
echo "build $OBJECT: cxx $SOURCE"
OBJECTS="$OBJECTS $OBJECT"
done
for SOURCE in $(find "src/service/$SERVICE" -regex '.*\.asm'); do
OBJECT="$(echo $SOURCE | sed 's/src/build/').o"
echo "build $OBJECT: asm $SOURCE"
OBJECTS="$OBJECTS $OBJECT"
done
echo "build build/service/$SERVICE.elf: service_ld$OBJECTS"
}
app_name=$(echo $dir | sed -e 's/^src\/user-apps\///')
build_all $dir user_cc user_app_ld $app_name.elf "$lib_paths"
build_disk() {
echo "build build/disk.iso: disk | $@"
}
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
default() {
echo "default build/disk.iso"
}
cp_apps=$(echo "$cp_apps" \
"mkdir -p $disk_dir &&" \
"cp $build_elf $disk_elf &&")
app_deps="$app_deps $build_elf"
rules >> build.ninja
DISK_DEPS="build/core.elf"
build_core >> build.ninja
for DIRECTORY in src/service/*; do
if [ -d "$DIRECTORY" ]; then
SERVICE="$(basename $DIRECTORY)"
DISK_DEPS="$DISK_DEPS build/service/$SERVICE.elf"
build_service $SERVICE >> build.ninja
fi
done
build_disk $DISK_DEPS >> build.ninja
default >> build.ninja
echo "rule disk" >> build.ninja
echo " command =" \
"rm -rf build/disk &&" \
"cp -r disk build/disk &&" \
"cp dependencies/limine/limine-uefi-cd.bin build/disk/limine/ &&" \
"cp dependencies/limine/limine-bios-cd.bin build/disk/limine/ &&" \
"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 -p build/disk/calcite &&" \
"cp build/kernel/kernel.elf build/disk/calcite/ &&" \
$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 $app_deps" >> build.ninja
echo "default build/disk.iso" >> build.ninja

View file

@ -1,5 +1,5 @@
target remote | qemu-system-x86_64 -gdb stdio -cdrom build/disk.iso -boot d
symbol-file build/core.elf
symbol-file build/kernel/kernel.elf
set disassembly-flavor intel
set print asm-demangle on
layout src

View file

@ -1,95 +1,72 @@
___ _ _ _
/ __|__ _| |__(_) |_ ___
| (__/ _` | / _| | _/ -_)
\___\__,_|_\__|_|\__\___|
____ _ _ _
/ ___|__ _| | ___(_) |_ ___
| | / _` | |/ __| | __/ _ \
| |__| (_| | | (__| | || __/
\____\__,_|_|\___|_|\__\___|
Calcite is a hobby operating system. I am maintaining a weblog with occasional
status updates at <https://www.benjidial.net/weblog/calcite>.
Calcite is very much alpha software. Do not expect it to do anything useful.
=== Building ===
=== Building
Calcite requires some software to be installed before it can be built:
* a C compiler supporting C99 (e.g. GCC)
* a C++ compiler supporting C++17 (e.g. GCC)
* curl
* GNU ld
* GNU tar
* GNU xorriso
* NASM
* Ninja build system
* curl
* GCC (Any C compiler supporting both C99 and C23 should work.)
* GNU Binutils (Specifically, "ld" is used to link the kernel.)
* GNU xorriso
* NASM
* Ninja build system
On Debian, it is sufficient to run this command:
apt install binutils curl g++ gcc nasm ninja-build tar xorriso
apt install binutils curl gcc nasm ninja-build xorriso
The Calcite disk includes some software that is not in this repository.
To download that software into the repository tree, use the get-dependencies.sh
script. To remove those files, use the clean-dependencies.sh script.
To build Calcite, run these commands:
sh get-dependencies.sh
sh make-build.sh release
ninja
Next, use the make-build.sh script to make the ninja.build file. Running this
script with no arguments will display information about the arguments that this
script accepts.
This will build a disk image at "build/disk.iso" that can be booted with
either BIOS or UEFI.
Finally, just run ninja to build the disk. A disk image will be placed at
build/disk.iso. This disk must be booted with UEFI. All build files, including
ninja.build, but not including the files downloaded by get-dependencies.sh,
can be removed with the clean-build.sh script.
=== Debugging
=== Debugging ===
More software must be installed to debug Calcite:
* GNU's GDB
* QEMU, including the x86_64 system
Once Calcite has been built, some more software is required to debug it:
* GNU's GDB
* QEMU, including the x86_64 system
On Debian, it is sufficient to run this command:
apt install gdb qemu-system-x86
Run "gdb -x qemu.gdb" to boot Calcite in QEMU and attach GDB to it.
The debugging experience will be nicer if debugging symbols were
enabled during the make-build.sh step.
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.
=== License ===
While debugging, it is useful to disable optimizations and enable debugging
information. If you already built Calcite in "release" mode, run:
rm -r build build.ninja
sh make-build.sh debug
ninja
Calcite is Copyright 2026 Benji Dial.
If you did not already build Calcite, then you do not need the first command.
Calcite 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 (version 3 only).
=== License
Calcite 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.
Calcite is Copyright 2025-2026 Benji Dial, and licensed under the GNU GPL v3.
See "license.txt" for the text of the license.
You should have received a copy of the GNU General Public License along with
Calcite (in license.txt). If not, see <https://www.gnu.org/licenses/>.
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 Calcite disk image includes the Limine bootloader, version 12.5.2. Limine is
Copyright 2019-2026 Mintsuki and contributors, and is used under the terms of
the BSD 2-Clause License, reproduced below:
Copyright (C) 2019-2026 Mintsuki and contributors.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must n the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Limine has some transitive dependencies. Information about these can be found at
<https://github.com/Limine-Bootloader/Limine/blob/v12.5.2/3RDPARTY.md>.
The terminal, included in the disk image, is linked with the Flanterm library,
version 2.1.0. Flanterm is Copyright 2022-2025 minstuki and contributors, and
is released under the BSD 2-Clause license. You can find Flanterm online at
<https://codeberg.org/Mintsuki/Flanterm>.
After running "sh get-dependencies.sh", you can find the license of Flanterm
at "dependencies/flanterm/LICENSE".

View file

@ -1,28 +0,0 @@
; src/core/core.asm
; The core of the Calcite Operating System.
;
; Copyright 2026 Benji Dial
; This file is part of the Calcite Operating System.
;
; Calcite 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, version 3 only.
;
; Calcite 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
; Calcite. If not, see <https://www.gnu.org/licenses/>.
bits 64
section .text
global asm_set_cr3
asm_set_cr3:
mov cr3, rdi
mov rsp, rsi
push qword 0
xor rbp, rbp
jmp rdx

View file

@ -1,456 +0,0 @@
/* src/core/core.cxx
* The core of the Calcite Operating System.
*
* Copyright 2026 Benji Dial
* This file is part of the Calcite Operating System.
*
* Calcite 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, version 3 only.
*
* Calcite 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
* Calcite. If not, see <https://www.gnu.org/licenses/>.
*/
#include <calcite/assert.hxx>
#include <calcite/result.hxx>
#include "limine.hxx"
using namespace calcite;
//the following abbreviations are used in here:
// p1 = page table
// p2 = page directory
// p3 = page directory pointer table
// p4 = page map level 4
// pma = physical memory address
// vma = virtual memory address
//the core has one p3 that is mapped in all services at:
// 0xffff.ff80.0000.0000 - 0xffff.ffff.ffff.ffff
//the core's p1's are mapped into:
// 0xffff.ffff.c000.0000 - 0xffff.ffff.ffff.ffff
//each service has its own p3 that is mapped in only that service at:
// 0xffff.ff00.0000.0000 - 0xffff.ff7f.ffff.ffff
//in a p1, a stack guard is 0x1000
static uint64_t max_physical;
static uint64_t *physical_free_bitmap;
static uint64_t physical_free_count_bytes;
static uint64_t next_try_physical_free;
alignas(4096) static uint64_t core_p4[512];
alignas(4096) static uint64_t core_p3[512];
alignas(4096) static uint64_t core_p2s[512 * 512];
alignas(4096) static uint64_t core_top_p1s[512 * 512];
constexpr uint64_t temp_stack_length = 65536;
alignas(4096) static uint64_t temp_stack_and_guards[temp_stack_length + 8192];
static uint64_t hhdm;
extern uint8_t __image_rx_start;
extern uint8_t __image_rx_end;
extern uint8_t __image_ro_start;
extern uint8_t __image_ro_end;
extern uint8_t __image_rw_start;
extern uint8_t __image_rw_end;
//remove 4 once limine updates
uint64_t limine_base_revision[4] = LIMINE_BASE_REVISION(6);
limine_hhdm_request hhdm_request = {
.id = LIMINE_HHDM_REQUEST_ID,
.revision = 0,
.response = 0
};
limine_executable_address_request executable_address_request = {
.id = LIMINE_EXECUTABLE_ADDRESS_REQUEST_ID,
.revision = 0,
.response = 0
};
limine_memmap_request memmap_request = {
.id = LIMINE_MEMMAP_REQUEST_ID,
.revision = 0,
.response = 0
};
[[noreturn]] static void die() {
while (true)
__asm__ ("hlt");
}
[[noreturn]] void calcite::assert_fail() {
die();
}
static void mark_physical_free(uint64_t start, uint64_t end) {
assert(start % 4096 == 0);
assert(end % 4096 == 0);
assert(start <= end);
assert(end <= max_physical);
physical_free_count_bytes += end - start;
start /= 4096;
end /= 4096;
if (start / 64 == end / 64) {
uint64_t mask = (1ULL << (end % 64)) - (1ULL << (start % 64));
assert((physical_free_bitmap[start / 64] & mask) == 0);
physical_free_bitmap[start / 64] |= mask;
return;
}
if (start % 64 != 0) {
uint64_t mask = -(1ULL << (start % 64));
assert((physical_free_bitmap[start / 64] & mask) == 0);
physical_free_bitmap[start / 64] |= mask;
start += 64 - start % 64;
}
if (end % 64 != 0) {
uint64_t mask = (1ULL << (end % 64)) - 1;
assert((physical_free_bitmap[end / 64] & mask) == 0);
physical_free_bitmap[end / 64] |= mask;
end -= end % 64;
}
for (uint64_t i = start / 64; i < end / 64; ++i) {
assert(physical_free_bitmap[i] == 0);
physical_free_bitmap[i] = -1ULL;
}
}
static result<uint64_t> take_free_physical_page() {
uint64_t i = next_try_physical_free;
while (true) {
if (physical_free_bitmap[i] != 0) {
next_try_physical_free = i;
for (int j = 0;; ++j)
if (physical_free_bitmap[i] & (1ULL << j)) {
physical_free_bitmap[i] &= ~(1ULL << j);
physical_free_count_bytes -= 4096;
return (i * 64 + j) * 4096;
}
}
i = (i + 1) % (max_physical / 4096 / 64);
if (i == next_try_physical_free)
return result_code::out_of_memory;
}
}
//maps p1 if needed. returns old p1e value.
template <bool hhdm_p1s = false>
static result<uint64_t> set_core_p1e(void *vma, uint64_t p1e) {
uint64_t vma64 = (uint64_t)vma;
assert(vma64 % 4096 == 0);
assert(vma64 >= 0xffffff8000000000);
assert(vma64 < 0xffffffffc0000000);
int p1i = (vma64 / 4096) % 512;
int p2i = (vma64 / 4096 / 512) % 512;
int p3i = (vma64 / 4096 / 512 / 512) % 512;
bool p1_is_new = false;
if (core_top_p1s[p3i * 512 + p2i] == 0) {
auto result = take_free_physical_page();
if (result.is_error())
return result.get_error();
uint64_t p1_pma = result.unwrap();
core_top_p1s[p3i * 512 + p2i] = p1_pma | 0x8000000000000003;
core_p2s[p3i * 512 + p2i] = p1_pma | 0x3;
p1_is_new = true;
}
uint64_t *p1_vma;
if constexpr (hhdm_p1s) {
uint64_t p1_pma = core_top_p1s[p3i * 512 + p2i] & 0x7ffffffffffff000;
p1_vma = (uint64_t *)(hhdm + p1_pma);
}
else
p1_vma = (uint64_t *)(0xffffffffc0000000 + (p3i * 512 + p2i) * 4096);
if (p1_is_new)
for (int i = 0; i < 512; ++i)
p1_vma[i] = 0;
uint64_t old_p1e = p1_vma[p1i];
p1_vma[p1i] = p1e;
return old_p1e;
}
template <bool hhdm_p1s = false>
static result<empty> create_core_stack_guard(void *vma) {
auto result = set_core_p1e<hhdm_p1s>(vma, 0x1000);
if (result.is_error())
return result.get_error();
assert(result.unwrap() == 0);
return {};
}
template <bool hhdm_p1s = false>
static result<empty> map_core_page(
void *vma, uint64_t pma, bool executable, bool writable) {
auto result = set_core_p1e<hhdm_p1s>(vma,
pma | 0x1 | (executable ? 0 : 0x8000000000000000) | (writable ? 0x2 : 0));
if (result.is_error())
return result.get_error();
assert(result.unwrap() == 0);
return {};
}
//might return error with some mapped
template <bool hhdm_p1s = false>
static result<empty> map_core_page_range(
void *vma, uint64_t pma, uint64_t page_count,
bool executable, bool writable) {
for (uint64_t i = 0; i < page_count; ++i) {
auto result = map_core_page<hhdm_p1s>(
(uint8_t *)vma + 4096 * i, pma + 4096 * i, executable, writable);
if (result.is_error())
return result.get_error();
}
return {};
}
template <bool hhdm_p1s = false>
static result<void *> find_free_core_virtual_pages(uint64_t page_count) {
uint64_t start_i = 0;
uint64_t on_i = 0;
uint64_t count = 0;
while (true) {
if (count >= page_count)
return (void *)(0xffffff8000000000 + start_i * 4096);
if (on_i >= 512 * 512 * 511)
return result_code::out_of_memory;
int p2si = on_i / 512;
int p1i = on_i % 512;
if (p1i == 0 && core_p2s[p2si] == 0) {
count += 512;
on_i += 512;
continue;
}
uint64_t *p1;
if constexpr (hhdm_p1s) {
uint64_t p1_pma = core_p2s[p2si] & 0x7ffffffffffff000;
p1 = (uint64_t *)(hhdm + p1_pma);
}
else
p1 = (uint64_t *)(0xffffffffc0000000 + p2si * 4096);
if (p1[p1i] == 0) {
++count;
++on_i;
continue;
}
count = 0;
++on_i;
start_i = on_i;
}
}
template <bool hhdm_p1s = false>
static result<empty> unmap_and_free_core_page(void *vma) {
auto result = set_core_p1e<hhdm_p1s>(vma, 0);
if (result.is_error())
return result.get_error();
uint64_t old_p1e = result.unwrap();
assert(old_p1e & 0x1);
uint64_t pma = old_p1e & 0x7ffffffffffff000;
mark_physical_free(pma, pma + 4096);
return {};
}
extern "C" [[noreturn]] void asm_set_cr3(
uint64_t cr3, void *stack_top, void (*jump_to)());
[[noreturn]] static void with_cr3();
extern "C" [[noreturn]] void __limine_entry() {
if (!LIMINE_BASE_REVISION_SUPPORTED(limine_base_revision))
die();
auto hhdm_response = hhdm_request.response;
if (hhdm_response == 0)
die();
hhdm = hhdm_response->offset;
auto executable_address_response = executable_address_request.response;
if (executable_address_response == 0)
die();
auto exe_offset =
executable_address_response->virtual_base -
executable_address_response->physical_base;
auto memmap_response = memmap_request.response;
if (memmap_response == 0)
die();
for (uint64_t i = 0; i < memmap_response->entry_count; ++i) {
auto entry = memmap_response->entries[i];
if (entry->type == LIMINE_MEMMAP_BOOTLOADER_RECLAIMABLE ||
entry->type == LIMINE_MEMMAP_USABLE) {
if (entry->base % 4096 != 0 || entry->length % 4096 != 0)
die();
if (entry->base + entry->length > max_physical)
max_physical = entry->base + entry->length;
}
}
uint64_t physical_map_pages_needed = (max_physical - 1) / 32768 + 1;
uint64_t physical_map_physical;
bool found_physical_map_physical = false;
for (uint64_t i = 0; i < memmap_response->entry_count; ++i) {
auto entry = memmap_response->entries[i];
if (entry->type == LIMINE_MEMMAP_USABLE &&
entry->length >= physical_map_pages_needed * 4096) {
physical_map_physical = entry->base;
entry->base += physical_map_pages_needed * 4096;
entry->length -= physical_map_pages_needed * 4096;
found_physical_map_physical = true;
break;
}
}
if (!found_physical_map_physical)
die();
physical_free_bitmap = (uint64_t *)(hhdm + physical_map_physical);
for (uint64_t i = 0; i < physical_map_pages_needed * 4096 / 8; ++i)
physical_free_bitmap[i] = 0;
for (uint64_t i = 0; i < memmap_response->entry_count; ++i) {
auto entry = memmap_response->entries[i];
if (entry->type == LIMINE_MEMMAP_USABLE)
mark_physical_free(entry->base, entry->base + entry->length);
}
for (int i = 0; i < 511; ++i)
core_p4[i] = 0;
core_p4[511] = ((uint64_t)core_p3 - exe_offset) | 0x3;
for (int i = 0; i < 512; ++i)
core_p3[i] = ((uint64_t)&core_p2s[i * 512] - exe_offset) | 0x3;
for (int i = 0; i < 512 * 511; ++i)
core_p2s[i] = 0;
for (int i = 0; i < 512; ++i)
core_p2s[512 * 511 + i] =
((uint64_t)&core_top_p1s[i * 512] - exe_offset) | 0x3;
for (int i = 0; i < 512 * 512; ++i)
core_top_p1s[i] = 0;
auto result = map_core_page_range<true>(
&__image_rx_start, (uint64_t)&__image_rx_start - exe_offset,
(&__image_rx_end - &__image_rx_start) / 4096, true, false);
if (result.is_error())
die();
auto result_2 = map_core_page_range<true>(
&__image_ro_start, (uint64_t)&__image_ro_start - exe_offset,
(&__image_ro_end - &__image_ro_start) / 4096, false, false);
if (result_2.is_error())
die();
auto result_3 = map_core_page_range<true>(
&__image_rw_start, (uint64_t)&__image_rw_start - exe_offset,
(&__image_rw_end - &__image_rw_start) / 4096, false, true);
if (result_3.is_error())
die();
auto new_physical_free_bitmap_result =
find_free_core_virtual_pages(physical_map_physical);
if (new_physical_free_bitmap_result.is_error())
die();
void *new_physical_free_bitmap = new_physical_free_bitmap_result.unwrap();
auto result_4 = map_core_page_range<true>(
new_physical_free_bitmap, physical_map_physical,
physical_map_pages_needed, false, true);
if (result_4.is_error())
die();
auto result_5 = unmap_and_free_core_page<true>(temp_stack_and_guards);
if (result_5.is_error())
die();
auto result_6 = create_core_stack_guard<true>(temp_stack_and_guards);
if (result_6.is_error())
die();
auto result_7 = unmap_and_free_core_page<true>(
&temp_stack_and_guards[4096 + temp_stack_length]);
if (result_7.is_error())
die();
auto result_8 = create_core_stack_guard<true>(
&temp_stack_and_guards[4096 + temp_stack_length]);
if (result_8.is_error())
die();
physical_free_bitmap = (uint64_t *)new_physical_free_bitmap;
asm_set_cr3(
(uint64_t)core_p4 - exe_offset,
&temp_stack_and_guards[4096 + temp_stack_length],
&with_cr3);
}
[[noreturn]] static void with_cr3() {
while (true)
;
}

View file

@ -1 +0,0 @@
../../dependencies/limine-protocol/include/limine.h

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

328
src/kernel/entry.c Normal file
View file

@ -0,0 +1,328 @@
/* Calcite, src/kernel/entry.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 "framebuffer.h"
#include "interrupts.h"
#include "ipc-dgram.h"
#include "scheduler.h"
#include "syscalls.h"
#include "process.h"
#include "utility.h"
#include "drives.h"
#include "paging.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)
static volatile struct limine_framebuffer_request fb_request = {
.id = LIMINE_FRAMEBUFFER_REQUEST,
.revision = 0,
.response = 0
};
static volatile struct limine_hhdm_request hhdm_request = {
.id = LIMINE_HHDM_REQUEST,
.revision = 0,
.response = 0
};
static volatile struct limine_kernel_address_request ka_request = {
.id = LIMINE_KERNEL_ADDRESS_REQUEST,
.revision = 0,
.response = 0
};
static volatile struct limine_memmap_request memmap_request = {
.id = LIMINE_MEMMAP_REQUEST,
.revision = 0,
.response = 0
};
static volatile struct limine_executable_cmdline_request cmdline_request = {
.id = LIMINE_EXECUTABLE_CMDLINE_REQUEST,
.revision = 0,
.response = 0
};
[[noreturn]] static void die() {
while (1)
__asm__ ("hlt");
}
//these are defined in the linker script. they are page-aligned.
extern uint8_t __kernel_rx_start;
extern uint8_t __kernel_rx_end;
extern uint8_t __kernel_ro_start;
extern uint8_t __kernel_ro_end;
extern uint8_t __kernel_rw_start;
extern uint8_t __kernel_rw_end;
static void map_kernel_region(
uint64_t physical_start, void *virtual_start,
uint64_t length, int writable, int executable) {
for (uint64_t i = 0; i < length; i += 4096)
map_in_kernel_page_table(
physical_start + i, (uint8_t *)virtual_start + i, writable, executable);
}
#define MAX_CMDLINE_LENGTH 1000
static char cmdline_copy[MAX_CMDLINE_LENGTH + 1];
[[noreturn]] static void with_kernel_page_tables();
[[noreturn]] void kernel_entry() {
//die if the bootloader hasn't given us something that we need.
if (fb_request.response == 0 || hhdm_request.response == 0 ||
ka_request.response == 0 || memmap_request.response == 0 ||
fb_request.response->framebuffer_count == 0)
die();
struct limine_framebuffer *fb = fb_request.response->framebuffers[0];
if (fb->memory_model != LIMINE_FRAMEBUFFER_RGB || fb->bpp != 32 ||
fb->red_mask_shift != 16 || fb->red_mask_size != 8 ||
fb->green_mask_shift != 8 || fb->green_mask_size != 8 ||
fb->blue_mask_shift != 0 || fb->blue_mask_size != 8)
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
//page tables, and we remap the framebuffer to somewhere in kernel memory.
//we do not map the bootloader structures, so we also need to not switch
//to the new tables until we are done using them.
uint64_t kernel_physical_base = ka_request.response->physical_base;
uint8_t *kernel_virtual_base = (uint8_t *)ka_request.response->virtual_base;
init_paging(kernel_physical_base, kernel_virtual_base);
struct limine_memmap_response *mm_response = memmap_request.response;
for (uint64_t i = 0; i < mm_response->entry_count; ++i) {
struct limine_memmap_entry *entry = mm_response->entries[i];
if (entry->type == LIMINE_MEMMAP_USABLE ||
entry->type == LIMINE_MEMMAP_BOOTLOADER_RECLAIMABLE)
//limine guarantees these two types are page-aligned already.
mark_physical_memory_free(entry->base, entry->length);
}
map_kernel_region(
&__kernel_rx_start - kernel_virtual_base + kernel_physical_base,
&__kernel_rx_start, &__kernel_rx_end - &__kernel_rx_start, 0, 1);
map_kernel_region(
&__kernel_ro_start - kernel_virtual_base + kernel_physical_base,
&__kernel_ro_start, &__kernel_ro_end - &__kernel_ro_start, 0, 0);
map_kernel_region(
&__kernel_rw_start - kernel_virtual_base + kernel_physical_base,
&__kernel_rw_start, &__kernel_rw_end - &__kernel_rw_start, 1, 0);
//we round up to a multiple of a page.
uint64_t fb_length = ((fb->height * fb->pitch - 1) / 4096 + 1) * 4096;
fb_physical_base = (uint64_t)fb->address - hhdm_request.response->offset;
fb_base = find_free_kernel_region(fb_length);
map_kernel_region(
fb_physical_base, fb_base, fb_length, 1, 0);
//store rest of framebuffer information
fb_width = fb->width;
fb_height = fb->height;
fb_pitch = fb->pitch;
//make a copy of the kernel cmdline
const char *original_cmdline = cmdline_request.response->cmdline;
int cmdline_length = 0;
while (original_cmdline[cmdline_length] != 0)
++cmdline_length;
if (cmdline_length > MAX_CMDLINE_LENGTH)
die();
memcpy(cmdline_copy, original_cmdline, cmdline_length + 1);
//switch to kernel page tables!
switch_to_kernel_page_tables(&with_kernel_page_tables);
}
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() {
init_serial();
//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);
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);
//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();
if (start_elf("root://calcite/apps/init/init.elf") == 0)
panic("could not start init.elf")
resume_next_continuation();
}

24
src/kernel/framebuffer.c Normal file
View file

@ -0,0 +1,24 @@
/* Calcite, src/kernel/framebuffer.c
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 "framebuffer.h"
uint64_t fb_physical_base;
uint8_t *fb_base;
int fb_width;
int fb_height;
int fb_pitch;

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

@ -0,0 +1,26 @@
/* Calcite, src/kernel/framebuffer.h
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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>
extern uint64_t fb_physical_base;
extern uint8_t *fb_base;
extern int fb_width;
extern int fb_height;
extern int fb_pitch;

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

104
src/kernel/heap.c Normal file
View file

@ -0,0 +1,104 @@
/* Calcite, src/kernel/heap.c
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "paging.h"
#include "heap.h"
struct dealloc_record {
//0 for unused
uint64_t length;
void *start;
};
struct dealloc_record_page {
struct dealloc_record records[255];
struct dealloc_record_page *prev_page;
};
static struct dealloc_record_page *last_page;
static void *map_pages(uint64_t page_count) {
void *vma = find_free_kernel_region(page_count * 4096);
for (uint64_t i = 0; i < page_count; ++i) {
uint64_t pma = take_free_physical_page();
map_in_kernel_page_table(pma, vma + i * 4096, 1, 0);
}
return vma;
}
static struct dealloc_record *get_unused_record() {
for (struct dealloc_record_page *page = last_page;
page != 0; page = page->prev_page)
for (int i = 0; i < 255; ++i)
if (page->records[i].length == 0)
return &page->records[i];
struct dealloc_record_page *page = map_pages(1);
page->prev_page = last_page;
last_page = page;
page->records[0].start = (void *)page + 4096 - 8;
page->records[0].length = 8;
for (int i = 2; i < 255; ++i)
page->records[i].length = 0;
return &page->records[1];
}
void *heap_alloc(uint64_t length) {
for (struct dealloc_record_page *page = last_page;
page != 0; page = page->prev_page)
for (int i = 0; i < 255; ++i)
if (page->records[i].length == length) {
page->records[i].length = 0;
return page->records[i].start;
}
for (struct dealloc_record_page *page = last_page;
page != 0; page = page->prev_page)
for (int i = 0; i < 255; ++i)
if (page->records[i].length > length) {
page->records[i].length -= length;
return page->records[i].start + page->records[i].length;
}
uint64_t pages = (length - 1) / 4096 + 1;
void *vma = map_pages(pages);
if (pages * 4096 != length) {
struct dealloc_record *record = get_unused_record();
record->start = vma + length;
record->length = pages * 4096 - length;
}
return vma;
}
void heap_dealloc(void *start, uint64_t length) {
struct dealloc_record *record = get_unused_record();
record->start = start;
record->length = length;
}

23
src/kernel/heap.h Normal file
View file

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

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

171
src/kernel/interrupts.asm Normal file
View file

@ -0,0 +1,171 @@
; Calcite, src/kernel/interrupts.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
; 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
;both defined in interrupts.c
extern isr_exception_c
extern isr_irq_c
section .rodata
;referenced in interrupts.c
global isrs
isrs:
%assign n 0
%rep 48
dq isr_ %+ n
%assign n n + 1
%endrep
section .text
%assign n 0
%rep 32
isr_ %+ n:
%if n <= 0x07 || n = 0x09 || (n >= 0x0f && n <= 0x1c && n <> 0x11 && n <> 0x15) || n = 0x1f
push qword 0
%endif
push qword n
jmp isr_exception_common
%assign n n + 1
%endrep
%rep 16
isr_ %+ n:
push rdi
mov rdi, n - 32
jmp isr_irq_common
%assign n n + 1
%endrep
isr_exception_common:
push rax
push rbx
push rcx
push rdx
push rdi
push rsi
push rbp
push r8
push r9
push r10
push r11
push r12
push r13
push r14
push r15
mov rax, cr2
push rax
mov rax, cr3
push rax
mov rdi, rsp
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
push rcx
push rdx
push rsi
push r8
push r9
push r10
push r11
test dil, 0x08
jnz .high
call isr_irq_c
mov al, 0x20
out 0x20, al
jmp .common_end
.high:
call isr_irq_c
mov al, 0x20
out 0xa0, al
out 0x20, al
.common_end:
pop r11
pop r10
pop r9
pop r8
pop rsi
pop rdx
pop rcx
pop rax
pop rdi
iretq
;referenced in interrupts.c
;rdi is pointer to gdt descriptor
;rsi is pointer to idt descriptor
;dx is offset of tss into gdt
global init_interrupts_asm
init_interrupts_asm:
;load tables
lgdt [rdi]
ltr dx
lidt [rsi]
;initialize interrupt controllers
mov al, 0x11
out 0x20, al
mov al, 0x20
out 0x21, al
mov al, 0x04
out 0x21, al
mov al, 0x01
out 0x21, al
mov al, 0x00
out 0x21, al
mov al, 0x11
out 0xa0, al
mov al, 0x28
out 0xa1, al
mov al, 0x02
out 0xa1, al
mov al, 0x01
out 0xa1, al
mov al, 0x00
out 0xa1, al
ret

320
src/kernel/interrupts.c Normal file
View file

@ -0,0 +1,320 @@
/* Calcite, src/kernel/interrupts.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/>.
*/
//relevant sources:
// amd64 architecture programmer's manual, volume 2: system programming
// https://osdev.wiki/wiki/Global_Descriptor_Table
// https://osdev.wiki/wiki/Interrupt_Descriptor_Table
// https://osdev.wiki/wiki/Task_State_Segment
#include "interrupts.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;
uint64_t r12;
uint64_t r11;
uint64_t r10;
uint64_t r9;
uint64_t r8;
uint64_t rbp;
uint64_t rsi;
uint64_t rdi;
uint64_t rdx;
uint64_t rcx;
uint64_t rbx;
uint64_t rax;
uint64_t exception_number;
//0 if exception pushes no error code
uint64_t error_code;
uint64_t rip;
uint64_t cs;
uint64_t rflags;
uint64_t rsp;
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(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])() = {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
//referenced in interrupts.asm
void isr_irq_c(uint64_t irq_number) {
if (irq_handlers[irq_number])
(*irq_handlers[irq_number])();
}
void set_irq_handler(uint8_t irq_line, void (*handler)(void)) {
assert(irq_line < 16);
irq_handlers[irq_line] = handler;
}
enum {
GDT_AB_CODE_DATA_ACCESSED = 0x01,
GDT_AB_DATA_WRITABLE = 0x02,
GDT_AB_TYPE_TSS = 0x09,
GDT_AB_TYPE_DATA = 0x10,
GDT_AB_TYPE_CODE = 0x18,
GDT_AB_DPL_ZERO = 0x00,
GDT_AB_DPL_THREE = 0x60,
GDT_AB_PRESENT = 0x80,
GDT_FLAG_LONG = 0x20
};
struct [[gnu::packed]] gdt_entry {
uint32_t zero;
uint8_t zero_;
uint8_t access_byte;
uint8_t flags;
uint8_t zero__;
};
struct [[gnu::packed]] gdt_entry_system {
uint16_t limit_0;
uint16_t base_0;
uint8_t base_16;
uint8_t access_byte;
uint8_t flags;
uint8_t base_24;
uint32_t base_32;
uint32_t zero;
};
static struct gdt_entry the_gdt[7];
struct [[gnu::packed]] gdt_descriptor {
uint16_t limit;
void *base;
};
static struct gdt_descriptor the_gdt_descriptor = {
.limit = 7 * sizeof(struct gdt_entry) - 1,
.base = the_gdt
};
struct [[gnu::packed]] tss {
uint32_t zero;
uint64_t rsp0;
uint64_t rsp1;
uint64_t rsp2;
uint64_t zero_;
uint64_t ist1;
uint64_t ist2;
uint64_t ist3;
uint64_t ist4;
uint64_t ist5;
uint64_t ist6;
uint64_t ist7;
uint64_t zero__;
uint16_t zero___;
uint16_t io_permission_map_offset;
};
static struct tss the_tss;
//maybe these should be allocated dynamically with guard pages?
#define INTERRUPT_STACK_SIZE (16 << 20)
static uint8_t interrupt_stack_one[INTERRUPT_STACK_SIZE];
static uint8_t interrupt_stack_two[INTERRUPT_STACK_SIZE];
enum {
//"interrupt" here just means disable interrupts on entry.
//in our case, we use this for both irq's and exceptions.
IDT_TYPE_INTERRUPT = 0x0e,
IDT_TYPE_DPL_ZERO = 0x00,
IDT_TYPE_DPL_THREE = 0x60,
IDT_TYPE_PRESENT = 0x80
};
struct [[gnu::packed]] idt_entry {
uint16_t offset_0;
uint16_t segment_selector;
uint8_t interrupt_stack_index;
uint8_t type;
uint16_t offset_16;
uint32_t offset_32;
uint32_t zero;
};
static struct idt_entry the_idt[256];
struct [[gnu::packed]] idt_descriptor {
uint16_t limit;
void *base;
};
static struct idt_descriptor the_idt_descriptor = {
.limit = 256 * sizeof(struct idt_entry) - 1,
.base = the_idt
};
//defined in interrupts.asm
extern void *isrs[48];
//defined in interrupts.asm
void init_interrupts_asm(
struct gdt_descriptor *gdtr,
struct idt_descriptor *idtr,
uint16_t tssr);
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;
//having the table past the end of the tss will lead to a gpf when the
//cpu tries to access it, which is the same effect as a disabled port.
the_tss.io_permission_map_offset = 256;
uint64_t tss_base = (uint64_t)&the_tss;
uint16_t tss_limit = sizeof(struct tss) - 1;
struct gdt_entry_system *tss_entry = (struct gdt_entry_system *)&the_gdt[1];
tss_entry->limit_0 = tss_limit;
tss_entry->base_0 = tss_base & 0xffff;
tss_entry->base_16 = (tss_base >> 16) & 0xff;
tss_entry->access_byte =
GDT_AB_TYPE_TSS | GDT_AB_DPL_ZERO | GDT_AB_PRESENT;
tss_entry->flags = GDT_FLAG_LONG;
tss_entry->base_24 = (tss_base >> 24) & 0xff;
tss_entry->base_32 = tss_base >> 32;
struct gdt_entry *user_data_entry = &the_gdt[3];
user_data_entry->access_byte =
GDT_AB_CODE_DATA_ACCESSED | GDT_AB_DATA_WRITABLE |
GDT_AB_TYPE_DATA | GDT_AB_DPL_THREE | GDT_AB_PRESENT;
user_data_entry->flags = GDT_FLAG_LONG;
struct gdt_entry *user_code_entry = &the_gdt[4];
user_code_entry->access_byte =
GDT_AB_CODE_DATA_ACCESSED | GDT_AB_TYPE_CODE |
GDT_AB_DPL_THREE | GDT_AB_PRESENT;
user_code_entry->flags = GDT_FLAG_LONG;
struct gdt_entry *kernel_code_entry = &the_gdt[5];
kernel_code_entry->access_byte =
GDT_AB_CODE_DATA_ACCESSED | GDT_AB_TYPE_CODE |
GDT_AB_DPL_ZERO | GDT_AB_PRESENT;
kernel_code_entry->flags = GDT_FLAG_LONG;
struct gdt_entry *kernel_data_entry = &the_gdt[6];
kernel_data_entry->access_byte =
GDT_AB_CODE_DATA_ACCESSED | GDT_AB_DATA_WRITABLE |
GDT_AB_TYPE_DATA | GDT_AB_DPL_ZERO | GDT_AB_PRESENT;
kernel_data_entry->flags = GDT_FLAG_LONG;
for (int i = 0; i < 48; ++i) {
uint64_t offset = (uint64_t)isrs[i];
the_idt[i].offset_0 = offset & 0xffff;
the_idt[i].segment_selector = 0x0028;
the_idt[i].interrupt_stack_index = i < 32 ? 0x02 : 0x01;
the_idt[i].type =
IDT_TYPE_INTERRUPT | IDT_TYPE_DPL_ZERO | IDT_TYPE_PRESENT;
the_idt[i].offset_16 = (offset >> 16) & 0xffff;
the_idt[i].offset_32 = offset >> 32;
}
init_interrupts_asm(&the_gdt_descriptor, &the_idt_descriptor, 0x08);
}

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

@ -0,0 +1,26 @@
/* Calcite, src/kernel/interrupts.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>
//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

@ -1,7 +1,7 @@
OUTPUT_FORMAT(elf64-x86-64)
OUTPUT_ARCH(i386:x86-64)
ENTRY(__limine_entry)
ENTRY(kernel_entry)
PHDRS {
rx PT_LOAD FLAGS(5);
@ -11,36 +11,36 @@ PHDRS {
SECTIONS {
. = 0xffffffff80000000;
. = 0xffffffffc0000000;
__kernel_rx_start = .;
__image_rx_start = .;
.text : {
*(.text .text.*)
} : rx
. = ALIGN(4096);
__image_rx_end = .;
__image_ro_start = .;
. = ALIGN(4096);
__kernel_rx_end = .;
__kernel_ro_start = .;
.rodata : {
*(.rodata .rodata.*)
} : ro
. = ALIGN(4096);
__image_ro_end = .;
__image_rw_start = .;
. = ALIGN(4096);
__kernel_ro_end = .;
__kernel_rw_start = .;
.data : {
*(.data .data.*)
} : rw
. = ALIGN(4096);
.bss : {
*(.bss .bss.*)
*(COMMON)
} : rw
. = ALIGN(4096);
__image_rw_end = .;
/DISCARD/ : {
*(.eh_frame)
}
. = ALIGN(4096);
__kernel_rw_end = .;
}

49
src/kernel/paging.asm Normal file
View file

@ -0,0 +1,49 @@
; Calcite, src/kernel/paging.asm
; Copyright 2025 Benji Dial
;
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; 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
;both defined in paging.c
extern kernel_p4_physical_address
extern init_stack
init_stack_length equ 16384
section .text
;referenced in paging.c
extern invlpg
invlpg:
invlpg [rdi]
ret
extern switch_to_kernel_page_tables
switch_to_kernel_page_tables:
;switch the page table
mov rax, qword [kernel_p4_physical_address]
mov cr3, rax
;set the stack
mov rsp, init_stack + init_stack_length
;push 0 before jumping to continuation so that if the continuation
;does return (which it shouldn't), it returns to null instead of
;popping garbage and returning there
push qword 0
jmp rdi

232
src/kernel/paging.c Normal file
View file

@ -0,0 +1,232 @@
/* Calcite, src/kernel/paging.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 "paging.h"
#include "debug.h"
#define MAX_PHYSICAL_GB 64ULL
//this is a simple bitmap. physical_map[i / 8] & (1 << (i % 8)) is nonzero if
//and only if the physical page with base i * 4096 is free.
static uint8_t physical_map[MAX_PHYSICAL_GB << 15];
//kernel virtual memory is the top gigabyte, i.e. one "page directory" worth
//of pages. in the future i might make it more reactive, and allow new page
//directories to be allocated as needed, but for now there is just the one
//page directory, and all of the page tables in that directory are statically
//allocated. i do not like the intel/amd names for the paging structures, so
//i will use these names throughout the rest of this file:
// p4 = "page map level 4"
// p3 = "page directory pointer table"
// p2 = "page directory"
// p1 = "page table"
//referenced from paging.asm
uint64_t kernel_p4_physical_address;
uint64_t kernel_p3_physical_address;
alignas(4096) static uint64_t kernel_p4[512];
alignas(4096) static uint64_t kernel_p3[512];
alignas(4096) static uint64_t kernel_p2[512];
alignas(4096) static uint64_t kernel_p1s[512 * 512];
//referenced from paging.asm. at the end of init, this will either be marked
//free or reused as an interrupt / syscall stack. if the length of this is
//changed, it also needs to be changed in paging.asm. there is no guard page,
//but i don't expect init to need much stack.
alignas(4096) uint64_t init_stack[16384];
void init_paging(uint64_t kernel_physical_base, void *kernel_virtual_base) {
uint64_t kernel_load_offset =
(uint64_t)kernel_virtual_base - kernel_physical_base;
kernel_p4_physical_address = (uint64_t)kernel_p4 - kernel_load_offset;
kernel_p3_physical_address = (uint64_t)kernel_p3 - kernel_load_offset;
kernel_p4[511] = ((uint64_t)kernel_p3 - kernel_load_offset) | 0x3;
for (int i = 0; i < 511; ++i)
kernel_p4[i] = 0;
kernel_p3[511] = ((uint64_t)kernel_p2 - kernel_load_offset) | 0x3;
for (int i = 0; i < 511; ++i)
kernel_p3[i] = 0;
for (int i = 0; i < 512; ++i)
kernel_p2[i] =
((uint64_t)kernel_p1s - kernel_load_offset + i * 4096) | 0x03;
for (int i = 0; i < 512 * 512; ++i)
kernel_p1s[i] = 0;
}
void mark_physical_memory_free(uint64_t base, uint64_t length) {
if (base + length > MAX_PHYSICAL_GB << 30)
length = (MAX_PHYSICAL_GB << 30) - base;
while (length && (base & (0x7 << 12))) {
physical_map[base >> 15] |= 1 << ((base >> 12) % 8);
base += 4096;
length -= 4096;
}
while (length >= 4096 * 8) {
physical_map[base >> 15] = 0xff;
base += 4096 * 8;
length -= 4096 * 8;
}
while (length) {
physical_map[base >> 15] |= 1 << ((base >> 12) % 8);
base += 4096;
length -= 4096;
}
}
//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) {
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] =
physical_base | (writable ? 0x3 : 0x1) |
(executable ? 0 : 0x8000000000000000);
invlpg(virtual_base);
}
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;
assert(virtual_base_u64 >= 0xffffffffc0000000);
uint64_t p1s_index = (virtual_base_u64 - 0xffffffffc0000000) >> 12;
assert(kernel_p1s[p1s_index] != 0);
kernel_p1s[p1s_index] = 0;
invlpg(virtual_base);
}
void unmap_and_free_kernel_page(void *virtual_base) {
uint64_t virtual_base_u64 = (uint64_t)virtual_base;
assert(virtual_base_u64 >= 0xffffffffc0000000);
uint64_t p1s_index = (virtual_base_u64 - 0xffffffffc0000000) >> 12;
assert(kernel_p1s[p1s_index] != 0);
uint64_t pma = kernel_p1s[p1s_index] & 0x7ffffffffffff000;
mark_physical_memory_free(pma, 4096);
kernel_p1s[p1s_index] = 0;
invlpg(virtual_base);
}
void *find_free_kernel_region(uint64_t length) {
length /= 4096;
uint64_t next = 0;
uint64_t run_start = 0;
uint64_t run_length = 0;
while (1) {
if (run_length == length)
return (void *)(run_start * 4096 + 0xffffffffc0000000);
if (next == 512 * 512)
panic("out of kernel virtual memory");
if (kernel_p1s[next] == 0)
++run_length;
else {
run_length = 0;
run_start = next + 1;
}
++next;
}
}
uint64_t take_free_physical_page() {
for (uint64_t i = 0; i < (MAX_PHYSICAL_GB << 15); ++i)
for (int j = 0; j < 8; ++j)
if (physical_map[i] & (1 << j)) {
physical_map[i] &= ~(1 << j);
return (i << 15) + (j << 12);
}
panic("out of physical memory");
}
#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;
}

63
src/kernel/paging.h Normal file
View file

@ -0,0 +1,63 @@
/* Calcite, src/kernel/paging.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>
extern uint64_t kernel_p3_physical_address;
//kernel physical and virtual bases are passed so that we can compute the
//physical addresses of the kernel's statically allocated paging structures.
void init_paging(uint64_t kernel_physical_base, void *kernel_virtual_base);
//base and length should be page-aligned
void mark_physical_memory_free(uint64_t base, uint64_t length);
//maps one page. physical and virtual bases should be page-aligned.
//virtual address should be within kernel range.
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);
//unmaps one page, and frees the corresponding physical page.
//base should be page-aligned and within kernel range.
void unmap_and_free_kernel_page(void *virtual_base);
//returns a region of contiguous pages in kernel virtual memory where nothing
//is mapped. length should be page-aligned.
void *find_free_kernel_region(uint64_t length);
uint64_t take_free_physical_page();
//implemented in paging.asm. the continuation should be noreturn.
[[noreturn]] void switch_to_kernel_page_tables(void (*continuation)());
//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);
}
}

23
src/kernel/pata.h Normal file
View file

@ -0,0 +1,23 @@
/* Calcite, src/kernel/pata.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>
//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

1098
src/kernel/process.c Normal file

File diff suppressed because it is too large Load diff

205
src/kernel/process.h Normal file
View file

@ -0,0 +1,205 @@
/* Calcite, src/kernel/process.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 "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;
uint64_t *p4_virtual_base;
uint64_t p3_physical_base;
uint64_t *p3_virtual_base;
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;
//both page-aligned
void *stack_bottom;
void *stack_top;
//both page-aligned
void *syscall_stack_bottom;
void *syscall_stack_top;
};
extern struct thread *running_thread;
void create_process(struct process *process_out);
void create_thread(struct process *process, struct thread *thread_out);
//physical and virtual bases must be page-aligned.
//virtual base must be in bottom p3 and not zero.
void map_page_for_process(
struct process *process, uint64_t physical_base,
void *virtual_base, int writable, int executable, 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);
//finds a free memory region in the bottom p3
//of the address space, and not the bottom p2.
void *find_free_process_region(
struct process *process, uint64_t page_count);
//returns 0 on failure, 1 on success.
int load_elf(
struct process *process, uint64_t *entry_out,
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, 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();
void syscall_map_framebuffer(struct 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);

71
src/kernel/ps2.asm Normal file
View file

@ -0,0 +1,71 @@
; Calcite, src/kernel/ps2.asm
; Copyright 2025 Benji Dial
;
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; 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_ps2
init_ps2:
mov al, 0x60
out 0x64, al
.wait_ready:
in al, 0x64
test al, 0x02
jnz .wait_ready
mov al, 0x07
out 0x60, al
mov al, 0xd4
out 0x64, al
.wait_ready_2:
in al, 0x64
test al, 0x02
jnz .wait_ready_2
mov al, 0xf4
out 0x60, al
.wait_ready_3:
in al, 0x64
test al, 0x01
jz .wait_ready_3
in al, 0x60
ret
;referenced in ps2.c
;return type is int
;returns -1 if no byte available
global read_ps2_byte
read_ps2_byte:
in al, 0x64
test al, 0x01
jz .no_data
xor eax, eax
in al, 0x60
ret
.no_data:
mov eax, -1
ret

118
src/kernel/ps2.c Normal file
View file

@ -0,0 +1,118 @@
/* Calcite, src/kernel/ps2.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 "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 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;
void on_mouse_irq() {
int byte = read_ps2_byte();
if (byte == -1)
return;
mouse_packet[mouse_packet_length] = byte;
if (mouse_packet_length < 2) {
++mouse_packet_length;
return;
}
mouse_packet_length = 0;
int x_change = mouse_packet[1];
if (mouse_packet[0] & 0x10)
x_change -= 256;
int y_change = mouse_packet[2];
if (mouse_packet[0] & 0x20)
y_change -= 256;
add_mouse_movement(x_change, y_change);
}

23
src/kernel/ps2.h Normal file
View file

@ -0,0 +1,23 @@
/* Calcite, src/kernel/ps2.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
void init_ps2();
void on_keyboard_irq();
void on_mouse_irq();

82
src/kernel/scheduler.asm Normal file
View file

@ -0,0 +1,82 @@
; 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
; 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
extern resume_next_continuation
extern running_thread
extern add_to_queue
;referenced in scheduler.c
global resume_continuation
resume_continuation:
mov rax, qword [rdi]
mov rbx, qword [rdi + 8]
mov rbp, qword [rdi + 16]
mov rsp, qword [rdi + 24]
mov r12, qword [rdi + 32]
mov r13, qword [rdi + 40]
mov r14, qword [rdi + 48]
mov r15, qword [rdi + 56]
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

100
src/kernel/scheduler.c Normal file
View file

@ -0,0 +1,100 @@
/* Calcite, src/kernel/scheduler.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 "utility.h"
#include "debug.h"
#include "heap.h"
struct continuation_queue ready_continuations;
void init_scheduler() {
create_queue(&ready_continuations, 128);
}
//defined in scheduler.asm
[[noreturn]] void resume_continuation(const struct continuation_info *info);
[[noreturn]] void resume_next_continuation() {
running_thread = 0;
struct continuation_info ci;
while (!take_from_queue(&ready_continuations, &ci)) {
__asm__ ("sti");
__asm__ ("hlt");
__asm__ ("cli");
}
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;
}

64
src/kernel/scheduler.h Normal file
View file

@ -0,0 +1,64 @@
/* Calcite, src/kernel/scheduler.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>
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();
[[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
mov ecx, 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);

87
src/kernel/syscalls.asm Normal file
View file

@ -0,0 +1,87 @@
; Calcite, src/kernel/syscalls.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
; 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
;defined in syscalls.c
extern syscall_entry_c
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 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 r11
pop rcx
pop rsp
o64 sysret
.assert_fail:
ud2
global init_syscalls
init_syscalls:
mov ecx, 0xc0000080
rdmsr
or al, 0x01
wrmsr
mov edx, 0x00130028
xor eax, eax
mov ecx, 0xc0000081
wrmsr
mov rdx, syscall_entry
mov eax, edx
shr rdx, 32
mov ecx, 0xc0000082
wrmsr
xor edx, edx
xor eax, eax
mov ecx, 0xc0000084
wrmsr
ret

46
src/kernel/syscalls.c Normal file
View file

@ -0,0 +1,46 @@
/* Calcite, src/kernel/syscalls.c
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "syscalls.h"
#include "debug.h"
#define MAX_SYSCALL_NUMBER 99
static uint64_t (*syscall_handlers[MAX_SYSCALL_NUMBER])(
uint64_t, uint64_t, uint64_t);
void register_syscall(
int number,
uint64_t (*handler)(uint64_t, uint64_t, uint64_t)) {
assert(number >= 0 && number <= MAX_SYSCALL_NUMBER)
assert(syscall_handlers[number] == 0)
syscall_handlers[number] = handler;
}
//referenced in syscalls.asm
uint64_t syscall_entry_c(
uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t number) {
if (number > MAX_SYSCALL_NUMBER || syscall_handlers[number] == 0)
panic("TODO: bad syscall");
return (*syscall_handlers[number])(arg1, arg2, arg3);
}

27
src/kernel/syscalls.h Normal file
View file

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

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

62
src/kernel/utility.asm Normal file
View file

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

117
src/kernel/utility.c Normal file
View file

@ -0,0 +1,117 @@
/* Calcite, src/kernel/utility.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 "heap.h"
int strequ(const char *str1, const char *str2) {
while (1) {
if (!*str1 && !*str2)
return 1;
if (*str1 != *str2)
return 0;
++str1;
++str2;
}
}
uint32_t end_swap_u32(uint32_t value) {
return
(value >> 24) |
(((value >> 16) & 0xff) << 8) |
(((value >> 8) & 0xff) << 16) |
((value & 0xff) << 24);
}
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);
}
}

57
src/kernel/utility.h Normal file
View file

@ -0,0 +1,57 @@
/* Calcite, src/kernel/utility.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>
//returns 1 if equal, 0 if not.
int strequ(const char *str1, const char *str2);
void memcpy(void *to, const void *from, uint64_t bytes);
void memzero(void *start, uint64_t bytes);
//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);

View file

@ -1,22 +0,0 @@
/* src/service/hello/hello.cxx
* A temporary dummy service.
*
* Copyright 2026 Benji Dial
* This file is part of the Calcite Operating System.
*
* Calcite 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, version 3 only.
*
* Calcite 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
* Calcite. If not, see <https://www.gnu.org/licenses/>.
*/
extern "C" [[noreturn]] void __entry() {
while (true)
;
}

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

View file

@ -0,0 +1 @@
calcite

View file

@ -1,7 +1,7 @@
OUTPUT_FORMAT(elf64-x86-64)
OUTPUT_ARCH(i386:x86-64)
ENTRY(__entry)
ENTRY(_start)
PHDRS {
rx PT_LOAD FLAGS(5);
@ -11,30 +11,29 @@ PHDRS {
SECTIONS {
. = 0xffffff0000000000;
. = 0x400000;
.text : {
*(.text .text.*)
} : rx
. = ALIGN(4096);
.rodata : {
*(.rodata .rodata.*)
} : ro
. = ALIGN(4096);
.data : {
*(.data .data.*)
} : rw
. = ALIGN(4096);
.bss : {
*(.bss .bss.*)
*(COMMON)
} : rw
. = ALIGN(4096);
/DISCARD/ : {
*(.eh_frame)
}
}

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

@ -0,0 +1,25 @@
/* Calcite, src/user-libs/calcite/entry.c
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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>
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;
}

Some files were not shown because too many files have changed in this diff Show more