diff --git a/disk/calcite/apps/hello/pointer.pam b/disk/calcite/apps/hello/pointer.pam
new file mode 100644
index 0000000..18bd8ce
Binary files /dev/null and b/disk/calcite/apps/hello/pointer.pam differ
diff --git a/include/calcite/file-streams.h b/include/calcite/file-streams.h
new file mode 100644
index 0000000..597c070
--- /dev/null
+++ b/include/calcite/file-streams.h
@@ -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 .
+ */
+
+#pragma once
+
+#include
+
+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);
diff --git a/include/calcite/memory.h b/include/calcite/memory.h
new file mode 100644
index 0000000..70993bd
--- /dev/null
+++ b/include/calcite/memory.h
@@ -0,0 +1,28 @@
+/* Calcite, include/calcite/memory.h
+ * Copyright 2025 Benji Dial
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#pragma once
+
+#include
+
+void *heap_alloc(uint64_t 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);
diff --git a/include/calcite/syscalls.h b/include/calcite/syscalls.h
new file mode 100644
index 0000000..2ed46f0
--- /dev/null
+++ b/include/calcite/syscalls.h
@@ -0,0 +1,38 @@
+/* Calcite, include/calcite/syscalls.h
+ * Copyright 2025 Benji Dial
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#pragma once
+
+#include
+#include
+#include
+
+[[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);
+
+void *map_pages(uint64_t count);
diff --git a/include/kernel-public/files.h b/include/kernel-public/files.h
new file mode 100644
index 0000000..32205af
--- /dev/null
+++ b/include/kernel-public/files.h
@@ -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 .
+ */
+
+#pragma once
+
+#include
+
+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;
+};
diff --git a/include/kernel-public/input.h b/include/kernel-public/input.h
new file mode 100644
index 0000000..e5cc1e5
--- /dev/null
+++ b/include/kernel-public/input.h
@@ -0,0 +1,23 @@
+/* Calcite, include/kernel-public/input.h
+ * Copyright 2025 Benji Dial
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#pragma once
+
+struct mouse_packet {
+ int x_change;
+ int y_change;
+};
diff --git a/include/kernel-public/syscall-numbers.h b/include/kernel-public/syscall-numbers.h
index 05f7a6e..22d25bd 100644
--- a/include/kernel-public/syscall-numbers.h
+++ b/include/kernel-public/syscall-numbers.h
@@ -19,5 +19,11 @@
enum {
SYSCALL_END_THREAD,
- SYSCALL_MAP_FRAMEBUFFER
+ SYSCALL_MAP_FRAMEBUFFER,
+ SYSCALL_OPEN_FILE,
+ SYSCALL_CLOSE_FILE,
+ SYSCALL_GET_FILE_SIZE,
+ SYSCALL_READ_FILE,
+ SYSCALL_WAIT_FOR_MOUSE_PACKET,
+ SYSCALL_MAP_PAGES
};
diff --git a/include/silver/image.h b/include/silver/image.h
new file mode 100644
index 0000000..73061d9
--- /dev/null
+++ b/include/silver/image.h
@@ -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 .
+ */
+
+#pragma once
+
+#include
+
+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);
diff --git a/include/calcite/calcite.h b/include/silver/pam.h
similarity index 80%
rename from include/calcite/calcite.h
rename to include/silver/pam.h
index 60f4597..8566c2d 100644
--- a/include/calcite/calcite.h
+++ b/include/silver/pam.h
@@ -1,4 +1,4 @@
-/* Calcite, include/calcite/calcite.h
+/* Calcite, include/silver/pam.h
* Copyright 2025 Benji Dial
*
* This program is free software: you can redistribute it and/or modify
@@ -17,8 +17,7 @@
#pragma once
-#include
+#include
-[[noreturn]] void end_thread();
-
-void map_framebuffer(struct framebuffer_info *info_out);
+//returns 0 on failure, 1 on success.
+int load_pam(const char *path, struct image **image_out);
diff --git a/make-build.sh b/make-build.sh
index 8c44eb2..9f9a710 100644
--- a/make-build.sh
+++ b/make-build.sh
@@ -75,13 +75,13 @@ app_deps=""
for dir in src/user-apps/*; do
if [ -e $dir/libraries.txt ]; then
- lib_paths=$(cat $dir/libraries.txt | sed -e 's/^.*$/build\/user-libs\/\0\/lib\0.o/')
+ lib_paths=$(cat $dir/libraries.txt | sed -e 's/^.*$/build\/user-libs\/\0\/lib\0.o/' | tr '\n' ' ')
else
lib_paths=""
fi
app_name=$(echo $dir | sed -e 's/^src\/user-apps\///')
- build_all $dir user_cc user_app_ld $app_name.elf $lib_paths
+ build_all $dir user_cc user_app_ld $app_name.elf "$lib_paths"
build_elf=$(echo $dir | sed -e 's/^src/build/')/$app_name.elf
disk_dir=build/disk/calcite/apps/$app_name
@@ -103,7 +103,7 @@ echo " command =" \
"cp dependencies/limine/limine-bios.sys build/disk/limine/ &&"\
"mkdir -p build/disk/EFI/BOOT &&" \
"cp dependencies/limine/BOOTX64.EFI build/disk/EFI/BOOT/ &&" \
- "mkdir build/disk/calcite &&" \
+ "mkdir -p build/disk/calcite &&" \
"cp build/kernel/kernel.elf build/disk/calcite/ &&" \
$cp_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 build/disk -o build/disk.iso &&" \
diff --git a/qemu.gdb b/qemu.gdb
index 8ec043a..6de1ab4 100644
--- a/qemu.gdb
+++ b/qemu.gdb
@@ -1,5 +1,6 @@
target remote | qemu-system-x86_64 -gdb stdio -cdrom build/disk.iso -boot d
symbol-file build/kernel/kernel.elf
+add-symbol-file build/user-apps/hello/hello.elf
set disassembly-flavor intel
set print asm-demangle on
layout src
diff --git a/src/kernel/drives.h b/src/kernel/drives.h
index ee3992a..6baef36 100644
--- a/src/kernel/drives.h
+++ b/src/kernel/drives.h
@@ -31,7 +31,7 @@ struct drive_info {
uint64_t block_count;
const void *driver_info;
- //start and count are both in blocks
+ //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);
diff --git a/src/kernel/entry.c b/src/kernel/entry.c
index 1c551e7..87af06f 100644
--- a/src/kernel/entry.c
+++ b/src/kernel/entry.c
@@ -15,7 +15,6 @@
* with this program. If not, see .
*/
-#include
#include "framebuffer.h"
#include "interrupts.h"
#include "scheduler.h"
@@ -24,12 +23,14 @@
#include "utility.h"
#include "drives.h"
#include "paging.h"
+#include "input.h"
#include "panic.h"
#include "heap.h"
#include "pata.h"
#include "ps2.h"
#include "fs.h"
+#include
#include
LIMINE_BASE_REVISION(3)
@@ -252,6 +253,12 @@ static const char *cmdline_look_up(const char *key) {
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);
//probe for drives
@@ -269,26 +276,19 @@ static const char *cmdline_look_up(const char *key) {
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;
- if (create_fs_info(root_drive, &root_fs, root_fs_type) != FAR_SUCCESS)
+ 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 hello and start it
init_scheduler();
- void *hello_node;
- if ((*root_fs.look_up_file)(
- &root_fs, &hello_node,
- "calcite/apps/hello/hello.elf") != FAR_SUCCESS)
- panic("could not look up hello.elf")
-
- if (!start_elf(&root_fs, hello_node))
+ if (!start_elf("root://calcite/apps/hello/hello.elf"))
panic("could not start hello.elf")
- if ((*root_fs.free_node)(&root_fs, hello_node) != FAR_SUCCESS)
- panic("could not free hello.elf node")
-
resume_next_continuation();
}
diff --git a/src/kernel/fs.c b/src/kernel/fs.c
index 583908c..b13a0eb 100644
--- a/src/kernel/fs.c
+++ b/src/kernel/fs.c
@@ -17,8 +17,12 @@
#include "iso9660.h"
#include "utility.h"
+#include "panic.h"
+#include "heap.h"
#include "fs.h"
+#include
+
enum fs_access_result create_fs_info(
const struct drive_info *drive, struct fs_info *fs_out, const char *fs_type) {
@@ -28,3 +32,74 @@ enum fs_access_result create_fs_info(
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]);
+
+}
diff --git a/src/kernel/fs.h b/src/kernel/fs.h
index 7dc1886..a60d679 100644
--- a/src/kernel/fs.h
+++ b/src/kernel/fs.h
@@ -19,16 +19,9 @@
#include "drives.h"
+#include
#include
-enum fs_access_result {
- FAR_SUCCESS,
- FAR_HARDWARE_ERROR,
- FAR_FORMAT_ERROR,
- FAR_UNKNOWN_FS_TYPE,
- FAR_NOT_FOUND
-};
-
struct fs_stat {
uint64_t bytes;
};
@@ -47,7 +40,7 @@ struct fs_info {
enum fs_access_result (*stat_file)(
const struct fs_info *info, void *node, struct fs_stat *stat_out);
- //start is in bytes
+ //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);
@@ -56,3 +49,10 @@ struct fs_info {
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);
diff --git a/src/kernel/input.c b/src/kernel/input.c
new file mode 100644
index 0000000..4c24293
--- /dev/null
+++ b/src/kernel/input.c
@@ -0,0 +1,68 @@
+/* Calcite, src/kernel/input.c
+ * Copyright 2025 Benji Dial
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#include "scheduler.h"
+#include "process.h"
+#include "input.h"
+#include "panic.h"
+
+static int is_somebody_waiting_for_mouse_packet = 0;
+static struct continuation_info waiting_continuation;
+static struct mouse_packet *resume_by_reporting_to;
+
+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->x_change = total_unreported_x_movement;
+ resume_by_reporting_to->y_change = total_unreported_y_movement;
+ add_to_queue(&ready_continuations, &waiting_continuation);
+ total_unreported_x_movement = 0;
+ total_unreported_y_movement = 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 = packet_out;
+ yield(&waiting_continuation);
+ is_somebody_waiting_for_mouse_packet = 0;
+
+}
diff --git a/src/kernel/input.h b/src/kernel/input.h
new file mode 100644
index 0000000..bb5970b
--- /dev/null
+++ b/src/kernel/input.h
@@ -0,0 +1,24 @@
+/* Calcite, src/kernel/input.h
+ * Copyright 2025 Benji Dial
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#pragma once
+
+#include
+
+void add_mouse_movement(int x, int y);
+
+void syscall_wait_for_mouse_packet(struct mouse_packet *packet_out);
diff --git a/src/kernel/paging.c b/src/kernel/paging.c
index ec664df..5783fbb 100644
--- a/src/kernel/paging.c
+++ b/src/kernel/paging.c
@@ -187,3 +187,19 @@ uint64_t take_free_physical_page() {
}
panic("out of physical memory");
}
+
+#define SYSCALL_STACK_BYTES (1 << 15)
+
+void *create_syscall_stack() {
+ void *vma = find_free_kernel_region(SYSCALL_STACK_BYTES);
+ for (uint64_t i = 0; i < SYSCALL_STACK_BYTES; i += 4096) {
+ uint64_t pma = take_free_physical_page();
+ map_in_kernel_page_table(pma, vma + i * 4096, 1, 0);
+ }
+ return vma + SYSCALL_STACK_BYTES;
+}
+
+void destroy_syscall_stack(void *stack_top) {
+ for (uint64_t i = 0; i < SYSCALL_STACK_BYTES; i += 4096)
+ unmap_and_free_kernel_page(stack_top - SYSCALL_STACK_BYTES + i * 4096);
+}
diff --git a/src/kernel/paging.h b/src/kernel/paging.h
index a572f6b..f7bf4c4 100644
--- a/src/kernel/paging.h
+++ b/src/kernel/paging.h
@@ -48,3 +48,7 @@ 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);
diff --git a/src/kernel/process.c b/src/kernel/process.c
index 4f17557..25eb148 100644
--- a/src/kernel/process.c
+++ b/src/kernel/process.c
@@ -17,6 +17,7 @@
#include "framebuffer.h"
#include "scheduler.h"
+#include "syscalls.h"
#include "process.h"
#include "utility.h"
#include "paging.h"
@@ -24,6 +25,14 @@
#include "heap.h"
#include "fs.h"
+#include
+
+struct process_file_info *get_file_info(struct process *process, file_handle_t handle) {
+ if (handle > (uint64_t)process->files_buffer_size || process->files[handle].fs == 0)
+ return 0;
+ return &process->files[handle];
+}
+
void create_process(struct process *process_out) {
process_out->p4_physical_base = take_free_physical_page();
@@ -51,7 +60,8 @@ void create_process(struct process *process_out) {
process_out->p1_virtual_bases[i] = 0;
}
-process_out->n_threads = 0;
+ process_out->n_threads = 0;
+ process_out->files = 0;
}
@@ -122,7 +132,7 @@ void unmap_page_for_process(
}
-int is_mapped_writable(struct process *process, void *start, uint64_t length) {
+static int is_mapped_with_flags(struct process *process, const void *start, uint64_t length, uint64_t flags) {
uint64_t vma_start = (uint64_t)start;
uint64_t vma_end = vma_start + length;
@@ -141,7 +151,7 @@ int is_mapped_writable(struct process *process, void *start, uint64_t length) {
if (!process->p1_virtual_bases[p3i] ||
!process->p1_virtual_bases[p3i][p2i] ||
!process->p1_virtual_bases[p3i][p2i][p1i] ||
- !(process->p1_virtual_bases[p3i][p2i][p1i] & 0x2))
+ (process->p1_virtual_bases[p3i][p2i][p1i] & flags) != flags)
return 0;
}
@@ -150,6 +160,30 @@ int is_mapped_writable(struct process *process, void *start, uint64_t length) {
}
+int is_mapped_writable(struct process *process, const void *start, uint64_t length) {
+ return is_mapped_with_flags(process, start, length, 0x7);
+}
+
+int is_mapped_readable(struct process *process, const void *start, uint64_t length) {
+ return is_mapped_with_flags(process, start, length, 0x5);
+}
+
+int is_mapped_readable_string(struct process *process, const char *start) {
+
+ if (!is_mapped_readable(process, start, 1))
+ return 0;
+
+ while (1) {
+ if ((uint64_t)start % 4096 == 0)
+ if (!is_mapped_readable(process, start, 1))
+ return 0;
+ if (*start == 0)
+ return 1;
+ ++start;
+ }
+
+}
+
void *find_free_process_region(
struct process *process, uint64_t page_count) {
@@ -293,18 +327,24 @@ int load_elf(
// all other registers zeroed
extern uint8_t thread_start;
-int start_elf(const struct fs_info *fs_info, void *fs_node) {
+int start_elf(const char *uri) {
struct process *process = heap_alloc(sizeof(struct process));
create_process(process);
+ const struct fs_info *fs_info;
+ void *fs_node;
uint64_t entry;
- if (!load_elf(process, &entry, fs_info, fs_node)) {
+ if (look_up_file(uri, &fs_info, &fs_node) != FAR_SUCCESS ||
+ !load_elf(process, &entry, fs_info, fs_node)) {
destroy_process(process);
return 0;
}
+ if ((*fs_info->free_node)(fs_info, fs_node) != FAR_SUCCESS)
+ panic("TODO")
+
struct thread *thread = heap_alloc(sizeof(struct thread));
create_thread(process, thread);
@@ -315,12 +355,21 @@ int start_elf(const struct fs_info *fs_info, void *fs_node) {
ci.rsp = (uint64_t)thread->stack_top;
ci.r12 = entry;
- add_ready_continuation(&ci);
+ add_to_queue(&ready_continuations, &ci);
+
return 1;
}
void destroy_process(struct process *process) {
+
+ if (process->files) {
+ for (int i = 0; i < process->files_buffer_size; ++i)
+ if (process->files[i].fs != 0)
+ (*process->files[i].fs->free_node)(process->files[i].fs, process->files[i].node);
+ heap_dealloc(process->files, process->files_buffer_size * sizeof(struct process_file_info));
+ }
+
for (int p3i = 0; p3i < 512; ++p3i)
if (process->p3_virtual_base[p3i]) {
for (int p2i = 0; p2i < 512; ++p2i)
@@ -341,6 +390,7 @@ void destroy_process(struct process *process) {
unmap_and_free_kernel_page(process->p3_virtual_base);
unmap_and_free_kernel_page(process->p4_virtual_base);
heap_dealloc(process, sizeof(struct process));
+
}
void destroy_thread(struct thread *thread) {
@@ -393,21 +443,32 @@ void create_thread(struct process *process, struct thread *thread_out) {
}
+[[noreturn]] static void syscall_illegal_args() {
+ panic("bad syscall")
+}
+
struct thread *running_thread = 0;
+[[noreturn]] static void syscall_end_thread_with_temporary_stack() {
+ destroy_syscall_stack(most_recent_syscall_stack);
+ resume_next_continuation();
+}
+
[[noreturn]] void syscall_end_thread() {
assert(running_thread != 0)
destroy_thread(running_thread);
running_thread = 0;
- resume_next_continuation();
+ with_temporary_stack(&syscall_end_thread_with_temporary_stack);
}
void syscall_map_framebuffer(struct framebuffer_info *info_out) {
+ assert(running_thread != 0)
+
if (!is_mapped_writable(
running_thread->process,
info_out, sizeof(struct framebuffer_info)))
- panic("bad syscall");
+ syscall_illegal_args();
uint64_t pages_needed = (fb_pitch * fb_height - 1) / 4096 + 1;
@@ -425,3 +486,128 @@ void syscall_map_framebuffer(struct framebuffer_info *info_out) {
info_out->fb_width = fb_width;
}
+
+#define INITIAL_FILE_HANDLE_COUNT 128
+
+enum fs_access_result syscall_open_file(const char *path, file_handle_t *handle_out) {
+
+ assert(running_thread != 0)
+
+ if (!is_mapped_readable_string(running_thread->process, path) ||
+ !is_mapped_writable(running_thread->process, handle_out, sizeof(file_handle_t)))
+ syscall_illegal_args();
+
+ const struct fs_info *fs;
+ void *node;
+ enum fs_access_result result = look_up_file(path, &fs, &node);
+ if (result != FAR_SUCCESS)
+ return result;
+
+ struct fs_stat stat;
+ result = (*fs->stat_file)(fs, node, &stat);
+ if (result != FAR_SUCCESS) {
+ (*fs->free_node)(fs, node);
+ return result;
+ }
+
+ if (running_thread->process->files == 0) {
+ running_thread->process->files =
+ heap_alloc(INITIAL_FILE_HANDLE_COUNT * sizeof(struct process_file_info));
+ running_thread->process->files_buffer_size = INITIAL_FILE_HANDLE_COUNT;
+ for (int i = 0; i < INITIAL_FILE_HANDLE_COUNT; ++i)
+ running_thread->process->files[i].fs = 0;
+ }
+
+ for (int i = 0; i < running_thread->process->files_buffer_size; ++i)
+ if (running_thread->process->files[i].fs == 0) {
+ running_thread->process->files[i].fs = fs;
+ running_thread->process->files[i].node = node;
+ memcpy(&running_thread->process->files[i].stat, &stat, sizeof(struct fs_stat));
+ *handle_out = i;
+ return FAR_SUCCESS;
+ }
+
+ struct process_file_info *old_buffer = running_thread->process->files;
+ int old_size = running_thread->process->files_buffer_size;
+
+ struct process_file_info *new_buffer = heap_alloc(2 * old_size * sizeof(struct process_file_info));
+ memcpy(new_buffer, old_buffer, old_size * sizeof(struct process_file_info));
+ heap_dealloc(old_buffer, old_size * sizeof(struct process_file_info));
+
+ new_buffer[old_size].fs = fs;
+ new_buffer[old_size].node = node;
+ memcpy(&new_buffer[old_size].stat, &stat, sizeof(struct fs_stat));
+ for (int i = old_size + 1; i < old_size * 2; ++i)
+ new_buffer[i].fs = 0;
+
+ running_thread->process->files = new_buffer;
+ running_thread->process->files_buffer_size *= 2;
+
+ *handle_out = old_size;
+ return FAR_SUCCESS;
+
+}
+
+void syscall_close_file(file_handle_t handle) {
+
+ assert(running_thread != 0)
+
+ struct process_file_info *file = get_file_info(running_thread->process, handle);
+ if (file != 0) {
+ (*file->fs->free_node)(file->fs, file->node);
+ file->fs = 0;
+ }
+
+}
+
+enum fs_access_result syscall_get_file_size(file_handle_t handle, uint64_t *bytes_out) {
+
+ assert(running_thread != 0)
+
+ struct process_file_info *file = get_file_info(running_thread->process, handle);
+ if (file == 0)
+ return FAR_BAD_HANDLE;
+
+ *bytes_out = file->stat.bytes;
+ return FAR_SUCCESS;
+
+}
+
+enum fs_access_result syscall_read_file(struct read_file_parameter *parameter) {
+
+ assert(running_thread != 0)
+
+ if (!is_mapped_readable(
+ running_thread->process, parameter, sizeof(struct read_file_parameter)) ||
+ !is_mapped_writable(
+ running_thread->process, parameter->buffer, parameter->bytes))
+ syscall_illegal_args();
+
+ struct process_file_info *file = get_file_info(running_thread->process, parameter->handle);
+
+ if (file == 0)
+ return FAR_BAD_HANDLE;
+
+ if (parameter->start + parameter->bytes > file->stat.bytes)
+ return FAR_OUT_OF_BOUNDS;
+
+ return (*file->fs->read_file)(file->fs, file->node, parameter->buffer, parameter->start, parameter->bytes);
+
+}
+
+void *syscall_map_pages(uint64_t count) {
+
+ assert(running_thread != 0)
+
+ void *vma = find_free_process_region(running_thread->process, count);
+ for (uint64_t i = 0; i < count; ++i) {
+ uint64_t pma = take_free_physical_page();
+ map_page_for_process(
+ running_thread->process,
+ pma, vma + i * 4096,
+ 1, 0);
+ }
+
+ return vma;
+
+}
diff --git a/src/kernel/process.h b/src/kernel/process.h
index 4f4fb02..e2a2a40 100644
--- a/src/kernel/process.h
+++ b/src/kernel/process.h
@@ -17,9 +17,17 @@
#pragma once
-#include
#include "fs.h"
+#include
+#include
+
+struct process_file_info {
+ const struct fs_info *fs;
+ void *node;
+ struct fs_stat stat;
+};
+
struct process {
uint64_t p4_physical_base;
@@ -29,12 +37,21 @@ struct process {
int n_threads;
+ //handles are indices into this.
+ //0 for fs means unused handle.
+ struct process_file_info *files;
+ int files_buffer_size;
+
//0 for missing levels. just bottom p3 of address space.
uint64_t *p2_virtual_bases[512];
uint64_t **p1_virtual_bases[512];
};
+//returns 0 if that handle is not used.
+//return value might be invalidated by future allocation of file handles for this process.
+struct process_file_info *get_file_info(struct process *process, file_handle_t handle);
+
struct thread {
struct process *process;
@@ -74,14 +91,27 @@ int load_elf(
//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.
-int start_elf(const struct fs_info *fs_info, void *fs_node);
+int start_elf(const char *uri);
void destroy_process(struct process *process);
void destroy_thread(struct thread *thread);
//returs 1 if [start, start + length) is writable by process, otherwise 0.
-int is_mapped_writable(struct process *process, void *start, uint64_t length);
+int is_mapped_writable(struct process *process, const void *start, uint64_t length);
+
+//returs 1 if [start, start + length) is readable by process, otherwise 0.
+int is_mapped_readable(struct process *process, const void *start, uint64_t length);
+
+//return 1 if the entire null-terminated string starting at start is readable by process, otherwise 0.
+int is_mapped_readable_string(struct process *process, const char *start);
[[noreturn]] void syscall_end_thread();
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);
diff --git a/src/kernel/ps2.c b/src/kernel/ps2.c
index 27854e8..74679d1 100644
--- a/src/kernel/ps2.c
+++ b/src/kernel/ps2.c
@@ -15,10 +15,11 @@
* with this program. If not, see .
*/
-#include "framebuffer.h"
-#include "panic.h"
+#include "input.h"
#include "ps2.h"
+#include
+
//defined in ps2.asm
//returns -1 if no byte available
int read_ps2_byte();
@@ -36,9 +37,6 @@ void on_keyboard_irq() {
static uint8_t mouse_packet[3];
static int mouse_packet_length = 0;
-static int total_x = 0;
-static int total_y = 0;
-
void on_mouse_irq() {
int byte = read_ps2_byte();
@@ -52,29 +50,14 @@ void on_mouse_irq() {
}
mouse_packet_length = 0;
- int x = mouse_packet[1];
+ int x_change = mouse_packet[1];
if (mouse_packet[0] & 0x10)
- x -= 256;
+ x_change -= 256;
- int y = mouse_packet[2];
+ int y_change = mouse_packet[2];
if (mouse_packet[0] & 0x20)
- y -= 256;
+ y_change -= 256;
- total_x += x;
- total_y -= y;
-
- if (total_x < 0)
- total_x = 0;
- if (total_x >= fb_width)
- total_x = fb_width - 1;
-
- if (total_y < 0)
- total_y = 0;
- if (total_y >= fb_height)
- total_y = fb_height - 1;
-
- fb_base[total_y * fb_pitch + total_x * 4] = 0xff;
- fb_base[total_y * fb_pitch + total_x * 4 + 1] = 0xff;
- fb_base[total_y * fb_pitch + total_x * 4 + 2] = 0xff;
+ add_mouse_movement(x_change, y_change);
}
diff --git a/src/kernel/scheduler.asm b/src/kernel/scheduler.asm
index 5fa150d..7068c3e 100644
--- a/src/kernel/scheduler.asm
+++ b/src/kernel/scheduler.asm
@@ -17,6 +17,8 @@
bits 64
+extern resume_next_continuation
+
;referenced in scheduler.c
global resume_continuation
resume_continuation:
@@ -31,3 +33,22 @@ resume_continuation:
mov r15, qword [rdi + 56]
jmp rax
+
+ret:
+ ret
+
+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
+
+ jmp resume_next_continuation
+
+.ret:
+ ret
diff --git a/src/kernel/scheduler.c b/src/kernel/scheduler.c
index dd2ae77..3285f4d 100644
--- a/src/kernel/scheduler.c
+++ b/src/kernel/scheduler.c
@@ -19,69 +19,69 @@
#include "utility.h"
#include "heap.h"
-static struct continuation_info *ready_continuations = 0;
-static int rc_buffer_length = 0;
-static int rc_read_ptr = 0;
-static int rc_count = 0;
-
-#define INITIAL_RC_BUFFER_LENGTH 128
+struct continuation_queue ready_continuations;
void init_scheduler() {
- ready_continuations = heap_alloc(INITIAL_RC_BUFFER_LENGTH * sizeof(struct continuation_info));
- rc_buffer_length = INITIAL_RC_BUFFER_LENGTH;
-}
-
-void add_ready_continuation(struct continuation_info *info) {
-
- if (rc_count == rc_buffer_length) {
-
- struct continuation_info *new_rc_buffer =
- heap_alloc(2 * rc_buffer_length * sizeof(struct continuation_info));
-
- memcpy(
- new_rc_buffer,
- ready_continuations + rc_read_ptr,
- (rc_buffer_length - rc_read_ptr) * sizeof(struct continuation_info));
-
- memcpy(
- new_rc_buffer + rc_buffer_length - rc_read_ptr,
- ready_continuations,
- rc_read_ptr * sizeof(struct continuation_info));
-
- heap_dealloc(ready_continuations, rc_buffer_length * sizeof(struct continuation_info));
-
- memcpy(&new_rc_buffer[rc_buffer_length], info, sizeof(struct continuation_info));
-
- ready_continuations = new_rc_buffer;
- rc_buffer_length *= 2;
- rc_read_ptr = 0;
- ++rc_count;
-
- }
-
- else {
- memcpy(
- &ready_continuations[(rc_read_ptr + rc_count) % rc_buffer_length],
- info, sizeof(struct continuation_info));
- ++rc_count;
- }
-
+ create_queue(&ready_continuations, 128);
}
//defined in scheduler.asm
-[[noreturn]] void resume_continuation(struct continuation_info *info);
+[[noreturn]] void resume_continuation(const struct continuation_info *info);
[[noreturn]] void resume_next_continuation() {
- while (rc_count == 0)
+ struct continuation_info ci;
+
+ while (!take_from_queue(&ready_continuations, &ci))
__asm__ ("hlt");
- struct continuation_info info;
- memcpy(&info, &ready_continuations[rc_read_ptr], sizeof(struct continuation_info));
-
- rc_read_ptr = (rc_read_ptr + 1) % rc_buffer_length;
- --rc_count;
-
- resume_continuation(&info);
+ 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 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;
}
diff --git a/src/kernel/scheduler.h b/src/kernel/scheduler.h
index ccdf439..cac8936 100644
--- a/src/kernel/scheduler.h
+++ b/src/kernel/scheduler.h
@@ -34,5 +34,23 @@ 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);
+
//ci is copied
-void add_ready_continuation(struct continuation_info *ci);
+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);
diff --git a/src/kernel/syscalls.asm b/src/kernel/syscalls.asm
index de55a1d..abbcf05 100644
--- a/src/kernel/syscalls.asm
+++ b/src/kernel/syscalls.asm
@@ -17,13 +17,22 @@
bits 64
+extern destroy_syscall_stack
+extern create_syscall_stack
extern syscall_entry_c
section .bss
-;this should have guard pages blah blah blah
- resb 16 << 20
-syscall_stack_top:
+ resb 1 << 15
+temp_syscall_stack:
+
+available_syscall_stack_count equ 128
+available_syscall_stacks:
+ resq available_syscall_stack_count
+
+global most_recent_syscall_stack
+most_recent_syscall_stack:
+ resq 1
section .text
@@ -31,8 +40,48 @@ section .text
;system call arguments are in rdi, rsi, rdx.
;system call returns a value in rax.
syscall_entry:
- mov qword [syscall_stack_top - 8], rsp
- mov rsp, syscall_stack_top - 8
+
+ xor r8, r8
+.find_stack_loop:
+
+ mov r9, qword [available_syscall_stacks + r8 * 8]
+ test r9, r9
+ jnz .got_syscall_stack
+
+ inc r8
+ cmp r8, available_syscall_stack_count
+ jne .find_stack_loop
+
+ mov qword [temp_syscall_stack - 8], rsp
+ mov rsp, temp_syscall_stack - 8
+ push r11
+ push rcx
+ push rax
+ push rdi
+ push rsi
+ push rdx
+
+ call create_syscall_stack
+ mov r9, rax
+
+ pop rdx
+ pop rsi
+ pop rdi
+ pop rax
+ pop rcx
+ pop r11
+ pop rsp
+
+ jmp .common
+
+.got_syscall_stack:
+ mov qword [available_syscall_stacks + r8 * 8], 0
+
+.common:
+ mov qword [r9 - 8], rsp
+ mov rsp, r9
+ mov qword [most_recent_syscall_stack], rsp
+ sub rsp, 8
push r11
push rcx
@@ -41,11 +90,59 @@ syscall_entry:
pop rcx
pop r11
+
+ xor r8, r8
+.find_place_to_put_stack:
+
+ mov r9, qword [available_syscall_stacks + r8 * 8]
+ test r9, r9
+ jz .got_place
+
+ inc r8
+ cmp r8, available_syscall_stack_count
+ jne .find_stack_loop
+
+ mov rdi, rsp
+ add rdi, 8
+
+ pop rsp
+
+ mov qword [temp_syscall_stack - 8], rsp
+ mov rsp, temp_syscall_stack - 8
+ push rax
+ push rcx
+ push r11
+
+ call destroy_syscall_stack
+
+ pop r11
+ pop rcx
+ pop rax
+ jmp .common_2
+
+.got_place:
+ mov r9, rsp
+ add r9, 8
+ mov qword [available_syscall_stacks + r8 * 8], r9
+
+.common_2:
pop rsp
o64 sysret
global init_syscalls
init_syscalls:
+ sub rsp, 8
+ mov qword [rsp], 0
+
+.create_stack_loop:
+ call create_syscall_stack
+ mov rdi, qword [rsp]
+ mov qword [available_syscall_stacks + rdi * 8], rax
+ inc qword [rsp]
+ cmp rdi, available_syscall_stack_count - 1
+ jne .create_stack_loop
+
+ add rsp, 8
mov ecx, 0xc0000080
rdmsr
@@ -69,3 +166,8 @@ init_syscalls:
wrmsr
ret
+
+global with_temporary_stack
+with_temporary_stack:
+ mov rsp, temp_syscall_stack
+ jmp rdi
diff --git a/src/kernel/syscalls.h b/src/kernel/syscalls.h
index 3171a7a..b05df9a 100644
--- a/src/kernel/syscalls.h
+++ b/src/kernel/syscalls.h
@@ -25,3 +25,11 @@ void init_syscalls();
void register_syscall(
int number,
uint64_t (*handler)(uint64_t arg1, uint64_t arg2, uint64_t arg3));
+
+//the top of the syscall stack of the most recently started syscall.
+//kind of a hack, used in the end_thread syscall to deallocate that stack.
+extern void *most_recent_syscall_stack;
+
+//also kind of a hack. switches to temporary stack and runs f with that stack.
+//f should not return. this does not touch most_recent_syscall_stack.
+[[noreturn]] void with_temporary_stack(void (*f)());
diff --git a/src/user-apps/hello/hello.c b/src/user-apps/hello/hello.c
index 7d32f09..744abfb 100644
--- a/src/user-apps/hello/hello.c
+++ b/src/user-apps/hello/hello.c
@@ -15,18 +15,88 @@
* with this program. If not, see .
*/
-#include
+#include
+#include
+
+static struct framebuffer_info fb_info;
+static struct image *fbb;
+
+static struct image *cursor_image;
+
+static void draw_bg(int startx, int starty, int width, int height) {
+ if (startx + width > fbb->width)
+ width = fbb->width - startx;
+ if (starty + height > fbb->height)
+ height = fbb->height - starty;
+ for (int y = starty; y < starty + height; ++y)
+ for (int x = startx; x < startx + width; ++x) {
+ struct pixel *pixel = &fbb->pixels[y * fbb->width + x];
+ pixel->r = 0;
+ pixel->g = y * 256 / fb_info.fb_height;
+ pixel->b = x * 256 / fb_info.fb_width;
+ }
+}
+
+static void draw_cursor(int x, int y) {
+ for (int yy = 0; yy < cursor_image->height && y + yy < fbb->height; ++yy)
+ for (int xx = 0; xx < cursor_image->width && x + xx < fbb->width; ++xx) {
+ struct pixel *fb_pixel = &fbb->pixels[(y + yy) * fbb->width + (x + xx)];
+ struct pixel *ci_pixel = &cursor_image->pixels[yy * cursor_image->width + xx];
+ fb_pixel->r = (fb_pixel->r * (255 - ci_pixel->a) + ci_pixel->r * ci_pixel->a) / 255;
+ fb_pixel->g = (fb_pixel->g * (255 - ci_pixel->a) + ci_pixel->g * ci_pixel->a) / 255;
+ fb_pixel->b = (fb_pixel->b * (255 - ci_pixel->a) + ci_pixel->b * ci_pixel->a) / 255;
+ }
+}
+
+static void copy_frame() {
+ for (int y = 0; y < fbb->height; ++y)
+ for (int x = 0; x < fbb->width; ++x) {
+ uint8_t *fb_pixel = &fb_info.fb_base[y * fb_info.fb_pitch + x * 4];
+ struct pixel *fbb_pixel = &fbb->pixels[y * fbb->width + x];
+ fb_pixel[0] = fbb_pixel->b;
+ fb_pixel[1] = fbb_pixel->g;
+ fb_pixel[2] = fbb_pixel->r;
+ }
+}
void main() {
- struct framebuffer_info fb_info;
- map_framebuffer(&fb_info);
+ if (!load_pam("root://calcite/apps/hello/pointer.pam", &cursor_image))
+ return;
- for (int y = 0; y < fb_info.fb_height; ++y)
- for (int x = 0; x < fb_info.fb_width; ++x) {
- uint8_t *pixel = fb_info.fb_base + y * fb_info.fb_pitch + x * 4;
- pixel[0] = x * 256 / fb_info.fb_width;
- pixel[1] = y * 256 / fb_info.fb_height;
- }
+ map_framebuffer(&fb_info);
+ create_image(fb_info.fb_width, fb_info.fb_height, &fbb);
+
+ int cursor_x = fb_info.fb_width / 2;
+ int cursor_y = fb_info.fb_height / 2;
+
+ draw_bg(0, 0, fbb->width, fbb->height);
+ draw_cursor(cursor_x, cursor_y);
+ copy_frame();
+
+ while (1) {
+
+ struct mouse_packet packet;
+ wait_for_mouse_packet(&packet);
+
+ draw_bg(cursor_x, cursor_y, cursor_image->width, cursor_image->height);
+
+ cursor_x += packet.x_change;
+ cursor_y -= packet.y_change;
+
+ if (cursor_x < 0)
+ cursor_x = 0;
+ if (cursor_x >= fbb->width)
+ cursor_x = fbb->width - 1;
+
+ if (cursor_y < 0)
+ cursor_y = 0;
+ if (cursor_y >= fbb->height)
+ cursor_y = fbb->height - 1;
+
+ draw_cursor(cursor_x, cursor_y);
+ copy_frame();
+
+ }
}
diff --git a/src/user-apps/hello/libraries.txt b/src/user-apps/hello/libraries.txt
index 8503661..8412fe5 100644
--- a/src/user-apps/hello/libraries.txt
+++ b/src/user-apps/hello/libraries.txt
@@ -1 +1,2 @@
calcite
+silver
diff --git a/src/user-libs/calcite/entry.c b/src/user-libs/calcite/entry.c
index f9ac4ba..71dd204 100644
--- a/src/user-libs/calcite/entry.c
+++ b/src/user-libs/calcite/entry.c
@@ -15,7 +15,7 @@
* with this program. If not, see .
*/
-#include
+#include
void main();
diff --git a/src/user-libs/calcite/file-streams.c b/src/user-libs/calcite/file-streams.c
new file mode 100644
index 0000000..3f3bdd5
--- /dev/null
+++ b/src/user-libs/calcite/file-streams.c
@@ -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 .
+ */
+
+#include
+#include
+#include
+#include
+
+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];
+
+}
diff --git a/src/user-libs/calcite/memory.c b/src/user-libs/calcite/memory.c
new file mode 100644
index 0000000..06f5c8f
--- /dev/null
+++ b/src/user-libs/calcite/memory.c
@@ -0,0 +1,191 @@
+/* Calcite, src/user-libs/calcite/memory.c
+ * Copyright 2025 Benji Dial
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#include
+#include
+
+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];
+}
diff --git a/src/user-libs/calcite/syscalls.c b/src/user-libs/calcite/syscalls.c
index d1584ce..013ae67 100644
--- a/src/user-libs/calcite/syscalls.c
+++ b/src/user-libs/calcite/syscalls.c
@@ -16,7 +16,8 @@
*/
#include
-#include
+#include
+#include
//defined in syscalls.asm
uint64_t do_syscall
@@ -30,3 +31,32 @@ uint64_t do_syscall
void map_framebuffer(struct framebuffer_info *info_out) {
do_syscall((uint64_t)info_out, 0, 0, SYSCALL_MAP_FRAMEBUFFER);
}
+
+enum fs_access_result open_file(const char *path, file_handle_t *handle_out) {
+ return do_syscall((uint64_t)path, (uint64_t)handle_out, 1, SYSCALL_OPEN_FILE);
+}
+
+void close_file(file_handle_t handle) {
+ do_syscall(handle, 0, 0, SYSCALL_CLOSE_FILE);
+}
+
+enum fs_access_result get_file_size(file_handle_t handle, uint64_t *bytes_out) {
+ return do_syscall(handle, (uint64_t)bytes_out, 0, SYSCALL_GET_FILE_SIZE);
+}
+
+enum fs_access_result read_file(struct read_file_parameter *parameter) {
+ return do_syscall((uint64_t)parameter, 0, 0, SYSCALL_READ_FILE);
+}
+
+enum fs_access_result read_file_splat(file_handle_t handle, void *buffer, uint64_t start, uint64_t bytes) {
+ struct read_file_parameter parameter = { .handle = handle, .buffer = buffer, .start = start, .bytes = bytes };
+ return do_syscall((uint64_t)¶meter, 0, 0, SYSCALL_READ_FILE);
+}
+
+void wait_for_mouse_packet(struct mouse_packet *packet_out) {
+ do_syscall((uint64_t)packet_out, 0, 0, SYSCALL_WAIT_FOR_MOUSE_PACKET);
+}
+
+void *map_pages(uint64_t count) {
+ return (void *)do_syscall(count, 0, 0, SYSCALL_MAP_PAGES);
+}
diff --git a/src/user-libs/silver/image.c b/src/user-libs/silver/image.c
new file mode 100644
index 0000000..c78eb18
--- /dev/null
+++ b/src/user-libs/silver/image.c
@@ -0,0 +1,31 @@
+/* Calcite, src/user-libs/silver/image.c
+ * Copyright 2025 Benji Dial
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#include
+#include
+
+void create_image(int width, int height, struct image **image_out) {
+ *image_out = heap_alloc(sizeof(struct image));
+ (*image_out)->width = width;
+ (*image_out)->height = height;
+ (*image_out)->pixels = heap_alloc(width * height * sizeof(struct pixel));
+}
+
+void destroy_image(struct image *image) {
+ heap_dealloc(image->pixels, image->width * image->height * sizeof(struct pixel));
+ heap_dealloc(image, sizeof(struct image));
+}
diff --git a/src/user-libs/silver/pam.c b/src/user-libs/silver/pam.c
new file mode 100644
index 0000000..2d5625c
--- /dev/null
+++ b/src/user-libs/silver/pam.c
@@ -0,0 +1,128 @@
+/* Calcite, src/user-libs/silver/pam.c
+ * Copyright 2025 Benji Dial
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+#include
+#include
+
+static int starts_with(const char *big_string, const char *little_string) {
+ while (*little_string) {
+ if (*big_string != *little_string)
+ return 0;
+ ++big_string;
+ ++little_string;
+ }
+ return 1;
+}
+
+static int strequ(const char *str1, const char *str2) {
+ while (1) {
+ if (!*str1 && !*str2)
+ return 1;
+ if (*str1 != *str2)
+ return 0;
+ ++str1;
+ ++str2;
+ }
+}
+
+static int parse_uint(const char *str) {
+ int i = 0;
+ while (*str)
+ i = i * 10 + *(str++) - '0';
+ return i;
+}
+
+enum pam_tupltype {
+ PTT_RGB_ALPHA
+};
+
+#define MAX_LINE_LENGTH 1023
+
+int load_pam(const char *path, struct image **image_out) {
+
+ struct file_stream *stream;
+ if (open_file_stream(path, &stream) != FAR_SUCCESS)
+ return 0;
+
+ if (read_file_stream_byte(stream) != 'P' ||
+ read_file_stream_byte(stream) != '7' ||
+ read_file_stream_byte(stream) != '\n') {
+ close_file_stream(stream);
+ return 0;
+ }
+
+ int width = -1, height = -1, depth = -1, maxval = -1, tupltype = -1;
+
+ while (1) {
+
+ char line[MAX_LINE_LENGTH + 1];
+ int line_length = 0;
+ while (1) {
+ int c = read_file_stream_byte(stream);
+ if (c == -1) {
+ close_file_stream(stream);
+ return 0;
+ }
+ if (c == '\n') {
+ line[line_length] = 0;
+ break;
+ }
+ if (line_length == MAX_LINE_LENGTH) {
+ close_file_stream(stream);
+ return 0;
+ }
+ line[line_length++] = c;
+ }
+
+ if (line[0] == '#')
+ continue;
+
+ if (starts_with(line, "WIDTH "))
+ width = parse_uint(&line[6]);
+ else if (starts_with(line, "HEIGHT "))
+ height = parse_uint(&line[7]);
+ else if (starts_with(line, "DEPTH "))
+ depth = parse_uint(&line[6]);
+ else if (starts_with(line, "MAXVAL "))
+ maxval = parse_uint(&line[7]);
+ else if (strequ(line, "TUPLTYPE RGB_ALPHA"))
+ tupltype = PTT_RGB_ALPHA;
+ else if (strequ(line, "ENDHDR"))
+ break;
+
+ }
+
+ if (width == -1 || height == -1 || depth != 4 || maxval != 255 || tupltype != PTT_RGB_ALPHA) {
+ close_file_stream(stream);
+ return 0;
+ }
+
+ create_image(width, height, image_out);
+
+ if (read_file_stream(
+ stream,
+ (*image_out)->pixels,
+ width * height * 4) != FAR_SUCCESS) {
+ close_file_stream(stream);
+ destroy_image(*image_out);
+ return 0;
+ }
+
+ close_file_stream(stream);
+ return 1;
+
+}