int kgetc(void);
+void klog_set_suppress(int suppress);
size_t klog_read(char* out, size_t out_size);
#endif
void vga_print(const char* str);
void vga_set_color(uint8_t fg, uint8_t bg);
void vga_clear(void);
+void vga_scroll_back(void);
+void vga_scroll_fwd(void);
#endif
#include "spinlock.h"
static volatile uint16_t* VGA_BUFFER = 0;
-static const int VGA_WIDTH = 80;
-static const int VGA_HEIGHT = 25;
+#define VGA_WIDTH 80
+#define VGA_HEIGHT 25
static int term_col = 0;
static int term_row = 0;
static spinlock_t vga_lock = {0};
+/* --- Scrollback buffer --- */
+#define SB_LINES 200
+static uint16_t sb_buf[SB_LINES * VGA_WIDTH];
+static int sb_head = 0; /* next write line (circular) */
+static int sb_count = 0; /* stored lines (max SB_LINES) */
+
+static int view_offset = 0; /* 0 = live view, >0 = scrolled back N lines */
+static uint16_t live_buf[VGA_HEIGHT * VGA_WIDTH]; /* saved live VGA when scrolled */
+
+static void vga_update_hw_cursor(void) {
+ hal_video_set_cursor(term_row, term_col);
+}
+
static void vga_scroll(void) {
+ /* Save row 0 (about to be lost) into scrollback ring */
+ for (int x = 0; x < VGA_WIDTH; x++) {
+ sb_buf[sb_head * VGA_WIDTH + x] = VGA_BUFFER[x];
+ }
+ sb_head = (sb_head + 1) % SB_LINES;
+ if (sb_count < SB_LINES) sb_count++;
+
+ /* Shift visible content up */
for (int y = 1; y < VGA_HEIGHT; y++) {
for (int x = 0; x < VGA_WIDTH; x++) {
VGA_BUFFER[(y - 1) * VGA_WIDTH + x] = VGA_BUFFER[y * VGA_WIDTH + x];
term_row = VGA_HEIGHT - 1;
}
-static void vga_update_hw_cursor(void) {
- hal_video_set_cursor(term_row, term_col);
+/* Restore live view if currently scrolled back */
+static void vga_unscroll(void) {
+ if (view_offset > 0) {
+ for (int i = 0; i < VGA_HEIGHT * VGA_WIDTH; i++) {
+ VGA_BUFFER[i] = live_buf[i];
+ }
+ view_offset = 0;
+ }
+}
+
+/* Render scrollback + live content at current view_offset */
+static void render_scrollback_view(void) {
+ for (int y = 0; y < VGA_HEIGHT; y++) {
+ /*
+ * line_from_end: how far this row is from the bottom of live content.
+ * 0..VGA_HEIGHT-1 = live rows, VGA_HEIGHT+ = scrollback
+ */
+ int line_from_end = view_offset + (VGA_HEIGHT - 1 - y);
+
+ if (line_from_end < VGA_HEIGHT) {
+ /* Live content */
+ int live_row = VGA_HEIGHT - 1 - line_from_end;
+ for (int x = 0; x < VGA_WIDTH; x++) {
+ VGA_BUFFER[y * VGA_WIDTH + x] = live_buf[live_row * VGA_WIDTH + x];
+ }
+ } else {
+ /* Scrollback: sb_idx 0 = most recent scrolled-off line */
+ int sb_idx = line_from_end - VGA_HEIGHT;
+ if (sb_idx < sb_count) {
+ int buf_line = (sb_head - 1 - sb_idx + SB_LINES) % SB_LINES;
+ for (int x = 0; x < VGA_WIDTH; x++) {
+ VGA_BUFFER[y * VGA_WIDTH + x] = sb_buf[buf_line * VGA_WIDTH + x];
+ }
+ } else {
+ /* Beyond scrollback — blank */
+ for (int x = 0; x < VGA_WIDTH; x++) {
+ VGA_BUFFER[y * VGA_WIDTH + x] = (uint16_t)' ' | (uint16_t)term_color << 8;
+ }
+ }
+ }
+ }
+ /* Hide cursor when scrolled back */
+ hal_video_set_cursor(VGA_HEIGHT, 0);
}
static void vga_put_char_unlocked(char c) {
if (!VGA_BUFFER) return;
+ /* Any new output auto-returns to live view */
+ vga_unscroll();
+
switch (c) {
case '\n':
term_col = 0;
return;
}
- for (int y = 0; y < VGA_HEIGHT; y++) {
- for (int x = 0; x < VGA_WIDTH; x++) {
- VGA_BUFFER[y * VGA_WIDTH + x] = (uint16_t)' ' | (uint16_t)term_color << 8;
- }
+ for (int i = 0; i < VGA_HEIGHT * VGA_WIDTH; i++) {
+ VGA_BUFFER[i] = (uint16_t)' ' | (uint16_t)term_color << 8;
}
term_col = 0;
term_row = 0;
+ view_offset = 0;
+ sb_count = 0;
+ sb_head = 0;
vga_update_hw_cursor();
spin_unlock_irqrestore(&vga_lock, flags);
}
+
+void vga_scroll_back(void) {
+ uintptr_t flags = spin_lock_irqsave(&vga_lock);
+
+ if (!VGA_BUFFER || sb_count == 0) {
+ spin_unlock_irqrestore(&vga_lock, flags);
+ return;
+ }
+
+ if (view_offset == 0) {
+ /* First scroll back — save current live screen */
+ for (int i = 0; i < VGA_HEIGHT * VGA_WIDTH; i++) {
+ live_buf[i] = VGA_BUFFER[i];
+ }
+ }
+
+ view_offset += VGA_HEIGHT / 2;
+ if (view_offset > sb_count) view_offset = sb_count;
+
+ render_scrollback_view();
+ spin_unlock_irqrestore(&vga_lock, flags);
+}
+
+void vga_scroll_fwd(void) {
+ uintptr_t flags = spin_lock_irqsave(&vga_lock);
+
+ if (!VGA_BUFFER || view_offset == 0) {
+ spin_unlock_irqrestore(&vga_lock, flags);
+ return;
+ }
+
+ if (view_offset <= VGA_HEIGHT / 2) {
+ /* Return to live view */
+ for (int i = 0; i < VGA_HEIGHT * VGA_WIDTH; i++) {
+ VGA_BUFFER[i] = live_buf[i];
+ }
+ view_offset = 0;
+ vga_update_hw_cursor();
+ } else {
+ view_offset -= VGA_HEIGHT / 2;
+ render_scrollback_view();
+ }
+
+ spin_unlock_irqrestore(&vga_lock, flags);
+}
#if defined(__i386__)
#include "arch/x86/idt.h"
#include "io.h"
+#include "vga_console.h"
static hal_keyboard_char_cb_t g_cb = 0;
static hal_keyboard_scan_cb_t g_scan_cb = 0;
+/* Modifier state */
+static volatile int shift_held = 0;
+
+/* Extended scancode state (0xE0 prefix) */
+static volatile int e0_prefix = 0;
+
static char scancode_map[128] = {
0, 27, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b',
'\t',
0,
};
+static char scancode_map_shift[128] = {
+ 0, 27, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\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,
+ -1,
+ 0,
+ 0,
+ 0,
+ '+',
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0, 0, 0,
+ 0,
+ 0,
+ 0,
+};
+
+/* Emit a VT100 escape sequence through the char callback */
+static void emit_escape_seq(const char* seq) {
+ if (!g_cb) return;
+ for (int i = 0; seq[i]; i++) {
+ g_cb(seq[i]);
+ }
+}
+
+/* PS/2 scan set 1 scancodes */
+#define SC_LSHIFT_PRESS 0x2A
+#define SC_RSHIFT_PRESS 0x36
+#define SC_LSHIFT_REL 0xAA
+#define SC_RSHIFT_REL 0xB6
+
+/* Extended (0xE0-prefixed) scancodes */
+#define SC_E0_UP 0x48
+#define SC_E0_DOWN 0x50
+#define SC_E0_LEFT 0x4B
+#define SC_E0_RIGHT 0x4D
+#define SC_E0_HOME 0x47
+#define SC_E0_END 0x4F
+#define SC_E0_PGUP 0x49
+#define SC_E0_PGDN 0x51
+#define SC_E0_DEL 0x53
+
+static void handle_extended_press(uint8_t sc) {
+ switch (sc) {
+ case SC_E0_UP: emit_escape_seq("\033[A"); break;
+ case SC_E0_DOWN: emit_escape_seq("\033[B"); break;
+ case SC_E0_RIGHT: emit_escape_seq("\033[C"); break;
+ case SC_E0_LEFT: emit_escape_seq("\033[D"); break;
+ case SC_E0_HOME: emit_escape_seq("\033[H"); break;
+ case SC_E0_END: emit_escape_seq("\033[F"); break;
+ case SC_E0_PGUP:
+ if (shift_held) {
+ vga_scroll_back();
+ } else {
+ emit_escape_seq("\033[5~");
+ }
+ break;
+ case SC_E0_PGDN:
+ if (shift_held) {
+ vga_scroll_fwd();
+ } else {
+ emit_escape_seq("\033[6~");
+ }
+ break;
+ case SC_E0_DEL: emit_escape_seq("\033[3~"); break;
+ default: break;
+ }
+}
+
static void kbd_irq(struct registers* regs) {
(void)regs;
uint8_t status = inb(0x64);
- if (status & 0x01) {
- uint8_t scancode = inb(0x60);
+ if (!(status & 0x01)) return;
- /* Raw scancode callback (key press and release) */
- if (g_scan_cb) {
- g_scan_cb(scancode);
- }
+ uint8_t scancode = inb(0x60);
+
+ /* Raw scancode callback (key press and release) */
+ if (g_scan_cb) {
+ g_scan_cb(scancode);
+ }
+
+ /* 0xE0 prefix: next byte is an extended scancode */
+ if (scancode == 0xE0) {
+ e0_prefix = 1;
+ return;
+ }
+ if (e0_prefix) {
+ e0_prefix = 0;
if (!(scancode & 0x80)) {
- if (scancode < 128) {
- char c = scancode_map[scancode];
- if (c != 0 && g_cb) {
- g_cb(c);
- }
- }
+ handle_extended_press(scancode);
+ }
+ return;
+ }
+
+ /* Track shift state */
+ if (scancode == SC_LSHIFT_PRESS || scancode == SC_RSHIFT_PRESS) {
+ shift_held = 1;
+ return;
+ }
+ if (scancode == SC_LSHIFT_REL || scancode == SC_RSHIFT_REL) {
+ shift_held = 0;
+ return;
+ }
+
+ /* Ignore key releases for normal keys */
+ if (scancode & 0x80) return;
+
+ if (scancode < 128) {
+ char c = shift_held ? scancode_map_shift[scancode] : scancode_map[scancode];
+ if (c != 0 && g_cb) {
+ g_cb(c);
}
}
}
static size_t klog_head = 0; // next write position
static size_t klog_count = 0; // total bytes stored (capped at KLOG_BUF_SIZE)
static spinlock_t klog_lock = {0};
+static volatile int klog_suppress_flag = 0;
static void klog_append(const char* s, size_t len) {
for (size_t i = 0; i < len; i++) {
int len = kvsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
- if (len > 0) {
+ if (len > 0 && !klog_suppress_flag) {
size_t slen = (size_t)len;
if (slen >= sizeof(buf)) slen = sizeof(buf) - 1;
console_write(buf);
}
+void klog_set_suppress(int suppress) {
+ klog_suppress_flag = suppress ? 1 : 0;
+}
+
int kgetc(void) {
for (;;) {
char c = 0;
}
if (!fs_root) {
- kprintf("[INIT] No root filesystem — cannot start userspace.\n");
+ kprintf("[INIT] No root filesystem -- cannot start userspace.\n");
return -1;
}
#define KCMD_MAX 128
+/* ---- Output helpers ---- */
+
static void kc_puts(const char* s) {
console_write(s);
}
+static void kc_putc(char c) {
+ console_put_char(c);
+}
+
+/* ---- Command history ---- */
+
+#define HIST_MAX 16
+static char hist_buf[HIST_MAX][KCMD_MAX];
+static int hist_head = 0; /* next write slot */
+static int hist_count = 0; /* entries stored */
+
+static void hist_add(const char* line) {
+ if (line[0] == '\0') return;
+ /* Don't add duplicates of the last command */
+ if (hist_count > 0) {
+ int prev = (hist_head - 1 + HIST_MAX) % HIST_MAX;
+ if (strcmp(hist_buf[prev], line) == 0) return;
+ }
+ strncpy(hist_buf[hist_head], line, KCMD_MAX - 1);
+ hist_buf[hist_head][KCMD_MAX - 1] = '\0';
+ hist_head = (hist_head + 1) % HIST_MAX;
+ if (hist_count < HIST_MAX) hist_count++;
+}
+
+static const char* hist_get(int idx) {
+ /* idx 0 = most recent, 1 = second most recent, etc. */
+ if (idx < 0 || idx >= hist_count) return 0;
+ int slot = (hist_head - 1 - idx + HIST_MAX) % HIST_MAX;
+ return hist_buf[slot];
+}
+
+/* ---- Line editing helpers ---- */
+
+static void kc_cursor_left(int n) {
+ for (int i = 0; i < n; i++) kc_putc('\b');
+}
+
+static void kc_erase_line(char* buf, int* len, int* cur) {
+ kc_cursor_left(*cur);
+ for (int i = 0; i < *len; i++) kc_putc(' ');
+ kc_cursor_left(*len);
+ *len = 0;
+ *cur = 0;
+ buf[0] = '\0';
+}
+
+static void kc_replace_line(char* buf, int* len, int* cur, const char* text) {
+ kc_erase_line(buf, len, cur);
+ int nlen = (int)strlen(text);
+ if (nlen >= KCMD_MAX) nlen = KCMD_MAX - 1;
+ memcpy(buf, text, (size_t)nlen);
+ buf[nlen] = '\0';
+ *len = nlen;
+ *cur = nlen;
+ for (int i = 0; i < nlen; i++) kc_putc(buf[i]);
+}
+
+/* ---- Readline with VT100 escape parsing ---- */
+
+static int kc_readline(char* buf, int maxlen) {
+ int len = 0;
+ int cur = 0;
+ int hist_nav = -1; /* -1 = editing current, 0+ = history index */
+ char saved[KCMD_MAX]; /* saved current line when navigating history */
+ saved[0] = '\0';
+
+ enum { ST_NORMAL, ST_ESC, ST_CSI } state = ST_NORMAL;
+ char csi_buf[8];
+ int csi_len = 0;
+
+ buf[0] = '\0';
+
+ for (;;) {
+ int ch = kgetc();
+ if (ch < 0) continue;
+
+ if (state == ST_ESC) {
+ if (ch == '[') {
+ state = ST_CSI;
+ csi_len = 0;
+ } else {
+ state = ST_NORMAL;
+ }
+ continue;
+ }
+
+ if (state == ST_CSI) {
+ if (ch >= '0' && ch <= '9' && csi_len < 7) {
+ csi_buf[csi_len++] = (char)ch;
+ continue;
+ }
+ csi_buf[csi_len] = '\0';
+ state = ST_NORMAL;
+
+ switch (ch) {
+ case 'A': /* Up arrow — previous history */
+ if (hist_nav + 1 < hist_count) {
+ if (hist_nav == -1) {
+ memcpy(saved, buf, (size_t)(len + 1));
+ }
+ hist_nav++;
+ kc_replace_line(buf, &len, &cur, hist_get(hist_nav));
+ }
+ break;
+ case 'B': /* Down arrow — next history / restore */
+ if (hist_nav > 0) {
+ hist_nav--;
+ kc_replace_line(buf, &len, &cur, hist_get(hist_nav));
+ } else if (hist_nav == 0) {
+ hist_nav = -1;
+ kc_replace_line(buf, &len, &cur, saved);
+ }
+ break;
+ case 'C': /* Right arrow */
+ if (cur < len) {
+ kc_putc(buf[cur]);
+ cur++;
+ }
+ break;
+ case 'D': /* Left arrow */
+ if (cur > 0) {
+ cur--;
+ kc_putc('\b');
+ }
+ break;
+ case 'H': /* Home */
+ kc_cursor_left(cur);
+ cur = 0;
+ break;
+ case 'F': /* End */
+ for (int i = cur; i < len; i++) kc_putc(buf[i]);
+ cur = len;
+ break;
+ case '~': { /* Delete (CSI 3 ~) */
+ int param = 0;
+ for (int i = 0; i < csi_len; i++)
+ param = param * 10 + (csi_buf[i] - '0');
+ if (param == 3 && cur < len) {
+ memmove(&buf[cur], &buf[cur + 1], (size_t)(len - cur - 1));
+ len--;
+ buf[len] = '\0';
+ for (int i = cur; i < len; i++) kc_putc(buf[i]);
+ kc_putc(' ');
+ kc_cursor_left(len - cur + 1);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ continue;
+ }
+
+ /* ST_NORMAL */
+ if (ch == 0x1B) {
+ state = ST_ESC;
+ continue;
+ }
+
+ if (ch == '\n' || ch == '\r') {
+ kc_putc('\n');
+ buf[len] = '\0';
+ return len;
+ }
+
+ if (ch == '\b' || ch == 127) {
+ if (cur > 0) {
+ cur--;
+ memmove(&buf[cur], &buf[cur + 1], (size_t)(len - cur - 1));
+ len--;
+ buf[len] = '\0';
+ kc_putc('\b');
+ for (int i = cur; i < len; i++) kc_putc(buf[i]);
+ kc_putc(' ');
+ kc_cursor_left(len - cur + 1);
+ }
+ continue;
+ }
+
+ /* Ctrl-A = Home, Ctrl-E = End, Ctrl-U = kill line, Ctrl-K = kill to end */
+ if (ch == 0x01) { /* Ctrl-A */
+ kc_cursor_left(cur);
+ cur = 0;
+ continue;
+ }
+ if (ch == 0x05) { /* Ctrl-E */
+ for (int i = cur; i < len; i++) kc_putc(buf[i]);
+ cur = len;
+ continue;
+ }
+ if (ch == 0x15) { /* Ctrl-U: kill whole line */
+ kc_erase_line(buf, &len, &cur);
+ continue;
+ }
+ if (ch == 0x0B) { /* Ctrl-K: kill to end of line */
+ for (int i = cur; i < len; i++) kc_putc(' ');
+ kc_cursor_left(len - cur);
+ len = cur;
+ buf[len] = '\0';
+ continue;
+ }
+
+ if (ch < ' ' || ch > '~') continue;
+
+ /* Insert printable character at cursor */
+ if (len >= maxlen - 1) continue;
+ memmove(&buf[cur + 1], &buf[cur], (size_t)(len - cur));
+ buf[cur] = (char)ch;
+ len++;
+ buf[len] = '\0';
+ for (int i = cur; i < len; i++) kc_putc(buf[i]);
+ cur++;
+ kc_cursor_left(len - cur);
+ }
+}
+
+/* ---- Commands ---- */
+
static void kconsole_help(void) {
kc_puts("kconsole commands:\n");
kc_puts(" help - Show this list\n");
}
}
+/* ---- Main entry ---- */
+
void kconsole_enter(void) {
keyboard_set_callback(0);
kc_puts("Type 'help' for available commands.\n");
char line[KCMD_MAX];
- int pos = 0;
for (;;) {
kc_puts("kconsole> ");
+ kc_readline(line, KCMD_MAX);
- pos = 0;
- while (pos < KCMD_MAX - 1) {
- int ch = kgetc();
- if (ch < 0) continue;
-
- if (ch == '\n' || ch == '\r') {
- console_write("\n");
- break;
- }
-
- if (ch == '\b' || ch == 127) {
- if (pos > 0) {
- pos--;
- console_write("\b \b");
- }
- continue;
- }
-
- if (ch < ' ' || ch > '~') continue;
-
- line[pos++] = (char)ch;
- char echo[2] = { (char)ch, 0 };
- console_write(echo);
+ if (line[0] != '\0') {
+ hist_add(line);
}
- line[pos] = '\0';
+ klog_set_suppress(1);
kconsole_exec(line);
+ klog_set_suppress(0);
}
}
int init_ret = init_start(bi);
if (init_ret < 0) {
- kprintf("\n[PANIC] Userspace init failed — dropping to emergency console.\n");
- kprintf(" (type 'help' for commands, 'reboot' to restart)\n\n");
+ console_write("\n[PANIC] Userspace init failed -- dropping to emergency console.\n");
+ console_write(" (type 'help' for commands, 'reboot' to restart)\n\n");
kconsole_enter();
}