From 0330ab168ee7bb26656829d7c6d45da3c5cc6fb9 Mon Sep 17 00:00:00 2001 From: Benji Dial Date: Tue, 6 Jan 2026 13:04:05 -0500 Subject: [PATCH] start forth shell --- disk/calcite/qwerty.bin | Bin 0 -> 2048 bytes include/calcite/syscalls.h | 4 + include/calcite/terminal.h | 28 ++- include/kernel-public/input.h | 132 +++++++++- include/kernel-public/syscall-numbers.h | 4 +- src/kernel/entry.c | 4 +- src/kernel/input.c | 91 ++++++- src/kernel/input.h | 7 +- src/kernel/interrupts.asm | 7 +- src/kernel/interrupts.c | 7 +- src/kernel/interrupts.h | 6 +- src/kernel/process.c | 24 ++ src/kernel/process.h | 2 + src/kernel/ps2.c | 59 ++++- src/kernel/scheduler.asm | 20 -- src/kernel/scheduler.c | 5 +- src/kernel/scheduler.h | 5 +- src/kernel/syscalls.asm | 2 + src/kernel/timer.c | 22 +- src/user-apps/shell/assembler.c | 218 +++++++++++++++++ src/user-apps/shell/assembler.h | 38 +++ src/user-apps/shell/builtin-words.c | 226 ++++++++++++++++++ .../shell/{shell.c => builtin-words.h} | 12 +- src/user-apps/shell/main.c | 36 +++ src/user-apps/shell/runtime.asm | 40 ++++ src/user-apps/shell/runtime.c | 218 +++++++++++++++++ src/user-apps/shell/runtime.h | 63 +++++ src/user-apps/terminal/terminal.c | 82 ++++++- src/user-libs/calcite/syscalls.c | 8 + src/user-libs/calcite/terminal.c | 163 ++++++++++++- 30 files changed, 1446 insertions(+), 87 deletions(-) create mode 100644 disk/calcite/qwerty.bin create mode 100644 src/user-apps/shell/assembler.c create mode 100644 src/user-apps/shell/assembler.h create mode 100644 src/user-apps/shell/builtin-words.c rename src/user-apps/shell/{shell.c => builtin-words.h} (76%) create mode 100644 src/user-apps/shell/main.c create mode 100644 src/user-apps/shell/runtime.asm create mode 100644 src/user-apps/shell/runtime.c create mode 100644 src/user-apps/shell/runtime.h diff --git a/disk/calcite/qwerty.bin b/disk/calcite/qwerty.bin new file mode 100644 index 0000000000000000000000000000000000000000..a5141fe4132e49989e843efabd3725eed703b9ae GIT binary patch literal 2048 zcmds%S5v}J5QWbx`4x60F%UX}1$#pUdpGum3ikfnJ$tVY4l|i?95WNxm(A`bnVfuQ zFYqHmt}bdZ=GVLZr#Nul04mD=e>R*(hPkmFlm3eb%c&z}8u8)m(v{V1;#PVV% zFh4Rixv|Un+{{XsiE#hy^g_4?7#u3*N_*_L;mu-#@qu^A9uY|WuKw7v@E85$FZvaK z(NF$LzwoDi#h>~$|6;z>B>Zc_U;JOt{#X3P|KzXyFZ}6$#h?Dy{42R?lkoRr|6@pI q_}83BJeAJ!!b;dCkB$F}fbV}p=yx>^KlZ;N^naiKe(Zlh^?w0*`i6x7 literal 0 HcmV?d00001 diff --git a/include/calcite/syscalls.h b/include/calcite/syscalls.h index 3fcdadd..fd881b8 100644 --- a/include/calcite/syscalls.h +++ b/include/calcite/syscalls.h @@ -37,6 +37,7 @@ enum fs_access_result read_file(struct read_file_parameter *parameter); enum fs_access_result read_file_splat(file_handle_t handle, void *buffer, uint64_t start, uint64_t bytes); void wait_for_mouse_packet(struct mouse_packet *packet_out); +int wait_for_keyboard_packet(); void *map_pages(uint64_t count); @@ -82,3 +83,6 @@ void wait_event(event_handle_t handle); uint64_t get_ms_since_boot(); void sleep_until_ms_since_boot(uint64_t ms); + +//vma should be page-aligned. writable and executable should be 1 or 0. +void change_page_permissions(void *vma, int writable, int executable); diff --git a/include/calcite/terminal.h b/include/calcite/terminal.h index d6e4986..f910161 100644 --- a/include/calcite/terminal.h +++ b/include/calcite/terminal.h @@ -17,6 +17,30 @@ #pragma once +#include + void init_terminal(); -void terminal_write(const char *data, int bytes); -void terminal_read(char *buffer, int bytes); + +void terminal_write(const void *data, int bytes); +void terminal_flush(); + +enum terminal_key_flags { + TKF_LEFT_SHIFT = 0x20000, + TKF_RIGHT_SHIFT = 0x40000, + TKF_LEFT_CTRL = 0x80000, + TKF_RIGHT_CTRL = 0x100000, + TKF_LEFT_ALT = 0x200000, + TKF_RIGHT_ALT = 0x400000, + TKF_CAPS_LOCK = 0x800000, + TKF_NUM_LOCK = 0x1000000 +}; + +//0x000000ff is printable character, if any, otherwise 0. +// this includes space, newline, and tab, but not delete or backspace. +// control, alt, and make are ignored, but shift, caps lock, and num lock are considered. +//0x0001ff00 is result of syscall_wait_for_keyboard_packet, shifted left. +//0x01fe0000 is flags as above. +int terminal_readkey(); + +//has null terminator, no newline. returned pointer is invalidated by next call. +const char *terminal_readline(); diff --git a/include/kernel-public/input.h b/include/kernel-public/input.h index e5cc1e5..8736a31 100644 --- a/include/kernel-public/input.h +++ b/include/kernel-public/input.h @@ -1,5 +1,5 @@ /* Calcite, include/kernel-public/input.h - * Copyright 2025 Benji Dial + * Copyright 2025-2026 Benji Dial * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,3 +21,133 @@ struct mouse_packet { int x_change; int y_change; }; + +//named after qwerty value of key +enum key_value { + KEY_F9 = 0x01, + KEY_F5 = 0x03, + KEY_F3 = 0x04, + KEY_F1 = 0x05, + KEY_F2 = 0x06, + KEY_F12 = 0x07, + KEY_F10 = 0x09, + KEY_F8 = 0x0a, + KEY_F6 = 0x0b, + KEY_F4 = 0x0c, + KEY_TAB = 0x0d, + KEY_GRAVE = 0x0e, + KEY_LEFT_ALT = 0x11, + KEY_LEFT_SHIFT = 0x12, + KEY_LEFT_CTRL = 0x14, + KEY_Q = 0x15, + KEY_1 = 0x16, + KEY_Z = 0x1a, + KEY_S = 0x1b, + KEY_A = 0x1c, + KEY_W = 0x1d, + KEY_2 = 0x1e, + KEY_C = 0x21, + KEY_X = 0x22, + KEY_D = 0x23, + KEY_E = 0x24, + KEY_4 = 0x25, + KEY_3 = 0x26, + KEY_SPACE = 0x29, + KEY_V = 0x2a, + KEY_F = 0x2b, + KEY_T = 0x2c, + KEY_R = 0x2d, + KEY_5 = 0x2e, + KEY_N = 0x31, + KEY_B = 0x32, + KEY_H = 0x33, + KEY_G = 0x34, + KEY_Y = 0x35, + KEY_6 = 0x36, + KEY_M = 0x3a, + KEY_J = 0x3b, + KEY_U = 0x3c, + KEY_7 = 0x3d, + KEY_8 = 0x3e, + KEY_COMMA = 0x41, + KEY_K = 0x42, + KEY_I = 0x43, + KEY_O = 0x44, + KEY_0 = 0x45, + KEY_9 = 0x46, + KEY_PERIOD = 0x49, + KEY_SLASH = 0x4a, + KEY_L = 0x4b, + KEY_SEMICOLON = 0x4c, + KEY_P = 0x4d, + KEY_MINUS = 0x4e, + KEY_APOSTRAPHE = 0x52, + KEY_OPEN_BRACKET = 0x54, + KEY_EQUALS = 0x55, + KEY_CAPS_LOCK = 0x58, + KEY_RIGHT_SHIFT = 0x59, + KEY_ENTER = 0x5a, + KEY_CLOSE_BRACKET = 0x5b, + KEY_BACKSLASH = 0x5d, + KEY_BACKSPACE = 0x66, + KEY_NUM_1 = 0x69, + KEY_NUM_4 = 0x6b, + KEY_NUM_7 = 0x6c, + KEY_NUM_0 = 0x70, + KEY_NUM_PERIOD = 0x71, + KEY_NUM_2 = 0x72, + KEY_NUM_5 = 0x73, + KEY_NUM_6 = 0x74, + KEY_NUM_8 = 0x75, + KEY_ESCAPE = 0x76, + KEY_NUM_LOCK = 0x77, + KEY_F11 = 0x78, + KEY_NUM_PLUS = 0x79, + KEY_NUM_3 = 0x7a, + KEY_NUM_MINUS = 0x7b, + KEY_NUM_TIMES = 0x7c, + KEY_NUM_9 = 0x7d, + KEY_SCROLL_LOCK = 0x7e, + KEY_F7 = 0x83, + KEY_WEB_SEARCH = 0x90, + KEY_RIGHT_ALT = 0x91, + KEY_RIGHT_CTRL = 0x94, + KEY_MEDIA_PREVIOUS = 0x95, + KEY_WEB_FAVORITES = 0x98, + KEY_LEFT_GUI = 0x9f, + KEY_WEB_REFRESH = 0xa0, + KEY_MEDIA_VOLUME_DOWN = 0xa1, + KEY_MEDIA_MUTE = 0xa3, + KEY_RIGHT_GUI = 0xa7, + KEY_WEB_STOP = 0xa8, + KEY_CALCULATOR = 0xab, + KEY_MENU = 0xaf, + KEY_WEB_FORWARD = 0xb0, + KEY_MEDIA_VOLUME_UP = 0xb2, + KEY_MEDIA_PAUSE = 0xb4, + KEY_ACPI_POWER = 0xb7, + KEY_WEB_BACK = 0xb8, + KEY_WEB_HOME = 0xba, + KEY_MEDIA_STOP = 0xbb, + KEY_ACPI_SLEEP = 0xbf, + KEY_COMPUTER = 0xc0, + KEY_EMAIL = 0xc8, + KEY_NUM_DIVIDE = 0xca, + KEY_MEDIA_NEXT = 0xcd, + KEY_MEDIA_SELECT = 0xd0, + KEY_NUM_ENTER = 0xda, + KEY_ACPI_WAKE = 0xde, + KEY_END = 0xe9, + KEY_LEFT = 0xeb, + KEY_HOME = 0xec, + KEY_INSERT = 0xf0, + KEY_DELETE = 0xf1, + KEY_DOWN = 0xf2, + KEY_RIGHT = 0xf4, + KEY_UP = 0xf5, + KEY_PAGE_DOWN = 0xfa, + KEY_PAGE_UP = 0xfd, + KEY_PRINT_SCREEN = 0xfe, + KEY_PAUSE_BREAK = 0xff, + KEY_FLAG_MAKE = 0x100 +}; diff --git a/include/kernel-public/syscall-numbers.h b/include/kernel-public/syscall-numbers.h index 351d804..6e80c4e 100644 --- a/include/kernel-public/syscall-numbers.h +++ b/include/kernel-public/syscall-numbers.h @@ -44,5 +44,7 @@ enum { SYSCALL_IPC_CREATE_PRIVATE_DGRAM_RECEIVER, SYSCALL_IPC_CREATE_PRIVATE_DGRAM_SENDER, SYSCALL_GET_MS_SINCE_BOOT, - SYSCALL_SLEEP_UNTIL_MS_SINCE_BOOT + SYSCALL_SLEEP_UNTIL_MS_SINCE_BOOT, + SYSCALL_CHANGE_PAGE_PERMISSIONS, + SYSCALL_WAIT_FOR_KEYBOARD_PACKET }; diff --git a/src/kernel/entry.c b/src/kernel/entry.c index b6af873..44e6486 100644 --- a/src/kernel/entry.c +++ b/src/kernel/entry.c @@ -259,7 +259,7 @@ static const char *cmdline_look_up(const char *key) { set_irq_handler(0x01, &on_keyboard_irq); set_irq_handler(0x0c, &on_mouse_irq); - enable_interrupts(); + init_interrupts(); //set up syscalls @@ -291,6 +291,8 @@ static const char *cmdline_look_up(const char *key) { register_syscall(SYSCALL_IPC_CREATE_PRIVATE_DGRAM_SENDER, (void *)&syscall_ipc_create_private_dgram_sender); register_syscall(SYSCALL_GET_MS_SINCE_BOOT, (void *)&syscall_get_ms_since_boot); register_syscall(SYSCALL_SLEEP_UNTIL_MS_SINCE_BOOT, (void *)&syscall_sleep_until_ms_since_boot); + register_syscall(SYSCALL_CHANGE_PAGE_PERMISSIONS, (void *)&syscall_change_page_permissions); + register_syscall(SYSCALL_WAIT_FOR_KEYBOARD_PACKET, (void *)&syscall_wait_for_keyboard_packet); //probe pci devices diff --git a/src/kernel/input.c b/src/kernel/input.c index 93e9841..ca7f831 100644 --- a/src/kernel/input.c +++ b/src/kernel/input.c @@ -1,5 +1,5 @@ /* Calcite, src/kernel/input.c - * Copyright 2025 Benji Dial + * Copyright 2025-2026 Benji Dial * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,10 +19,11 @@ #include "process.h" #include "debug.h" #include "input.h" +#include "heap.h" static int is_somebody_waiting_for_mouse_packet = 0; -static struct continuation_info waiting_continuation; -static struct mouse_packet *resume_by_reporting_to; +static struct continuation_info waiting_continuation_mouse; +static struct mouse_packet *resume_by_reporting_to_mouse; static int total_unreported_x_movement = 0; static int total_unreported_y_movement = 0; @@ -33,9 +34,9 @@ void add_mouse_movement(int x, int y) { total_unreported_y_movement += y; if (is_somebody_waiting_for_mouse_packet) { - resume_by_reporting_to->x_change = total_unreported_x_movement; - resume_by_reporting_to->y_change = total_unreported_y_movement; - add_to_queue(&ready_continuations, &waiting_continuation); + resume_by_reporting_to_mouse->x_change = total_unreported_x_movement; + resume_by_reporting_to_mouse->y_change = total_unreported_y_movement; + add_to_queue(&ready_continuations, &waiting_continuation_mouse); total_unreported_x_movement = 0; total_unreported_y_movement = 0; is_somebody_waiting_for_mouse_packet = 0; @@ -47,8 +48,6 @@ void syscall_wait_for_mouse_packet(struct mouse_packet *packet_out) { assert(running_thread != 0); - __asm__ ("cli"); - //TODO: handle these if (is_somebody_waiting_for_mouse_packet || !is_mapped_writable( @@ -60,12 +59,82 @@ void syscall_wait_for_mouse_packet(struct mouse_packet *packet_out) { packet_out->y_change = total_unreported_y_movement; total_unreported_x_movement = 0; total_unreported_y_movement = 0; - __asm__ ("sti"); return; } is_somebody_waiting_for_mouse_packet = 1; - resume_by_reporting_to = packet_out; - yield_sti(&waiting_continuation); + resume_by_reporting_to_mouse = packet_out; + yield(&waiting_continuation_mouse); + +} + +static int is_somebody_waiting_for_keyboard_packet = 0; +static struct continuation_info waiting_continuation_keyboard; +static int *resume_by_reporting_to_keyboard; + +//in packets +#define INITIAL_KEYBOARD_BUFFER_LENGTH 1024 + +static int *keyboard_buffer = 0; +static int keyboard_buffer_length = 0; +static int keyboard_buffer_used = 0; +static int keyboard_buffer_read_pointer = 0; + +void add_keyboard_packet(int packet) { + + if (is_somebody_waiting_for_keyboard_packet) { + *resume_by_reporting_to_keyboard = packet; + add_to_queue(&ready_continuations, &waiting_continuation_keyboard); + is_somebody_waiting_for_keyboard_packet = 0; + } + + else if (keyboard_buffer_used < keyboard_buffer_length) { + int write_pointer = (keyboard_buffer_read_pointer + keyboard_buffer_used) % keyboard_buffer_length; + keyboard_buffer[write_pointer] = packet; + ++keyboard_buffer_used; + } + + else if (keyboard_buffer == 0) { + keyboard_buffer = heap_alloc(INITIAL_KEYBOARD_BUFFER_LENGTH * sizeof(int)); + keyboard_buffer_length = INITIAL_KEYBOARD_BUFFER_LENGTH; + keyboard_buffer[0] = packet; + keyboard_buffer_used = 1; + } + + else { + int *new_buffer = heap_alloc(2 * keyboard_buffer_length * sizeof(int)); + memcpy( + new_buffer, &keyboard_buffer[keyboard_buffer_read_pointer], + keyboard_buffer_length - keyboard_buffer_read_pointer); + memcpy( + &new_buffer[keyboard_buffer_length - keyboard_buffer_read_pointer], + keyboard_buffer, keyboard_buffer_read_pointer); + heap_dealloc(keyboard_buffer, keyboard_buffer_length * sizeof(int)); + keyboard_buffer = new_buffer; + keyboard_buffer_length *= 2; + keyboard_buffer_read_pointer = 0; + keyboard_buffer[keyboard_buffer_length / 2] = packet; + ++keyboard_buffer_used; + } + +} + +int syscall_wait_for_keyboard_packet() { + + if (keyboard_buffer_used > 0) { + int value = keyboard_buffer[keyboard_buffer_read_pointer]; + keyboard_buffer_read_pointer = (keyboard_buffer_read_pointer + 1) % keyboard_buffer_length; + --keyboard_buffer_used; + return value; + } + + if (is_somebody_waiting_for_keyboard_packet) + panic("TODO") + + is_somebody_waiting_for_keyboard_packet = 1; + int result; + resume_by_reporting_to_keyboard = &result; + yield(&waiting_continuation_keyboard); + return result; } diff --git a/src/kernel/input.h b/src/kernel/input.h index 65059b0..ef215ea 100644 --- a/src/kernel/input.h +++ b/src/kernel/input.h @@ -1,5 +1,5 @@ /* Calcite, src/kernel/input.h - * Copyright 2025 Benji Dial + * Copyright 2025-2026 Benji Dial * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,7 +19,10 @@ #include -//should only be called with interrupts disabled void add_mouse_movement(int x, int y); void syscall_wait_for_mouse_packet(struct mouse_packet *packet_out); + +void add_keyboard_packet(int packet); + +int syscall_wait_for_keyboard_packet(); diff --git a/src/kernel/interrupts.asm b/src/kernel/interrupts.asm index ded890a..769c2ab 100644 --- a/src/kernel/interrupts.asm +++ b/src/kernel/interrupts.asm @@ -135,8 +135,8 @@ isr_irq_common: ;rdi is pointer to gdt descriptor ;rsi is pointer to idt descriptor ;dx is offset of tss into gdt -global enable_interrupts_asm -enable_interrupts_asm: +global init_interrupts_asm +init_interrupts_asm: ;load tables @@ -168,7 +168,4 @@ enable_interrupts_asm: mov al, 0x00 out 0xa1, al - ;enable interrupts and return - - sti ret diff --git a/src/kernel/interrupts.c b/src/kernel/interrupts.c index 99521da..6a9f4da 100644 --- a/src/kernel/interrupts.c +++ b/src/kernel/interrupts.c @@ -96,7 +96,6 @@ const char *exception_names[32] = { log(" r15 = 0x%h", parameter->r15, 16) log("halting") - __asm__ ("cli"); while (1) __asm__ ("hlt"); @@ -254,12 +253,12 @@ static struct idt_descriptor the_idt_descriptor = { extern void *isrs[48]; //defined in interrupts.asm -void enable_interrupts_asm( +void init_interrupts_asm( struct gdt_descriptor *gdtr, struct idt_descriptor *idtr, uint16_t tssr); -void enable_interrupts() { +void init_interrupts() { the_tss.ist1 = (uint64_t)interrupt_stack_one + INTERRUPT_STACK_SIZE; the_tss.ist2 = (uint64_t)interrupt_stack_two + INTERRUPT_STACK_SIZE; @@ -316,6 +315,6 @@ void enable_interrupts() { the_idt[i].offset_32 = offset >> 32; } - enable_interrupts_asm(&the_gdt_descriptor, &the_idt_descriptor, 0x08); + init_interrupts_asm(&the_gdt_descriptor, &the_idt_descriptor, 0x08); } diff --git a/src/kernel/interrupts.h b/src/kernel/interrupts.h index b10bce4..3a1d227 100644 --- a/src/kernel/interrupts.h +++ b/src/kernel/interrupts.h @@ -1,5 +1,5 @@ /* Calcite, src/kernel/interrupts.h - * Copyright 2025 Benji Dial + * Copyright 2025-2026 Benji Dial * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,8 +19,8 @@ #include -//switches to kernel gdt and idt, maps and unmasks irqs, enables interrupts -void enable_interrupts(); +//switches to kernel gdt and idt, maps and unmasks irqs +void init_interrupts(); //the handler should have normal C linkage void set_irq_handler(uint8_t irq_line, void (*handler)()); diff --git a/src/kernel/process.c b/src/kernel/process.c index 3a3bea0..10df5a1 100644 --- a/src/kernel/process.c +++ b/src/kernel/process.c @@ -135,6 +135,30 @@ void map_page_for_process( } +void syscall_change_page_permissions(void *vma, int writable, int executable) { + + assert(running_thread != 0) + + if (((uint64_t)vma & 0xfff) != 0 || (uint64_t)vma >= 0x0000008000000000 || vma == 0 || + writable < 0 || writable > 1 || executable < 0 || executable > 1) + syscall_illegal_args(); + + int p1i, p2i, p3i; + get_page_table_entry( + running_thread->process, vma, &p1i, &p2i, &p3i); + + uint64_t *entry = &running_thread->process->p1_virtual_bases[p3i][p2i][p1i]; + + if (!(*entry & 0x1)) + syscall_illegal_args(); + + *entry = + (*entry & 0x7ffffffffffffffd) | + (executable ? 0 : 0x8000000000000000) | + (writable ? 0x2 : 0x0); + +} + void create_guard_page_for_process(struct process *process, void *virtual_base) { int p1i, p2i, p3i; diff --git a/src/kernel/process.h b/src/kernel/process.h index bc9ba50..20aa9bc 100644 --- a/src/kernel/process.h +++ b/src/kernel/process.h @@ -167,6 +167,8 @@ enum fs_access_result syscall_read_file(struct read_file_parameter *parameter); void *syscall_map_pages(uint64_t count); +void syscall_change_page_permissions(void *vma, int writable, int executable); + enum ipc_dgram_result syscall_ipc_create_dgram_receiver( const char *address, ipc_dgram_receiver_handle_t *handle_out); diff --git a/src/kernel/ps2.c b/src/kernel/ps2.c index 74679d1..5abe7c8 100644 --- a/src/kernel/ps2.c +++ b/src/kernel/ps2.c @@ -1,5 +1,5 @@ /* Calcite, src/kernel/ps2.c - * Copyright 2025 Benji Dial + * Copyright 2025-2026 Benji Dial * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,19 +18,74 @@ #include "input.h" #include "ps2.h" +#include #include //defined in ps2.asm //returns -1 if no byte available int read_ps2_byte(); +static uint8_t keyboard_packet[7]; +static int keyboard_packet_length = 0; + void on_keyboard_irq() { int byte = read_ps2_byte(); if (byte == -1) return; - //TODO + if (keyboard_packet_length == 0 && byte != 0xe0 && byte != 0xe1 && byte != 0xf0) { + add_keyboard_packet(KEY_FLAG_MAKE | byte); + return; + } + + if (keyboard_packet_length == 1 && keyboard_packet[0] == 0xe0 && byte != 0xf0 && byte != 0x12) { + add_keyboard_packet(KEY_FLAG_MAKE | 0x80 | byte); + keyboard_packet_length = 0; + return; + } + + if (keyboard_packet_length == 1 && keyboard_packet[0] == 0xf0) { + add_keyboard_packet(byte); + keyboard_packet_length = 0; + return; + } + + if (keyboard_packet_length == 2 && keyboard_packet[0] == 0xe0 && keyboard_packet[1] == 0xf0 && byte != 0x7c) { + add_keyboard_packet(0x80 | byte); + keyboard_packet_length = 0; + return; + } + + if (keyboard_packet_length == 3 && keyboard_packet[0] == 0xe0 && keyboard_packet[1] == 0x12 && + keyboard_packet[2] == 0xe0 && byte == 0x7c) { + add_keyboard_packet(KEY_FLAG_MAKE | KEY_PRINT_SCREEN); + keyboard_packet_length = 0; + return; + } + + if (keyboard_packet_length == 5 && keyboard_packet[0] == 0xe0 && keyboard_packet[1] == 0xf0 && + keyboard_packet[2] == 0x7c && keyboard_packet[3] == 0xe0 && keyboard_packet[4] == 0xf0 && byte == 0x12) { + add_keyboard_packet(KEY_PRINT_SCREEN); + keyboard_packet_length = 0; + return; + } + + if (keyboard_packet_length == 7 && keyboard_packet[0] == 0xe1 && keyboard_packet[1] == 0x14 && + keyboard_packet[2] == 0x77 && keyboard_packet[3] == 0xe1 && keyboard_packet[4] == 0xf0 && + keyboard_packet[5] == 0x14 && keyboard_packet[6] == 0xf0 && byte == 0x77) { + add_keyboard_packet(KEY_FLAG_MAKE | KEY_PAUSE_BREAK); + add_keyboard_packet(KEY_PAUSE_BREAK); + keyboard_packet_length = 0; + return; + } + + if (keyboard_packet_length == 7) { + keyboard_packet_length = 0; + return; + } + + keyboard_packet[keyboard_packet_length++] = byte; } diff --git a/src/kernel/scheduler.asm b/src/kernel/scheduler.asm index c6ae2b1..61bd1d1 100644 --- a/src/kernel/scheduler.asm +++ b/src/kernel/scheduler.asm @@ -60,26 +60,6 @@ yield: .ret: ret -global yield_sti -yield_sti: - mov qword [rdi], .ret - mov qword [rdi + 8], rbx - mov qword [rdi + 16], rbp - mov qword [rdi + 24], rsp - mov qword [rdi + 32], r12 - mov qword [rdi + 40], r13 - mov qword [rdi + 48], r14 - mov qword [rdi + 56], r15 - mov qword rsi, qword [running_thread] - mov qword [rdi + 64], rsi - - sti - - jmp resume_next_continuation - -.ret: - ret - global yield_to_queue yield_to_queue: mov qword [rsp - 72], .ret diff --git a/src/kernel/scheduler.c b/src/kernel/scheduler.c index 68aaaa6..1994539 100644 --- a/src/kernel/scheduler.c +++ b/src/kernel/scheduler.c @@ -35,8 +35,11 @@ void init_scheduler() { running_thread = 0; struct continuation_info ci; - while (!take_from_queue(&ready_continuations, &ci)) + while (!take_from_queue(&ready_continuations, &ci)) { + __asm__ ("sti"); __asm__ ("hlt"); + __asm__ ("cli"); + } assert(running_thread == 0) assert(ci.running_thread != 0) diff --git a/src/kernel/scheduler.h b/src/kernel/scheduler.h index 2475f98..59c3cb6 100644 --- a/src/kernel/scheduler.h +++ b/src/kernel/scheduler.h @@ -50,7 +50,7 @@ extern struct continuation_queue ready_continuations; void create_queue(struct continuation_queue *queue, int buffer_size); void destroy_queue(struct continuation_queue *queue); -//ci is copied +//ci is copied. void add_to_queue(struct continuation_queue *queue, struct continuation_info *ci); //if queue is empty, returns 0. otherwise, copies next read to ci, advances read, and returns 1. @@ -60,8 +60,5 @@ int take_from_queue(struct continuation_queue *queue, struct continuation_info * //the next ready continuation (thus doesn't return until save_current_continuation_to is resumed). void yield(struct continuation_info *save_current_continuation_to); -//save as yield but enables interrupts between saving this continuation and resuming next ready one. -void yield_sti(struct continuation_info *save_current_continuation_to); - //same as yield, but instead of saving continuation to a pointer, saves it to the end of the queue. void yield_to_queue(struct continuation_queue *queue); diff --git a/src/kernel/syscalls.asm b/src/kernel/syscalls.asm index 42fb580..a064002 100644 --- a/src/kernel/syscalls.asm +++ b/src/kernel/syscalls.asm @@ -42,7 +42,9 @@ syscall_entry: push r11 mov rcx, rax + cli call syscall_entry_c + sti xor rdi, rdi xor rsi, rsi diff --git a/src/kernel/timer.c b/src/kernel/timer.c index 31a792f..80e7278 100644 --- a/src/kernel/timer.c +++ b/src/kernel/timer.c @@ -32,6 +32,8 @@ static struct sleeping_list_entry *last_entry = 0; //pit irq happens ~ once per millisecond static uint64_t pit_irqs_since_boot = 0; +static struct sleeping_list_entry *local_sleeping_list_entry_pool = 0; + void on_pit_irq() { ++pit_irqs_since_boot; @@ -42,7 +44,8 @@ void on_pit_irq() { struct sleeping_list_entry *old_first = first_entry; first_entry = old_first->next; - heap_dealloc(old_first, sizeof(struct sleeping_list_entry)); + *(struct sleeping_list_entry **)old_first = local_sleeping_list_entry_pool; + local_sleeping_list_entry_pool = old_first; if (first_entry == 0) last_entry = 0; @@ -51,13 +54,20 @@ void on_pit_irq() { } -//until in pit irqs since boot +//until in pit irqs since boot. +//must be called with interrupts disabled. +//on return, interrupts are enabled. static void sleep_until(uint64_t until) { - struct sleeping_list_entry *entry = heap_alloc(sizeof(struct sleeping_list_entry)); - entry->sleeping_until = until; + struct sleeping_list_entry *entry; + if (local_sleeping_list_entry_pool != 0) { + entry = local_sleeping_list_entry_pool; + local_sleeping_list_entry_pool = *(struct sleeping_list_entry **)entry; + } + else + entry = heap_alloc(sizeof(struct sleeping_list_entry)); - __asm__ ("cli"); + entry->sleeping_until = until; if (first_entry == 0) { entry->next = 0; @@ -84,7 +94,7 @@ static void sleep_until(uint64_t until) { just_before->next = entry; } - yield_sti(&entry->continuation); + yield(&entry->continuation); } diff --git a/src/user-apps/shell/assembler.c b/src/user-apps/shell/assembler.c new file mode 100644 index 0000000..b07a602 --- /dev/null +++ b/src/user-apps/shell/assembler.c @@ -0,0 +1,218 @@ +/* Calcite, src/user-apps/shell/assembler.c + * Copyright 2026 Benji Dial + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "assembler.h" + +#include +#include +#include + +static uint8_t *function_buffer = 0; +static int function_buffer_length; +static int function_buffer_used; + +struct relocation_info { + int offset_into_function; + int from_offset_into_function; + void *to; + struct relocation_info *previous; +}; + +static struct relocation_info *relocations = 0; +static char *function_name = 0; +static int function_name_length; + +struct function_alloc { + void *start; + int bytes; + struct function_alloc *previous; +}; + +static struct function_alloc *allocs = 0; + +#define FUNCTION_BUFFER_INITIAL_SIZE 4096 + +void start_function(const char *name, int name_length) { + + if (function_buffer == 0) { + function_buffer_length = FUNCTION_BUFFER_INITIAL_SIZE; + function_buffer = heap_alloc(function_buffer_length); + } + + function_buffer_used = 0; + function_name_length = name_length; + + char *name_copy = heap_alloc(name_length); + memcpy(name_copy, name, name_length); + function_name = name_copy; + +} + +static void destroy_relocations() { + struct relocation_info *r = relocations; + while (r != 0) { + struct relocation_info *p = r->previous; + heap_dealloc(r, sizeof(struct relocation_info)); + r = p; + } + relocations = 0; +} + +static void destroy_function_allocs(int and_free) { + struct function_alloc *a = allocs; + while (a != 0) { + struct function_alloc *p = a->previous; + if (and_free) + heap_dealloc(a->start, a->bytes); + heap_dealloc(a, sizeof(struct function_alloc)); + a = p; + } + allocs = 0; +} + +const char *get_function_name() { + return function_name; +} + +int get_function_name_length() { + return function_name_length; +} + +//must be multiple of 4096. +#define MIN_RX_SPACE (1 << 20) + +static void *rx_pointer; +static int rx_space = 0; + +void (*end_function())() { + + void *function_start = 0; + + if (function_buffer_used <= rx_space) { + function_start = rx_pointer; + rx_pointer += function_buffer_used; + rx_space -= function_buffer_used; + } + + else { + int to_allocate = ((function_buffer_length - 1) / 4096 + 1) * 4096; + if (to_allocate < MIN_RX_SPACE) + to_allocate = MIN_RX_SPACE; + void *pages = map_pages(to_allocate / 4096); + for (int i = 0; i < to_allocate; i += 4096) + change_page_permissions(pages + i, 1, 1); + function_start = pages; + rx_pointer = pages + function_buffer_used; + rx_space = to_allocate - function_buffer_used; + } + + memcpy(function_start, function_buffer, function_buffer_used); + + for (struct relocation_info *r = relocations; r != 0; r = r->previous) { + int32_t *at = function_start + r->offset_into_function; + void *from = function_start + r->from_offset_into_function; + *at = r->to - from; + } + + destroy_relocations(); + destroy_function_allocs(0); + function_name = 0; + + return function_start; + +} + +void abort_function() { + destroy_relocations(); + destroy_function_allocs(1); + if (function_name != 0) { + heap_dealloc(function_name, function_name_length); + function_name = 0; + } +} + +void *function_data_alloc(int bytes) { + void *start = heap_alloc(bytes); + struct function_alloc *a = heap_alloc(sizeof(struct function_alloc)); + a->start = start; + a->bytes = bytes; + a->previous = allocs; + allocs = a; + return start; +} + +static void emit_bytes(const uint8_t *bytes, int count) { + + if (function_buffer_used + count > function_buffer_length) { + int new_length = function_name_length * 2; + while (function_buffer_used + count > function_buffer_length) + new_length *= 2; + void *new_buffer = heap_alloc(new_length); + memcpy(new_buffer, function_buffer, function_buffer_used); + heap_dealloc(function_buffer, function_buffer_length); + function_buffer = new_buffer; + function_buffer_length = new_length; + } + + memcpy(function_buffer + function_buffer_used, bytes, count); + function_buffer_used += count; + +} + +static struct relocation_info *new_relocation() { + struct relocation_info *r = heap_alloc(sizeof(struct relocation_info)); + r->previous = relocations; + relocations = r; + return r; +} + +void emit_ret() { + uint8_t byte = 0xc3; + emit_bytes(&byte, 1); +} + +void emit_call(void (*to)()) { + struct relocation_info *r = new_relocation(); + r->offset_into_function = function_buffer_used + 1; + r->from_offset_into_function = function_buffer_used + 5; + r->to = to; + uint8_t bytes[5]; + bytes[0] = 0xe8; + emit_bytes(bytes, 5); +} + +void emit_mov_edi(int value) { + uint8_t bytes[5]; + bytes[0] = 0xbf; + *(int32_t *)&bytes[1] = value; + emit_bytes(bytes, 5); +} + +void emit_mov_esi(int value) { + uint8_t bytes[5]; + bytes[0] = 0xbe; + *(int32_t *)&bytes[1] = value; + emit_bytes(bytes, 5); +} + +void emit_mov_rdi(uint64_t value) { + uint8_t bytes[10]; + bytes[0] = 0x48; + bytes[1] = 0xbf; + *(uint64_t *)&bytes[2] = value; + emit_bytes(bytes, 10); +} diff --git a/src/user-apps/shell/assembler.h b/src/user-apps/shell/assembler.h new file mode 100644 index 0000000..f5f25f7 --- /dev/null +++ b/src/user-apps/shell/assembler.h @@ -0,0 +1,38 @@ +/* Calcite, src/user-apps/shell/assembler.h + * Copyright 2026 Benji Dial + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#pragma once + +#include + +//name is copied +void start_function(const char *name, int name_length); + +const char *get_function_name(); +int get_function_name_length(); + +//returns interpret +void (*end_function())(); +void abort_function(); + +void *function_data_alloc(int bytes); + +void emit_ret(); +void emit_call(void (*to)()); +void emit_mov_edi(int value); +void emit_mov_esi(int value); +void emit_mov_rdi(uint64_t value); diff --git a/src/user-apps/shell/builtin-words.c b/src/user-apps/shell/builtin-words.c new file mode 100644 index 0000000..f177c44 --- /dev/null +++ b/src/user-apps/shell/builtin-words.c @@ -0,0 +1,226 @@ +/* Calcite, src/user-apps/shell/builtin-words.c + * Copyright 2026 Benji Dial + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include +#include + +#include "builtin-words.h" +#include "assembler.h" +#include "runtime.h" + +static void words_interpret() { + print_all_words(); +} + +static void dup_interpret() { + int x = pop(); + push(x); + push(x); +} + +static void drop_interpret() { + pop(); +} + +static void swap_interpret() { + int y = pop(); + int x = pop(); + push(y); + push(x); +} + +static void rot_interpret() { + int z = pop(); + int y = pop(); + int x = pop(); + push(y); + push(z); + push(x); +} + +static void plus_interpret() { + int y = pop(); + int x = pop(); + push(x + y); +} + +static void minus_interpret() { + int y = pop(); + int x = pop(); + push(x - y); +} + +static void times_interpret() { + int y = pop(); + int x = pop(); + push(x * y); +} + +static void divide_interpret() { + int y = pop(); + int x = pop(); + if (y == 0) + error("division by zero"); + push(x / y); +} + +static void mod_interpret() { + int y = pop(); + int x = pop(); + if (y == 0) + error("division by zero"); + push(x % y); +} + +static void print_integer(int x) { + + if (x == 0) { + terminal_write("0 ", 2); + return; + } + + int is_negative; + if (x < 0) { + x = -x; + is_negative = 1; + } + else + is_negative = 0; + + char text[12]; + text[11] = ' '; + int text_ptr = 11; + while (x > 0) { + --text_ptr; + text[text_ptr] = '0' + x % 10; + x /= 10; + } + + if (is_negative) { + --text_ptr; + text[text_ptr] = '-'; + } + + terminal_write(&text[text_ptr], 12 - text_ptr); + +} + +static void dot_s_interpret() { + int *stack_pointer = get_data_stack_pointer(); + int *stack_top = get_data_stack_top(); + for (int *i = stack_top - 1; i >= stack_pointer; --i) + print_integer(*i); +} + +static void dot_interpret() { + print_integer(pop()); +} + +static void dot_quote_interpret() { + const char *start; + int length; + read_until('"', &start, &length); + terminal_write(start, length); +} + +static void dot_quote_compile() { + const char *start; + int length; + read_until('"', &start, &length); + void *address = function_data_alloc(length); + memcpy(address, start, length); + emit_mov_rdi((uint64_t)address); + emit_mov_esi(length); + emit_call((void *)&terminal_write); +} + +static void cr_interpret() { + terminal_write("\n", 1); +} + +static void colon_interpret() { + + const char *name_start; + int name_length; + read_input_word(&name_start, &name_length); + if (name_length == 0) + error("colon at end of input"); + + start_function(name_start, name_length); + compiling = 1; + +} + +static void semicolon_compile() { + + emit_ret(); + + const char *function_name = get_function_name(); + int function_name_length = get_function_name_length(); + void (*interpret)() = end_function(); + + struct word *word = new_word(); + word->name_length = function_name_length; + word->name = function_name; + word->interpret = interpret; + word->compile = COMPILE_EMIT_CALL; + + compiling = 0; + +} + +static void add(const char *name, void (*interpret)(), void (*compile)()) { + int name_length = 0; + while (name[name_length] != 0) + ++name_length; + struct word *w = new_word(); + w->name_length = name_length; + w->name = name; + w->interpret = interpret; + w->compile = compile; +} + +static void add_compile_only(const char *name, void (*compile)()) { + add(name, INTERPRET_ERROR, compile); +} + +static void add_interpret_only(const char *name, void (*interpret)()) { + add(name, interpret, COMPILE_ERROR); +} + +static void add_emit_call(const char *name, void (*interpret)()) { + add(name, interpret, COMPILE_EMIT_CALL); +} + +void add_builtin_words() { + add_emit_call( "words", &words_interpret); + add_emit_call( "dup", &dup_interpret); + add_emit_call( "drop", &drop_interpret); + add_emit_call( "swap", &swap_interpret); + add_emit_call( "rot", &rot_interpret); + add_emit_call( "+", &plus_interpret); + add_emit_call( "-", &minus_interpret); + add_emit_call( "*", ×_interpret); + add_emit_call( "/", ÷_interpret); + add_emit_call( "mod", &mod_interpret); + add_emit_call( ".s", &dot_s_interpret); + add_emit_call( ".", &dot_interpret); + add( ".\"", &dot_quote_interpret, &dot_quote_compile); + add_emit_call( "cr", &cr_interpret); + add_interpret_only(":", &colon_interpret); + add_compile_only( ";", &semicolon_compile); +} diff --git a/src/user-apps/shell/shell.c b/src/user-apps/shell/builtin-words.h similarity index 76% rename from src/user-apps/shell/shell.c rename to src/user-apps/shell/builtin-words.h index fd3f3db..335fb4d 100644 --- a/src/user-apps/shell/shell.c +++ b/src/user-apps/shell/builtin-words.h @@ -1,4 +1,4 @@ -/* Calcite, src/user-apps/shell/shell.c +/* Calcite, src/user-apps/shell/builtin-words.h * Copyright 2026 Benji Dial * * This program is free software: you can redistribute it and/or modify @@ -15,12 +15,6 @@ * with this program. If not, see . */ -#include -#include +#pragma once -void main() { - init_terminal(); - terminal_write("hello\n", 6); - while (1) - sleep_ms(1000000000); -} +void add_builtin_words(); diff --git a/src/user-apps/shell/main.c b/src/user-apps/shell/main.c new file mode 100644 index 0000000..bf7435e --- /dev/null +++ b/src/user-apps/shell/main.c @@ -0,0 +1,36 @@ +/* Calcite, src/user-apps/shell/main.c + * Copyright 2026 Benji Dial + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "builtin-words.h" +#include "runtime.h" + +#include + +void main() { + + init_terminal(); + save_return_stack(); + create_data_stack(); + add_builtin_words(); + + terminal_write("words: ", 7); + print_all_words(); + terminal_write("\n", 1); + + main_loop(); + +} diff --git a/src/user-apps/shell/runtime.asm b/src/user-apps/shell/runtime.asm new file mode 100644 index 0000000..6f3fceb --- /dev/null +++ b/src/user-apps/shell/runtime.asm @@ -0,0 +1,40 @@ + ; Calcite, src/user-apps/shell/runtime.asm + ; Copyright 2026 Benji Dial + ; + ; This program is free software: you can redistribute it and/or modify + ; it under the terms of the GNU General Public License as published by + ; the Free Software Foundation, either version 3 of the License, or + ; (at your option) any later version. + ; + ; This program is distributed in the hope that it will be useful, but + ; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + ; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + ; for more details. + ; + ; You should have received a copy of the GNU General Public License along + ; with this program. If not, see . + + +bits 64 +default rel + +section .bss + +saved_return_stack: + resq 1 + +section .text + +global save_return_stack +save_return_stack: + mov rax, rsp + and ax, 0xf000 + add rax, 0x1000 + mov qword [saved_return_stack], rax + ret + +global restore_return_stack +restore_return_stack: + mov rsp, qword [saved_return_stack] + push qword 0 + jmp rdi diff --git a/src/user-apps/shell/runtime.c b/src/user-apps/shell/runtime.c new file mode 100644 index 0000000..b983390 --- /dev/null +++ b/src/user-apps/shell/runtime.c @@ -0,0 +1,218 @@ +/* Calcite, src/user-apps/shell/runtime.c + * Copyright 2026 Benji Dial + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#include "assembler.h" +#include "runtime.h" + +#include +#include + +static int *data_stack_bottom; +static int *data_stack_top; +static int *data_stack_pointer; + +#define INITIAL_DATA_STACK_INTS 1024 + +void create_data_stack() { + data_stack_bottom = heap_alloc(INITIAL_DATA_STACK_INTS * sizeof(int)); + data_stack_top = data_stack_bottom + INITIAL_DATA_STACK_INTS; + data_stack_pointer = data_stack_top; +} + +void push(int value) { + + if (data_stack_pointer == data_stack_bottom) { + int old_size = data_stack_top - data_stack_bottom; + int new_size = old_size * 2; + int *new_stack = heap_alloc(new_size * sizeof(int)); + memcpy(new_stack + old_size, data_stack_bottom, old_size * sizeof(int)); + heap_dealloc(data_stack_bottom, old_size * sizeof(int)); + data_stack_bottom = new_stack; + data_stack_top = new_stack + new_size; + data_stack_pointer = new_stack + old_size; + } + + *--data_stack_pointer = value; + +} + +int pop() { + + if (data_stack_pointer == data_stack_top) + error("stack underflow"); + + return *(data_stack_pointer++); + +} + +int *get_data_stack_pointer() { + return data_stack_pointer; +} + +int *get_data_stack_top() { + return data_stack_top; +} + +//null-terminated +static const char *input_buffer; +static const char *input_pointer; + +void set_input(const char *buffer) { + input_buffer = buffer; + input_pointer = buffer; +} + +static int is_ws(char ch) { + return ch == ' '; +} + +void read_input_word(const char **start_out, int *length_out) { + const char *start = input_pointer; + while (start[0] != 0 && is_ws(start[0])) + ++start; + int length = 0; + while (start[length] != 0 && !is_ws(start[length])) + ++length; + *start_out = start; + *length_out = length; + if (start[length] == 0) + input_pointer = &start[length]; + else + input_pointer = &start[length + 1]; +} + +void read_until(char end, const char **start_out, int *length_out) { + const char *start = input_pointer; + int length = 0; + while (start[length] != 0 && start[length] != end) + ++length; + if (start[length] == 0) + error("unexpected eof"); + *start_out = start; + *length_out = length; + input_pointer = &start[length + 1]; +} + +static const struct word *dictionary = 0; + +const struct word *look_up_word(int name_length, const char *name) { + for (const struct word *word = dictionary; word != 0; word = word->previous) { + if (word->name_length == name_length) { + for (int i = 0; i < name_length; ++i) + if (word->name[i] != name[i]) + goto next_word; + return word; + } + next_word: + ; + } + return 0; +} + +struct word *new_word() { + struct word *word = heap_alloc(sizeof(struct word)); + word->previous = dictionary; + dictionary = word; + return word; +} + +void print_all_words() { + for (const struct word *w = dictionary; w != 0; w = w->previous) { + terminal_write(w->name, w->name_length); + terminal_write(" ", 1); + } +} + +[[noreturn]] void error(const char *message) { + terminal_write("*** error: ", 11); + int length = 0; + while (message[length] != 0) + ++length; + terminal_write(message, length); + terminal_write("\n", 1); + abort_function(); + restore_return_stack(&main_loop); +} + +static void interpret_all() { + + while (1) { + + const char *start; + int length; + read_input_word(&start, &length); + if (length == 0) + return; + + const struct word *word = look_up_word(length, start); + if (word != 0) { + if (compiling) { + if (word->compile == COMPILE_ERROR) + error("interpret-only word in compile context"); + else if (word->compile == COMPILE_EMIT_CALL) + emit_call(word->interpret); + else + (*word->compile)(); + } + else { + if (word->interpret == INTERPRET_ERROR) + error("compile-only word in interpret context"); + else + (*word->interpret)(); + } + continue; + } + + int is_negative; + if (start[0] == '-') { + is_negative = 1; + ++start; + --length; + } + else + is_negative = 0; + + int abs = 0; + for (int i = 0; i < length; ++i) { + if (start[i] < '0' || start[i] > '9') + error("unknown word"); + abs = abs * 10 + start[i] - '0'; + } + + int x = is_negative ? -abs : abs; + + if (compiling) { + emit_mov_edi(x); + emit_call((void *)&push); + } + else + push(x); + + } + +} + +[[noreturn]] void main_loop() { + compiling = 0; + while (1) { + terminal_write("> ", 2); + set_input(terminal_readline()); + interpret_all(); + } +} + +int compiling; diff --git a/src/user-apps/shell/runtime.h b/src/user-apps/shell/runtime.h new file mode 100644 index 0000000..58dffe4 --- /dev/null +++ b/src/user-apps/shell/runtime.h @@ -0,0 +1,63 @@ +/* Calcite, src/user-apps/shell/runtime.h + * Copyright 2026 Benji Dial + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +#pragma once + +void save_return_stack(); +//and_call should not return. +[[noreturn]] void restore_return_stack(void (*and_call)()); + +void create_data_stack(); +void push(int value); +int pop(); + +int *get_data_stack_pointer(); +int *get_data_stack_top(); + +//null-terminated. not copied. +void set_input(const char *buffer); + +//length_out is set to 0 on eof. skips all whitespace before and up to one character after. +void read_input_word(const char **start_out, int *length_out); + +//errors on eof before end. skips end. end not included in length. +void read_until(char end, const char **start_out, int *length_out); + +struct word { + int name_length; + const char *name; + void (*interpret)(); + void (*compile)(); + const struct word *previous; +}; + +#define INTERPRET_ERROR ((void (*)())-1) + +#define COMPILE_ERROR ((void (*)())-1) +#define COMPILE_EMIT_CALL ((void (*)())-2) + +//returns 0 if not found. name does not need to be null-terminated. +const struct word *look_up_word(int name_length, const char *name); +struct word *new_word(); + +void print_all_words(); + +[[noreturn]] void error(const char *message); + +extern int compiling; + +[[noreturn]] void main_loop(); diff --git a/src/user-apps/terminal/terminal.c b/src/user-apps/terminal/terminal.c index 84f168a..606ce51 100644 --- a/src/user-apps/terminal/terminal.c +++ b/src/user-apps/terminal/terminal.c @@ -18,9 +18,12 @@ #include #include #include +#include +#include #include #include #include +#include #include #include #include @@ -30,6 +33,8 @@ static struct flanterm_context *flanterm_context; static ipc_dgram_receiver_handle_t input_handle; static ipc_dgram_receiver_handle_t output_handle; +static char keytable[2048]; + static struct dispatch_queue *queue; struct got_output_param { @@ -64,16 +69,87 @@ static void got_output(void *x) { } +static int flags = 0; +static int entering_caps_lock = 0; +static int entering_num_lock = 0; + [[noreturn]] static void input_thread(uint64_t) { + while (1) { - //TODO - while (1) - sleep_ms(1000000000); + int key = wait_for_keyboard_packet(); + int shift = flags & (TKF_LEFT_SHIFT | TKF_RIGHT_SHIFT); + int caps = flags & TKF_CAPS_LOCK; + int num = flags & TKF_NUM_LOCK; + char printable = keytable[(key & 0xff) | (shift ? 0x100 : 0x0) | (caps ? 0x200 : 0x0) | (num ? 0x400 : 0x0)]; + if (key == KEY_LEFT_SHIFT) + flags &= ~TKF_LEFT_SHIFT; + else if (key == KEY_RIGHT_SHIFT) + flags &= ~TKF_RIGHT_SHIFT; + else if (key == KEY_LEFT_CTRL) + flags &= ~TKF_LEFT_CTRL; + else if (key == KEY_RIGHT_CTRL) + flags &= ~TKF_RIGHT_CTRL; + else if (key == KEY_LEFT_ALT) + flags &= ~TKF_LEFT_ALT; + else if (key == KEY_RIGHT_ALT) + flags &= ~TKF_RIGHT_ALT; + else if (key == KEY_CAPS_LOCK) { + if (!entering_caps_lock) + flags &= ~TKF_CAPS_LOCK; + else + entering_caps_lock = 0; + } + else if (key == KEY_NUM_LOCK) { + if (!entering_num_lock) + flags &= ~TKF_NUM_LOCK; + else + entering_num_lock = 0; + } + else if (key == (KEY_FLAG_MAKE | KEY_LEFT_SHIFT)) + flags |= TKF_LEFT_SHIFT; + else if (key == (KEY_FLAG_MAKE | KEY_RIGHT_SHIFT)) + flags |= TKF_RIGHT_SHIFT; + else if (key == (KEY_FLAG_MAKE | KEY_LEFT_CTRL)) + flags |= TKF_LEFT_CTRL; + else if (key == (KEY_FLAG_MAKE | KEY_RIGHT_CTRL)) + flags |= TKF_RIGHT_CTRL; + else if (key == (KEY_FLAG_MAKE | KEY_LEFT_ALT)) + flags |= TKF_LEFT_ALT; + else if (key == (KEY_FLAG_MAKE | KEY_RIGHT_ALT)) + flags |= TKF_RIGHT_ALT; + else if (key == (KEY_FLAG_MAKE | KEY_CAPS_LOCK)) { + if (!(flags & TKF_CAPS_LOCK)) { + flags |= TKF_CAPS_LOCK; + entering_caps_lock = 1; + } + } + else if (key == (KEY_FLAG_MAKE | KEY_NUM_LOCK)) { + if (!(flags & TKF_NUM_LOCK)) { + flags |= TKF_NUM_LOCK; + entering_num_lock = 1; + } + } + + int packet = flags | (key << 8) | printable; + ipc_send_dgram(input_handle, &packet, sizeof(int)); + + } } void main() { + file_handle_t keytable_handle; + if (open_file("root://calcite/qwerty.bin", &keytable_handle) == FAR_SUCCESS) { + struct read_file_parameter param; + param.handle = keytable_handle; + param.buffer = keytable; + param.start = 0; + param.bytes = 2048; + read_file(¶m); + close_file(keytable_handle); + } + uint64_t output_pipe = ipc_create_private_dgram_pipe(); uint64_t input_pipe = ipc_create_private_dgram_pipe(); diff --git a/src/user-libs/calcite/syscalls.c b/src/user-libs/calcite/syscalls.c index 215218a..ad6bba0 100644 --- a/src/user-libs/calcite/syscalls.c +++ b/src/user-libs/calcite/syscalls.c @@ -145,3 +145,11 @@ uint64_t get_ms_since_boot() { void sleep_until_ms_since_boot(uint64_t ms) { do_syscall(ms, 0, 0, SYSCALL_SLEEP_UNTIL_MS_SINCE_BOOT); } + +void change_page_permissions(void *vma, int writable, int executable) { + do_syscall((uint64_t)vma, writable, executable, SYSCALL_CHANGE_PAGE_PERMISSIONS); +} + +int wait_for_keyboard_packet() { + return do_syscall(0, 0, 0, SYSCALL_WAIT_FOR_KEYBOARD_PACKET); +} diff --git a/src/user-libs/calcite/terminal.c b/src/user-libs/calcite/terminal.c index 6f23e52..935be1b 100644 --- a/src/user-libs/calcite/terminal.c +++ b/src/user-libs/calcite/terminal.c @@ -15,6 +15,8 @@ * with this program. If not, see . */ +#include +#include #include #include #include @@ -27,7 +29,7 @@ static ipc_dgram_sender_handle_t output_handle; static int set_up_terminal_input = 0; static int set_up_terminal_output = 0; -static char *output_buffer; +static uint8_t *output_buffer; static int output_buffer_size; static int output_buffer_used; @@ -40,7 +42,7 @@ static void output_buffer_size_at_least(int bytes) { while (new_size < bytes) new_size *= 2; - char *new_buffer = heap_alloc(new_size); + uint8_t *new_buffer = heap_alloc(new_size); memcpy(new_buffer, output_buffer, output_buffer_used); heap_dealloc(output_buffer, output_buffer_size); output_buffer = new_buffer; @@ -50,7 +52,7 @@ static void output_buffer_size_at_least(int bytes) { #define MAX_PACKET_LENGTH 500 -static void send_output(const char *buffer, int bytes) { +static void send_output(const void *buffer, int bytes) { if (set_up_terminal_output == 0) return; @@ -93,13 +95,15 @@ void init_terminal() { } -void terminal_write(const char *data, int bytes) { +void terminal_write(const void *data, int bytes) { if (bytes == 0) return; + const uint8_t *data8 = (const uint8_t *)data; + int last_newline = bytes - 1; - while (last_newline >= 0 && data[last_newline] != '\n') + while (last_newline >= 0 && data8[last_newline] != '\n') --last_newline; if (last_newline == -1) { @@ -116,17 +120,152 @@ void terminal_write(const char *data, int bytes) { send_output(data, last_newline + 1); if (last_newline != bytes - 1) { output_buffer_size_at_least(bytes - last_newline - 1); - memcpy(output_buffer, &data[last_newline + 1], bytes - last_newline - 1); + memcpy(output_buffer, data + last_newline + 1, bytes - last_newline - 1); output_buffer_used = bytes - last_newline - 1; } } } -void terminal_read(char *buffer, int bytes) { - //TODO - (void)buffer; - (void)bytes; - while (1) - sleep_ms(1000000000); +void terminal_flush() { + if (output_buffer_used != 0) { + send_output(output_buffer, output_buffer_used); + output_buffer_used = 0; + } +} + +int terminal_readkey() { + + if (!set_up_terminal_input) + return -1; + + terminal_flush(); + int result; + int bytes = sizeof(int); + ipc_receive_dgram(input_handle, &result, &bytes); + //TODO: handle case where terminal misbehaves and sends more than sizeof(int) + + return result; + +} + +static char *readline_buffer = 0; +static int readline_buffer_length = 0; +static int readline_buffer_used = 0; +static int readline_cursor = 0; + +static void room_for_one_more() { + + if (readline_buffer == 0) { + readline_buffer_length = 1024; + readline_buffer = heap_alloc(readline_buffer_length); + } + + else if (readline_buffer_used == readline_buffer_length) { + char *new_buffer = heap_alloc(readline_buffer_length * 2); + memcpy(new_buffer, readline_buffer, readline_buffer_length); + heap_dealloc(readline_buffer, readline_buffer_length); + readline_buffer = new_buffer; + readline_buffer_length *= 2; + } + +} + +const char *terminal_readline() { + + readline_buffer_used = 0; + readline_cursor = 0; + + while (1) { + + int key_packet = terminal_readkey(); + if (!(key_packet & (KEY_FLAG_MAKE << 8)) || + (key_packet & (TKF_LEFT_CTRL | TKF_RIGHT_CTRL | TKF_LEFT_ALT | TKF_RIGHT_ALT))) + continue; + int key = (key_packet >> 8) & 0xff; + int num_lock = key_packet & TKF_NUM_LOCK; + + if (key == KEY_LEFT || (!num_lock && key == KEY_NUM_4)) { + if (readline_cursor != 0) { + --readline_cursor; + terminal_write("\x1b[D", 3); + } + } + + else if (key == KEY_RIGHT || (!num_lock && key == KEY_NUM_6)) { + if (readline_cursor != readline_buffer_used) { + ++readline_cursor; + terminal_write("\x1b[C", 3); + } + } + + else if (key == KEY_BACKSPACE) { + if (readline_cursor != 0) { + for (int i = readline_cursor; i < readline_buffer_used; ++i) + readline_buffer[i - 1] = readline_buffer[i]; + --readline_cursor; + --readline_buffer_used; + terminal_write("\x1b[D", 3); + terminal_write(&readline_buffer[readline_cursor], readline_buffer_used - readline_cursor); + terminal_write(" ", 1); + for (int i = readline_cursor; i < readline_buffer_used + 1; ++i) + terminal_write("\x1b[D", 3); + } + } + + else if (key == KEY_DELETE || (!num_lock && key == KEY_NUM_PERIOD)) { + if (readline_cursor != readline_buffer_used) { + ++readline_cursor; + for (int i = readline_cursor; i < readline_buffer_used; ++i) + readline_buffer[i - 1] = readline_buffer[i]; + --readline_cursor; + --readline_buffer_used; + terminal_write(&readline_buffer[readline_cursor], readline_buffer_used - readline_cursor); + terminal_write(" ", 1); + for (int i = readline_cursor; i < readline_buffer_used + 1; ++i) + terminal_write("\x1b[D", 3); + } + } + + else if (key == KEY_ENTER || key == KEY_NUM_ENTER) { + room_for_one_more(); + readline_buffer[readline_buffer_used] = 0; + for (int i = readline_cursor; i < readline_buffer_used; ++i) + terminal_write("\x1b[C", 3); + terminal_write("\n", 1); + return readline_buffer; + } + + else if (key == KEY_TAB) + continue; + + else if (key == KEY_HOME || (!num_lock && key == KEY_NUM_7)) { + for (int i = 0; i < readline_cursor; ++i) + terminal_write("\x1b[D", 3); + readline_cursor = 0; + } + + else if (key == KEY_END || (!num_lock && key == KEY_NUM_1)) { + for (int i = readline_cursor; i < readline_buffer_used; ++i) + terminal_write("\x1b[C", 3); + readline_cursor = readline_buffer_used; + } + + else { + char value = key_packet & 0xff; + if (value != 0) { + room_for_one_more(); + for (int i = readline_buffer_used; i > readline_cursor; --i) + readline_buffer[i] = readline_buffer[i - 1]; + readline_buffer[readline_cursor] = value; + ++readline_buffer_used; + terminal_write(&readline_buffer[readline_cursor], readline_buffer_used - readline_cursor); + ++readline_cursor; + for (int i = readline_buffer_used; i > readline_cursor; --i) + terminal_write("\x1b[D", 3); + } + } + + } + }