From b539b6302c3bf1c1f7ec7a1e1293de000f458db5 Mon Sep 17 00:00:00 2001 From: Benji Dial Date: Sun, 28 Dec 2025 14:08:55 -0500 Subject: [PATCH] more user space stuff, including files and mouse packets --- disk/calcite/apps/hello/pointer.pam | Bin 0 -> 1179 bytes include/calcite/file-streams.h | 30 +++ include/calcite/memory.h | 28 +++ include/calcite/syscalls.h | 38 ++++ include/kernel-public/files.h | 40 ++++ include/kernel-public/input.h | 23 +++ include/kernel-public/syscall-numbers.h | 8 +- include/silver/image.h | 36 ++++ include/{calcite/calcite.h => silver/pam.h} | 9 +- make-build.sh | 6 +- qemu.gdb | 1 + src/kernel/drives.h | 2 +- src/kernel/entry.c | 26 +-- src/kernel/fs.c | 75 ++++++++ src/kernel/fs.h | 18 +- src/kernel/input.c | 68 +++++++ src/kernel/input.h | 24 +++ src/kernel/paging.c | 16 ++ src/kernel/paging.h | 4 + src/kernel/process.c | 202 +++++++++++++++++++- src/kernel/process.h | 36 +++- src/kernel/ps2.c | 33 +--- src/kernel/scheduler.asm | 21 ++ src/kernel/scheduler.c | 108 +++++------ src/kernel/scheduler.h | 20 +- src/kernel/syscalls.asm | 112 ++++++++++- src/kernel/syscalls.h | 8 + src/user-apps/hello/hello.c | 88 ++++++++- src/user-apps/hello/libraries.txt | 1 + src/user-libs/calcite/entry.c | 2 +- src/user-libs/calcite/file-streams.c | 150 +++++++++++++++ src/user-libs/calcite/memory.c | 191 ++++++++++++++++++ src/user-libs/calcite/syscalls.c | 32 +++- src/user-libs/silver/image.c | 31 +++ src/user-libs/silver/pam.c | 128 +++++++++++++ 35 files changed, 1476 insertions(+), 139 deletions(-) create mode 100644 disk/calcite/apps/hello/pointer.pam create mode 100644 include/calcite/file-streams.h create mode 100644 include/calcite/memory.h create mode 100644 include/calcite/syscalls.h create mode 100644 include/kernel-public/files.h create mode 100644 include/kernel-public/input.h create mode 100644 include/silver/image.h rename include/{calcite/calcite.h => silver/pam.h} (80%) create mode 100644 src/kernel/input.c create mode 100644 src/kernel/input.h create mode 100644 src/user-libs/calcite/file-streams.c create mode 100644 src/user-libs/calcite/memory.c create mode 100644 src/user-libs/silver/image.c create mode 100644 src/user-libs/silver/pam.c diff --git a/disk/calcite/apps/hello/pointer.pam b/disk/calcite/apps/hello/pointer.pam new file mode 100644 index 0000000000000000000000000000000000000000..18bd8cebaa52c53c551ba32cd097b555cb4238c5 GIT binary patch literal 1179 zcmbW#%?g4*5CGuL`xFD6l{6(fnu;r^EVBKbqDVpr3;TgiKAMlVDYUSNKWhWaEW7i~ zT!t*r6zn`XK9e(?KH&Kwfp?NW(pv^|Raa+9Z0kUx5boF4MM*QX^&N^qGZ1!t4^z;V z$Z<*aWT4R6ZL9!v(?oPl0=g$I#NO&)1q55jjU4R6Xi^3HMSYhIZg?K+%92aQWGt^P z3dY7*(T%Z9Cu`fBjO}tmw$Jt1A=hPPT$`13P21(f9pE)>x$ei$kg4~O_h>VHdn!6{ TPM;CyjWMF*bBE@L|NodTAr3wL literal 0 HcmV?d00001 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; + +}