From c8edeab7508fe18f2f21f03aa4562daef48b05b1 Mon Sep 17 00:00:00 2001 From: Freywar Ulvnaudgari Date: Fri, 5 Jun 2026 21:44:09 +0300 Subject: [PATCH] Use streams for inter-"process" communications --- meson.build | 4 +- src/app/cat.c | 78 +++++++++ src/app/cat.h | 5 + src/app/ls.c | 81 +++++++++ src/app/ls.h | 5 + src/app/terminal.c | 255 +++++++++++++++++++++++++++++ src/app/terminal.h | 5 + src/fat16.c | 3 +- src/kernel.c | 22 ++- src/keyboard.c | 157 +++++++++++++++++- src/keyboard.h | 7 +- src/memory.c | 2 +- src/panic.c | 2 +- src/stream.h | 36 ++++ src/terminal.c | 397 --------------------------------------------- src/terminal.h | 3 - src/vga.c | 176 +++++++++++++++++--- src/vga.h | 10 +- 18 files changed, 786 insertions(+), 462 deletions(-) create mode 100644 src/app/cat.c create mode 100644 src/app/cat.h create mode 100644 src/app/ls.c create mode 100644 src/app/ls.h create mode 100644 src/app/terminal.c create mode 100644 src/app/terminal.h create mode 100644 src/stream.h delete mode 100644 src/terminal.c delete mode 100644 src/terminal.h diff --git a/meson.build b/meson.build index 2f0c58a..8bbec38 100644 --- a/meson.build +++ b/meson.build @@ -41,6 +41,9 @@ kernel_entry_o = custom_target( cc = meson.get_compiler('c') kernel_sources = files( + 'src/app/cat.c', + 'src/app/ls.c', + 'src/app/terminal.c', 'src/ata.c', 'src/fat16.c', 'src/fs.c', @@ -51,7 +54,6 @@ kernel_sources = files( 'src/panic.c', 'src/pic.c', 'src/string.c', - 'src/terminal.c', 'src/vga.c', ) diff --git a/src/app/cat.c b/src/app/cat.c new file mode 100644 index 0000000..8f1b8dc --- /dev/null +++ b/src/app/cat.c @@ -0,0 +1,78 @@ +#include "src/app/cat.h" +#include "src/fs.h" +#include "src/memory.h" +#include "src/string.h" +#include "src/util.h" + +#include + +static stream_t *stream_in; +static stream_t *stream_out; + +#define WRITE_S(s) stream_out->write(stream_out, s, sizeof(s) - 1); +#define WRITE_D(s) stream_out->write(stream_out, s, string_length(s)); + +void cat(stream_t *stdin, stream_t *stdout, uint8_t argc, char **argv) { + stream_in = stdin; + stream_out = stdout; + + if (argc != 2) { + WRITE_S("cat: requires a single path\n"); + return; + } + + char *path_components[16]; + uint64_t path_length = string_split(argv[1], '/', 16, path_components); + if (!string_empty(path_components[0])) { + WRITE_S("cat: relative paths not supported\n"); + return; + } + + fs_node_t *root = fs_mount(); + + fs_node_t *prev = NUL; + fs_node_t *curr = root; + uint8_t i = 1; + while (i < path_length) { + if (string_empty(path_components[i])) { + i++; + continue; + } + + if (!curr->is_dir) { + WRITE_S("cat: can not navigate into a file\n"); + fs_close(curr); + return; + } + + prev = curr; + curr = fs_open_by(curr, path_components[i]); + if (prev != root) { + fs_close(prev); + } + + if (!curr) { + WRITE_S("cat: invalid path\n"); + return; + } + i++; + } + + if (curr->is_dir) { + WRITE_S("cat: can not print a directory\n"); + if (curr != root) { + fs_close(curr); + } + return; + } + + char *content = memory_allocate(curr->size + 1); + fs_read(curr, 0, curr->size, content); + stream_out->write(stream_out, content, curr->size); + memory_free(content); + fs_close(curr); + + fs_unmount(root); + + stream_in = stream_out = NUL; +} diff --git a/src/app/cat.h b/src/app/cat.h new file mode 100644 index 0000000..91891d5 --- /dev/null +++ b/src/app/cat.h @@ -0,0 +1,5 @@ +#pragma once + +#include "src/stream.h" + +void cat(stream_t *stdin, stream_t *stdout, uint8_t argc, char **argv); diff --git a/src/app/ls.c b/src/app/ls.c new file mode 100644 index 0000000..6133daa --- /dev/null +++ b/src/app/ls.c @@ -0,0 +1,81 @@ +#include "src/app/ls.h" +#include "src/fs.h" +#include "src/string.h" +#include "src/util.h" + +#include + +static stream_t *stream_in; +static stream_t *stream_out; + +#define WRITE_S(s) stream_out->write(stream_out, s, sizeof(s) - 1); +#define WRITE_D(s) stream_out->write(stream_out, s, string_length(s)); + +void ls(stream_t *stdin, stream_t *stdout, uint8_t argc, char **argv) { + stream_in = stdin; + stream_out = stdout; + + if (argc != 2) { + WRITE_S("ls: requires a single path\n"); + return; + } + + char *path_components[16]; + uint64_t path_length = string_split(argv[1], '/', 16, path_components); + if (!string_empty(path_components[0])) { + WRITE_S("ls: relative paths not supported\n"); + return; + } + + fs_node_t *root = fs_mount(); + + fs_node_t *prev = NUL; + fs_node_t *curr = root; + uint8_t i = 1; + while (i < path_length) { + if (string_empty(path_components[i])) { + i++; + continue; + } + + if (!curr->is_dir) { + WRITE_S("ls: can not navigate into a file\n"); + fs_close(curr); + return; + } + + prev = curr; + curr = fs_open_by(curr, path_components[i]); + if (prev != root) { + fs_close(prev); + } + + if (!curr) { + WRITE_S("ls: invalid path\n"); + return; + } + i++; + } + + if (!curr->is_dir) { + WRITE_S("ls: can not list file\n"); + fs_close(curr); + return; + } + + uint8_t j = 0; + fs_node_t *item = fs_open_at(curr, j++); + while (item) { + WRITE_D(item->name); + WRITE_S("\n"); + fs_close(item); + item = fs_open_at(curr, j++); + } + if (curr != root) { + fs_close(curr); + } + + fs_unmount(root); + + stream_in = stream_out = NUL; +} diff --git a/src/app/ls.h b/src/app/ls.h new file mode 100644 index 0000000..b8c7a6a --- /dev/null +++ b/src/app/ls.h @@ -0,0 +1,5 @@ +#pragma once + +#include "src/stream.h" + +void ls(stream_t *stdin, stream_t *stdout, uint8_t argc, char **argv); diff --git a/src/app/terminal.c b/src/app/terminal.c new file mode 100644 index 0000000..d29f00c --- /dev/null +++ b/src/app/terminal.c @@ -0,0 +1,255 @@ +#include "src/app/terminal.h" +#include "src/app/cat.h" +#include "src/app/ls.h" +#include "src/fs.h" +#include "src/memory.h" +#include "src/string.h" +#include "src/util.h" + +#include + +#define PROMPT_LENGTH 255 + +fs_node_t *root; + +static const char bs[PROMPT_LENGTH + 1]; +static const char ws[PROMPT_LENGTH + 1]; + +static char prompt[PROMPT_LENGTH + 1]; +static uint8_t prompt_length = 0; +static uint8_t prompt_offset = 0; + +static stream_t *stream_in; +static stream_t *stream_out; + +static uint8_t active = 1; + +#define WRITE_S(s) stream_out->write(stream_out, s, sizeof(s) - 1); +#define WRITE_D(s) stream_out->write(stream_out, s, string_length(s)); + +static void on_home_pressed() { + if (!prompt_offset) { + return; + } + + stream_out->write(stream_out, bs, prompt_offset); + prompt_offset = 0; +} + +static void on_left_pressed() { + if (!prompt_offset) { + return; + } + + stream_out->write(stream_out, bs, 1); + prompt_offset--; +} + +static void on_right_pressed() { + if (prompt_offset == prompt_length) { + return; + } + + stream_out->write(stream_out, prompt + prompt_offset, 1); + prompt_offset++; +} + +static void on_end_pressed() { + if (prompt_offset == prompt_length) { + return; + } + + stream_out->write(stream_out, prompt + prompt_offset, prompt_length - prompt_offset); + prompt_offset = prompt_length; +} + +static void help(uint8_t argc, __attribute__((unused)) char **argv) { + if (argc > 1) { + WRITE_S("help: accepts no arguments\n"); + } + + WRITE_S("Available commands:\n" + "help\n" + "ls DIR\n" + "cat FILE\n") +} + +static void on_enter_pressed() { + prompt[prompt_length] = '\0'; + + char *argv[16]; + uint8_t argc = (uint8_t)string_split(prompt, ' ', 16, argv); + + WRITE_S("\n"); + + if (string_equal(argv[0], "help")) { + help(argc, argv); + } else if (string_equal(argv[0], "ls")) { + ls(stream_in, stream_out, argc, argv); + } else if (string_equal(argv[0], "cat")) { + cat(stream_in, stream_out, argc, argv); + } else { + WRITE_S("Unknown command\n"); + } + WRITE_S("$ "); + prompt_length = prompt_offset = 0; +} + +static void on_cancel_pressed() { + WRITE_S("^C\n"); + WRITE_S("$ "); + prompt_length = prompt_offset = 0; +} + +static void on_disconnect_pressed() { + WRITE_S("^D\n"); + active = 0; +} + +static void on_ctrl_character_pressed(char c) { + if (c == 'c') { + on_cancel_pressed(); + } else if (c == 'd') { + on_disconnect_pressed(); + } +} + +static void redraw_from_cursor() { + uint64_t tail = prompt_length - prompt_offset; + stream_out->write(stream_out, prompt + prompt_offset, tail); + stream_out->write(stream_out, ws, 1); // erase the character past the end + stream_out->write(stream_out, bs, tail + 1); // move back to cursor position +} + +static void on_character_pressed(char c) { + if (prompt_offset >= PROMPT_LENGTH) + return; + + if (prompt_offset != prompt_length) { + memory_move(prompt + prompt_offset, prompt_length - prompt_offset, prompt + prompt_offset + 1); + } + prompt[prompt_offset] = c; + prompt_length++; + prompt_offset++; + + stream_out->write(stream_out, &c, 1); // emit the character itself + redraw_from_cursor(); +} + +static void on_backspace_pressed() { + if (!prompt_offset) + return; + + if (prompt_offset != prompt_length) { + memory_move(prompt + prompt_offset, prompt_length - prompt_offset, prompt + prompt_offset - 1); + } + prompt_offset--; + prompt_length--; + + stream_out->write(stream_out, bs, 1); + redraw_from_cursor(); +} + +static void on_delete_pressed() { + if (prompt_offset == prompt_length) + return; + + memory_move(prompt + prompt_offset + 1, prompt_length - prompt_offset - 1, prompt + prompt_offset); + prompt_length--; + + redraw_from_cursor(); +} + +typedef enum { + PARSE_NORMAL, + PARSE_ESC, + PARSE_CSI, +} parse_state_t; + +static parse_state_t parser_state = PARSE_NORMAL; +static char parser_csi_param[8]; +static uint8_t parser_csi_len; + +static void on_char_received(char c) { + switch (parser_state) { + case PARSE_NORMAL: + if (c == '\x1B') { + parser_state = PARSE_ESC; + } else if (c == '\b' || c == '\x7F') { + on_backspace_pressed(); + } else if (c == '\n' || c == '\r') { + on_enter_pressed(); + } else if (c >= '\x01' && c <= '\x1A') { + on_ctrl_character_pressed(c + 'a' - 1); + } else if (c >= ' ') { + on_character_pressed(c); + } + break; + + case PARSE_ESC: + if (c == '[') { + parser_state = PARSE_CSI; + parser_csi_len = 0; + } else { + parser_state = PARSE_NORMAL; + } + break; + + case PARSE_CSI: + if ((c >= '0' && c <= '9') || c == ';') { + if (parser_csi_len < sizeof(parser_csi_param) - 1) { + parser_csi_param[parser_csi_len++] = c; + } + } else { + parser_state = PARSE_NORMAL; + parser_csi_param[parser_csi_len] = '\0'; + + switch (c) { + case 'C': + on_right_pressed(); + break; + case 'D': + on_left_pressed(); + break; + case 'H': + on_home_pressed(); + break; + case 'F': + on_end_pressed(); + break; + case '~': + if (parser_csi_param[0] == '3') { + on_delete_pressed(); + } + break; + } + } + break; + } +} + +void terminal(stream_t *stdin, stream_t *stdout, __attribute__((unused)) uint8_t argc, __attribute__((unused)) char **argv) { + memory_set('\b', PROMPT_LENGTH, (char *)bs); + memory_set(' ', PROMPT_LENGTH, (char *)ws); + + stream_in = stdin; + stream_out = stdout; + + root = fs_mount(); + + char *greeting = string_format("Welcome to FreywarOS v%s!\n\n", VERSION); + WRITE_D(greeting); + memory_free(greeting); + + WRITE_S("$ "); + + while (active) { + char c; + if (stream_in->read(stream_in, 1, &c)) { + on_char_received(c); + } + } + + // fs_unmount(root); + stream_in = stream_out = NUL; +} diff --git a/src/app/terminal.h b/src/app/terminal.h new file mode 100644 index 0000000..b1e8f11 --- /dev/null +++ b/src/app/terminal.h @@ -0,0 +1,5 @@ +#pragma once + +#include "src/stream.h" + +void terminal(stream_t *stdin, stream_t *stdout, uint8_t argc, char **argv); diff --git a/src/fat16.c b/src/fat16.c index 7de8e2b..a6be2c1 100644 --- a/src/fat16.c +++ b/src/fat16.c @@ -255,5 +255,6 @@ void fat16_close(fs_node_t *node) { void fat16_unmount(fs_node_t *node) { ASSERT(node->type == FAT16, "fat16_unmount: node is not FAT16"); ASSERT(node == fs, "fat16_unmount: node is not filesystem"); - ASSERT(0, "fat16_unmount: not implemented"); + memory_free(node); + fs = NUL; } diff --git a/src/kernel.c b/src/kernel.c index f7700ca..3140efe 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -1,30 +1,31 @@ +#include "src/app/terminal.h" #include "src/idt.h" #include "src/keyboard.h" #include "src/memory.h" #include "src/pic.h" -#include "src/terminal.h" +#include "src/stream.h" #include "src/util.h" #include "src/vga.h" -__attribute__((interrupt)) void isr_divide_by_zero([[maybe_unused]] struct interrupt_frame *frame) { +__attribute__((interrupt)) void isr_divide_by_zero(__attribute__((unused)) struct interrupt_frame *frame) { vga_set_string(VGA_HEIGHT - 1, 0, "EXCEPTION: divide by zero", 0x4F); while (1) ; } -__attribute__((interrupt)) void isr_page_fault([[maybe_unused]] struct interrupt_frame *frame) { +__attribute__((interrupt)) void isr_page_fault(__attribute__((unused)) struct interrupt_frame *frame) { vga_set_string(VGA_HEIGHT - 1, 0, "EXCEPTION: page fault", 0x4F); while (1) ; } -__attribute__((interrupt)) void isr_general_violation([[maybe_unused]] struct interrupt_frame *frame) { +__attribute__((interrupt)) void isr_general_violation(__attribute__((unused)) struct interrupt_frame *frame) { vga_set_string(VGA_HEIGHT - 1, 0, "EXCEPTION: general violation", 0x4F); while (1) ; } -__attribute__((interrupt)) void isr_ata_primary([[maybe_unused]] struct interrupt_frame *frame) { +__attribute__((interrupt)) void isr_ata_primary(__attribute__((unused)) struct interrupt_frame *frame) { outb(0x20, 0x20); outb(0xA0, 0x20); } @@ -40,9 +41,12 @@ void kernel_main() { __asm__ volatile("sti"); memory_init(); - keyboard_init(); - terminal_init(); - while (1) - ; + stream_t *stdin = keyboard_init(); + stream_t *stdout = vga_init(); + + terminal(stdin, stdout, 0, NUL); + + outw(0x604, 0x2000); // TODO: parse ACPI tables for real hardware + __asm__ volatile("cli; hlt"); } diff --git a/src/keyboard.c b/src/keyboard.c index dc985ae..4fd5b98 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -1,22 +1,161 @@ #include "src/keyboard.h" #include "src/idt.h" +#include "src/memory.h" +#include "src/stream.h" #include "src/util.h" +#include -static keyboard_handler_t current_handler = 0; +#define KEYBOARD_STATE_LSHIFT 0b00000001 +#define KEYBOARD_STATE_RSHIFT 0b00000010 +#define KEYBOARD_STATE_LCTRL 0b00000100 +#define KEYBOARD_STATE_RCTRL 0b00001000 +#define KEYBOARD_STATE_LALT 0b00010000 +#define KEYBOARD_STATE_RALT 0b00100000 +#define KEYBOARD_STATE_SEQ 0b01000000 -__attribute__((interrupt)) static void isr_keyboard([[maybe_unused]] struct interrupt_frame *frame) { +static uint8_t keyboard_state = 0; + +// clang-format off +static const char scancode_normal[128] = { + 0, 0, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b', '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', + 'i', 'o', 'p', '[', ']', '\n', 0, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', 0, '\\', 'z', 'x', + 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 0, '*', 0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, '-', 0, 0, 0, '+', 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static const char scancode_shifted[128] = { + 0, 0, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b', '\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', + 'I', 'O', 'P', '{', '}', '\n', 0, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~', 0, '|', 'Z', 'X', + 'C', 'V', 'B', 'N', 'M', '<', '>', '?', 0, '*', 0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, '-', 0, 0, 0, '+', 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; +// clang-format on + +#define BUFFER_SIZE 256 + +static char buffer[BUFFER_SIZE]; +static uint16_t buffer_offset = 0; +static uint16_t buffer_length = 0; + +static void append(char c) { + buffer[(buffer_offset + buffer_length) % BUFFER_SIZE] = c; + if (buffer_length < BUFFER_SIZE) { + buffer_length++; + } +} + +static void append_sequence(const char *s) { + while (*s) { + append(*s); + s++; + } +} + +static void stream_write(__attribute__((unused)) stream_t *self, __attribute__((unused)) const char *from, __attribute__((unused)) uint64_t size) { +} + +static uint64_t stream_read(__attribute__((unused)) stream_t *self, uint64_t max, char *to) { + if (max == 0 || buffer_length == 0) { + return 0; + } + + uint64_t size = buffer_length > max ? max : buffer_length; + + if (buffer_offset + size < BUFFER_SIZE) { + memory_copy(buffer + buffer_offset, size, to); + } else { + uint64_t chunk_0 = BUFFER_SIZE - buffer_offset; + memory_copy(buffer + buffer_offset, chunk_0, to); + memory_copy(buffer, size - chunk_0, to + chunk_0); + } + + buffer_length -= size; + buffer_offset = (buffer_offset + size) % BUFFER_SIZE; + + return size; +} + +static stream_t stream = {stream_write, stream_read}; + +static void on_key(uint8_t scancode) { + const uint8_t pressed = !(scancode & 0x80); + const uint8_t code = scancode & ~0x80; + + if (!(keyboard_state & KEYBOARD_STATE_SEQ)) { + switch (code) { + case 0x60: + keyboard_state = keyboard_state | KEYBOARD_STATE_SEQ; + break; + case 0x2A: + keyboard_state = pressed ? keyboard_state | KEYBOARD_STATE_LSHIFT : keyboard_state & ~KEYBOARD_STATE_LSHIFT; + break; + case 0x36: + keyboard_state = pressed ? keyboard_state | KEYBOARD_STATE_RSHIFT : keyboard_state & ~KEYBOARD_STATE_RSHIFT; + break; + case 0x38: + keyboard_state = pressed ? keyboard_state | KEYBOARD_STATE_LALT : keyboard_state & ~KEYBOARD_STATE_LALT; + break; + case 0x1D: + keyboard_state = pressed ? keyboard_state | KEYBOARD_STATE_LCTRL : keyboard_state & ~KEYBOARD_STATE_LCTRL; + break; + case 0x0E: + pressed ? append('\b') : 0; + break; + case 0x1C: + pressed ? append('\n') : 0; + break; + default: + if (pressed && scancode_normal[code]) { + if (keyboard_state && (keyboard_state & (KEYBOARD_STATE_LCTRL | KEYBOARD_STATE_RCTRL)) == keyboard_state) { + if ((scancode_normal[code] >= '0' && scancode_normal[code] <= '9') || + (scancode_normal[code] >= 'a' && scancode_normal[code] <= 'z')) { + append(scancode_normal[code] - 'a' + 1); + } + } else if (keyboard_state && (keyboard_state & (KEYBOARD_STATE_LALT | KEYBOARD_STATE_RALT)) == keyboard_state) { + // TODO Alt combinations. + } else { + append((keyboard_state & (KEYBOARD_STATE_LSHIFT | KEYBOARD_STATE_RSHIFT) ? scancode_shifted : scancode_normal)[code]); + } + } + break; + } + } else { + keyboard_state = keyboard_state & ~KEYBOARD_STATE_SEQ; + switch (code) { + case 0x38: + keyboard_state = pressed ? keyboard_state | KEYBOARD_STATE_RALT : keyboard_state & ~KEYBOARD_STATE_RALT; + break; + case 0x1D: + keyboard_state = pressed ? keyboard_state | KEYBOARD_STATE_RCTRL : keyboard_state & ~KEYBOARD_STATE_RCTRL; + break; + case 0x47: + pressed ? append_sequence(STREAM_SEQ_HOME) : 0; + break; + case 0x4B: + pressed ? append_sequence(STREAM_SEQ_LEFT) : 0; + break; + case 0x4D: + pressed ? append_sequence(STREAM_SEQ_RIGHT) : 0; + break; + case 0x4F: + pressed ? append_sequence(STREAM_SEQ_END) : 0; + break; + case 0x53: + pressed ? append_sequence(STREAM_SEQ_DELETE) : 0; + break; + } + } +} + +__attribute__((interrupt)) static void isr_keyboard(__attribute__((unused)) struct interrupt_frame *frame) { uint8_t scancode = inb(0x60); outb(0x20, 0x20); - if (current_handler) { - current_handler(scancode); - } + on_key(scancode); } -void keyboard_init() { +stream_t *keyboard_init() { outb(0x21, inb(0x21) & ~0x02); idt_set_entry(33, isr_keyboard, 0x8E); -} -void keyboard_set_handler(keyboard_handler_t handler) { - current_handler = handler; + return &stream; } diff --git a/src/keyboard.h b/src/keyboard.h index 7fb80e8..ddf730d 100644 --- a/src/keyboard.h +++ b/src/keyboard.h @@ -1,9 +1,6 @@ #pragma once +#include "src/stream.h" #include -void keyboard_init(); - -typedef void (*keyboard_handler_t)(uint8_t scancode); - -void keyboard_set_handler(keyboard_handler_t handler); +stream_t *keyboard_init(); diff --git a/src/memory.c b/src/memory.c index 522707a..f3070b6 100644 --- a/src/memory.c +++ b/src/memory.c @@ -40,7 +40,7 @@ static void *memory_page_allocate() { return (void *)((uint64_t)page * PAGE_SIZE); } -[[maybe_unused]] static void memory_page_free(void *address) { +__attribute__((unused)) static void memory_page_free(void *address) { uint16_t page = (uint16_t)((uint64_t)address / PAGE_SIZE); ASSERT(get_allocated(page), "memory_page_free: page not allocated") set_allocated(page, 0); diff --git a/src/panic.c b/src/panic.c index 38970cc..207016b 100644 --- a/src/panic.c +++ b/src/panic.c @@ -1,7 +1,7 @@ #include "src/panic.h" #include "src/vga.h" -void kernel_panic(const char *msg, [[maybe_unused]] const char *file, [[maybe_unused]] int line) { +void kernel_panic(const char *msg, __attribute__((unused)) const char *file, __attribute__((unused)) int line) { vga_set_string(0, VGA_HEIGHT - 1, msg, 0x28); while (1) ; diff --git a/src/stream.h b/src/stream.h new file mode 100644 index 0000000..a2967c7 --- /dev/null +++ b/src/stream.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#define STREAM_SEQ_UP "\x1B[A" +#define STREAM_SEQ_DOWN "\x1B[B" +#define STREAM_SEQ_RIGHT "\x1B[C" +#define STREAM_SEQ_LEFT "\x1B[D" +#define STREAM_SEQ_HOME "\x1B[H" +#define STREAM_SEQ_END "\x1B[F" +#define STREAM_SEQ_INSERT "\x1B[2~" +#define STREAM_SEQ_DELETE "\x1B[3~" +#define STREAM_SEQ_PAGE_UP "\x1B[5~" +#define STREAM_SEQ_PAGE_DOWN "\x1B[6~" +#define STREAM_SEQ_F1 "\x1B[11~" +#define STREAM_SEQ_F2 "\x1B[12~" +#define STREAM_SEQ_F3 "\x1B[13~" +#define STREAM_SEQ_F4 "\x1B[14~" +#define STREAM_SEQ_F5 "\x1B[15~" +#define STREAM_SEQ_F6 "\x1B[16~" +#define STREAM_SEQ_F7 "\x1B[17~" +#define STREAM_SEQ_F8 "\x1B[18~" +#define STREAM_SEQ_F9 "\x1B[19~" +#define STREAM_SEQ_F10 "\x1B[1A~" +#define STREAM_SEQ_F11 "\x1B[1B~" +#define STREAM_SEQ_F12 "\x1B[1C~" + +typedef struct stream stream_t; + +typedef void (*stream_write_t)(stream_t *self, const char *from, uint64_t size); +typedef uint64_t (*stream_read_t)(stream_t *self, uint64_t max, char *to); + +typedef struct stream { + stream_write_t write; + stream_read_t read; +} stream_t; diff --git a/src/terminal.c b/src/terminal.c deleted file mode 100644 index a74d1b1..0000000 --- a/src/terminal.c +++ /dev/null @@ -1,397 +0,0 @@ -#include "src/terminal.h" -#include "src/fs.h" -#include "src/keyboard.h" -#include "src/memory.h" -#include "src/string.h" -#include "src/util.h" -#include "src/vga.h" -#include - -#define KEYBOARD_STATE_LSHIFT 0b00000001 -#define KEYBOARD_STATE_RSHIFT 0b00000010 -#define KEYBOARD_STATE_LCTRL 0b00000100 -#define KEYBOARD_STATE_RCTRL 0b00001000 -#define KEYBOARD_STATE_LALT 0b00010000 -#define KEYBOARD_STATE_RALT 0b00100000 -#define KEYBOARD_STATE_SEQ 0b01000000 - -static uint8_t keyboard_state = 0; - -// clang-format off -static const char scancode_normal[128] = { - 0, 0, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b', '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', - 'i', 'o', 'p', '[', ']', '\n', 0, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', 0, '\\', 'z', 'x', - 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 0, '*', 0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, '-', 0, 0, 0, '+', 0, 0, 0, 0, 0, 0, 0, 0, 0 -}; - -static const char scancode_shifted[128] = { - 0, 0, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b', '\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', - 'I', 'O', 'P', '{', '}', '\n', 0, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~', 0, '|', 'Z', 'X', - 'C', 'V', 'B', 'N', 'M', '<', '>', '?', 0, '*', 0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, '-', 0, 0, 0, '+', 0, 0, 0, 0, 0, 0, 0, 0, 0 -}; -// clang-format on - -#define PROMPT_PREFIX 2 -#define PROMPT_LENGTH 255 - -fs_node_t *root; - -static char prompt[PROMPT_LENGTH + 1]; -static char prompt_swap[PROMPT_LENGTH + 1]; -static uint8_t prompt_row = 0; -static uint8_t prompt_length = 0; -static uint8_t prompt_offset = 0; -static char prompt_buf[VGA_WIDTH + 1] = "$ "; - -static void render_prompt() { - uint8_t offset = PROMPT_PREFIX + prompt_offset < VGA_WIDTH ? 0 : PROMPT_PREFIX + prompt_offset - VGA_WIDTH + 1; - uint8_t length = PROMPT_PREFIX + prompt_length < VGA_WIDTH ? prompt_length : VGA_WIDTH - PROMPT_PREFIX; - prompt_buf[1] = PROMPT_PREFIX + prompt_offset < VGA_WIDTH ? ' ' : '<'; - memory_copy(prompt + offset, length, prompt_buf + PROMPT_PREFIX); - if (PROMPT_PREFIX + prompt_length > VGA_WIDTH && prompt_offset < prompt_length - 1) { - prompt_buf[VGA_WIDTH - 1] = '>'; - } - prompt_buf[PROMPT_PREFIX + length] = '\0'; - - vga_clear(prompt_row, 1); - vga_set_string(prompt_row, 0, prompt_buf, 0x0F); - vga_set_cursor(prompt_row, PROMPT_PREFIX + prompt_offset - offset); -} - -static void on_home_pressed() { - if (keyboard_state || !prompt_offset) { - return; - } - - prompt_offset = 0; - render_prompt(); -} - -static void on_left_pressed() { - if (keyboard_state || !prompt_offset) { - return; - } - - prompt_offset--; - render_prompt(); -} - -static void on_right_pressed() { - if (keyboard_state || prompt_offset == prompt_length) { - return; - } - - prompt_offset++; - render_prompt(); -} - -static void on_end_pressed() { - if (keyboard_state || prompt_offset == prompt_length) { - return; - } - - prompt_offset = prompt_length; - render_prompt(); -} - -static void advance_row(uint8_t rows) { - while (rows--) { - if (prompt_row == VGA_HEIGHT - 1) { - vga_copy(1, VGA_HEIGHT - 1, 0); - } else { - prompt_row++; - } - } -} - -static void print_line(const char *string) { - advance_row(1); - vga_clear(prompt_row - 1, 1); - vga_set_string(prompt_row - 1, 0, string, 0x0F); -} - -static void help(uint8_t argc, [[maybe_unused]] char **argv) { - if (argc > 1) { - print_line("help: accepts no arguments"); - } - - advance_row(4); - vga_set_string(prompt_row - 4, 0, "Available commands:", 0x0F); - vga_set_string(prompt_row - 3, 0, "help", 0x0F); - vga_set_string(prompt_row - 2, 0, "ls DIR", 0x0F); - vga_set_string(prompt_row - 1, 0, "cat FILE", 0x0F); -} - -static void ls(uint8_t argc, char **argv) { - if (argc != 2) { - print_line("ls: requires a single path"); - return; - } - - char *path_components[16]; - uint64_t path_length = string_split(argv[1], '/', 16, path_components); - if (!string_empty(path_components[0])) { - print_line("ls: relative paths not supported"); - return; - } - - fs_node_t *prev = NUL; - fs_node_t *curr = root; - uint8_t i = 1; - while (i < path_length) { - if (string_empty(path_components[i])) { - i++; - continue; - } - - if (!curr->is_dir) { - print_line("ls: can not navigate into a file"); - fs_close(curr); - return; - } - - prev = curr; - curr = fs_open_by(curr, path_components[i]); - if (prev != root) { - fs_close(prev); - } - - if (!curr) { - print_line("ls: invalid path"); - return; - } - i++; - } - - if (!curr->is_dir) { - print_line("ls: can not list file"); - fs_close(curr); - return; - } - - uint8_t j = 0; - fs_node_t *item = fs_open_at(curr, j++); - while (item) { - print_line(item->name); - fs_close(item); - item = fs_open_at(curr, j++); - } - if (curr != root) { - fs_close(curr); - } -} - -static void cat(uint8_t argc, char **argv) { - if (argc != 2) { - print_line("cat: requires a single path"); - return; - } - - char *path_components[16]; - uint64_t path_length = string_split(argv[1], '/', 16, path_components); - if (!string_empty(path_components[0])) { - print_line("cat: relative paths not supported"); - return; - } - - fs_node_t *prev = NUL; - fs_node_t *curr = root; - uint8_t i = 1; - while (i < path_length) { - if (string_empty(path_components[i])) { - i++; - continue; - } - - if (!curr->is_dir) { - print_line("cat: can not navigate into a file"); - fs_close(curr); - return; - } - - prev = curr; - curr = fs_open_by(curr, path_components[i]); - if (prev != root) { - fs_close(prev); - } - - if (!curr) { - print_line("cat: invalid path"); - return; - } - i++; - } - - if (curr->is_dir) { - print_line("cat: can not print a directory"); - if (curr != root) { - fs_close(curr); - } - return; - } - - char *content = memory_allocate(curr->size + 1); - fs_read(curr, 0, curr->size, content); - content[curr->size] = '\0'; - - char **lines = memory_allocate(curr->size * sizeof(char *)); - uint64_t lines_count = string_split(content, '\n', curr->size, lines); - - for (uint64_t i = 0; i < lines_count; i++) { - print_line(lines[i]); - } - - memory_free(lines); - memory_free(content); - fs_close(curr); -} - -static void on_enter_pressed() { - prompt[prompt_length] = '\0'; - - char *argv[16]; - uint8_t argc = (uint8_t)string_split(prompt, ' ', 16, argv); - - advance_row(1); - - if (string_equal(argv[0], "help")) { - help(argc, argv); - } else if (string_equal(argv[0], "ls")) { - ls(argc, argv); - } else if (string_equal(argv[0], "cat")) { - cat(argc, argv); - } else { - print_line("Unknown command"); - } - prompt_length = prompt_offset = 0; - render_prompt(); -} - -static void on_cancel_pressed() { - advance_row(1); - prompt_length = prompt_offset = 0; - render_prompt(); -} - -static void on_character_pressed(uint8_t code) { - if (keyboard_state && (keyboard_state & (KEYBOARD_STATE_LCTRL | KEYBOARD_STATE_RCTRL)) == keyboard_state) { - switch (code) { - case 0x2E: // C - on_cancel_pressed(); - } - return; - } - - if (keyboard_state & ~(KEYBOARD_STATE_LSHIFT | KEYBOARD_STATE_RSHIFT) || prompt_length >= PROMPT_LENGTH) { - return; - } - - if (prompt_offset == prompt_length) { - prompt[prompt_offset + 1] = '\0'; - } else { - memory_copy(prompt, prompt_length + 1, prompt_swap); - memory_copy(prompt_swap + prompt_offset, prompt_length - prompt_offset, prompt + prompt_offset + 1); - } - prompt[prompt_offset] = (keyboard_state & (KEYBOARD_STATE_LSHIFT | KEYBOARD_STATE_RSHIFT) ? scancode_shifted : scancode_normal)[code]; - prompt_offset++; - prompt_length++; - render_prompt(); -} - -static void on_backspace_pressed() { - if (keyboard_state || !prompt_offset) { - return; - } - - if (prompt_offset == prompt_length) { - prompt[prompt_offset - 1] = '\0'; - } else { - memory_copy(prompt, prompt_length + 1, prompt_swap); - memory_copy(prompt_swap + prompt_offset, prompt_length - prompt_offset, prompt + prompt_offset - 1); - } - prompt_offset--; - prompt_length--; - render_prompt(); -} - -static void on_delete_pressed() { - if (keyboard_state || prompt_offset == prompt_length) { - return; - } - - prompt_offset++; - on_backspace_pressed(); -} - -static void terminal_on_key(uint8_t scancode) { - const uint8_t pressed = !(scancode & 0x80); - const uint8_t code = scancode & ~0x80; - - if (!(keyboard_state & KEYBOARD_STATE_SEQ)) { - switch (code) { - case 0x60: - keyboard_state = keyboard_state | KEYBOARD_STATE_SEQ; - break; - case 0x2A: - keyboard_state = pressed ? keyboard_state | KEYBOARD_STATE_LSHIFT : keyboard_state & ~KEYBOARD_STATE_LSHIFT; - break; - case 0x36: - keyboard_state = pressed ? keyboard_state | KEYBOARD_STATE_RSHIFT : keyboard_state & ~KEYBOARD_STATE_RSHIFT; - break; - case 0x38: - keyboard_state = pressed ? keyboard_state | KEYBOARD_STATE_LALT : keyboard_state & ~KEYBOARD_STATE_LALT; - break; - case 0x1D: - keyboard_state = pressed ? keyboard_state | KEYBOARD_STATE_LCTRL : keyboard_state & ~KEYBOARD_STATE_LCTRL; - break; - case 0x0E: - pressed ? on_backspace_pressed() : 0; - break; - case 0x1C: - pressed ? on_enter_pressed() : 0; - break; - default: - pressed &&scancode_normal[code] ? on_character_pressed(code) : 0; - break; - } - } else { - keyboard_state = keyboard_state & ~KEYBOARD_STATE_SEQ; - switch (code) { - case 0x38: - keyboard_state = pressed ? keyboard_state | KEYBOARD_STATE_RALT : keyboard_state & ~KEYBOARD_STATE_RALT; - break; - case 0x1D: - keyboard_state = pressed ? keyboard_state | KEYBOARD_STATE_RCTRL : keyboard_state & ~KEYBOARD_STATE_RCTRL; - break; - case 0x47: - pressed ? on_home_pressed() : 0; - break; - case 0x4B: - pressed ? on_left_pressed() : 0; - break; - case 0x4D: - pressed ? on_right_pressed() : 0; - break; - case 0x4F: - pressed ? on_end_pressed() : 0; - break; - case 0x53: - pressed ? on_delete_pressed() : 0; - break; - } - } -} - -void terminal_init() { - root = fs_mount(); - - keyboard_set_handler(terminal_on_key); - vga_clear(0, VGA_HEIGHT); - char *greeting = string_format("Welcome to FreywarOS v%s!", VERSION); - vga_set_string(0, 0, greeting, 0x0F); - memory_free(greeting); - prompt_row += 2; - render_prompt(); -} diff --git a/src/terminal.h b/src/terminal.h deleted file mode 100644 index 64c6c41..0000000 --- a/src/terminal.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -void terminal_init(); diff --git a/src/vga.c b/src/vga.c index 8e6d641..49e9e69 100644 --- a/src/vga.c +++ b/src/vga.c @@ -1,48 +1,170 @@ #include "src/vga.h" #include "src/memory.h" -#include "src/panic.h" #include "src/util.h" +#include static uint16_t *vga = (uint16_t *)0x000B8000; +static uint16_t color = (uint16_t)0x0F << 8; +static uint16_t offset = 0; -void vga_set_cursor(uint8_t row, uint8_t col) { - ASSERT(row < VGA_HEIGHT && col < VGA_WIDTH, "vga_set_cursor: invalid coordinates") - uint16_t pos = row * VGA_WIDTH + col; +static void set_char(char c) { + vga[offset] = color | (uint16_t)c; +} + +static void set_cursor() { outb(0x3D4, 0x0F); - outb(0x3D5, pos & 0xFF); + outb(0x3D5, offset & 0xFF); outb(0x3D4, 0x0E); - outb(0x3D5, (pos >> 8) & 0xFF); + outb(0x3D5, (offset >> 8) & 0xFF); } -void vga_set_char(uint8_t row, uint8_t col, char c, uint8_t color) { - ASSERT(row < VGA_HEIGHT && col < VGA_WIDTH, "vga_set_char: invalid coordinates"); +static void scroll() { + memory_move(vga + VGA_WIDTH, 2 * VGA_WIDTH * (VGA_HEIGHT - 1), vga); + for (offset = VGA_WIDTH * (VGA_HEIGHT - 1); offset < VGA_WIDTH * VGA_HEIGHT; offset++) { + set_char(' '); + } + offset = VGA_WIDTH * (VGA_HEIGHT - 1); +} - vga[row * VGA_WIDTH + col] = (uint16_t)(((uint16_t)color << 8) | (uint16_t)c); +static void advance() { + offset++; + if (offset >= VGA_WIDTH * VGA_HEIGHT) { + scroll(); + } } -void vga_set_string(uint8_t row, uint8_t col, const char *str, uint8_t color) { - ASSERT(row < VGA_HEIGHT && col < VGA_WIDTH, "vga_set_string: invalid coordinates") - while (*str) { - vga_set_char(row, col++, *str++, color); - if (col >= VGA_WIDTH) { - col = 0; - row++; - ASSERT(row < VGA_HEIGHT && col < VGA_WIDTH, "vga_set_string: invalid coordinates") +static void clear() { + uint16_t *ptr = vga; + uint16_t fill = 0x0F20; + uint32_t count = VGA_WIDTH * VGA_HEIGHT; + __asm__ volatile("rep stosw" : "=D"(ptr), "=c"(count) : "D"(ptr), "a"(fill), "c"(count) : "memory"); + + offset = 0; + set_cursor(); +} + +typedef enum { + PARSE_NORMAL, + PARSE_ESC, + PARSE_CSI, +} parse_state_t; + +static parse_state_t parser_state = PARSE_NORMAL; +static char parser_csi_param[8]; +static uint8_t parser_csi_len; + +static void on_char_received(char c) { + switch (parser_state) { + case PARSE_NORMAL: + switch (c) { + case '\x1B': + parser_state = PARSE_ESC; + break; + case '\b': + case '\x7F': + if (offset > 0) { + offset--; + set_cursor(); + } + break; + case '\r': + offset -= offset % VGA_WIDTH; + set_cursor(); + break; + case '\n': + if (offset / VGA_WIDTH == VGA_HEIGHT - 1) { + scroll(); + } else { + offset += VGA_WIDTH; + } + offset -= offset % VGA_WIDTH; + set_cursor(); + break; + default: + if (c >= '\x01' && c <= '\x1A') { + set_char('^'); + advance(); + set_char(c + 'A' - 1); + advance(); + set_cursor(); + } else if (c >= ' ') { + set_char(c); + advance(); + set_cursor(); + } + break; } + break; + + case PARSE_ESC: + if (c == '[') { + parser_state = PARSE_CSI; + parser_csi_len = 0; + } else { + parser_state = PARSE_NORMAL; + } + break; + + case PARSE_CSI: + if ((c >= '0' && c <= '9') || c == ';') { + if (parser_csi_len < sizeof(parser_csi_param) - 1) { + parser_csi_param[parser_csi_len++] = c; + } + } else { + parser_state = PARSE_NORMAL; + parser_csi_param[parser_csi_len] = '\0'; + + switch (c) { + case 'C': + if (offset < VGA_WIDTH * VGA_HEIGHT - 1) { + offset++; + } + set_cursor(); + break; + case 'D': + if (offset > 0) { + offset--; + } + set_cursor(); + break; + case 'H': + offset -= offset % VGA_WIDTH; + set_cursor(); + break; + case 'F': + offset -= offset % VGA_WIDTH; + offset += VGA_WIDTH - 1; + set_cursor(); + break; + } + } + break; } } -void vga_copy(uint8_t src_row, uint8_t rows, uint8_t dst_row) { - ASSERT(src_row < VGA_HEIGHT && dst_row < VGA_HEIGHT && src_row + rows <= VGA_HEIGHT && dst_row + rows <= VGA_HEIGHT, - "vga_copy: invalid copy dimensions"); - memory_move(vga + src_row * VGA_WIDTH, rows * VGA_WIDTH * 2, vga + dst_row * VGA_WIDTH); +static void stream_write(__attribute__((unused)) stream_t *self, const char *from, uint64_t size) { + while (size--) { + on_char_received(*from++); + } } -void vga_clear(uint8_t row, uint8_t rows) { - ASSERT(row < VGA_HEIGHT && rows <= VGA_HEIGHT && row + rows <= VGA_HEIGHT, "vga_clear: invalid clear dimensions"); +static uint64_t stream_read(__attribute__((unused)) stream_t *self, __attribute__((unused)) uint64_t max, + __attribute__((unused)) char *to) { + return 0; +} - uint16_t *dst = vga + row * VGA_WIDTH; - uint16_t fill = 0x0F20; - uint32_t count = VGA_WIDTH * rows; - __asm__ volatile("rep stosw" : "=D"(dst), "=c"(count) : "D"(dst), "a"(fill), "c"(count) : "memory"); +static stream_t stream = {stream_write, stream_read}; + +stream_t *vga_init() { + clear(); + return &stream; +} + +void vga_set_string(uint8_t row, uint8_t col, const char *str, uint8_t c) { + ASSERT(row < VGA_HEIGHT && col < VGA_WIDTH, "vga_set_string: invalid coordinates") + color = (uint16_t)((uint16_t)c << 8); + offset = row * VGA_WIDTH + col; + while (*str) { + on_char_received(*str++); + } } diff --git a/src/vga.h b/src/vga.h index 1063313..cb5a220 100644 --- a/src/vga.h +++ b/src/vga.h @@ -1,16 +1,10 @@ #pragma once -#include +#include "src/stream.h" #define VGA_WIDTH 80 #define VGA_HEIGHT 25 -void vga_set_cursor(uint8_t row, uint8_t col); - -void vga_set_char(uint8_t row, uint8_t col, char c, uint8_t color); +stream_t *vga_init(); void vga_set_string(uint8_t row, uint8_t col, const char *str, uint8_t color); - -void vga_copy(uint8_t src_row, uint8_t rows, uint8_t dst_row); - -void vga_clear(uint8_t row, uint8_t rows);