From 18ab2c762837046d260077ac2b6bbdac8e5e8b5c Mon Sep 17 00:00:00 2001 From: xamidev Date: Sun, 10 May 2026 21:25:41 +0200 Subject: [PATCH] syscalls needed for doom (tell/eof/draw_fb) + minor fixes, compiler shut up etc --- docs/MANUAL.md | 10 +- docs/SOFTWARE.md | 2 + docs/SYSCALLS.md | 11 ++ include/fs/initfs.h | 6 + include/kernel.h | 1 + src/arch/x86/syscall.c | 160 -------------------------- src/kapps/kshell.c | 1 + src/kmain.c | 1 + src/syscall/files.c | 252 +++++++++++++++++++++++++++++++++++++++++ src/syscall/proc.c | 28 +++++ src/syscall/screen.c | 105 +++++++++++++++++ src/syscall/syscall.c | 99 ++++++++++++++++ 12 files changed, 507 insertions(+), 169 deletions(-) create mode 100644 docs/SYSCALLS.md delete mode 100644 src/arch/x86/syscall.c create mode 100644 src/syscall/files.c create mode 100644 src/syscall/proc.c create mode 100644 src/syscall/screen.c create mode 100644 src/syscall/syscall.c diff --git a/docs/MANUAL.md b/docs/MANUAL.md index b8dcda2..b126ba4 100644 --- a/docs/MANUAL.md +++ b/docs/MANUAL.md @@ -41,12 +41,4 @@ The recommended hardware to run PepperOS is the following: ## III. Syscall table The syscall interface in the Pepper kernel uses the System V ABI convention for argument order. -It vaguely mimics Unix-like systems. - -Name | Number (%rax) | arg0 (%rdi) | arg1 (%rsi) | arg2 (%rdx) | -|---|---|---|---|---| -| sys_read | 0 | unsigned int fd | char* buf | size_t count | -| sys_write | 1 | unsigned int fd | const char* buf | size_t count | -| sys_open | 2 | const char* filename | int flags | | -| sys_close | 3 | unsigned int fd | | | -| sys_exit | 60 | int error_code | | | \ No newline at end of file +It vaguely mimics Unix-like systems. You will find it in [SYSCALLS.md](SYSCALLS.md). \ No newline at end of file diff --git a/docs/SOFTWARE.md b/docs/SOFTWARE.md index 29c5370..50c9ace 100644 --- a/docs/SOFTWARE.md +++ b/docs/SOFTWARE.md @@ -19,6 +19,8 @@ Of course, all of the freestanding headers are available: Also available is the `` header that gives access to low-level system call interface, notably the `syscallX` function family, X being the amount of arguments to use. +(TODO: put the other headers here once libc is more complete) + ## 1. Write the source code PepperOS is able to run programs written in x86 assembly, and C programs. diff --git a/docs/SYSCALLS.md b/docs/SYSCALLS.md new file mode 100644 index 0000000..5faa69a --- /dev/null +++ b/docs/SYSCALLS.md @@ -0,0 +1,11 @@ +# Pepper kernel system call table + +The following table contains all of the system calls supported by PepperOS, as well as their arguments. The explanation for what every system call does is available as a comment above each function in corresponding files in the `src/syscall` folder. + +Name | Number (%rax) | arg0 (%rdi) | arg1 (%rsi) | arg2 (%rdx) | +|---|---|---|---|---| +| sys_read | 0 | unsigned int fd | char* buf | size_t count | +| sys_write | 1 | unsigned int fd | const char* buf | size_t count | +| sys_open | 2 | const char* filename | int flags | | +| sys_close | 3 | unsigned int fd | | | +| sys_exit | 60 | int error_code | | | \ No newline at end of file diff --git a/include/fs/initfs.h b/include/fs/initfs.h index f7a3ca8..0ab4794 100644 --- a/include/fs/initfs.h +++ b/include/fs/initfs.h @@ -15,4 +15,10 @@ int tar_exists(const char* filename); int tar_read(char* filename, char* out, int count, int offset); void tar_list(); +enum Seek { + SEEK_SET, + SEEK_CUR, + SEEK_END +}; + #endif \ No newline at end of file diff --git a/include/kernel.h b/include/kernel.h index 3d8d0b7..0bf2017 100644 --- a/include/kernel.h +++ b/include/kernel.h @@ -9,6 +9,7 @@ #include "limine.h" +// Not in POSIX order. enum ErrorCodes { ENOMEM, // No memory EIO, // Input/output error diff --git a/src/arch/x86/syscall.c b/src/arch/x86/syscall.c deleted file mode 100644 index 15f9128..0000000 --- a/src/arch/x86/syscall.c +++ /dev/null @@ -1,160 +0,0 @@ -/* - * @author xamidev - * @brief System call handling - * @license GPL-3.0-only - */ - -#include "config.h" -#include "sched/scheduler.h" -#include -#include -#include -#include -#include -#include -#include -#include - -extern struct process* current_process; - -// Return fd on success, -errno on error -int sys_open(const char* filename, int flags) -{ - if (tar_exists(filename) < 0) { - return -ENOENT; // file doesn't exist.. - } - // file exists here! - if (current_process->next_free_fd >= FDT_MAX) { - return -EMFILE; - } - int fd = current_process->next_free_fd++; - current_process->fdt[fd].fd = fd; - current_process->fdt[fd].open = true; - current_process->fdt[fd].cursor = 0; - strncpy(current_process->fdt[fd].filename, filename, PROCESS_NAME_MAX - 1); - return fd; -} - -// Return 0 on success, -EBADFD if invalid FD -int sys_close(int fd) -{ - if (fd < 0 || fd >= FDT_MAX) { - return -EBADFD; - } - - if (!current_process->fdt[fd].open) { - return -EBADFD; // FD not opened in the first place - } - - current_process->fdt[fd].open = false; - current_process->fdt[fd].filename[0] = '\0'; - current_process->fdt[fd].cursor = 0; - return 0; -} - -// Should return the number of bytes read -int sys_read(unsigned int fd, char* buf, size_t count) -{ - size_t i; - switch (fd) { - case 0: //read from stdin (keyboard) - for (i=0; ifdt[fd].open == false) { - return -EBADFD; // File descriptor wasn't open - } - // Here fd refers to a valid opened file.. - int sz = tar_read(current_process->fdt[fd].filename, buf, count, - current_process->fdt[fd].cursor); - if (sz == 0) { - return -ENOENT; - } else { - current_process->fdt[fd].cursor += sz; - return sz; - } - } - - return -EBADFD; -} - -// TODO: Should have a return value: number of bytes written on success, -1 on error (errno set) -int sys_write(unsigned int fd, const char* buf, size_t count) -{ - switch (fd) { - case 1: //stdout - case 2: //stderr - for (size_t i=0; istatus = DEAD; - DEBUG("(pid=%u, name=%s)", current_process->pid, current_process->name); - return error_code; -} - -/* - * syscall_handler - System call dispatcher - * @regs: CPU state - * - * This function is called from the interrupt dispatcher, - * when an interrupt 0x80 is emitted from userland. - * - * It switches control to the syscall number provided - * in %rax. - * - * We try to follow the System V convention here: - * - syscall number in %rax - * - args in %rdi, %rsi, %rdx, %r10, %r8, %r9 - * - return value (if any) in %rax - * - * Return: - * - CPU state after system call - */ -struct cpu_status* syscall_handler(struct cpu_status* regs) -{ - switch (regs->rax) - { - case 0: - DEBUG("sys_read(fd=%u, buf=%p, count=%u)", regs->rdi, regs->rsi, regs->rdx); - regs->rax = sys_read(regs->rdi, (char*)regs->rsi, regs->rdx); - break; - case 1: - DEBUG("sys_write(fd=%u, buf=%p, count=%u)", regs->rdi, regs->rsi, regs->rdx); - regs->rax = sys_write(regs->rdi, (char*)regs->rsi, regs->rdx); - break; - case 2: - DEBUG("sys_open(filename=%s, flags=%u)", regs->rdi, regs->rsi); - regs->rax = sys_open((const char*)regs->rdi, regs->rsi); - break; - case 3: - DEBUG("sys_close(fd=%u)", regs->rdi); - regs->rax = sys_close(regs->rdi); - break; - case 60: - DEBUG("sys_exit(error_code=%d)", regs->rdi); - regs->rax = sys_exit(regs->rdi); - break; - default: - DEBUG("Bad syscall! (rax=%p, rdi=%p, rsi=%p, rdx=%p)", - regs->rax, regs->rdi, regs->rsi, regs->rdx); - regs->rax = 0xbad515ca11; - break; - } - - DEBUG("returned rax=%p (%u)", regs->rax, regs->rax); - - return regs; -} \ No newline at end of file diff --git a/src/kapps/kshell.c b/src/kapps/kshell.c index 01f0dd4..1da7cb1 100644 --- a/src/kapps/kshell.c +++ b/src/kapps/kshell.c @@ -82,6 +82,7 @@ void kill() */ void pedicel_main(void* arg) { + (void)arg; printf("Welcome to the kernel shell!\r\nType 'help' for a list of commands.\r\n"); for (;;) { diff --git a/src/kmain.c b/src/kmain.c index 33ee671..ff2818a 100644 --- a/src/kmain.c +++ b/src/kmain.c @@ -73,6 +73,7 @@ struct process* idle_proc; void idle_main(void* arg) { + (void)arg; for (;;) { asm("hlt"); } diff --git a/src/syscall/files.c b/src/syscall/files.c new file mode 100644 index 0000000..de897cd --- /dev/null +++ b/src/syscall/files.c @@ -0,0 +1,252 @@ +/* + * @author xamidev + * @brief File-related system calls + * @license GPL-3.0-only + */ + +#include +#include +#include +#include +#include +#include +#include + +extern struct process* current_process; + +/* + * normalize_path - remove leading slashes from a path + * @path: path to normalize + * + * Return: + * %path - normalized path + * %NULL - if bad argument + */ +static const char* normalize_path(const char* path) +{ + if (!path) return NULL; + + while (*path == '/') { + path++; + } + + return path; +} + +/* + * sys_open - Open a file + * @filename: Absolute path to file + * @flags: (Not implemented) + * + * This system call opens a file in read-only mode, + * because of the TAR filesystem. + * + * Return: + * %fd - file descriptor refering to file + * On error, a negative number representing the error code is returned. + */ +int sys_open(const char* filename, int flags) +{ + // TODO: support flags (right now everything is read only, O_RDONLY) + (void)flags; // gcc shut up + const char* path = normalize_path(filename); + + if (tar_exists(path) < 0) { + return -ENOENT; // file doesn't exist.. + } + // file exists here! + if (current_process->next_free_fd >= FDT_MAX) { + return -EMFILE; + } + int fd = current_process->next_free_fd++; + current_process->fdt[fd].fd = fd; + current_process->fdt[fd].open = true; + current_process->fdt[fd].cursor = 0; + strncpy(current_process->fdt[fd].filename, path, PROCESS_NAME_MAX - 1); + return fd; +} + +/* + * sys_close - Close a file + * @fd: file descriptor + * + * This system call closes the file referred to by + * the file descriptor @fd. + * + * Return: + * %0 - file closed + * %-EBADFD - bad file descriptor + */ +int sys_close(int fd) +{ + if (fd < 0 || fd >= FDT_MAX) { + return -EBADFD; + } + + if (!current_process->fdt[fd].open) { + return -EBADFD; // FD not opened in the first place + } + + current_process->fdt[fd].open = false; + current_process->fdt[fd].filename[0] = '\0'; + current_process->fdt[fd].cursor = 0; + return 0; +} + +/* + * sys_lseek - reposition file cursor + * @fd: file descriptor referring to file + * @offset: byte amount to be used according to @whence + * @whence: resposition directive + * + * This system call repositions the cursor for @fd by @offset, + * from somewhere in the file, depending on the value of @whence: + * SEEK_SET (0) -> from the beginning + * SEEK_CUR (1) -> from the actual value of the cursor + * SEEK_END (2) -> from the end of the file + * + * Return: + * %new_cursor - the new cursor value + * On error, a negative value corresponding to an error code is returned. + */ +int sys_lseek(int fd, int offset, int whence) +{ + if (fd < 0 || fd >= FDT_MAX) { + return -EBADFD; + } + if (!current_process->fdt[fd].open) { + return -EBADFD; + } + + int filesize = tar_exists(current_process->fdt[fd].filename); + if (filesize < 0) { + return -ENOENT; + } + + int cursor = current_process->fdt[fd].cursor; + int base; + switch (whence) { + case SEEK_SET: base = 0; break; + case SEEK_CUR: base = cursor; break; + case SEEK_END: base = filesize; break; + default: return -EINVAL; + } + + int new_cursor = base + offset; + if (new_cursor < 0) new_cursor = 0; + if (new_cursor > filesize) new_cursor = filesize; + + current_process->fdt[fd].cursor = new_cursor; + return new_cursor; +} + +/* + * sys_tell - Get cursor position for a file + * @fd: file descriptor to use + * + * Return: + * %cursor - cursor position of the file descriptor + * On error, a negative value with the corresponding error code is set. + */ +int sys_tell(int fd) +{ + if (fd < 0 || fd >= FDT_MAX) { + return -EBADFD; + } + if (!current_process->fdt[fd].open) { + return -EBADFD; + } + return current_process->fdt[fd].cursor; +} + +/* + * sys_eof - Are we at the end of the file yet? + * @fd: file descriptor to use + * + * This function determines if the cursor for the + * file descriptor @fd is at or past the end of + * the file it refers to. + * + * Return: + * %>0 - we are at or past EOF + * %0 - not yet (still have bytes to read) + */ +int sys_eof(int fd) +{ + if (fd < 0 || fd >= FDT_MAX) { + return -EBADFD; + } + if (!current_process->fdt[fd].open) { + return -EBADFD; + } + + int filesize = tar_exists(current_process->fdt[fd].filename); + if (filesize < 0) { + return -ENOENT; + } + + return current_process->fdt[fd].cursor >= (uint64_t)filesize; +} + +/* + * sys_read - read from an open file + * @fd: file descriptor to use + * @buf: out buffer for read data + * @count: amount of bytes to read + * + * Return: + * %sz - amount of bytes read (on success) + * On error, a negative value with an error code is returned. + */ +int sys_read(unsigned int fd, char* buf, size_t count) +{ + size_t i; + switch (fd) { + case 0: //read from stdin (keyboard) + for (i=0; ifdt[fd].open == false) { + return -EBADFD; // File descriptor wasn't open + } + // Here fd refers to a valid opened file.. + int sz = tar_read(current_process->fdt[fd].filename, buf, count, + current_process->fdt[fd].cursor); + if (sz == 0) { + return -ENOENT; + } else { + current_process->fdt[fd].cursor += sz; + return sz; + } + } + return -EBADFD; +} + +/* + * sys_write - write to a file + * @fd: file descriptor to write to + * @buf: buffer of bytes to write + * @count: number of bytes to write + * + * Return: + * %count - number of bytes written + * On error, a negative value with corresponding error code is returned. + */ +int sys_write(unsigned int fd, const char* buf, size_t count) +{ + switch (fd) { + case 1: //stdout + case 2: //stderr + for (size_t i=0; i + * @brief Process-wide system calls + * @license GPL-3.0-only + */ + +#include +#include + +extern struct process* current_process; + +/* + * sys_exit - Terminate the current process + * @error_code: error code to return + * + * This system call exits the running process. Be aware that + * it does nothing except that. All resources are supposed to + * be cleaned up before calling this. + * + * Return: + * %error_code - the error code to return + */ +int sys_exit(int error_code) +{ + current_process->status = DEAD; + DEBUG("(pid=%u, name=%s)", current_process->pid, current_process->name); + return error_code; +} \ No newline at end of file diff --git a/src/syscall/screen.c b/src/syscall/screen.c new file mode 100644 index 0000000..fb280be --- /dev/null +++ b/src/syscall/screen.c @@ -0,0 +1,105 @@ +/* + * @author xamidev + * @brief Screen/framebuffer system calls + * @license GPL-3.0-only + */ + +// This is not part of POSIX or anything btw + +#include +#include + +extern struct boot_context boot_ctx; + +/* + * pack_rbga_to_fb - Convert rgb values to rgba pixel for framebuffer + * @r: red value + * @g: green value + * @b: blue value + * + * This function uses the mask sizes and shifts of the Limine + * framebuffer to convert raw rgb values into usable pixels. + * + * Return: + * - usable pixel + */ +static uint32_t pack_rgba_to_fb(uint8_t r, uint8_t g, uint8_t b) +{ + uint32_t pixel = 0; + + if (boot_ctx.fb->red_mask_size) { + pixel |= ((uint32_t)(r >> (8 - boot_ctx.fb->red_mask_size)) << boot_ctx.fb->red_mask_shift); + } + if (boot_ctx.fb->green_mask_size) { + pixel |= ((uint32_t)(g >> (8 - boot_ctx.fb->green_mask_size)) << boot_ctx.fb->green_mask_shift); + } + if (boot_ctx.fb->blue_mask_size) { + pixel |= ((uint32_t)(b >> (8 - boot_ctx.fb->blue_mask_size)) << boot_ctx.fb->blue_mask_shift); + } + + return pixel; +} + +/* + * sys_draw_fb - Draw a framebuffer subset + * @src: Source frame + * @width: width of the frame + * @height: height of the frame + * @channels: how many channels (RGB, RGBA) + * + * This system call draws the frame @src, having some + * @width and @height, in the top right of the Limine + * framebuffer. (Used for DOOM) + * + * Return: + * %0 - on success + * On error, a negative value with an error code is returned. + */ +int sys_draw_fb(const uint8_t* src, int width, int height, int channels) +{ + if (!boot_ctx.fb || !src) { + return -EINVAL; + } + if (channels != 4 || width <= 0 || height <= 0) { + return -EINVAL; + } + if (boot_ctx.fb->bpp < 24) { + return -EIO; + } + + uint32_t* dst = (uint32_t*)boot_ctx.fb->address; + uint64_t dst_w = boot_ctx.fb->width; + uint64_t dst_h = boot_ctx.fb->height; + uint64_t dst_pitch_px = boot_ctx.fb->pitch / 4; + uint64_t scale = 2; + uint64_t scaled_w = (uint64_t)width * scale; + uint64_t scaled_h = (uint64_t)height * scale; + + if (scaled_w > dst_w || scaled_h > dst_h) { + return -EINVAL; + } + + uint64_t dst_x = dst_w - scaled_w; + uint64_t dst_y = 0; + + for (uint64_t y = 0; y < (uint64_t)height; ++y) { + const uint8_t* src_row = src + y * (uint64_t)width * 4ULL; + uint32_t* dst_row0 = dst + (dst_y + y * scale) * dst_pitch_px + dst_x; + uint32_t* dst_row1 = dst_row0 + dst_pitch_px; + + for (uint64_t x = 0; x < (uint64_t)width; ++x) { + const uint8_t* p = src_row + x * 4ULL; + uint32_t pixel = pack_rgba_to_fb(p[0], p[1], p[2]); + uint64_t dx = x * scale; + + // Write 2x2 block. This is because source DOOM frame (320x200) + // is quite small. + dst_row0[dx + 0] = pixel; + dst_row0[dx + 1] = pixel; + dst_row1[dx + 0] = pixel; + dst_row1[dx + 1] = pixel; + } + } + + return 0; +} \ No newline at end of file diff --git a/src/syscall/syscall.c b/src/syscall/syscall.c new file mode 100644 index 0000000..b3cd3ec --- /dev/null +++ b/src/syscall/syscall.c @@ -0,0 +1,99 @@ +/* + * @author xamidev + * @brief System call handling + * @license GPL-3.0-only + */ + +#include "config.h" +#include "sched/scheduler.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern struct process* current_process; + +int sys_open(const char* filename, int flags); +int sys_close(int fd); +int sys_lseek(int fd, int offset, int whence); +int sys_tell(int fd); // needed by doom, therefore TOP PRIORITY +int sys_eof(int fd); // same +int sys_read(unsigned int fd, char* buf, size_t count); +int sys_write(unsigned int fd, const char* buf, size_t count); +int sys_exit(int error_code); +int sys_draw_fb(const uint8_t* src, int width, int height, int channels); + +/* + * syscall_handler - System call dispatcher + * @regs: CPU state + * + * This function is called from the interrupt dispatcher, + * when an interrupt 0x80 is emitted from userland. + * + * It switches control to the syscall number provided + * in %rax. + * + * We try to follow the System V convention here: + * - syscall number in %rax + * - args in %rdi, %rsi, %rdx, %r10, %r8, %r9 + * - return value (if any) in %rax + * + * Return: + * - CPU state after system call + */ +struct cpu_status* syscall_handler(struct cpu_status* regs) +{ + switch (regs->rax) + { + case 0: + DEBUG("sys_read(fd=%u, buf=%p, count=%u)", regs->rdi, regs->rsi, regs->rdx); + regs->rax = sys_read(regs->rdi, (char*)regs->rsi, regs->rdx); + break; + case 1: + DEBUG("sys_write(fd=%u, buf=%p, count=%u)", regs->rdi, regs->rsi, regs->rdx); + regs->rax = sys_write(regs->rdi, (char*)regs->rsi, regs->rdx); + break; + case 2: + DEBUG("sys_open(filename=%s, flags=%u)", regs->rdi, regs->rsi); + regs->rax = sys_open((const char*)regs->rdi, regs->rsi); + break; + case 3: + DEBUG("sys_close(fd=%u)", regs->rdi); + regs->rax = sys_close(regs->rdi); + break; + case 8: + DEBUG("sys_lseek(fd=%u, off=%d, whence=%u)", regs->rdi, regs->rsi, regs->rdx); + regs->rax = sys_lseek(regs->rdi, regs->rsi, regs->rdx); + break; + case 9: + DEBUG("sys_tell(fd=%u)", regs->rdi); + regs->rax = sys_tell(regs->rdi); + break; + case 10: + DEBUG("sys_eof(fd=%u)", regs->rdi); + regs->rax = sys_eof(regs->rdi); + break; + case 11: // No DEBUG() here because it makes significant overhead + regs->rax = sys_draw_fb((const uint8_t*)regs->rdi, regs->rsi, regs->rdx, regs->r10); + break; + case 60: + DEBUG("sys_exit(error_code=%d)", regs->rdi); + regs->rax = sys_exit(regs->rdi); + break; + default: + DEBUG("Bad syscall! (rax=%p, rdi=%p, rsi=%p, rdx=%p)", + regs->rax, regs->rdi, regs->rsi, regs->rdx); + regs->rax = 0xbad515ca11; + break; + } + + // save overhead for present_fb + //DEBUG("returned rax=%p (%u)", regs->rax, regs->rax); + + return regs; +} \ No newline at end of file