From ba281ea3f32a01f07928a0189a889a16cf4a889e Mon Sep 17 00:00:00 2001 From: Lluciocc <114759545+Lluciocc@users.noreply.github.com> Date: Mon, 11 May 2026 19:32:26 +0200 Subject: [PATCH 01/24] Adding hexdump and ps.c --- src/userland/cli/hexdump.c | 111 ++++++++++++++ src/userland/cli/ps.c | 299 +++++++++++++++++++++++++++++++++++++ 2 files changed, 410 insertions(+) create mode 100644 src/userland/cli/hexdump.c create mode 100644 src/userland/cli/ps.c diff --git a/src/userland/cli/hexdump.c b/src/userland/cli/hexdump.c new file mode 100644 index 0000000..f722d63 --- /dev/null +++ b/src/userland/cli/hexdump.c @@ -0,0 +1,111 @@ +// Copyright (c) 2023-2026 Chris (boreddevnl) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. +// BOREDOS_APP_DESC: Display file contents in hexadecimal. + +#include "syscall.h" +#include "stdlib.h" +#include "string.h" +#include "stdio.h" + +#define BYTES_PER_LINE 16 + +static int sc_strcmp(const char *a, const char *b) { + while (*a && *a == *b) { + a++; + b++; + } + + return (unsigned char)*a - (unsigned char)*b; +} + +static void print_usage(void) { + printf("Usage: hexdump \n"); + printf("\n"); + printf("Display file contents in hexadecimal.\n"); +} + +static void print_hex_byte(unsigned char b) { + const char *hex = "0123456789ABCDEF"; + + putchar(hex[(b >> 4) & 0xF]); + putchar(hex[b & 0xF]); +} + +static void print_hex32(unsigned int v) { + const char *hex = "0123456789ABCDEF"; + + for (int i = 7; i >= 0; i--) { + putchar(hex[(v >> (i * 4)) & 0xF]); + } +} + +int main(int argc, char **argv) { + int fd; + int offset = 0; + + unsigned char buf[BYTES_PER_LINE]; + + if (argc < 2) { + print_usage(); + return 1; + } + + if (sc_strcmp(argv[1], "-h") == 0 || + sc_strcmp(argv[1], "--help") == 0) { + print_usage(); + return 0; + } + + fd = sys_open(argv[1], "r"); + + if (fd < 0) { + printf("hexdump: cannot open '%s'\n", argv[1]); + return 1; + } + + while (1) { + int bytes = sys_read(fd, buf, BYTES_PER_LINE); + + if (bytes <= 0) + break; + + // Offset + print_hex32(offset); + printf(" "); + + // Hex bytes + for (int i = 0; i < BYTES_PER_LINE; i++) { + if (i < bytes) { + print_hex_byte(buf[i]); + } else { + printf(" "); + } + + printf(" "); + + if (i == 7) + printf(" "); + } + + printf(" |"); + + // ASCII preview + for (int i = 0; i < bytes; i++) { + unsigned char c = buf[i]; + + if (c >= 32 && c <= 126) + putchar(c); + else + putchar('.'); + } + + printf("|\n"); + + offset += bytes; + } + + sys_close(fd); + + return 0; +} \ No newline at end of file diff --git a/src/userland/cli/ps.c b/src/userland/cli/ps.c new file mode 100644 index 0000000..f34554d --- /dev/null +++ b/src/userland/cli/ps.c @@ -0,0 +1,299 @@ +// Copyright (c) 2023-2026 Chris (boreddevnl) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. +// BOREDOS_APP_DESC: List running processes. + +#include "../libc/syscall.h" +#include "../libc/stdlib.h" +#include "../libc/string.h" +#include "../libc/stdio.h" + +#define MAX_PROC_ENTRIES 64 + +static int sc_strcmp(const char *a, const char *b) { + while (*a && *a == *b) { + a++; + b++; + } + return (unsigned char)*a - (unsigned char)*b; +} + +static int is_numeric(const char *s) { + int i = 0; + + if (!s || !s[0]) + return 0; + + while (s[i]) { + if (s[i] < '0' || s[i] > '9') + return 0; + i++; + } + + return 1; +} + +static void print_spaces(int count) { + for (int i = 0; i < count; i++) + printf(" "); +} + +static void print_padded(const char *s, int width) { + int len; + + if (!s) + s = ""; + + printf("%s", s); + + len = (int)strlen(s); + + if (len < width) + print_spaces(width - len); + else + printf(" "); +} + +static int find_value(const char *buf, const char *key) { + char *p = (char*)buf; + int key_len = strlen(key); + + while (*p) { + if (memcmp(p, key, key_len) == 0 && p[key_len] == ':') { + p += key_len + 1; + + while (*p == ' ' || *p == '\t') + p++; + + return atoi(p); + } + + while (*p && *p != '\n') + p++; + + if (*p == '\n') + p++; + } + + return 0; +} + +static void find_string(const char *buf, + const char *key, + char *out, + int max_len) { + char *p = (char*)buf; + int key_len = strlen(key); + + out[0] = 0; + + while (*p) { + if (memcmp(p, key, key_len) == 0 && p[key_len] == ':') { + int i = 0; + + p += key_len + 1; + + while (*p == ' ' || *p == '\t') + p++; + + while (*p && + *p != '\n' && + *p != '\r' && + i < max_len - 1) { + out[i++] = *p++; + } + + out[i] = 0; + return; + } + + while (*p && *p != '\n') + p++; + + if (*p == '\n') + p++; + } +} + +static int read_file_to_buf(const char *path, + char *buf, + int max_len) { + int fd; + int bytes; + + fd = sys_open(path, "r"); + + if (fd < 0) + return -1; + + bytes = sys_read(fd, buf, max_len - 1); + + sys_close(fd); + + if (bytes < 0) + return -1; + + buf[bytes] = 0; + + return bytes; +} + +static void format_mem(int kb, char *out) { + char tmp[32]; + + if (kb < 1024) { + itoa(kb, tmp); + + strcpy(out, tmp); + strcat(out, " KB"); + } else { + int mb = kb / 1024; + int frac = ((kb % 1024) * 10) / 1024; + + itoa(mb, tmp); + + strcpy(out, tmp); + strcat(out, "."); + + itoa(frac, tmp); + strcat(out, tmp); + strcat(out, " MiB"); + } +} + +static void print_usage(void) { + printf("Usage: ps [options]\n"); + printf("\n"); + printf("Options:\n"); + printf(" -a Show all processes\n"); + printf(" -i Include idle tasks\n"); + printf(" -m Show memory usage\n"); + printf(" -t Show scheduler ticks\n"); + printf(" -p PID Show only one process\n"); + printf(" -h Show this help\n"); +} + +static void print_header(int show_mem, + int show_ticks, + int show_idle) { + print_padded("PID", 8); + print_padded("NAME", 22); + + if (show_mem) + print_padded("MEMORY", 14); + + if (show_ticks) + print_padded("TICKS", 12); + + if (show_idle) + print_padded("IDLE", 8); + + printf("\n"); +} + +int main(int argc, char **argv) { + int show_mem = 0; + int show_ticks = 0; + int include_idle = 0; + int show_idle_col = 0; + int filter_pid = -1; + + FAT32_FileInfo entries[MAX_PROC_ENTRIES]; + + for (int i = 1; i < argc; i++) { + if (sc_strcmp(argv[i], "-m") == 0) { + show_mem = 1; + } else if (sc_strcmp(argv[i], "-t") == 0) { + show_ticks = 1; + } else if (sc_strcmp(argv[i], "-a") == 0) { + include_idle = 1; + show_idle_col = 1; + } else if (sc_strcmp(argv[i], "-i") == 0) { + include_idle = 1; + show_idle_col = 1; + } else if (sc_strcmp(argv[i], "-p") == 0 && i + 1 < argc) { + filter_pid = atoi(argv[++i]); + } else if (sc_strcmp(argv[i], "-h") == 0) { + print_usage(); + return 0; + } else { + print_usage(); + return 1; + } + } + + int count = sys_list("/proc", entries, MAX_PROC_ENTRIES); + + if (count < 0) { + printf("ps: failed to read /proc\n"); + return 1; + } + + print_header(show_mem, show_ticks, show_idle_col); + + for (int i = 0; i < count; i++) { + char path[96]; + char buf[512]; + char name[64]; + char mem_str[32]; + char tmp[32]; + + int pid; + int memory_kb; + int ticks; + int idle; + + if (!entries[i].is_directory) + continue; + + if (!is_numeric(entries[i].name)) + continue; + + pid = atoi(entries[i].name); + + if (filter_pid >= 0 && pid != filter_pid) + continue; + + strcpy(path, "/proc/"); + strcat(path, entries[i].name); + strcat(path, "/status"); + + if (read_file_to_buf(path, buf, sizeof(buf)) <= 0) + continue; + + find_string(buf, "Name", name, sizeof(name)); + + memory_kb = find_value(buf, "Memory"); + ticks = find_value(buf, "Ticks"); + idle = find_value(buf, "Idle"); + + if (idle && !include_idle) + continue; + + itoa(pid, tmp); + print_padded(tmp, 8); + + if (!name[0]) + strcpy(name, "Unknown"); + + print_padded(name, 22); + + if (show_mem) { + format_mem(memory_kb, mem_str); + print_padded(mem_str, 14); + } + + if (show_ticks) { + itoa(ticks, tmp); + print_padded(tmp, 12); + } + + if (show_idle_col) { + print_padded(idle ? "yes" : "no", 8); + } + + printf("\n"); + } + + return 0; +} \ No newline at end of file From f94384e572f0f83e63a1ae291d24f55f51394a45 Mon Sep 17 00:00:00 2001 From: Lluciocc <114759545+Lluciocc@users.noreply.github.com> Date: Mon, 11 May 2026 19:36:27 +0200 Subject: [PATCH 02/24] Add hexdump and ps command descriptions to help --- src/userland/cli/help.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/userland/cli/help.c b/src/userland/cli/help.c index d5f7903..490a1eb 100644 --- a/src/userland/cli/help.c +++ b/src/userland/cli/help.c @@ -25,6 +25,8 @@ int main(int argc, char **argv) { printf("date - Print current date and time\n"); printf("uptime - Print system uptime\n"); printf("meminfo - Print memory information\n"); + printf("hexdump - Display file contents in hexadecimal.\n"); + printf("ps [options] - List running processes\n") printf("lsblk - List block devices and partitions\n"); printf("cowsay [msg] - Fun cow says something\n"); printf("beep - Make a beep sound\n"); From fe1ba182d95676423beef510d129ed655f84b82b Mon Sep 17 00:00:00 2001 From: Lluciocc <114759545+Lluciocc@users.noreply.github.com> Date: Mon, 11 May 2026 20:25:35 +0200 Subject: [PATCH 03/24] Update with missing ; --- src/userland/cli/help.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/userland/cli/help.c b/src/userland/cli/help.c index 490a1eb..20ff62a 100644 --- a/src/userland/cli/help.c +++ b/src/userland/cli/help.c @@ -26,7 +26,7 @@ int main(int argc, char **argv) { printf("uptime - Print system uptime\n"); printf("meminfo - Print memory information\n"); printf("hexdump - Display file contents in hexadecimal.\n"); - printf("ps [options] - List running processes\n") + printf("ps [options] - List running processes\n"); printf("lsblk - List block devices and partitions\n"); printf("cowsay [msg] - Fun cow says something\n"); printf("beep - Make a beep sound\n"); From 70ab1837c2cd3db216c3f5ffb3d02ecf054743cc Mon Sep 17 00:00:00 2001 From: Lluciocc <114759545+Lluciocc@users.noreply.github.com> Date: Tue, 12 May 2026 09:04:27 +0200 Subject: [PATCH 04/24] Add ptime --- src/userland/cli/ptime.c | 140 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 src/userland/cli/ptime.c diff --git a/src/userland/cli/ptime.c b/src/userland/cli/ptime.c new file mode 100644 index 0000000..10a411e --- /dev/null +++ b/src/userland/cli/ptime.c @@ -0,0 +1,140 @@ +// Copyright (c) 2023-2026 Chris (boreddevnl) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. + +#include "../libc/stdlib.h" + +#define CMDLINE_MAX 512 + +static int sc_strcmp(const char *a, const char *b) { + while (*a && *a == *b) { + a++; + b++; + } + + return (unsigned char)*a - (unsigned char)*b; +} + +static int has_slash(const char *s) { + while (s && *s) { + if (*s == '/') + return 1; + s++; + } + + return 0; +} + +static int ends_with_elf(const char *s) { + int len; + + if (!s) + return 0; + + len = strlen(s); + + if (len < 4) + return 0; + + return sc_strcmp(s + len - 4, ".elf") == 0; +} + +static void print_usage(void) { + printf("Usage: ptime [args...]\n"); + printf("\n"); + printf("Examples:\n"); + printf(" ptime ls\n"); + printf(" ptime hexdump file.txt\n"); + printf(" ptime /bin/hexdump.elf file.txt\n"); +} + +// Read the system uptime in milliseconds by reading /proc/uptime and parsing the first number (seconds). +static unsigned long long read_uptime_ms(void) { + char buf[64]; + int fd; + int bytes; + int seconds; + + fd = sys_open("/proc/uptime", "r"); + + if (fd < 0) + return 0; + + bytes = sys_read(fd, buf, sizeof(buf) - 1); + + sys_close(fd); + + if (bytes <= 0) + return 0; + + buf[bytes] = 0; + + seconds = atoi(buf); + + return (unsigned long long)seconds * 1000ULL; +} + +// Build the command line for execution +// If the first argument contains a slash, use it as is. Otherwise, prepend "/bin/" and append ".elf" if it doesn't already end with ".elf". +static void build_command_line(int argc, char **argv, char *out) { + int i; + + out[0] = 0; + + if (has_slash(argv[1])) { + strcat(out, argv[1]); + } else { + strcat(out, "/bin/"); + strcat(out, argv[1]); + + if (!ends_with_elf(argv[1])) { + strcat(out, ".elf"); + } + } + + for (i = 2; i < argc; i++) { + strcat(out, " "); + strcat(out, argv[i]); + } +} + +int main(int argc, char **argv) { + char cmdline[CMDLINE_MAX]; + + unsigned long long start; + unsigned long long end; + unsigned long long elapsed; + + int ret; + + if (argc < 2) { + print_usage(); + return 1; + } + + if (sc_strcmp(argv[1], "-h") == 0 || + sc_strcmp(argv[1], "--help") == 0) { + print_usage(); + return 0; + } + + build_command_line(argc, argv, cmdline); + + start = read_uptime_ms(); + + ret = system(cmdline); + + end = read_uptime_ms(); + + if (end >= start) + elapsed = end - start; + else + elapsed = 0; + + printf("\n"); + printf("Command: %s\n", cmdline); + printf("Exit code: %d\n", ret); + printf("Elapsed: %llu ms\n", elapsed); + + return ret; +} From 11593b1b2384ae1199dc1fbe743ddc5499d3ba7d Mon Sep 17 00:00:00 2001 From: Lluciocc <114759545+Lluciocc@users.noreply.github.com> Date: Tue, 12 May 2026 09:06:05 +0200 Subject: [PATCH 05/24] Add ptime command help message --- src/userland/cli/help.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/userland/cli/help.c b/src/userland/cli/help.c index 20ff62a..83ee46b 100644 --- a/src/userland/cli/help.c +++ b/src/userland/cli/help.c @@ -38,6 +38,7 @@ int main(int argc, char **argv) { printf("clear - Clear the screen\n"); printf("exit - Exit the terminal\n"); printf("net - Network tools\n"); + printf("ptime - Measure command execution time\n"); printf("\nHint: Use Ctrl+C to force quit any running application.\n"); return 0; } From 0075493fbaa5e22f92a6bbb88a74ed5a09527c40 Mon Sep 17 00:00:00 2001 From: Lluciocc <114759545+Lluciocc@users.noreply.github.com> Date: Tue, 12 May 2026 18:31:25 +0200 Subject: [PATCH 06/24] Rename object files in Makefile to include 'libc_' prefix --- src/userland/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/userland/Makefile b/src/userland/Makefile index 5b80484..a0d6906 100644 --- a/src/userland/Makefile +++ b/src/userland/Makefile @@ -20,7 +20,7 @@ $(if $(filter doom,$1),$(APP_METADATA_SOURCE_DOOM),$(if $(filter lua,$1),$(APP_M endef LIBC_SOURCES = $(wildcard libc/*.c) -LIBC_OBJS = $(patsubst libc/%.c, $(BIN_DIR)/%.o, $(LIBC_SOURCES)) $(BIN_DIR)/crt0.o $(BIN_DIR)/libwidget.o $(BIN_DIR)/stb_image.o +LIBC_OBJS = $(patsubst libc/%.c, $(BIN_DIR)/libc_%.o, $(LIBC_SOURCES)) $(BIN_DIR)/crt0.o $(BIN_DIR)/libwidget.o $(BIN_DIR)/stb_image.o VPATH = cli gui sys games libc net cli/third_party vpath %.c cli gui sys games libc net cli/third_party @@ -46,7 +46,7 @@ $(BIN_DIR): $(BIN_DIR)/crt0.o: crt0.asm $(AS) -f elf64 $< -o $@ -$(BIN_DIR)/%.o: libc/%.c | $(BIN_DIR) +$(BIN_DIR)/libc_%.o: libc/%.c | $(BIN_DIR) $(CC) $(CFLAGS) -c $< -o $@ $(BIN_DIR)/libwidget.o: ../wm/libwidget.c | $(BIN_DIR) From c275da6145d4d8e1a7e7ee8742663115c9dc0230 Mon Sep 17 00:00:00 2001 From: Lluciocc <114759545+Lluciocc@users.noreply.github.com> Date: Tue, 12 May 2026 18:32:36 +0200 Subject: [PATCH 07/24] Rename ptime to time --- src/userland/cli/{ptime.c => time.c} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename src/userland/cli/{ptime.c => time.c} (94%) diff --git a/src/userland/cli/ptime.c b/src/userland/cli/time.c similarity index 94% rename from src/userland/cli/ptime.c rename to src/userland/cli/time.c index 10a411e..9a8ffaa 100644 --- a/src/userland/cli/ptime.c +++ b/src/userland/cli/time.c @@ -40,12 +40,12 @@ static int ends_with_elf(const char *s) { } static void print_usage(void) { - printf("Usage: ptime [args...]\n"); + printf("Usage: time [args...]\n"); printf("\n"); printf("Examples:\n"); - printf(" ptime ls\n"); - printf(" ptime hexdump file.txt\n"); - printf(" ptime /bin/hexdump.elf file.txt\n"); + printf(" time ls\n"); + printf(" time hexdump file.txt\n"); + printf(" time /bin/hexdump.elf file.txt\n"); } // Read the system uptime in milliseconds by reading /proc/uptime and parsing the first number (seconds). From 078ad437a5d38939e26633a09334b3ac273ad00b Mon Sep 17 00:00:00 2001 From: Lluciocc <114759545+Lluciocc@users.noreply.github.com> Date: Tue, 12 May 2026 18:33:08 +0200 Subject: [PATCH 08/24] Rename 'ptime' command to 'time' in help text --- src/userland/cli/help.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/userland/cli/help.c b/src/userland/cli/help.c index 83ee46b..8f651e3 100644 --- a/src/userland/cli/help.c +++ b/src/userland/cli/help.c @@ -38,7 +38,7 @@ int main(int argc, char **argv) { printf("clear - Clear the screen\n"); printf("exit - Exit the terminal\n"); printf("net - Network tools\n"); - printf("ptime - Measure command execution time\n"); + printf("time - Measure command execution time\n"); printf("\nHint: Use Ctrl+C to force quit any running application.\n"); return 0; } From 2580700ff928e2bceb68c88848530dae882c66d1 Mon Sep 17 00:00:00 2001 From: Lluciocc <114759545+Lluciocc@users.noreply.github.com> Date: Tue, 12 May 2026 18:40:27 +0200 Subject: [PATCH 09/24] Update time.c --- src/userland/cli/time.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/userland/cli/time.c b/src/userland/cli/time.c index 9a8ffaa..96776a1 100644 --- a/src/userland/cli/time.c +++ b/src/userland/cli/time.c @@ -2,7 +2,8 @@ // This software is released under the GNU General Public License v3.0. See LICENSE file for details. // This header needs to maintain in any file it is present in, as per the GPL license terms. -#include "../libc/stdlib.h" +#include "stdlib.h" +#include "syscall.h" #define CMDLINE_MAX 512 From e313e9dfcc415cc5cff552a21618590d99ebcbec Mon Sep 17 00:00:00 2001 From: whitehai11 Date: Tue, 12 May 2026 22:57:15 +0200 Subject: [PATCH 10/24] feat(cli): add grep command Implements grep for searching text inside files. Supported options: -n Show line numbers -i Case-insensitive search -c Print match count only -h Show help Closes part of #22 --- src/userland/cli/grep.c | 148 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 src/userland/cli/grep.c diff --git a/src/userland/cli/grep.c b/src/userland/cli/grep.c new file mode 100644 index 0000000..aaa69ad --- /dev/null +++ b/src/userland/cli/grep.c @@ -0,0 +1,148 @@ +// Copyright (c) 2023-2026 Chris (boreddevnl) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. +// BOREDOS_APP_DESC: Search for text inside a file. + +#include "../libc/syscall.h" +#include "../libc/stdlib.h" +#include "../libc/string.h" +#include "../libc/stdio.h" + +#define READ_BUF_SIZE 4096 +#define LINE_BUF_SIZE 1024 + +static int sc_strcmp(const char *a, const char *b) { + while (*a && *a == *b) { a++; b++; } + return (unsigned char)*a - (unsigned char)*b; +} + +static void print_usage(void) { + printf("Usage: grep [options] \n"); + printf("\n"); + printf("Search for text inside a file.\n"); + printf("\n"); + printf("Options:\n"); + printf(" -n Show line numbers\n"); + printf(" -i Case-insensitive search\n"); + printf(" -c Print match count only\n"); + printf(" -h Show this help\n"); +} + +static char to_lower(char c) { + if (c >= 'A' && c <= 'Z') return c + 32; + return c; +} + +static int str_contains(const char *haystack, const char *needle, int ignore_case) { + int h_len = (int)strlen(haystack); + int n_len = (int)strlen(needle); + + if (n_len == 0) return 1; + if (n_len > h_len) return 0; + + for (int i = 0; i <= h_len - n_len; i++) { + int match = 1; + for (int j = 0; j < n_len; j++) { + char h = ignore_case ? to_lower(haystack[i + j]) : haystack[i + j]; + char n = ignore_case ? to_lower(needle[j]) : needle[j]; + if (h != n) { match = 0; break; } + } + if (match) return 1; + } + return 0; +} + +int main(int argc, char **argv) { + int show_numbers = 0; + int ignore_case = 0; + int count_only = 0; + int arg_offset = 1; + + for (int i = 1; i < argc; i++) { + if (argv[i][0] != '-') break; + if (sc_strcmp(argv[i], "-n") == 0) { + show_numbers = 1; arg_offset++; + } else if (sc_strcmp(argv[i], "-i") == 0) { + ignore_case = 1; arg_offset++; + } else if (sc_strcmp(argv[i], "-c") == 0) { + count_only = 1; arg_offset++; + } else if (sc_strcmp(argv[i], "-h") == 0 || + sc_strcmp(argv[i], "--help") == 0) { + print_usage(); + return 0; + } else { + printf("grep: unknown option '%s'\n", argv[i]); + return 1; + } + } + + if (argc - arg_offset < 2) { + print_usage(); + return 1; + } + + const char *pattern = argv[arg_offset]; + const char *path = argv[arg_offset + 1]; + + int fd = sys_open(path, "r"); + if (fd < 0) { + printf("grep: cannot open '%s'\n", path); + return 1; + } + + static char read_buf[READ_BUF_SIZE]; + static char line[LINE_BUF_SIZE]; + int line_pos = 0; + int line_num = 0; + int match_cnt = 0; + + while (1) { + int bytes = sys_read(fd, read_buf, READ_BUF_SIZE); + if (bytes <= 0) break; + + for (int i = 0; i < bytes; i++) { + char c = read_buf[i]; + + if (c == '\n' || line_pos >= LINE_BUF_SIZE - 1) { + line[line_pos] = '\0'; + line_num++; + + if (str_contains(line, pattern, ignore_case)) { + match_cnt++; + if (!count_only) { + if (show_numbers) + printf("%d: %s\n", line_num, line); + else + printf("%s\n", line); + } + } + + line_pos = 0; + } else if (c != '\r') { + line[line_pos++] = c; + } + } + } + + // Handle last line if file doesn't end with newline + if (line_pos > 0) { + line[line_pos] = '\0'; + line_num++; + if (str_contains(line, pattern, ignore_case)) { + match_cnt++; + if (!count_only) { + if (show_numbers) + printf("%d: %s\n", line_num, line); + else + printf("%s\n", line); + } + } + } + + sys_close(fd); + + if (count_only) + printf("%d\n", match_cnt); + + return match_cnt > 0 ? 0 : 1; +} From 69d5f8fefffa1afd529649265b22d497b330d00c Mon Sep 17 00:00:00 2001 From: Lluciocc Date: Wed, 13 May 2026 08:11:00 +0200 Subject: [PATCH 11/24] Update header --- src/userland/cli/time.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/userland/cli/time.c b/src/userland/cli/time.c index 96776a1..c132fc9 100644 --- a/src/userland/cli/time.c +++ b/src/userland/cli/time.c @@ -1,4 +1,4 @@ -// Copyright (c) 2023-2026 Chris (boreddevnl) +// Copyright (c) 2026 Lluciocc (https://github.com/lluciocc) // This software is released under the GNU General Public License v3.0. See LICENSE file for details. // This header needs to maintain in any file it is present in, as per the GPL license terms. From 29e1b362ff5f74cacdcf39cf61e92ffb56b7ab98 Mon Sep 17 00:00:00 2001 From: Lluciocc Date: Wed, 13 May 2026 09:17:08 +0200 Subject: [PATCH 12/24] replacing with strcmp && Adding comments && adding helper when the command don't exist --- src/userland/cli/time.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/userland/cli/time.c b/src/userland/cli/time.c index c132fc9..cfd13db 100644 --- a/src/userland/cli/time.c +++ b/src/userland/cli/time.c @@ -5,17 +5,12 @@ #include "stdlib.h" #include "syscall.h" +// CMDLINE_MAX includes the trailing NUL, so at most 511 command-line bytes can +// be reconstructed here. Unchecked concatenation can overflow CMDLINE_MAX; all +// command-line construction in this file must go through the checked append +// helpers below. #define CMDLINE_MAX 512 -static int sc_strcmp(const char *a, const char *b) { - while (*a && *a == *b) { - a++; - b++; - } - - return (unsigned char)*a - (unsigned char)*b; -} - static int has_slash(const char *s) { while (s && *s) { if (*s == '/') @@ -37,7 +32,7 @@ static int ends_with_elf(const char *s) { if (len < 4) return 0; - return sc_strcmp(s + len - 4, ".elf") == 0; + return strcmp(s + len - 4, ".elf") == 0; } static void print_usage(void) { @@ -113,8 +108,8 @@ int main(int argc, char **argv) { return 1; } - if (sc_strcmp(argv[1], "-h") == 0 || - sc_strcmp(argv[1], "--help") == 0) { + if (strcmp(argv[1], "-h") == 0 || + strcmp(argv[1], "--help") == 0) { print_usage(); return 0; } @@ -135,7 +130,15 @@ int main(int argc, char **argv) { printf("\n"); printf("Command: %s\n", cmdline); printf("Exit code: %d\n", ret); + + if (ret == -1) { + printf("Command failed with non-zero exit code, not reporting time.\n"); + return ret; + } + printf("Elapsed: %llu ms\n", elapsed); + sys_system(SYSTEM_CMD_SLEEP, 1, 0, 0, 0); + return ret; } From 7a6769c2ec509fcf12319076aa336421859df905 Mon Sep 17 00:00:00 2001 From: whitehai11 Date: Wed, 13 May 2026 15:05:27 +0200 Subject: [PATCH 13/24] Extend grep with -r, -v, -l, -w and -x flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the following options to grep based on maintainer feedback: - `-r` / `-R` — recursive search through directories - `-v` — invert match, print non-matching lines - `-l` — print only filenames that contain matches - `-w` — match whole words only - `-x` — match whole lines only Multi-file output is automatically prefixed with the filename. --- src/userland/cli/grep.c | 276 ++++++++++++++++++++++++++++------------ 1 file changed, 195 insertions(+), 81 deletions(-) diff --git a/src/userland/cli/grep.c b/src/userland/cli/grep.c index aaa69ad..8b265f0 100644 --- a/src/userland/cli/grep.c +++ b/src/userland/cli/grep.c @@ -1,4 +1,4 @@ -// Copyright (c) 2023-2026 Chris (boreddevnl) +// Copyright (c) 2026 maro (whitehai11) // This software is released under the GNU General Public License v3.0. See LICENSE file for details. // This header needs to maintain in any file it is present in, as per the GPL license terms. // BOREDOS_APP_DESC: Search for text inside a file. @@ -10,6 +10,24 @@ #define READ_BUF_SIZE 4096 #define LINE_BUF_SIZE 1024 +#define MAX_PATH 512 +#define MAX_ENTRIES 256 + +// Flags +static int g_show_numbers = 0; +static int g_ignore_case = 0; +static int g_count_only = 0; +static int g_invert = 0; // -v +static int g_files_only = 0; // -l +static int g_word_match = 0; // -w +static int g_line_match = 0; // -x +static int g_recursive = 0; // -r / -R +static int g_multi_file = 0; // more than one file → prefix output with filename + +static const char *g_pattern = NULL; + +// Total match count across all files (used for -c with -r) +static int g_total_matches = 0; static int sc_strcmp(const char *a, const char *b) { while (*a && *a == *b) { a++; b++; } @@ -17,15 +35,20 @@ static int sc_strcmp(const char *a, const char *b) { } static void print_usage(void) { - printf("Usage: grep [options] \n"); + printf("Usage: grep [options] [file...]\n"); printf("\n"); - printf("Search for text inside a file.\n"); + printf("Search for text inside files.\n"); printf("\n"); printf("Options:\n"); - printf(" -n Show line numbers\n"); - printf(" -i Case-insensitive search\n"); - printf(" -c Print match count only\n"); - printf(" -h Show this help\n"); + printf(" -n Show line numbers\n"); + printf(" -i Case-insensitive search\n"); + printf(" -c Print match count only\n"); + printf(" -v Invert match (print non-matching lines)\n"); + printf(" -l Print only filenames with matches\n"); + printf(" -w Match whole words only\n"); + printf(" -x Match whole lines only\n"); + printf(" -r, -R Recursive search in directories\n"); + printf(" -h Show this help\n"); } static char to_lower(char c) { @@ -33,6 +56,26 @@ static char to_lower(char c) { return c; } +static int is_word_char(char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '_'; +} + +/* Check if needle appears in haystack at position i as a whole word */ +static int match_at(const char *haystack, int i, int h_len, + const char *needle, int n_len, int ignore_case) { + for (int j = 0; j < n_len; j++) { + char h = ignore_case ? to_lower(haystack[i + j]) : haystack[i + j]; + char n = ignore_case ? to_lower(needle[j]) : needle[j]; + if (h != n) return 0; + } + if (g_word_match) { + if (i > 0 && is_word_char(haystack[i - 1])) return 0; + if (i + n_len < h_len && is_word_char(haystack[i + n_len])) return 0; + } + return 1; +} + static int str_contains(const char *haystack, const char *needle, int ignore_case) { int h_len = (int)strlen(haystack); int n_len = (int)strlen(needle); @@ -41,31 +84,146 @@ static int str_contains(const char *haystack, const char *needle, int ignore_cas if (n_len > h_len) return 0; for (int i = 0; i <= h_len - n_len; i++) { - int match = 1; - for (int j = 0; j < n_len; j++) { - char h = ignore_case ? to_lower(haystack[i + j]) : haystack[i + j]; - char n = ignore_case ? to_lower(needle[j]) : needle[j]; - if (h != n) { match = 0; break; } - } - if (match) return 1; + if (match_at(haystack, i, h_len, needle, n_len, ignore_case)) + return 1; } return 0; } +static int line_matches(const char *line) { + if (g_line_match) { + /* Whole line must equal pattern */ + int h_len = (int)strlen(line); + int n_len = (int)strlen(g_pattern); + if (h_len != n_len) return 0; + return match_at(line, 0, h_len, g_pattern, n_len, g_ignore_case); + } + return str_contains(line, g_pattern, g_ignore_case); +} + +/* Grep a single open file descriptor, printing results prefixed by filename if needed */ +static int grep_fd(int fd, const char *filename) { + static char read_buf[READ_BUF_SIZE]; + static char line[LINE_BUF_SIZE]; + int line_pos = 0; + int line_num = 0; + int match_cnt = 0; + + /* Helper: process one complete line */ + #define PROCESS_LINE() do { \ + line[line_pos] = '\0'; \ + line_num++; \ + int matched = line_matches(line); \ + if (g_invert) matched = !matched; \ + if (matched) { \ + match_cnt++; \ + if (!g_count_only && !g_files_only) { \ + if (g_multi_file) printf("%s:", filename); \ + if (g_show_numbers) printf("%d:", line_num); \ + printf("%s\n", line); \ + } \ + } \ + line_pos = 0; \ + } while (0) + + while (1) { + int bytes = sys_read(fd, read_buf, READ_BUF_SIZE); + if (bytes <= 0) break; + + for (int i = 0; i < bytes; i++) { + char c = read_buf[i]; + if (c == '\n' || line_pos >= LINE_BUF_SIZE - 1) { + PROCESS_LINE(); + } else if (c != '\r') { + line[line_pos++] = c; + } + } + } + if (line_pos > 0) PROCESS_LINE(); + + #undef PROCESS_LINE + + if (g_count_only) + printf("%s%d\n", g_multi_file ? filename : "", match_cnt > 0 ? match_cnt : 0); + + if (g_files_only && match_cnt > 0) + printf("%s\n", filename); + + g_total_matches += match_cnt; + return match_cnt; +} + +static int grep_file(const char *path) { + int fd = sys_open(path, "r"); + if (fd < 0) { + printf("grep: cannot open '%s'\n", path); + return 0; + } + int n = grep_fd(fd, path); + sys_close(fd); + return n; +} + +static void grep_recursive(const char *path) { + FAT32_FileInfo info; + if (sys_get_file_info(path, &info) < 0) { + printf("grep: cannot access '%s'\n", path); + return; + } + + if (!info.is_directory) { + grep_file(path); + return; + } + + FAT32_FileInfo entries[MAX_ENTRIES]; + int count = sys_list(path, entries, MAX_ENTRIES); + if (count < 0) return; + + for (int i = 0; i < count; i++) { + const char *name = entries[i].name; + if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) + continue; + + char full[MAX_PATH]; + int plen = (int)strlen(path); + int nlen = (int)strlen(name); + if (plen + 1 + nlen + 1 > MAX_PATH) continue; + + int slash = (plen == 1 && path[0] == '/') ? 0 : 1; + for (int j = 0; j < plen; j++) full[j] = path[j]; + if (slash) full[plen] = '/'; + for (int j = 0; j <= nlen; j++) full[plen + slash + j] = name[j]; + + if (entries[i].is_directory) + grep_recursive(full); + else + grep_file(full); + } +} + int main(int argc, char **argv) { - int show_numbers = 0; - int ignore_case = 0; - int count_only = 0; - int arg_offset = 1; + int arg_offset = 1; for (int i = 1; i < argc; i++) { if (argv[i][0] != '-') break; if (sc_strcmp(argv[i], "-n") == 0) { - show_numbers = 1; arg_offset++; + g_show_numbers = 1; arg_offset++; } else if (sc_strcmp(argv[i], "-i") == 0) { - ignore_case = 1; arg_offset++; + g_ignore_case = 1; arg_offset++; } else if (sc_strcmp(argv[i], "-c") == 0) { - count_only = 1; arg_offset++; + g_count_only = 1; arg_offset++; + } else if (sc_strcmp(argv[i], "-v") == 0) { + g_invert = 1; arg_offset++; + } else if (sc_strcmp(argv[i], "-l") == 0) { + g_files_only = 1; arg_offset++; + } else if (sc_strcmp(argv[i], "-w") == 0) { + g_word_match = 1; arg_offset++; + } else if (sc_strcmp(argv[i], "-x") == 0) { + g_line_match = 1; arg_offset++; + } else if (sc_strcmp(argv[i], "-r") == 0 || + sc_strcmp(argv[i], "-R") == 0) { + g_recursive = 1; arg_offset++; } else if (sc_strcmp(argv[i], "-h") == 0 || sc_strcmp(argv[i], "--help") == 0) { print_usage(); @@ -76,73 +234,29 @@ int main(int argc, char **argv) { } } + if (argc - arg_offset < 1) { + print_usage(); + return 1; + } + + g_pattern = argv[arg_offset]; + + // Need at least a path when not reading stdin if (argc - arg_offset < 2) { print_usage(); return 1; } - const char *pattern = argv[arg_offset]; - const char *path = argv[arg_offset + 1]; + // Multiple files → prefix output with filename + g_multi_file = (argc - arg_offset > 2) || g_recursive; - int fd = sys_open(path, "r"); - if (fd < 0) { - printf("grep: cannot open '%s'\n", path); - return 1; - } - - static char read_buf[READ_BUF_SIZE]; - static char line[LINE_BUF_SIZE]; - int line_pos = 0; - int line_num = 0; - int match_cnt = 0; - - while (1) { - int bytes = sys_read(fd, read_buf, READ_BUF_SIZE); - if (bytes <= 0) break; - - for (int i = 0; i < bytes; i++) { - char c = read_buf[i]; - - if (c == '\n' || line_pos >= LINE_BUF_SIZE - 1) { - line[line_pos] = '\0'; - line_num++; - - if (str_contains(line, pattern, ignore_case)) { - match_cnt++; - if (!count_only) { - if (show_numbers) - printf("%d: %s\n", line_num, line); - else - printf("%s\n", line); - } - } - - line_pos = 0; - } else if (c != '\r') { - line[line_pos++] = c; - } + for (int i = arg_offset + 1; i < argc; i++) { + if (g_recursive) { + grep_recursive(argv[i]); + } else { + grep_file(argv[i]); } } - // Handle last line if file doesn't end with newline - if (line_pos > 0) { - line[line_pos] = '\0'; - line_num++; - if (str_contains(line, pattern, ignore_case)) { - match_cnt++; - if (!count_only) { - if (show_numbers) - printf("%d: %s\n", line_num, line); - else - printf("%s\n", line); - } - } - } - - sys_close(fd); - - if (count_only) - printf("%d\n", match_cnt); - - return match_cnt > 0 ? 0 : 1; + return g_total_matches > 0 ? 0 : 1; } From bb57a4eeff5d3788ef161859ffe953c650282273 Mon Sep 17 00:00:00 2001 From: whitehai11 Date: Wed, 13 May 2026 23:43:30 +0200 Subject: [PATCH 14/24] Improve uptime display in sysfetch Previously the uptime field only showed minutes. Now it displays a more detailed and human-readable format: Uptime: 3 mins Uptime: 2 hours, 15 mins Uptime: 1 day, 3 hours, 22 mins --- src/userland/cli/sysfetch.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/userland/cli/sysfetch.c b/src/userland/cli/sysfetch.c index 8bc07f2..ec953b3 100644 --- a/src/userland/cli/sysfetch.c +++ b/src/userland/cli/sysfetch.c @@ -323,13 +323,22 @@ int main(int argc, char **argv) { int b = sys_read(fd_u, u_buf, 63); u_buf[b] = 0; sys_close(fd_u); - int sec = atoi(u_buf); - int mins = sec / 60; + int sec = atoi(u_buf); + int days = sec / 86400; + int hrs = (sec % 86400) / 3600; + int mins = (sec % 3600) / 60; strcpy(info_lines[info_line_count], config.uptime_label); strcat(info_lines[info_line_count], ": "); - itoa(mins, temp_buf); - strcat(info_lines[info_line_count], temp_buf); - strcat(info_lines[info_line_count++], " mins"); + if (days > 0) { + itoa(days, temp_buf); strcat(info_lines[info_line_count], temp_buf); + strcat(info_lines[info_line_count], days == 1 ? " day, " : " days, "); + } + if (hrs > 0 || days > 0) { + itoa(hrs, temp_buf); strcat(info_lines[info_line_count], temp_buf); + strcat(info_lines[info_line_count], hrs == 1 ? " hour, " : " hours, "); + } + itoa(mins, temp_buf); strcat(info_lines[info_line_count], temp_buf); + strcat(info_lines[info_line_count++], mins == 1 ? " min" : " mins"); } } if (config.cpu_label[0]) { From fdd25b31cd8a8a884acd6e40f894f50c69faa2c0 Mon Sep 17 00:00:00 2001 From: Lluciocc Date: Thu, 14 May 2026 00:47:47 +0200 Subject: [PATCH 15/24] Feat: Implementing time command inside bsh.c --- src/userland/cli/bsh.c | 316 ++++++++++++++++++++++++++++++++-------- src/userland/cli/time.c | 144 ------------------ 2 files changed, 256 insertions(+), 204 deletions(-) delete mode 100644 src/userland/cli/time.c diff --git a/src/userland/cli/bsh.c b/src/userland/cli/bsh.c index 35b10a1..5f329df 100644 --- a/src/userland/cli/bsh.c +++ b/src/userland/cli/bsh.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "utf-8.h" #define MAX_LINE 512 @@ -37,6 +38,8 @@ typedef struct { static bsh_config_t g_cfg; static char g_paths[MAX_PATHS][MAX_PATH_LEN]; static int g_path_count = 0; +static char g_resolved_command_path[256]; +static int g_resolve_status = -1; static char g_history[MAX_HISTORY][MAX_LINE]; static int g_history_count = 0; @@ -665,55 +668,138 @@ static bool is_elf_file(const char *path) { return hdr[0] == 0x7F && hdr[1] == 'E' && hdr[2] == 'L' && hdr[3] == 'F'; } -static int resolve_command(const char *cmd, char *out, int out_len) { - if (!cmd || !cmd[0]) return -1; - - bool has_slash = false; +static bool contains_slash(const char *cmd) { + if (!cmd) return false; for (int i = 0; cmd[i]; i++) { - if (cmd[i] == '/') { has_slash = true; break; } + if (cmd[i] == '/') return true; + } + return false; +} + +static const char *env_get_value(char *const envp[], const char *name) { + int name_len; + + if (!envp || !name || !name[0]) return NULL; + + name_len = (int)strlen(name); + + for (int i = 0; envp[i]; i++) { + if (starts_with(envp[i], name) && envp[i][name_len] == '=') { + return envp[i] + name_len + 1; + } } - if (has_slash) { - if (sys_exists(cmd)) { - if (!is_file_path(cmd)) return -2; - if (!is_elf_file(cmd)) return -3; - str_copy(out, cmd, out_len); - return 0; - } - if (!ends_with(cmd, ".elf")) { - char temp[256]; - str_copy(temp, cmd, sizeof(temp)); - str_append(temp, ".elf", sizeof(temp)); - if (sys_exists(temp)) { - if (!is_file_path(temp)) return -2; - if (!is_elf_file(temp)) return -3; - str_copy(out, temp, out_len); - return 0; + return NULL; +} + +static void build_path_candidate(char *out, int out_len, const char *dir, int dir_len, const char *cmd) { + int pos = 0; + + if (!out || out_len <= 0) return; + out[0] = 0; + if (!dir || !cmd) return; + + for (int i = 0; i < dir_len && dir[i] && pos < out_len - 1; i++) { + out[pos++] = dir[i]; + } + out[pos] = 0; + + if (pos > 0 && out[pos - 1] != '/' && pos < out_len - 1) { + out[pos++] = '/'; + out[pos] = 0; + } + + for (int i = 0; cmd[i] && pos < out_len - 1; i++) { + out[pos++] = cmd[i]; + } + out[pos] = 0; +} + +static int accept_command_candidate(const char *candidate) { + if (access(candidate, X_OK) != 0) return -1; + if (!is_file_path(candidate)) return -2; + if (!is_elf_file(candidate)) return -3; + + str_copy(g_resolved_command_path, candidate, sizeof(g_resolved_command_path)); + return 0; +} + +static char *resolve_in_path_string(const char *cmd, const char *path_str) { + int first_error = -1; + int start = 0; + int i = 0; + + if (!path_str || !path_str[0]) { + g_resolve_status = -1; + return NULL; + } + + while (1) { + if (path_str[i] == ':' || path_str[i] == 0) { + int len = i - start; + + if (len > 0) { + char candidate[256]; + int res; + + build_path_candidate(candidate, sizeof(candidate), path_str + start, len, cmd); + res = accept_command_candidate(candidate); + if (res == 0) { + g_resolve_status = 0; + return g_resolved_command_path; + } + if (res != -1 && first_error == -1) first_error = res; + + if (!ends_with(cmd, ".elf")) { + str_append(candidate, ".elf", sizeof(candidate)); + res = accept_command_candidate(candidate); + if (res == 0) { + g_resolve_status = 0; + return g_resolved_command_path; + } + if (res != -1 && first_error == -1) first_error = res; + } } + + start = i + 1; } - return -1; + + if (path_str[i] == 0) break; + i++; } - for (int i = 0; i < g_path_count; i++) { - char temp[256]; - temp[0] = 0; - str_copy(temp, g_paths[i], sizeof(temp)); - if (temp[0] && temp[strlen(temp) - 1] != '/') str_append(temp, "/", sizeof(temp)); - str_append(temp, cmd, sizeof(temp)); - if (sys_exists(temp) && is_file_path(temp) && is_elf_file(temp)) { - str_copy(out, temp, out_len); - return 0; - } - if (!ends_with(cmd, ".elf")) { - str_append(temp, ".elf", sizeof(temp)); - if (sys_exists(temp) && is_file_path(temp) && is_elf_file(temp)) { - str_copy(out, temp, out_len); - return 0; - } - } + g_resolve_status = first_error; + return NULL; +} + +static char *resolve_command_path(const char *cmd, char *const envp[]) { + const char *path; + int res; + + g_resolved_command_path[0] = 0; + g_resolve_status = -1; + + if (!cmd || !cmd[0]) return NULL; + + if (contains_slash(cmd)) { + res = accept_command_candidate(cmd); + g_resolve_status = res; + return (res == 0) ? g_resolved_command_path : NULL; } - return -1; + path = env_get_value(envp, "PATH"); + if (!path) path = g_cfg.path; + + return resolve_in_path_string(cmd, path); +} + +static int resolve_command(const char *cmd, char *out, int out_len) { + char *resolved = resolve_command_path(cmd, NULL); + + if (!resolved) return g_resolve_status; + + str_copy(out, resolved, out_len); + return 0; } static void build_args_string(int argc, char *argv[], int start, char *out, int out_len) { @@ -783,7 +869,7 @@ static int collect_command_matches(const char *prefix, char matches[][MAX_MATCH_ int count = 0; const char *builtins[] = { "cd", "pwd", "ls", "cat", "echo", "clear", "mkdir", "rm", - "touch", "cp", "mv", "man", "alias", "unalias", ".", "exit" + "touch", "cp", "mv", "man", "alias", "unalias", "time", ".", "exit" }; for (int i = 0; i < (int)(sizeof(builtins) / sizeof(builtins[0])); i++) { if (starts_with(builtins[i], prefix)) count = add_match_unique(matches, count, builtins[i]); @@ -897,22 +983,145 @@ static void show_matches(const char *prompt_tmpl, const char *line, int len, cha redraw_input(prompt_tmpl, line, len, len); } -static void wait_for_pid(int pid) { +static int wait_for_pid_status(int pid, int *status) { while (1) { - int rc = sys_waitpid(pid, NULL, 1); - if (rc == pid || rc < 0) break; + int child_status = 0; + int rc = sys_waitpid(pid, &child_status, 1); + if (rc == pid) { + if (status) *status = child_status; + return 0; + } + if (rc < 0) return -1; if (g_tty_id >= 0) { int fg = sys_tty_get_fg(g_tty_id); - if (fg != pid) break; + if (fg != pid) return -1; } sleep(10); } } +static void wait_for_pid(int pid) { + wait_for_pid_status(pid, NULL); +} + static void cmd_clear(void) { sys_write(1, "\x1b[2J\x1b[H", 7); } +static void print_command_resolution_error(const char *who, const char *cmd, int res) { + set_color(g_color_error); + if (res == -2) { + printf("%s: is a directory: %s\n", who, cmd); + } else if (res == -3) { + printf("%s: not executable: %s\n", who, cmd); + } else { + printf("%s: command not found: %s\n", who, cmd); + } + reset_color(); +} + +static void builtin_time_usage(void) { + printf("Usage: time [args...]\n"); + printf("\n"); + printf("Examples:\n"); + printf(" time ls\n"); + printf(" time hexdump file.txt\n"); + printf(" time /bin/hexdump.elf file.txt\n"); +} + +// Reads the system uptime in milliseconds by parsing /proc/uptime +// before and after running the command, then calculating the difference. +static unsigned long long read_uptime_ms(void) { + char buf[64]; + int fd; + int bytes; + int seconds; + + fd = sys_open("/proc/uptime", "r"); + if (fd < 0) return 0; + + bytes = sys_read(fd, buf, sizeof(buf) - 1); + sys_close(fd); + + if (bytes <= 0) return 0; + + buf[bytes] = 0; + seconds = atoi(buf); + + return (unsigned long long)seconds * 1000ULL; +} + +static int builtin_time(int argc, char *argv[]) { + char *resolved; + char full_path[256]; + char args_buf[256]; + char cmdline[MAX_LINE]; + unsigned long long start; + unsigned long long end; + unsigned long long elapsed; + int pid = -1; + int ret = -1; + + if (argc < 2) { + builtin_time_usage(); + return 1; + } + + if (str_eq(argv[1], "-h") || str_eq(argv[1], "--help")) { + builtin_time_usage(); + return 0; + } + + resolved = resolve_command_path(argv[1], NULL); + if (!resolved) { + print_command_resolution_error("time", argv[1], g_resolve_status); + return 1; + } + str_copy(full_path, resolved, sizeof(full_path)); + + build_args_string(argc, argv, 2, args_buf, sizeof(args_buf)); + + str_copy(cmdline, full_path, sizeof(cmdline)); + if (args_buf[0]) { + str_append(cmdline, " ", sizeof(cmdline)); + str_append(cmdline, args_buf, sizeof(cmdline)); + } + + start = read_uptime_ms(); + + for (int attempt = 0; attempt < 5; attempt++) { + pid = sys_spawn(full_path, args_buf[0] ? args_buf : NULL, SPAWN_FLAG_TERMINAL | SPAWN_FLAG_INHERIT_TTY, 0); + if (pid >= 0) break; + sleep(10); + } + + if (pid >= 0) { + if (g_tty_id >= 0) sys_tty_set_fg(g_tty_id, pid); + if (wait_for_pid_status(pid, &ret) != 0) ret = -1; + if (g_tty_id >= 0) sys_tty_set_fg(g_tty_id, 0); + } + + end = read_uptime_ms(); + + if (end >= start) elapsed = end - start; + else elapsed = 0; + + printf("\n"); + printf("Command: %s\n", cmdline); + printf("Exit code: %d\n", ret); + + if (ret == -1) { + printf("Command failed with non-zero exit code, not reporting time.\n"); + return ret; + } + + printf("Elapsed: %llu ms\n", elapsed); + + sys_system(SYSTEM_CMD_SLEEP, 1, 0, 0, 0); + + return ret; +} + static int builtin_cd(int argc, char *argv[]) { const char *path = (argc > 1) ? argv[1] : "/"; if (chdir(path) != 0) { @@ -1292,6 +1501,7 @@ static int execute_builtin(int argc, char *argv[]) { if (str_eq(argv[0], "man")) return builtin_man(argc, argv); if (str_eq(argv[0], "alias")) return builtin_alias(argc, argv); if (str_eq(argv[0], "unalias")) return builtin_unalias(argc, argv); + if (str_eq(argv[0], "time")) return builtin_time(argc, argv); if (str_eq(argv[0], ".")) { if (argc < 2) { set_color(g_color_error); @@ -1347,22 +1557,8 @@ static int execute_line_inner(const char *line, int depth) { char full_path[256]; int cmd_res = resolve_command(argv[0], full_path, sizeof(full_path)); - if (cmd_res == -2) { - set_color(g_color_error); - printf("bsh: is a directory: %s\n", argv[0]); - reset_color(); - return 1; - } - if (cmd_res == -3) { - set_color(g_color_error); - printf("bsh: not executable: %s\n", argv[0]); - reset_color(); - return 1; - } if (cmd_res != 0) { - set_color(g_color_error); - printf("bsh: command not found: %s\n", argv[0]); - reset_color(); + print_command_resolution_error("bsh", argv[0], cmd_res); return 1; } diff --git a/src/userland/cli/time.c b/src/userland/cli/time.c deleted file mode 100644 index cfd13db..0000000 --- a/src/userland/cli/time.c +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) 2026 Lluciocc (https://github.com/lluciocc) -// This software is released under the GNU General Public License v3.0. See LICENSE file for details. -// This header needs to maintain in any file it is present in, as per the GPL license terms. - -#include "stdlib.h" -#include "syscall.h" - -// CMDLINE_MAX includes the trailing NUL, so at most 511 command-line bytes can -// be reconstructed here. Unchecked concatenation can overflow CMDLINE_MAX; all -// command-line construction in this file must go through the checked append -// helpers below. -#define CMDLINE_MAX 512 - -static int has_slash(const char *s) { - while (s && *s) { - if (*s == '/') - return 1; - s++; - } - - return 0; -} - -static int ends_with_elf(const char *s) { - int len; - - if (!s) - return 0; - - len = strlen(s); - - if (len < 4) - return 0; - - return strcmp(s + len - 4, ".elf") == 0; -} - -static void print_usage(void) { - printf("Usage: time [args...]\n"); - printf("\n"); - printf("Examples:\n"); - printf(" time ls\n"); - printf(" time hexdump file.txt\n"); - printf(" time /bin/hexdump.elf file.txt\n"); -} - -// Read the system uptime in milliseconds by reading /proc/uptime and parsing the first number (seconds). -static unsigned long long read_uptime_ms(void) { - char buf[64]; - int fd; - int bytes; - int seconds; - - fd = sys_open("/proc/uptime", "r"); - - if (fd < 0) - return 0; - - bytes = sys_read(fd, buf, sizeof(buf) - 1); - - sys_close(fd); - - if (bytes <= 0) - return 0; - - buf[bytes] = 0; - - seconds = atoi(buf); - - return (unsigned long long)seconds * 1000ULL; -} - -// Build the command line for execution -// If the first argument contains a slash, use it as is. Otherwise, prepend "/bin/" and append ".elf" if it doesn't already end with ".elf". -static void build_command_line(int argc, char **argv, char *out) { - int i; - - out[0] = 0; - - if (has_slash(argv[1])) { - strcat(out, argv[1]); - } else { - strcat(out, "/bin/"); - strcat(out, argv[1]); - - if (!ends_with_elf(argv[1])) { - strcat(out, ".elf"); - } - } - - for (i = 2; i < argc; i++) { - strcat(out, " "); - strcat(out, argv[i]); - } -} - -int main(int argc, char **argv) { - char cmdline[CMDLINE_MAX]; - - unsigned long long start; - unsigned long long end; - unsigned long long elapsed; - - int ret; - - if (argc < 2) { - print_usage(); - return 1; - } - - if (strcmp(argv[1], "-h") == 0 || - strcmp(argv[1], "--help") == 0) { - print_usage(); - return 0; - } - - build_command_line(argc, argv, cmdline); - - start = read_uptime_ms(); - - ret = system(cmdline); - - end = read_uptime_ms(); - - if (end >= start) - elapsed = end - start; - else - elapsed = 0; - - printf("\n"); - printf("Command: %s\n", cmdline); - printf("Exit code: %d\n", ret); - - if (ret == -1) { - printf("Command failed with non-zero exit code, not reporting time.\n"); - return ret; - } - - printf("Elapsed: %llu ms\n", elapsed); - - sys_system(SYSTEM_CMD_SLEEP, 1, 0, 0, 0); - - return ret; -} From 7339183bf15f93a87485e71492ed7cee9f394af1 Mon Sep 17 00:00:00 2001 From: Lluciocc <114759545+Lluciocc@users.noreply.github.com> Date: Thu, 14 May 2026 01:00:15 +0200 Subject: [PATCH 16/24] Add 'time' command to terminal usage documentation --- docs/usage/terminal.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/usage/terminal.md b/docs/usage/terminal.md index 8cf7b37..5f84cf2 100644 --- a/docs/usage/terminal.md +++ b/docs/usage/terminal.md @@ -132,6 +132,7 @@ Below are some of the most used commands available in `/bin`: | `lsblk` | List block devices and partitions. | | `du` | Report disk usage for files and directories. | | `sysfetch` | Display system information and BoredOS branding. | +| `time` | Measure command execution time. | --- From 46566c766cd0dd83a95b960c6b940668dbf819d9 Mon Sep 17 00:00:00 2001 From: Lluciocc <114759545+Lluciocc@users.noreply.github.com> Date: Thu, 14 May 2026 10:20:56 +0200 Subject: [PATCH 17/24] Update bsh.c --- src/userland/cli/bsh.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/userland/cli/bsh.c b/src/userland/cli/bsh.c index 5e8262e..529823b 100644 --- a/src/userland/cli/bsh.c +++ b/src/userland/cli/bsh.c @@ -1033,10 +1033,6 @@ static int bsh_open_file(const char *path, const char *mode, bool *is_kernel) { return sys_open(path, mode); } -static void wait_for_pid(int pid) { - wait_for_pid_status(pid, NULL); -} - static void cmd_clear(void) { sys_write(1, "\x1b[2J\x1b[H", 7); } From 637670f8b0a2ec73dc5766b9a6db2feed8e049c6 Mon Sep 17 00:00:00 2001 From: "Myles \"Mellurboo\" Wilson" <102391892+Mellurboo@users.noreply.github.com> Date: Thu, 14 May 2026 14:22:32 +0100 Subject: [PATCH 18/24] ACPI: I2C Device Enumeration & Logging Function Accessability (#46) * ACPI I2C Device Enumeration and Updated docs with better access to logging techniques * updated copyright notices bit of a hastle on my end, still getting used to being this cool --- docs/architecture/ACPI/acpi_interface.md | 90 +++-- src/core/kconsole.h | 9 + src/core/kutils.c | 16 +- src/core/kutils.h | 1 + src/core/main.c | 8 +- src/drivers/{ => ACPI}/acpi.c | 21 +- src/drivers/ACPI/acpi.h | 35 ++ src/drivers/ACPI/acpi_aml.c | 417 +++++++++++++++++++++++ src/drivers/ACPI/acpi_aml.h | 96 ++++++ src/drivers/{ => ACPI}/acpi_structures.h | 4 + src/drivers/I2C/acpi_i2c.c | 115 +++++++ src/drivers/I2C/acpi_i2c.h | 20 ++ src/drivers/acpi.h | 25 -- 13 files changed, 804 insertions(+), 53 deletions(-) rename src/drivers/{ => ACPI}/acpi.c (91%) create mode 100644 src/drivers/ACPI/acpi.h create mode 100644 src/drivers/ACPI/acpi_aml.c create mode 100644 src/drivers/ACPI/acpi_aml.h rename src/drivers/{ => ACPI}/acpi_structures.h (91%) create mode 100644 src/drivers/I2C/acpi_i2c.c create mode 100644 src/drivers/I2C/acpi_i2c.h delete mode 100644 src/drivers/acpi.h diff --git a/docs/architecture/ACPI/acpi_interface.md b/docs/architecture/ACPI/acpi_interface.md index aaad768..42ed3bb 100644 --- a/docs/architecture/ACPI/acpi_interface.md +++ b/docs/architecture/ACPI/acpi_interface.md @@ -1,35 +1,87 @@
-

ACPI

-

ACPI Power Interface

+

ACPI

+

Power management and hardware enumeration via the Advanced Configuration and Power Interface.

-ACPI Subsystem +--- -BoredOS implements an ACPI subsystem which manages power state transitions. -The implementation lives -in `src/acpi/` +BoredOS implements an ACPI subsystem that handles power state transitions, hardware discovery, and I2C device enumeration from the firmware tables. The implementation lives in `src/drivers/ACPI/`. + +## Table Discovery + +At boot, the kernel requests the RSDP (Root System Description Pointer) from Limine. If the pointer is absent or fails its checksum, the kernel panics - this is a hard requirement, not a graceful fallback. + +From the RSDP, the kernel determines which top-level descriptor table to use: + +- **XSDT** (64-bit pointers) - used when RSDP revision ≥ 2 and a valid `xsdt_address` is present. +- **RSDT** (32-bit pointers) - fallback for older firmware. + +This selection is handled transparently by `acpi_get_sdt()`, which walks whichever table is available and returns the first SDT matching a given 4-char signature. + +> [!NOTE] +> The DSDT is a special case - it is not listed in the XSDT/RSDT. It is instead pointed to directly by the `dsdt` field in the FADT. Use `acpi_get_dsdt()` to retrieve it. --- -### Startup and Table Discovery +## FADT and ACPI Enable -The first thing that ACPI does at boot time is to locate the RSDP.This can simply be requested fromlimine. -If the pointer is not present or the pointer is not valid we panic!It is a hardware requirement +The FADT (Fixed ACPI Description Table, signature `"FACP"`) is located first. It contains the port addresses needed for power management and the SMI command port used to hand off hardware control from firmware to the OS. -If this is not present then we also check a checksum if that fails then the RSDP is also not valid and we panic - -XSDT vs RSDT Fallback -When the RSDP specifies aRevision of 2 or greater, and we have a valid XSDT Address then we should try and use the XSDT instead,otherwise we use the RSDT.This is automatically abstracted by `acpi_get_sdt()` +If `smi_cmd` and `acpi_enable` are both non-zero, the kernel writes the enable byte to the SMI command port and spins on `PM1a_CNT` bit 0 until the hardware acknowledges. A timeout here is a fatal error. --- -### Shutdown and Power Off +## MADT and Interrupt Routing - This works by writing an appropriate value to PM1 Control Block register, and will prompt hardware to move into S5 power state. +The MADT (Multiple APIC Description Table, signature `"APIC"`) is parsed for Interrupt Source Override entries (type 2). These describe cases where a legacy ISA IRQ is wired to a different GSI on the I/O APIC - for example, IRQ 0 (PIT) may be redirected to GSI 2 on some platforms. -### Performing the power off -Once the sleep types are identified and so on, this will continue as follows -On hardware that actually obeys ACPI standards this process should only perform step 1, and your machine should be turned off. To try and handle virtual machines or emulators that are known to fail at step 1, +Two functions expose this mapping to the rest of the kernel: - we send the virtual machine interrupts second in case writing bytes makes our hardware think it is acting up and we do not wish to cause an unnecessary interrupt +- `acpi_irq_to_gsi(irq)` - returns the GSI for a given IRQ, or the IRQ itself if no override exists. +- `acpi_irq_flags(irq)` - returns the polarity and trigger mode flags for the override. +--- + +## AML Parsing + +The DSDT and any SSDTs present in the firmware contain AML (ACPI Machine Language) bytecode describing the hardware namespace. BoredOS does not implement a full AML interpreter - instead `src/drivers/ACPI/acpi_aml.c` implements a targeted byte-stream walker sufficient for I2C device enumeration. + +The walker handles: + +- **`_HID`** - device identification, either as an inline string or a packed EISAID integer. +- **`_CRS`** - current resource settings; specifically scans for `I2cSerialBusV2` descriptors (large item tag `0x8E`) to extract slave address, bus speed, and addressing mode. +- **`_DSM`** - device-specific method; scans the raw method body for the I2C-HID GUID (`3cdff6f7-4267-4555-ad05-b30a3d8938de`) and extracts the HID descriptor register address from the static return package. +- **Power states** - records presence of `_PS0`, `_PS3`, `_PR0`, `_PR3` as flags. + +> [!IMPORTANT] +> `_CRS` and `_DSM` must be statically defined `Name()` objects for the walker to read them. Dynamically computed resources via AML method evaluation are not supported. + +--- + +## I2C Enumeration + +`acpi_i2c_enumerate()` is called at the end of `acpi_init()`. It walks the DSDT via `acpi_get_dsdt()` followed by all SSDTs found in the XSDT/RSDT, feeding each into `aml_walk_table()`. + +Devices are emitted into a flat table when both a `_HID` and a valid `I2cSerialBusV2` resource (non-zero `connection_speed`) are found. The result is accessible via: + +- `acpi_i2c_count()` - number of enumerated devices. +- `acpi_i2c_get(index)` - pointer to an `aml_i2c_dev_t` record containing the name, HID, slave address, speed, DSM result, and power flags. + +--- + +## Shutdown + +Performing a clean S5 (soft power-off) requires writing the correct `SLP_TYP` value to the PM1 Control Block. This value is firmware-specific and must be read from the `_S5_` object in the DSDT. + +`aml_parse_s5()` scans the DSDT AML for the `_S5_` name and extracts `SLP_TYPa` and `SLP_TYPb`, pre-shifted to bit position 10 ready for `outw`. If the parse fails, the kernel falls back to `SLP_TYP = 5`, which works on the majority of hardware. + +### Shutdown sequence + +On compliant hardware, step 1 is all that runs and the machine powers off immediately. The remaining steps target known virtual machine implementations that don't respond to standard ACPI. + +1. Write `SLP_TYPa | SLP_EN` to `PM1a_CNT_BLK` (and `PM1b_CNT_BLK` if present). +2. Write known virtualizer-specific magic values - QEMU (`0x604`), VirtualBox (`0x4004`), Bochs (`0xB004`), Cloud Hypervisor (`0x600`) - to trigger their respective shutdown paths. +3. Disable interrupts and halt indefinitely as a last resort. + +> [!NOTE] +> The virtualizer ports are sent after the ACPI write deliberately. On real hardware, sending those port values is harmless, but the ordering avoids any ambiguity about what triggered the power-off. \ No newline at end of file diff --git a/src/core/kconsole.h b/src/core/kconsole.h index c09bcf8..c3d1ad1 100644 --- a/src/core/kconsole.h +++ b/src/core/kconsole.h @@ -10,4 +10,13 @@ void kconsole_putc(char c); void kconsole_write(const char *s); void kconsole_set_active(bool active); +void serial_write(const char *str); +void serial_write_num_locked(uint32_t n); +void serial_write_num(uint32_t n); +void serial_write_hex_locked(uint64_t n); +void serial_write_hex(uint64_t n); + +void log_ok(const char *msg); +void log_fail(const char *msg); + #endif // KCONSOLE_H diff --git a/src/core/kutils.c b/src/core/kutils.c index 02086de..59111f5 100644 --- a/src/core/kutils.c +++ b/src/core/kutils.c @@ -5,7 +5,7 @@ #include "wm.h" #include "io.h" -#include "../drivers/acpi.h" +#include "../drivers/ACPI/acpi.h" void memset(void *dest, int val, size_t len) { unsigned char *ptr = (unsigned char *)dest; @@ -75,6 +75,20 @@ void strcpy(char *dest, const char *src) { *dest = 0; } +char *strncpy(char *dest, const char *src, size_t n) { + size_t i; + + for (i = 0; i < n && src[i] != '\0'; i++) { + dest[i] = src[i]; + } + + for (; i < n; i++) { + dest[i] = '\0'; + } + + return dest; +} + int atoi(const char *str) { int res = 0; int sign = 1; diff --git a/src/core/kutils.h b/src/core/kutils.h index 8019995..c453042 100644 --- a/src/core/kutils.h +++ b/src/core/kutils.h @@ -17,6 +17,7 @@ size_t strlen(const char *str); int strcmp(const char *s1, const char *s2); int strncmp(const char *s1, const char *s2, size_t n); void strcpy(char *dest, const char *src); +char *strncpy(char *dest, const char *src, size_t n); int atoi(const char *str); void itoa(int n, char *buf); void itoa_hex(uint64_t n, char *buf); diff --git a/src/core/main.c b/src/core/main.c index da5ac03..fd69d69 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -35,7 +35,7 @@ #include "sys/bootfs_state.h" #include "input/keymap.h" #include "input/keyboard.h" -#include "../drivers/acpi.h" +#include "../drivers/ACPI/acpi.h" extern void sysfs_init_subsystems(void); @@ -134,7 +134,7 @@ void serial_write(const char *str) { spinlock_release_irqrestore(&serial_lock, flags); } -static void serial_write_num_locked(uint32_t n) { +void serial_write_num_locked(uint32_t n) { if (n >= 10) serial_write_num_locked(n / 10); char c = '0' + (n % 10); while ((inb(0x3F8 + 5) & 0x20) == 0); @@ -148,7 +148,7 @@ void serial_write_num(uint32_t n) { spinlock_release_irqrestore(&serial_lock, flags); } -static void serial_write_hex_locked(uint64_t n) { +void serial_write_hex_locked(uint64_t n) { char *hex = "0123456789ABCDEF"; if (n >= 16) serial_write_hex_locked(n / 16); char c = hex[n % 16]; @@ -378,7 +378,7 @@ void kmain(void) { acpi_init(); process_init(); - + fat32_init(); log_ok("FAT32 ready"); diff --git a/src/drivers/acpi.c b/src/drivers/ACPI/acpi.c similarity index 91% rename from src/drivers/acpi.c rename to src/drivers/ACPI/acpi.c index 4c607be..0e59a41 100644 --- a/src/drivers/acpi.c +++ b/src/drivers/ACPI/acpi.c @@ -1,7 +1,12 @@ +// Copyright (c) 2026 Myles Wilson (myles@bleedkernel.com) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. + #include #include #include "acpi_structures.h" +#include "../I2C/acpi_i2c.h" #include "acpi.h" #include "../sys/idt.h" #include "../core/limine.h" @@ -36,7 +41,7 @@ static int acpi_checksum(void *ptr, size_t len) { return sum == 0; } -static void *acpi_get_rsdp(void){ +struct acpi_rsdp *acpi_get_rsdp(void){ extern volatile struct limine_rsdp_request acpi_rsdp_request; if (!acpi_rsdp_request.response) @@ -47,7 +52,7 @@ static void *acpi_get_rsdp(void){ return acpi_rsdp_request.response->address; } -static struct acpi_sdt *acpi_get_sdt(const char signature[4]) { +struct acpi_sdt *acpi_get_sdt(const char signature[4]) { if (acpi_rsdp->revision >= 2 && acpi_rsdp->xsdt_address) { struct acpi_xsdt *acpi_xsdt = (struct acpi_xsdt *)p2v(acpi_rsdp->xsdt_address); if (acpi_checksum(acpi_xsdt, acpi_xsdt->header.length)) { @@ -81,8 +86,8 @@ static struct acpi_sdt *acpi_get_sdt(const char signature[4]) { return NULL; } -static uint16_t SLP_TYPa = 0; -static uint16_t SLP_TYPb = 0; +uint16_t SLP_TYPa = 0; +uint16_t SLP_TYPb = 0; void acpi_parse_s5(void) { if (!acpi_fadt || !acpi_fadt->dsdt) return; @@ -199,9 +204,12 @@ int acpi_init(void){ ptr += h->length; } + acpi_i2c_enumerate(); + return 0; } + uint32_t acpi_irq_to_gsi(uint32_t irq) { for (size_t i = 0; i < iso_count; i++) { if (iso_table[i].source == irq) @@ -218,3 +226,8 @@ uint16_t acpi_irq_flags(uint32_t irq) { return 0; } + +struct acpi_sdt *acpi_get_dsdt(void) { + if (!acpi_fadt || !acpi_fadt->dsdt) return NULL; + return (struct acpi_sdt *)p2v((uintptr_t)acpi_fadt->dsdt); +} \ No newline at end of file diff --git a/src/drivers/ACPI/acpi.h b/src/drivers/ACPI/acpi.h new file mode 100644 index 0000000..ae80890 --- /dev/null +++ b/src/drivers/ACPI/acpi.h @@ -0,0 +1,35 @@ +// Copyright (c) 2026 Myles Wilson (myles@bleedkernel.com) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. + +#ifndef ACPI_H +#define ACPI_H + +//power stuff +#define PM1A_CNT fadt->pm1a_cnt_blk +#define PM1B_CNT fadt->pm1b_cnt_blk + +#define ACPI_S5 0x5 +#define SLP_EN (1 << 13) + +#define ACPI_PM1_SLEEP_CMD(slp_typ) (((slp_typ) << 10) | SLP_EN) + +extern uint16_t SLP_TYPa; +extern uint16_t SLP_TYPb; + +int acpi_init(void); + +uint32_t acpi_irq_to_gsi(uint32_t irq); +uint16_t acpi_irq_flags(uint32_t irq); + +struct acpi_rsdp *acpi_get_rsdp(void); +struct acpi_sdt *acpi_get_sdt(const char signature[4]); +struct acpi_sdt *acpi_get_dsdt(void); + +void acpi_parse_s5(void); + +// ACPI Functionality Implementations +// - do reboot properly +__attribute__((noreturn)) void acpi_shutdown(void); + +#endif \ No newline at end of file diff --git a/src/drivers/ACPI/acpi_aml.c b/src/drivers/ACPI/acpi_aml.c new file mode 100644 index 0000000..040d711 --- /dev/null +++ b/src/drivers/ACPI/acpi_aml.c @@ -0,0 +1,417 @@ +// Copyright (c) 2026 Myles Wilson (myles@bleedkernel.com) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. + +#include "acpi_aml.h" +#include "kutils.h" +#include "kconsole.h" +#include +#include + +/// @brief Decode AML PkgLength field, advance past it +/// @param pp in/out byte pointer, advanced past the field on return +/// @param end hard bound; returns 0 if field exceeds it +/// @return total package length including the PkgLength bytes, 0 on error +static size_t decode_pkglen(const uint8_t **pp, const uint8_t *end) { + if (*pp >= end) return 0; + uint8_t b0 = *(*pp)++; + uint8_t extra = b0 >> 6; + size_t len; + + if (extra == 0) { + len = b0 & 0x3F; // 1-byte form: bits[5:0] + } else { + len = b0 & 0x0F; // multi-byte form: bits[3:0] are low nibble + if (*pp + extra > end) return 0; + for (uint8_t i = 0; i < extra; i++) + len |= (size_t)(*(*pp)++) << (4 + 8 * i); + } + return len; +} + +/// @brief Decode the last NameSeg of an AML NameString into out +/// @param pp in/out byte pointer, advanced past the full name on return +/// @param end hard bound +/// @param out receives null-terminated 4-char NameSeg +static void decode_nameseg(const uint8_t **pp, const uint8_t *end, + char out[AML_NAME_LEN]) { + out[0] = '\0'; + if (*pp >= end) return; + + while (*pp < end && (**pp == '\\' || **pp == '^')) (*pp)++; + if (*pp >= end) return; + + uint8_t lead = **pp; + + if (lead == 0x00) { + (*pp)++; + return; + } else if (lead == 0x2E) { // DualNamePath two 4-char segs + (*pp)++; + if (*pp + 8 > end) return; + (*pp) += 4; + for (int i = 0; i < 4; i++) out[i] = (char)(*pp)[i]; + out[4] = '\0'; + (*pp) += 4; + } else if (lead == 0x2F) { // MultiNamePath count + N segs + (*pp)++; + if (*pp >= end) return; + uint8_t count = *(*pp)++; + if (*pp + (size_t)count * 4 > end) return; + const uint8_t *last = *pp + (count - 1) * 4; + for (int i = 0; i < 4; i++) out[i] = (char)last[i]; + out[4] = '\0'; + *pp += (size_t)count * 4; + } else { // plain 4-char NameSeg + if (*pp + 4 > end) return; + for (int i = 0; i < 4; i++) out[i] = (char)(*pp)[i]; + out[4] = '\0'; + (*pp) += 4; + } +} + +/// @brief Decode any AML integer data object (Zero, One, Byte, Word, DWord, QWord) +/// @param pp in/out byte pointer, advanced past the object on return +/// @param end hard bound +/// @return decoded integer value, 0 on unknown opcode (pointer not advanced) +static uint64_t decode_integer(const uint8_t **pp, const uint8_t *end) { + if (*pp >= end) return 0; + uint8_t op = *(*pp)++; + switch (op) { + case AML_ZERO_OP: return 0; + case AML_ONE_OP: return 1; + case AML_BYTE_PREFIX: + if (*pp + 1 > end) return 0; + return *(*pp)++; + case AML_WORD_PREFIX: { + if (*pp + 2 > end) return 0; + uint16_t v = (uint16_t)((*pp)[0]) | ((uint16_t)((*pp)[1]) << 8); + *pp += 2; return v; + } + case AML_DWORD_PREFIX: { + if (*pp + 4 > end) return 0; + uint32_t v = (uint32_t)((*pp)[0]) + | ((uint32_t)((*pp)[1]) << 8) + | ((uint32_t)((*pp)[2]) << 16) + | ((uint32_t)((*pp)[3]) << 24); + *pp += 4; return v; + } + case AML_QWORD_PREFIX: { + if (*pp + 8 > end) return 0; + uint64_t v = 0; + for (int i = 0; i < 8; i++) v |= (uint64_t)((*pp)[i]) << (8 * i); + *pp += 8; return v; + } + default: + (*pp)--; + return 0; + } +} + +/// @brief Convert 32-bit packed EISAID value to ASCII HID string (e.g. "PNP0C0E") +/// @param id EISAID as decoded by decode_integer (little-endian from AML) +/// @param out receives null-terminated 8-char string +static void eisaid_to_str(uint32_t id, char out[AML_HID_LEN]) { + uint32_t v = ((id & 0x000000FF) << 24) + | ((id & 0x0000FF00) << 8) + | ((id & 0x00FF0000) >> 8) + | ((id & 0xFF000000) >> 24); + + out[0] = (char)('@' + ((v >> 26) & 0x1F)); + out[1] = (char)('@' + ((v >> 21) & 0x1F)); + out[2] = (char)('@' + ((v >> 16) & 0x1F)); + + static const char hex[] = "0123456789ABCDEF"; + out[3] = hex[(v >> 12) & 0xF]; + out[4] = hex[(v >> 8) & 0xF]; + out[5] = hex[(v >> 4) & 0xF]; + out[6] = hex[(v >> 0) & 0xF]; + out[7] = '\0'; +} + +/// @brief Parse _HID data object (string or EISAID DWord) into dev->hid +/// @param pp in/out byte pointer positioned at the data object +/// @param end hard bound +/// @param dev target device record +static void parse_hid(const uint8_t **pp, const uint8_t *end, + aml_i2c_dev_t *dev) { + if (*pp >= end) return; + + if (**pp == AML_STRING_PREFIX) { + (*pp)++; + const char *s = (const char *)*pp; + strncpy(dev->hid, s, AML_HID_LEN - 1); + dev->hid[AML_HID_LEN - 1] = '\0'; + while (*pp < end && **pp) (*pp)++; + if (*pp < end) (*pp)++; + } else if (**pp == AML_DWORD_PREFIX) { + uint32_t id = (uint32_t)decode_integer(pp, end); + eisaid_to_str(id, dev->hid); + } +} + +/// @brief Parse _CRS Buffer for an I2cSerialBusV2 descriptor, fill dev CRS fields +/// @param pp in/out byte pointer positioned at BufferOp +/// @param end hard bound +/// @param dev receives slave_address, speed_hz, ten_bit_addr on success +static void parse_crs(const uint8_t **pp, const uint8_t *end, + aml_i2c_dev_t *dev) { + if (*pp >= end || **pp != AML_BUFFER_OP) return; + (*pp)++; + + const uint8_t *pkg_start = *pp; + size_t pkglen = decode_pkglen(pp, end); + if (!pkglen) return; + + const uint8_t *buf_end = pkg_start + pkglen; + if (buf_end > end) buf_end = end; + + decode_integer(pp, buf_end); // skip BufferSize + + const uint8_t *res = *pp; + while (res < buf_end) { + uint8_t tag = *res; + + if (tag == ACPI_RESOURCE_END_TAG) break; + + if (tag == ACPI_LARGE_I2C_SERIAL_BUS) { + if (res + sizeof(aml_i2c_resource_t) > buf_end) break; + const aml_i2c_resource_t *r = (const aml_i2c_resource_t *)res; + if (r->serial_bus_type == ACPI_I2C_SERIAL_BUS_TYPE) { + dev->slave_address = r->slave_address; + dev->speed_hz = r->connection_speed; + dev->ten_bit_addr = (r->type_specific_flags & 0x01) ? 1 : 0; + } + res += sizeof(uint8_t) + sizeof(uint16_t) + r->length; + continue; + } + + if (tag & 0x80) { + if (res + 3 > buf_end) break; + uint16_t len = (uint16_t)(res[1]) | ((uint16_t)(res[2]) << 8); + res += 3 + len; + } else { + res += 1 + (tag & 0x07); + } + } + + *pp = buf_end; +} + +static void record_power_state(const char name[AML_NAME_LEN], + aml_i2c_dev_t *dev) { + if (memcmp(name, "_PS0", 4) == 0) dev->power_flags |= AML_PWR_HAS_PS0; + else if (memcmp(name, "_PS3", 4) == 0) dev->power_flags |= AML_PWR_HAS_PS3; + else if (memcmp(name, "_PR0", 4) == 0) dev->power_flags |= AML_PWR_HAS_PR0; + else if (memcmp(name, "_PR3", 4) == 0) dev->power_flags |= AML_PWR_HAS_PR3; +} + +/// @brief Best-effort skip of one AML data object to keep the scan on track +/// @param pp in/out byte pointer, advanced past the object on return +/// @param end hard bound +static void skip_object(const uint8_t **pp, const uint8_t *end) { + if (*pp >= end) return; + uint8_t op = **pp; + switch (op) { + case AML_ZERO_OP: + case AML_ONE_OP: + (*pp)++; return; + case AML_BYTE_PREFIX: (*pp) += 2; return; + case AML_WORD_PREFIX: (*pp) += 3; return; + case AML_DWORD_PREFIX: (*pp) += 5; return; + case AML_QWORD_PREFIX: (*pp) += 9; return; + case AML_STRING_PREFIX: + (*pp)++; + while (*pp < end && **pp) (*pp)++; + if (*pp < end) (*pp)++; + return; + case AML_BUFFER_OP: + case AML_PACKAGE_OP: { + (*pp)++; + const uint8_t *start = *pp; + size_t len = decode_pkglen(pp, end); + if (len) *pp = start + len; + return; + } + default: + (*pp)++; + } +} + +/// @brief Scan one Device() scope body for _HID, _CRS, _DSM, and power objects +/// @param p first byte of the scope body (after NameSeg) +/// @param end one past last byte of the scope +/// @param dev device record to populate +static void scan_device_scope(const uint8_t *p, const uint8_t *end, + aml_i2c_dev_t *dev) { + while (p < end) { + uint8_t op = *p; + + if (op == AML_NAME_OP) { + p++; + char seg[AML_NAME_LEN]; + decode_nameseg(&p, end, seg); + + if (memcmp(seg, "_HID", 4) == 0) parse_hid(&p, end, dev); + else if (memcmp(seg, "_CRS", 4) == 0) parse_crs(&p, end, dev); + else { record_power_state(seg, dev); skip_object(&p, end); } + continue; + } + + if (op == AML_METHOD_OP) { + p++; + const uint8_t *method_start = p; + size_t pkglen = decode_pkglen(&p, end); + if (!pkglen) break; + + const uint8_t *method_end = method_start + pkglen; + if (method_end > end) method_end = end; + + char seg[AML_NAME_LEN]; + const uint8_t *name_ptr = p; + decode_nameseg(&name_ptr, method_end, seg); + + if (memcmp(seg, "_DSM", 4) == 0) { + const uint8_t *body = name_ptr + 1; // skip MethodFlags we dont care abt it rn + const uint8_t *scan = body; + const uint8_t *guid = (const uint8_t *)ACPI_I2C_HID_DSM_GUID; + + while (scan + 16 < method_end) { + if (memcmp(scan, guid, 16) != 0) { scan++; continue; } + scan += 16; + while (scan + 4 < method_end) { + if (*scan != AML_RETURN_OP) { scan++; continue; } + scan++; + if (*scan != AML_PACKAGE_OP) { continue; } + scan++; + const uint8_t *ps = scan; + size_t pl = decode_pkglen(&scan, method_end); + if (!pl) break; + const uint8_t *pe = ps + pl; + if (pe > method_end || scan >= pe) break; + uint8_t nelem = *scan++; + if (nelem < 1) break; + uint64_t val = decode_integer(&scan, pe); + if (val <= 0xFFFF) { + dev->hid_desc_addr = (uint16_t)val; + dev->has_dsm = 1; + } + goto method_done; + } + break; + } + } else { + record_power_state(seg, dev); + } + + method_done: + p = method_end; + continue; + } + + // skip nested Device or Scope without recursing + if (op == AML_EXTOP_PREFIX && p + 1 < end && *(p + 1) == AML_DEVICE_OP) { + p += 2; + const uint8_t *s = p; + size_t l = decode_pkglen(&p, end); + if (l) p = s + l; else break; + continue; + } + + if (op == AML_SCOPE_OP) { + p++; + const uint8_t *s = p; + size_t l = decode_pkglen(&p, end); + if (l) p = s + l; else break; + continue; + } + + p++; + } +} + + +void aml_walk_table(const uint8_t *aml, size_t len, aml_walk_ctx_t *ctx) { + if (!aml || !len || !ctx || !ctx->devices) return; + + const uint8_t *p = aml; + const uint8_t *end = aml + len; + + while (p + 2 < end) { + if (p[0] != AML_EXTOP_PREFIX || p[1] != AML_DEVICE_OP) { p++; continue; } + p += 2; + + const uint8_t *scope_start = p; + size_t pkglen = decode_pkglen(&p, end); + if (!pkglen) continue; + + const uint8_t *scope_end = scope_start + pkglen; + if (scope_end > end) scope_end = end; + + char dev_name[AML_NAME_LEN]; + decode_nameseg(&p, scope_end, dev_name); + + if (ctx->count >= ctx->capacity) break; + + aml_i2c_dev_t *dev = &ctx->devices[ctx->count]; + memset(dev, 0, sizeof(aml_i2c_dev_t)); + memcpy(dev->name, dev_name, AML_NAME_LEN); + + scan_device_scope(p, scope_end, dev); + + scan_device_scope(p, scope_end, dev); + + if (dev->hid[0] && dev->speed_hz) { + dev->valid = 1; + ctx->count++; + } + + p = scope_end; + } +} + +int aml_parse_s5(const uint8_t *aml, size_t len, + uint16_t *slp_typa, uint16_t *slp_typb) { + if (!aml || !len || !slp_typa || !slp_typb) return 0; + + const uint8_t *end = aml + len; + const uint8_t *p = aml; + + while (p + 4 < end) { + if (p[0] != '_' || p[1] != 'S' || p[2] != '5' || p[3] != '_') { + p++; continue; + } + p += 4; + + // scan ahead up to 4 bytes for PackageOp; on diff firmware layouts YMMV + int found_pkg = 0; + for (int skip = 0; skip < 4 && p + skip < end; skip++) { + if (p[skip] == AML_PACKAGE_OP) { + p += skip + 1; + found_pkg = 1; + break; + } + } + if (!found_pkg) continue; + + const uint8_t *pkg_start = p; + size_t pkglen = decode_pkglen(&p, end); + if (!pkglen) continue; + + const uint8_t *pkg_end = pkg_start + pkglen; + if (pkg_end > end) pkg_end = end; + + if (p >= pkg_end) continue; + uint8_t num = *p++; + if (num < 2) continue; + + uint64_t typa = decode_integer(&p, pkg_end); + uint64_t typb = decode_integer(&p, pkg_end); + + *slp_typa = (uint16_t)((typa & 0x7) << 10); + *slp_typb = (uint16_t)((typb & 0x7) << 10); + return 1; + } + + return 0; +} diff --git a/src/drivers/ACPI/acpi_aml.h b/src/drivers/ACPI/acpi_aml.h new file mode 100644 index 0000000..beb4fea --- /dev/null +++ b/src/drivers/ACPI/acpi_aml.h @@ -0,0 +1,96 @@ +// Copyright (c) 2026 Myles Wilson (myles@bleedkernel.com) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. + +#ifndef ACPI_AML_H +#define ACPI_AML_H + +#include +#include + +#define AML_ZERO_OP 0x00 +#define AML_ONE_OP 0x01 +#define AML_NAME_OP 0x08 +#define AML_BYTE_PREFIX 0x0A +#define AML_WORD_PREFIX 0x0B +#define AML_DWORD_PREFIX 0x0C +#define AML_STRING_PREFIX 0x0D +#define AML_QWORD_PREFIX 0x0E +#define AML_SCOPE_OP 0x10 +#define AML_BUFFER_OP 0x11 +#define AML_PACKAGE_OP 0x12 +#define AML_METHOD_OP 0x14 +#define AML_EXTOP_PREFIX 0x5B +#define AML_DEVICE_OP 0x82 // always preceded by 0x5B +#define AML_RETURN_OP 0xA4 + +// ACPI resource descriptor tags +#define ACPI_RESOURCE_END_TAG 0x79 +#define ACPI_LARGE_ITEM 0x80 +#define ACPI_LARGE_I2C_SERIAL_BUS 0x8E +#define ACPI_I2C_SERIAL_BUS_TYPE 0x01 + +// I2cSerialBusV2 resource descriptor +typedef struct __attribute__((packed)) { + uint8_t tag; + uint16_t length; + uint8_t revision_id; + uint8_t resource_source_index; + uint8_t serial_bus_type; + uint8_t general_flags; + uint16_t type_specific_flags; + uint8_t type_specific_revision_id; + uint16_t type_data_length; + uint32_t connection_speed; + uint16_t slave_address; +} aml_i2c_resource_t; + +/* + GUID: 3cdff6f7-4267-4555-ad05-b30a3d8938de, mixed-endian AML layout + sorry i know its a magic number but tis the way of things +*/ +#define ACPI_I2C_HID_DSM_GUID \ + "\xf7\xf6\xdf\x3c\x67\x42\x55\x45\xad\x05\xb3\x0a\x3d\x89\x38\xde" + +#define AML_PWR_HAS_PS0 (1 << 0) +#define AML_PWR_HAS_PS3 (1 << 1) +#define AML_PWR_HAS_PR0 (1 << 2) +#define AML_PWR_HAS_PR3 (1 << 3) + +#define AML_HID_LEN 9 +#define AML_NAME_LEN 5 + +typedef struct { + char name[AML_NAME_LEN]; + char hid[AML_HID_LEN]; + uint16_t slave_address; + uint32_t speed_hz; + uint8_t ten_bit_addr; + uint16_t hid_desc_addr; + uint8_t has_dsm; + uint8_t power_flags; + uint8_t valid; +} aml_i2c_dev_t; + +typedef struct { + aml_i2c_dev_t *devices; + size_t capacity; + size_t count; +} aml_walk_ctx_t; + +/// @brief Walk one DSDT or SSDT AML region, emitting I2C device records +/// @param aml first AML byte (SDT base + 36) +/// @param len byte length of the AML region +/// @param ctx output context; devices array must be pre-allocated +void aml_walk_table(const uint8_t *aml, size_t len, aml_walk_ctx_t *ctx); + +/// @brief Scan DSDT AML for _S5_ and extract SLP_TYPa/b for S5 power-off +/// @param aml first AML byte (DSDT base + 36) +/// @param len byte length of the AML region +/// @param slp_typa out - SLP_TYPa pre-shifted to PM1_CNT bit 10 +/// @param slp_typb out - SLP_TYPb pre-shifted to PM1_CNT bit 10 +/// @return 1 if _S5_ found, 0 if not found (caller should use fallback) +int aml_parse_s5(const uint8_t *aml, size_t len, + uint16_t *slp_typa, uint16_t *slp_typb); + +#endif diff --git a/src/drivers/acpi_structures.h b/src/drivers/ACPI/acpi_structures.h similarity index 91% rename from src/drivers/acpi_structures.h rename to src/drivers/ACPI/acpi_structures.h index a8c7dc5..f7b682c 100644 --- a/src/drivers/acpi_structures.h +++ b/src/drivers/ACPI/acpi_structures.h @@ -1,3 +1,7 @@ +// Copyright (c) 2026 Myles Wilson (myles@bleedkernel.com) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. + #ifndef ACPI_STRUCTURES_H #define ACPI_STRUCTURES_H diff --git a/src/drivers/I2C/acpi_i2c.c b/src/drivers/I2C/acpi_i2c.c new file mode 100644 index 0000000..609be2a --- /dev/null +++ b/src/drivers/I2C/acpi_i2c.c @@ -0,0 +1,115 @@ +// Copyright (c) 2026 Myles Wilson (myles@bleedkernel.com) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. + +#include "acpi_i2c.h" +#include "../ACPI/acpi.h" +#include "../ACPI/acpi_aml.h" +#include "../ACPI/acpi_structures.h" +#include "../core/kconsole.h" +#include "../core/platform.h" + +static aml_i2c_dev_t i2c_devices[ACPI_I2C_MAX_DEVICES]; +static size_t i2c_device_count = 0; + +// ACPI SDT header is 36 bytes AML starts concurrently, nice and convenient +#define ACPI_SDT_HEADER_LEN 36 + +static void walk_sdt(struct acpi_sdt *sdt, aml_walk_ctx_t *ctx) { + if (!sdt) return; + if (sdt->length <= ACPI_SDT_HEADER_LEN) return; + + const uint8_t *aml = (const uint8_t *)sdt + ACPI_SDT_HEADER_LEN; + size_t len = sdt->length - ACPI_SDT_HEADER_LEN; + aml_walk_table(aml, len, ctx); +} + +static void walk_all_ssdts(aml_walk_ctx_t *ctx) { + struct acpi_rsdp *rsdp = acpi_get_rsdp(); + if (!rsdp) return; + + if (rsdp->revision >= 2 && rsdp->xsdt_address) { + struct acpi_xsdt *xsdt = (struct acpi_xsdt *)p2v(rsdp->xsdt_address); + size_t entries = (xsdt->header.length - sizeof(struct acpi_sdt)) / 8; + for (size_t i = 0; i < entries; i++) { + struct acpi_sdt *sdt = (struct acpi_sdt *)p2v(xsdt->tables[i]); + if (!sdt) continue; + if (__builtin_memcmp(sdt->signature, "SSDT", 4) == 0) + walk_sdt(sdt, ctx); + } + return; + } + + // RSDT fallback + if (!rsdp->rsdt_address) return; + struct acpi_sdt *rsdt = (struct acpi_sdt *)p2v(rsdp->rsdt_address); + uint32_t *tables = (uint32_t *)((uint8_t *)rsdt + sizeof(struct acpi_sdt)); + size_t entries = (rsdt->length - sizeof(struct acpi_sdt)) / 4; + for (size_t i = 0; i < entries; i++) { + struct acpi_sdt *sdt = (struct acpi_sdt *)p2v(tables[i]); + if (!sdt) continue; + if (__builtin_memcmp(sdt->signature, "SSDT", 4) == 0) + walk_sdt(sdt, ctx); + } +} + +int acpi_i2c_enumerate(void) { + i2c_device_count = 0; + + aml_walk_ctx_t ctx = { + .devices = i2c_devices, + .capacity = ACPI_I2C_MAX_DEVICES, + .count = 0, + }; + + // Walk DSDT - pointed to by FADT, not listed in XSDT/RSDT + struct acpi_sdt *dsdt = acpi_get_dsdt(); + if (dsdt) { + walk_sdt(dsdt, &ctx); + } else { + serial_write("[acpi_i2c] warning: DSDT not found\n"); + } + + // Walk all SSDTs + walk_all_ssdts(&ctx); + + i2c_device_count = ctx.count; + + for (size_t i = 0; i < i2c_device_count; i++) { + const aml_i2c_dev_t *d = &i2c_devices[i]; + serial_write("[acpi_i2c] "); + serial_write(d->name); + serial_write(" hid="); + serial_write(d->hid); + serial_write(" addr=0x"); + serial_write_hex(d->slave_address); + serial_write(" speed="); + serial_write_num(d->speed_hz); + serial_write(" Hz"); + if (d->ten_bit_addr) serial_write(" 10-bit"); + if (d->power_flags & (AML_PWR_HAS_PS0 | AML_PWR_HAS_PS3)) serial_write(" PS0/3"); + if (d->power_flags & (AML_PWR_HAS_PR0 | AML_PWR_HAS_PR3)) serial_write(" PR0/3"); + if (d->has_dsm) { + serial_write(" hid_desc_reg=0x"); + serial_write_hex(d->hid_desc_addr); + } + serial_write("\n"); + } + + if (i2c_device_count != 0) { + log_ok("I2C enumeration complete"); + } else { + log_fail("No I2C devices found in ACPI tables"); + } + + return (int)i2c_device_count; +} + +size_t acpi_i2c_count(void) { + return i2c_device_count; +} + +const aml_i2c_dev_t *acpi_i2c_get(size_t index) { + if (index >= i2c_device_count) return NULL; + return &i2c_devices[index]; +} \ No newline at end of file diff --git a/src/drivers/I2C/acpi_i2c.h b/src/drivers/I2C/acpi_i2c.h new file mode 100644 index 0000000..a27832f --- /dev/null +++ b/src/drivers/I2C/acpi_i2c.h @@ -0,0 +1,20 @@ +// Copyright (c) 2026 Myles Wilson (myles@bleedkernel.com) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. + +#ifndef ACPI_I2C_H +#define ACPI_I2C_H + +#include +#include +#include "../ACPI/acpi_aml.h" + +#define ACPI_I2C_MAX_DEVICES 32 + +int acpi_i2c_enumerate(void); + +size_t acpi_i2c_count(void); + +const aml_i2c_dev_t *acpi_i2c_get(size_t index); + +#endif \ No newline at end of file diff --git a/src/drivers/acpi.h b/src/drivers/acpi.h deleted file mode 100644 index 434d3cb..0000000 --- a/src/drivers/acpi.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef ACPI_H -#define ACPI_H - -int acpi_init(void); - -__attribute__((noreturn)) void acpi_shutdown(void); -__attribute__((noreturn)) void acpi_reboot(void); - -uint32_t acpi_irq_to_gsi(uint32_t irq); -uint16_t acpi_irq_flags(uint32_t irq); - -//power stuff - -#define PM1A_CNT fadt->pm1a_cnt_blk -#define PM1B_CNT fadt->pm1b_cnt_blk - -#define ACPI_S5 0x5 -#define SLP_EN (1 << 13) - -#define ACPI_PM1_SLEEP_CMD(slp_typ) (((slp_typ) << 10) | SLP_EN) - -void acpi_parse_s5(void); -__attribute__((noreturn)) void acpi_shutdown(void); - -#endif \ No newline at end of file From fd20a5df4112f745c47fd490a71d6fce157bc03e Mon Sep 17 00:00:00 2001 From: mellurboo Date: Thu, 14 May 2026 14:28:27 +0100 Subject: [PATCH 19/24] Window Manager Graphics Optimisations --- src/wm/graphics.c | 80 +++++++++++++++++++++++++++-------------------- src/wm/wm.c | 18 +++++++++-- 2 files changed, 62 insertions(+), 36 deletions(-) diff --git a/src/wm/graphics.c b/src/wm/graphics.c index 03d6434..b09387b 100644 --- a/src/wm/graphics.c +++ b/src/wm/graphics.c @@ -9,6 +9,9 @@ #include "../mem/memory_manager.h" #include "sys/spinlock.h" +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + static struct limine_framebuffer *g_fb = NULL; static uint32_t g_bg_color = 0xFF696969; @@ -122,7 +125,9 @@ int graphics_get_fb_bpp(void) { return g_fb ? g_fb->bpp : 0; } -// Merge new dirty rect with existing one +// faltten the structure, cache the edges, calculate the right and bottom edges +// calculate a new bounding box with some of my clever branchless math +// and perform exactly 1 Write to memory static void merge_dirty_rect(int x, int y, int w, int h) { if (!g_dirty.active) { g_dirty.x = x; @@ -130,47 +135,54 @@ static void merge_dirty_rect(int x, int y, int w, int h) { g_dirty.w = w; g_dirty.h = h; g_dirty.active = true; - } else { - // Calculate union of two rectangles - int x1 = g_dirty.x; - int y1 = g_dirty.y; - int x2 = g_dirty.x + g_dirty.w; - int y2 = g_dirty.y + g_dirty.h; - - int new_x1 = x; - int new_y1 = y; - int new_x2 = x + w; - int new_y2 = y + h; - - g_dirty.x = new_x1 < x1 ? new_x1 : x1; - g_dirty.y = new_y1 < y1 ? new_y1 : y1; - g_dirty.w = (new_x2 > x2 ? new_x2 : x2) - g_dirty.x; - g_dirty.h = (new_y2 > y2 ? new_y2 : y2) - g_dirty.y; + return; } + + int gx = g_dirty.x; + int gy = g_dirty.y; + + int gx2 = gx + g_dirty.w; + int gy2 = gy + g_dirty.h; + int nx2 = x + w; + int ny2 = y + h; + + int new_x = MIN(gx, x); + int new_y = MIN(gy, y); + int new_x2 = MAX(gx2, nx2); + int new_y2 = MAX(gy2, ny2); + + g_dirty.x = new_x; + g_dirty.y = new_y; + g_dirty.w = new_x2 - new_x; + g_dirty.h = new_y2 - new_y; } void graphics_mark_dirty(int x, int y, int w, int h) { - if (x < 0) { - w += x; - x = 0; - } - if (y < 0) { - h += y; - y = 0; - } - if (x + w > get_screen_width()) { - w = get_screen_width() - x; - } - if (y + h > get_screen_height()) { - h = get_screen_height() - y; - } - if (w <= 0 || h <= 0) { return; } - + + int x2 = x + w; + int y2 = y + h; + + // Cache screen boundaries because then we can avoid multiple calls + int screen_w = get_screen_width(); + int screen_h = get_screen_height(); + + int cx1 = MAX(0, x); + int cy1 = MAX(0, y); + int cx2 = MIN(screen_w, x2); + int cy2 = MIN(screen_h, y2); + + int cw = cx2 - cx1; + int ch = cy2 - cy1; + + if (cw <= 0 || ch <= 0) { + return; + } + uint64_t flags = spinlock_acquire_irqsave(&graphics_lock); - merge_dirty_rect(x, y, w, h); + merge_dirty_rect(cx1, cy1, cw, ch); spinlock_release_irqrestore(&graphics_lock, flags); } diff --git a/src/wm/wm.c b/src/wm/wm.c index 65357dd..d51ee42 100644 --- a/src/wm/wm.c +++ b/src/wm/wm.c @@ -220,6 +220,8 @@ static int pending_dock_click_index = -1; static int dock_drag_source_index = -1; static bool dock_drag_active = false; static int pending_desktop_icon_click = -1; +static int drag_repaint_accum_x = 0; +static int drag_repaint_accum_y = 0; // Desktop Context Menu static bool desktop_menu_visible = false; @@ -3362,10 +3364,22 @@ static void wm_handle_mouse_internal(int dx, int dy, uint8_t buttons, int dz) { } else if (right && !prev_right) { wm_handle_right_click(mx, my); } else if (left && is_dragging && drag_window) { + int old_x = drag_window->x; + int old_y = drag_window->y; drag_window->x = mx - drag_offset_x; drag_window->y = my - drag_offset_y; - // Mark for full redraw since window moved - force_redraw = true; + + drag_repaint_accum_x += (drag_window->x - old_x < 0 ? -(drag_window->x - old_x) : (drag_window->x - old_x)); + drag_repaint_accum_y += (drag_window->y - old_y < 0 ? -(drag_window->y - old_y) : (drag_window->y - old_y)); + + // Only repaint if we've moved at least 2px because we dont wanna redraw for subpixels + if (drag_repaint_accum_x >= 2 || drag_repaint_accum_y >= 2) { + wm_mark_dirty(old_x - 2, old_y - 2, drag_window->w + 4, drag_window->h + 4); + wm_mark_dirty(drag_window->x - 2, drag_window->y - 2, drag_window->w + 4, drag_window->h + 4); + drag_repaint_accum_x = 0; + drag_repaint_accum_y = 0; + } + } else if (left && is_resizing && drag_window) { int new_w = mx - drag_window->x + (drag_start_w - drag_offset_x); int new_h = my - drag_window->y + (drag_start_h - drag_offset_y); From deea3eaabcd08620947560a6e751129c11128d12 Mon Sep 17 00:00:00 2001 From: boreddevnl Date: Thu, 14 May 2026 16:21:28 +0200 Subject: [PATCH 20/24] brand: change default wallpaper to drift.png --- src/wm/wallpaper.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wm/wallpaper.c b/src/wm/wallpaper.c index b7fe97a..3e42e6b 100644 --- a/src/wm/wallpaper.c +++ b/src/wm/wallpaper.c @@ -244,10 +244,10 @@ void wallpaper_init(void) { wallpaper_load_setting(); if (pending_wallpaper_path == NULL) { - if (fat32_exists("/Library/images/Wallpapers/boredos.png")) { - wallpaper_request_set_from_file("/Library/images/Wallpapers/boredos.png"); - } else if (fat32_exists("/Library/images/Wallpapers/mountain.jpg")) { - wallpaper_request_set_from_file("/Library/images/Wallpapers/mountain.jpg"); + if (fat32_exists("/Library/images/Wallpapers/Drift.png")) { + wallpaper_request_set_from_file("/Library/images/Wallpapers/Drift.png"); + } else if (fat32_exists("/Library/images/Wallpapers/orbital.jpg")) { + wallpaper_request_set_from_file("/Library/images/Wallpapers/orbital.jpg"); } } } From 85c93cbcfa68ac9bf8bd5a9122a4ae0d130e0987 Mon Sep 17 00:00:00 2001 From: "Myles \"Mellurboo\" Wilson" <102391892+Mellurboo@users.noreply.github.com> Date: Thu, 14 May 2026 17:00:22 +0100 Subject: [PATCH 21/24] multithreaded makefile --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 4ce93a7..f7bd50e 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,8 @@ # This software is released under the GNU General Public License v3.0. See LICENSE file for details. # This header needs to maintain in any file it is present in, as per the GPL license terms. +export MAKEFLAGS += -j4 + CC = x86_64-elf-gcc LD = x86_64-elf-ld NASM = nasm From 9c2096f737a8f568818c130a7850c21eca40b57c Mon Sep 17 00:00:00 2001 From: boreddevnl Date: Fri, 15 May 2026 17:35:46 +0200 Subject: [PATCH 22/24] fix: added stability for SMP on Tiger Lake processors --- src/arch/process_asm.asm | 10 ++++++++-- src/core/kutils.c | 6 ++++-- src/core/kutils.h | 4 ++-- src/core/main.c | 13 ++++++++++--- src/dev/ps2.c | 8 ++++++++ src/sys/lapic.c | 23 +++++++++++++++++++---- src/sys/lapic.h | 3 +++ src/sys/process.c | 23 +++++++++++++++++++---- src/sys/smp.c | 11 ++++++++--- src/sys/smp.h | 3 +++ 10 files changed, 84 insertions(+), 20 deletions(-) diff --git a/src/arch/process_asm.asm b/src/arch/process_asm.asm index 286d825..8be307b 100644 --- a/src/arch/process_asm.asm +++ b/src/arch/process_asm.asm @@ -18,7 +18,7 @@ process_jump_usermode: ; Build the IRETQ stack frame ; 1. SS (User Data Segment) - push 0x23 + push 0x1B ; 2. RSP (User Stack) push rsi @@ -27,7 +27,7 @@ process_jump_usermode: push 0x202 ; 4. CS (User Code Segment) - push 0x1B + push 0x23 ; 5. RIP (Entry Point) push rdi @@ -40,6 +40,12 @@ process_jump_usermode: global context_switch_to context_switch_to: mov rsp, rdi + mov rcx, [rsp + 8192] + xor ecx, ecx + xgetbv + xrstor [rsp] + add rsp, 8192 + add rsp, rcx pop r15 pop r14 diff --git a/src/core/kutils.c b/src/core/kutils.c index 59111f5..340ebde 100644 --- a/src/core/kutils.c +++ b/src/core/kutils.c @@ -7,15 +7,17 @@ #include "../drivers/ACPI/acpi.h" -void memset(void *dest, int val, size_t len) { +void *memset(void *dest, int val, size_t len) { unsigned char *ptr = (unsigned char *)dest; while (len-- > 0) *ptr++ = (unsigned char)val; + return dest; } -void memcpy(void *dest, const void *src, size_t len) { +void *memcpy(void *dest, const void *src, size_t len) { unsigned char *d = (unsigned char *)dest; const unsigned char *s = (const unsigned char *)src; while (len-- > 0) *d++ = *s++; + return dest; } int memcmp(const void *str1, const void *str2, size_t count) { diff --git a/src/core/kutils.h b/src/core/kutils.h index c453042..8ec4ffb 100644 --- a/src/core/kutils.h +++ b/src/core/kutils.h @@ -10,8 +10,8 @@ // Kernel string utilities void *memmove(void *dest, const void *src, uint64_t n); -void memset(void *dest, int val, size_t len); -void memcpy(void *dest, const void *src, size_t len); +void *memset(void *dest, int val, size_t len); +void *memcpy(void *dest, const void *src, size_t len); int memcmp (const void *str1, const void *str2, size_t count); size_t strlen(const char *str); int strcmp(const char *s1, const char *s2); diff --git a/src/core/main.c b/src/core/main.c index fd69d69..8d064e9 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -493,16 +493,23 @@ void kmain(void) { serial_write_hex(current_rsp); serial_write("\n"); + serial_write("[DBG] before graphics_init_fonts\n"); graphics_init_fonts(); + serial_write("[DBG] after graphics_init_fonts\n"); - asm("cli"); + serial_write("[DBG] before ps2_init\n"); ps2_init(); - asm("sti"); + serial_write("[DBG] after ps2_init\n"); + asm("sti"); // Enable interrupts + serial_write("[DBG] before keymap_init\n"); keymap_init(); - serial_write("[INIT] Keymap initialized"); + serial_write("[DBG] after keymap_init\n"); + serial_write("[INIT] Keymap initialized\n"); + serial_write("[DBG] before lapic_init\n"); lapic_init(); + serial_write("[DBG] after lapic_init\n"); if (smp_request.response != NULL) { uint32_t online = smp_init(smp_request.response); diff --git a/src/dev/ps2.c b/src/dev/ps2.c index 9225c3a..b3091f8 100644 --- a/src/dev/ps2.c +++ b/src/dev/ps2.c @@ -11,6 +11,8 @@ #include "input/keyboard.h" #include "input/keymap.h" +extern void serial_write(const char *s); +extern void serial_write_num(uint32_t n); extern void serial_print(const char *s); extern void serial_print_hex(uint64_t n); @@ -25,6 +27,12 @@ uint64_t timer_handler(registers_t *regs) { extern void k_beep_process(void); k_beep_process(); + + if (kernel_ticks % 100 == 0) { + serial_write("[TIMER] Heartbeat: "); + serial_write_num((uint32_t)kernel_ticks); + serial_write(" ticks\n"); + } } outb(0x20, 0x20); diff --git a/src/sys/lapic.c b/src/sys/lapic.c index f070ad2..04e896f 100644 --- a/src/sys/lapic.c +++ b/src/sys/lapic.c @@ -3,11 +3,13 @@ // This header needs to maintain in any file it is present in, as per the GPL license terms. #include "lapic.h" #include "platform.h" +#include "spinlock.h" extern void serial_write(const char *str); // LAPIC is at physical 0xFEE00000. Access via HHDM. static volatile uint32_t *lapic_base = 0; +static spinlock_t lapic_lock = SPINLOCK_INIT; // LAPIC register offsets (byte offsets, divided by 4 for uint32_t* indexing) #define LAPIC_ID (0x020 / 4) @@ -41,14 +43,27 @@ void lapic_send_ipi_all(void) { if (!lapic_base) return; // Send IPI to all excluding self - // ICR format: + // ICR format: // bits 7:0 = vector (IPI_SCHED_VECTOR = 0x41) // bits 10:8 = delivery mode (000 = Fixed) // bit 11 = destination mode (0 = Physical) - // bit 14 = level (1 = Assert) + // bit 14 = level: 0 = Edge // bits 19:18 = destination shorthand (11 = All Excluding Self) - uint32_t icr_low = IPI_SCHED_VECTOR | (0b11 << 18) | (1 << 14); + uint32_t icr_low = IPI_SCHED_VECTOR | (0b11 << 18); - // Writing ICR_LOW triggers the IPI send + uint64_t rflags = spinlock_acquire_irqsave(&lapic_lock); lapic_base[LAPIC_ICR_LOW] = icr_low; + while (lapic_base[LAPIC_ICR_LOW] & (1 << 12)) {} + spinlock_release_irqrestore(&lapic_lock, rflags); +} + +void lapic_send_ipi(uint32_t lapic_id, uint8_t vector) { + if (!lapic_base) return; + uint32_t icr_low = (uint32_t)vector; + + uint64_t rflags = spinlock_acquire_irqsave(&lapic_lock); + lapic_base[LAPIC_ICR_HIGH] = (lapic_id << 24); + lapic_base[LAPIC_ICR_LOW] = icr_low; + while (lapic_base[LAPIC_ICR_LOW] & (1 << 12)) {} + spinlock_release_irqrestore(&lapic_lock, rflags); } diff --git a/src/sys/lapic.h b/src/sys/lapic.h index 06e1cc9..54f196d 100644 --- a/src/sys/lapic.h +++ b/src/sys/lapic.h @@ -21,4 +21,7 @@ void lapic_eoi(void); // Send a scheduling IPI to all APs (excludes self) void lapic_send_ipi_all(void); +// Send a targeted IPI to a specific LAPIC ID +void lapic_send_ipi(uint32_t lapic_id, uint8_t vector); + #endif diff --git a/src/sys/process.c b/src/sys/process.c index 0b57d2c..64f2313 100644 --- a/src/sys/process.c +++ b/src/sys/process.c @@ -17,6 +17,7 @@ extern void cmd_write(const char *str); extern void serial_write(const char *str); +extern void serial_write_num(uint32_t n); #define MAX_PROCESSES 16 #define MAX_CPUS_SCHED 32 @@ -507,7 +508,7 @@ process_t* process_create_elf(const char* filepath, const char* args_str, bool t asm volatile("fninit"); new_proc->fpu_initialized = true; - // Assign to an AP core via round-robin (if SMP is active) + // Assign to a CPU core via round-robin across APs (if SMP is active) uint32_t cpu_count = smp_cpu_count(); if (cpu_count > 1) { new_proc->cpu_affinity = next_cpu_assign; @@ -517,15 +518,29 @@ process_t* process_create_elf(const char* filepath, const char* args_str, bool t new_proc->cpu_affinity = 0; } - // Add to linked list (Critical Section) + rflags = spinlock_acquire_irqsave(&runqueue_lock); - new_proc->next = current_process[0]->next; - current_process[0]->next = new_proc; + uint32_t target_cpu = new_proc->cpu_affinity; + process_t *target_head = current_process[target_cpu]; + if (target_head) { + new_proc->next = target_head->next; + target_head->next = new_proc; + } else { + new_proc->next = new_proc; + current_process[target_cpu] = new_proc; + } spinlock_release_irqrestore(&runqueue_lock, rflags); serial_write("[PROC] Exec: "); serial_write(filepath); + serial_write(" on CPU "); + serial_write_num(new_proc->cpu_affinity); serial_write("\n"); + uint32_t target_lapic = smp_get_lapic_id(new_proc->cpu_affinity); + if (target_lapic != 0xFF) { + lapic_send_ipi(target_lapic, IPI_SCHED_VECTOR); + } + return new_proc; } diff --git a/src/sys/smp.c b/src/sys/smp.c index 31d57ee..407e84c 100644 --- a/src/sys/smp.c +++ b/src/sys/smp.c @@ -121,7 +121,7 @@ static void ap_entry(struct limine_smp_info *info) { } void smp_init_bsp(void) { - static cpu_state_t bsp_state_static = {0}; + static cpu_state_t bsp_state_static __attribute__((aligned(64))) = {0}; bsp_state_static.cpu_id = 0; bsp_lapic_id = read_lapic_id(); bsp_state_static.lapic_id = bsp_lapic_id; @@ -138,7 +138,7 @@ void smp_init_bsp(void) { uint32_t smp_init(struct limine_smp_response *smp_resp) { if (!smp_resp || smp_resp->cpu_count <= 1) { total_cpus = 1; - cpu_states = (cpu_state_t *)kmalloc(sizeof(cpu_state_t)); + cpu_states = (cpu_state_t *)kmalloc_aligned(sizeof(cpu_state_t), 64); if (!cpu_states) return 1; extern void mem_memset(void *, int, size_t); mem_memset(cpu_states, 0, sizeof(cpu_state_t)); @@ -158,7 +158,7 @@ uint32_t smp_init(struct limine_smp_response *smp_resp) { serial_write_num(bsp_lapic_id); serial_write("\n"); - cpu_states = (cpu_state_t *)kmalloc(total_cpus * sizeof(cpu_state_t)); + cpu_states = (cpu_state_t *)kmalloc_aligned(total_cpus * sizeof(cpu_state_t), 64); if (!cpu_states) { serial_write("[SMP] ERROR: Failed to allocate CPU state array!\n"); total_cpus = 1; @@ -231,3 +231,8 @@ uint32_t smp_init(struct limine_smp_response *smp_resp) { return online_count; } + +uint32_t smp_get_lapic_id(uint32_t cpu_id) { + if (cpu_id >= total_cpus || !cpu_states) return 0xFF; + return cpu_states[cpu_id].lapic_id; +} diff --git a/src/sys/smp.h b/src/sys/smp.h index c4ae47e..d78fa2c 100644 --- a/src/sys/smp.h +++ b/src/sys/smp.h @@ -17,6 +17,7 @@ typedef struct cpu_state { volatile bool online; uint64_t user_rsp_scratch; uint64_t kernel_syscall_stack; + uint8_t xsave_area[8192] __attribute__((aligned(64))); } cpu_state_t; void smp_init_bsp(void); @@ -31,4 +32,6 @@ uint32_t smp_cpu_count(void); cpu_state_t *smp_get_cpu(uint32_t cpu_id); +uint32_t smp_get_lapic_id(uint32_t cpu_id); + #endif From 61647f0e1f63f9e585666df47e2ed5b35f376016 Mon Sep 17 00:00:00 2001 From: boreddevnl Date: Fri, 15 May 2026 17:39:18 +0200 Subject: [PATCH 23/24] rm: debug log timer ticks --- src/dev/ps2.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/dev/ps2.c b/src/dev/ps2.c index b3091f8..0c75fb7 100644 --- a/src/dev/ps2.c +++ b/src/dev/ps2.c @@ -27,12 +27,6 @@ uint64_t timer_handler(registers_t *regs) { extern void k_beep_process(void); k_beep_process(); - - if (kernel_ticks % 100 == 0) { - serial_write("[TIMER] Heartbeat: "); - serial_write_num((uint32_t)kernel_ticks); - serial_write(" ticks\n"); - } } outb(0x20, 0x20); From 3fd1899f015988001394c7c2cb8064593e9b53fb Mon Sep 17 00:00:00 2001 From: boreddevnl Date: Fri, 15 May 2026 17:51:37 +0200 Subject: [PATCH 24/24] sys: unify write operations to prioritize VFS handles --- src/sys/syscall.c | 10 ++++++++++ src/userland/libc/posix_io.c | 8 ++------ src/userland/libc/stdio.c | 5 ++--- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/sys/syscall.c b/src/sys/syscall.c index 951683b..712b9fe 100644 --- a/src/sys/syscall.c +++ b/src/sys/syscall.c @@ -2519,8 +2519,18 @@ static const syscall_handler_fn sys_cmd_table[SYS_CMD_TABLE_SIZE] = { static uint64_t handle_sys_write(const syscall_args_t *args) { extern void cmd_write_len(const char *str, size_t len); process_t *proc = process_get_current(); + int fd = (int)args->arg1; const char *buf = (const char*)args->arg2; size_t len = (size_t)args->arg3; + + if (proc && fd >= 0 && fd < MAX_PROCESS_FDS && proc->fds[fd]) { + syscall_args_t fs_args = *args; + fs_args.arg2 = args->arg1; // fd + fs_args.arg3 = args->arg2; // buf + fs_args.arg4 = args->arg3; // len + return fs_cmd_write(&fs_args); + } + if (!proc || !proc->is_user) { cmd_write_len(buf, len); return len; diff --git a/src/userland/libc/posix_io.c b/src/userland/libc/posix_io.c index 3c52550..a159d46 100644 --- a/src/userland/libc/posix_io.c +++ b/src/userland/libc/posix_io.c @@ -391,13 +391,9 @@ __attribute__((weak)) ssize_t write(int fd, const void *buf, size_t count) { return (ssize_t)n; } - if (_b_is_stdio_handle(h)) { + n = sys_write_fs(h->kernel_fd, buf, (uint32_t)count); + if (n < 0 && _b_is_stdio_handle(h)) { n = sys_write(h->kernel_fd, (const char *)buf, (int)count); - } else { - n = sys_write_fs(h->kernel_fd, buf, (uint32_t)count); - if (n < 0) { - n = sys_write(h->kernel_fd, (const char *)buf, (int)count); - } } if (n < 0) { diff --git a/src/userland/libc/stdio.c b/src/userland/libc/stdio.c index 96020ef..52fe50d 100644 --- a/src/userland/libc/stdio.c +++ b/src/userland/libc/stdio.c @@ -152,10 +152,9 @@ size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) { return 0; } total = size * nmemb; - if (stream->fd <= 2) { + n = sys_write_fs(stream->fd, ptr, (uint32_t)total); + if (n < 0 && stream->fd <= 2) { n = sys_write(stream->fd, (const char *)ptr, (int)total); - } else { - n = sys_write_fs(stream->fd, ptr, (uint32_t)total); } if (n < 0) { stream->err = 1;