From cb9b06d46f4158195e4c8d5da91ecf29898c6954 Mon Sep 17 00:00:00 2001 From: Nexoniarz Date: Sun, 25 Oct 2026 01:11:04 +0200 Subject: [PATCH] GMMOS: the Guru Meditation Micro OS, alive under the PSOGM A panic no longer ends in a picture. The report is drawn as before, and then GMMOS runs under it: the real kernel stays dead -- the other processors are halted, and nothing of it is called that locks, sleeps, allocates or waits for an interrupt -- and GMMOS keeps its own clock (the TSC), its own keyboard (micro drivers, polled), and the screen. On the report: a live status line counting down the three-minute power-off, which any key cancels, and the keys. Space the last 300 lines of this boot's log as QR codes, a page per symbol, numbered; + and - trade module size for capacity L the log on screen, scrolled T a terminal: why, log, grep, ps, cpus, pci, mem (page tables walked first, so a bad address cannot fault), uptime, restart, poweroff R R restart; P P power off -- twice, so a stray key does neither The first micro driver is PS/2, polled: kbd.c with everything but the keys taken out. The probe asks the keyboard to echo, so a controller with nothing behind it is not offered as one. Polling also sidesteps the IRQ 1 polarity quirk, which is about interrupt routing. A panic inside GMMOS (or a second one in the report) is reported and then only blinks; it does not re-enter GMMOS. Checked under QEMU: every screen; two QR pages read off a screenshot with zbar decode to the log text exactly; restart reboots; left alone it still powers off at three minutes. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4 --- Makefile | 10 +- include/core/defs.h | 1 + include/core/klog.h | 1 + include/gmmos/gmmos.h | 38 ++ kernel/core/klog.c | 11 + kernel/core/panic.c | 51 ++- kernel/gmmos/gmmos.c | 1018 +++++++++++++++++++++++++++++++++++++++++ kernel/gmmos/ps2.c | 169 +++++++ kernel/proc/proc.c | 25 + 9 files changed, 1317 insertions(+), 7 deletions(-) create mode 100644 include/gmmos/gmmos.h create mode 100644 kernel/gmmos/gmmos.c create mode 100644 kernel/gmmos/ps2.c diff --git a/Makefile b/Makefile index ef58aff..0ba6fde 100644 --- a/Makefile +++ b/Makefile @@ -115,6 +115,9 @@ KERN_ZSTD = $(addprefix $(BUILD)/zstd/, \ decompress/zstd_decompress_block.o) KERN_ZSTD += $(BUILD)/kernel/scff/scff.o +# GMMOS, what runs after a panic, and its micro drivers. See include/gmmos/gmmos.h. +KERN_GMMOS = $(addprefix $(BUILD)/kernel/gmmos/, gmmos.o ps2.o) + KERN_PROC = $(addprefix $(BUILD)/kernel/proc/, \ proc.o) @@ -126,7 +129,8 @@ KERN_SYS = $(addprefix $(BUILD)/kernel/sys/, \ KERN_ARCH += $(BUILD)/kernel/arch/x86_64/vectors.o KERNOBJS = $(KERN_ARCH) $(KERN_CORE) $(KERN_DEV) $(KERN_DRIVERS) \ - $(KERN_FS) $(KERN_NET) $(KERN_ACPI) $(KERN_ZSTD) $(KERN_PROC) $(KERN_SYS) + $(KERN_FS) $(KERN_NET) $(KERN_ACPI) $(KERN_ZSTD) $(KERN_GMMOS) \ + $(KERN_PROC) $(KERN_SYS) # entry.o must be first in link order; remove it from KERNOBJS for the rule KERN_NOENTRY = $(filter-out $(BUILD)/kernel/arch/x86_64/entry.o, $(KERNOBJS)) @@ -963,6 +967,10 @@ $(BUILD)/kernel/dev/%.o: kernel/dev/%.c @mkdir -p $(BUILD)/kernel/dev $(CC) $(CFLAGS) -c $< -o $@ +$(BUILD)/kernel/gmmos/%.o: kernel/gmmos/%.c + @mkdir -p $(BUILD)/kernel/gmmos + $(CC) $(CFLAGS) -c $< -o $@ + # `$(dir $@)` rather than a fixed path: drivers are grouped into one directory # per device class (gpu/, net/, bus/, usb/), so the output directory varies with # the target and a hardcoded mkdir only creates the top one. diff --git a/include/core/defs.h b/include/core/defs.h index e05d5fc..5c8a5b3 100644 --- a/include/core/defs.h +++ b/include/core/defs.h @@ -566,6 +566,7 @@ int proc_exists(int); int proc_num(void); struct procinfo; int proc_snapshot(int idx, struct procinfo *out); +int proc_peek(int idx, struct procinfo *out); // no lock: GMMOS int proc_snapshot_pid(int pid, struct procinfo *out); void pinit(void); void procdump(void); diff --git a/include/core/klog.h b/include/core/klog.h index e03fa4d..3e9f4eb 100644 --- a/include/core/klog.h +++ b/include/core/klog.h @@ -41,4 +41,5 @@ void klog_putc(int c); int klog_read(uint64 off, char *dst, int n); uint64 klog_len(void); uint64 klog_prev_bytes(void); +uint64 klog_boot_bytes(void); int klog_recovered(void); diff --git a/include/gmmos/gmmos.h b/include/gmmos/gmmos.h new file mode 100644 index 0000000..0e8b31d --- /dev/null +++ b/include/gmmos/gmmos.h @@ -0,0 +1,38 @@ +#pragma once + +#include "core/types.h" + +// GMMOS -- Guru Meditation Micro OS. What runs after a panic. +// +// The PSOGM used to be the last thing the kernel did: a report, blinking +// keyboard lights, and a machine that needed its power button. GMMOS keeps the +// report and goes on living under it. The real kernel is dead and stays dead -- +// every other processor is halted (see trap()), and nothing of it is called +// again that takes a lock, sleeps, allocates or waits for an interrupt. What +// GMMOS has instead is its own: the framebuffer, a clock read from the TSC, and +// keyboards it polls itself through micro drivers. +// +// A micro driver is a cut-down copy of a real one that does only what a +// keyboard needs. The real drivers run on interrupts, threads and locks, all of +// which belong to the kernel that just died. +// +// See kernel/gmmos/gmmos.c. + +void gmmos_run(char *reason, addr_t *pcs, int npcs) __attribute__((noreturn)); + +// One keyboard GMMOS can read by polling. +struct gmmos_kbd { + const char *name; + int (*probe)(void); // nonzero if it is there and usable + // The next key, or 0 when there is none: a Unicode codepoint, or one of the + // KBD_* codes from include/dev/kbd.h for keys that are not characters. + int (*poll)(void); + void (*leds)(int on); // optional: all three lights on or off +}; + +extern const struct gmmos_kbd gmmos_ps2; + +// From kernel/core/panic.c: draw the report again, and where its closing +// lines are, which GMMOS writes its status and its keys into. +void psogm_repaint(void); +void psogm_footer(int *x, int *y, int *step); diff --git a/kernel/core/klog.c b/kernel/core/klog.c index fe034c7..20eb165 100644 --- a/kernel/core/klog.c +++ b/kernel/core/klog.c @@ -267,6 +267,17 @@ klog_len(void) } uint64 klog_prev_bytes(void) { return prev_bytes; } + +// How much of what the ring holds this boot wrote: the tail of it, since the +// ring is read oldest first. GMMOS shows this boot's log, not the last one's. +uint64 +klog_boot_bytes(void) +{ + if (!klog) + return 0; + uint64 n = klog->head - klog->mark; + return n < klog_len() ? n : klog_len(); +} int klog_recovered(void) { return recovered; } // Read at a byte offset into the log, where 0 is the oldest byte still held. diff --git a/kernel/core/panic.c b/kernel/core/panic.c index cedd853..98be107 100644 --- a/kernel/core/panic.c +++ b/kernel/core/panic.c @@ -46,6 +46,7 @@ #include "gfx/logo.h" /* LOGO_H -- the mark's height, for the layout below */ #include "gfx/qr.h" #include "dev/acpi.h" +#include "gmmos/gmmos.h" extern uint ticks; extern int panicked; @@ -389,6 +390,9 @@ trace_line(char *buf, int cap, int i, addr_t pc) /* ------------------------------------------------------------------ */ +// Where paint() put its closing lines, for GMMOS to write into. +static int foot_x, foot_y, foot_step; + static void paint(char *reason, addr_t *pcs, int npcs) { @@ -423,7 +427,7 @@ paint(char *reason, addr_t *pcs, int npcs) // the tops of "Call chain" underlining the line above. So the layout is // measured first -- the text, and room for two frames of the chain -- and // what gives way is the margin, then the logo. Never the report. - int foot = sh - ch * 4 - ch / 2; + int foot = sh - ch * 5 - 4 - ch / 2; int body = (ch + ch / 2) + ch * 2 + ch + nfields * (ch + 2) + ch + (ch + 2) + ch * 2; int logo = 1; @@ -500,15 +504,20 @@ paint(char *reason, addr_t *pcs, int npcs) // // Kept under about 45 characters each: the QR card is on the right, and the // 62-character command line this replaces used to run underneath it. - text_at(left, sh - ch * 4, + // + // Four lines now, the second and fourth GMMOS's: the second says whether + // and when the machine powers off, the fourth which keys do what. Drawn + // here as they stand before GMMOS has looked for a keyboard. + foot_x = left; + foot_y = sh - ch * 5 - 4; + foot_step = ch + 4; + text_at(left, foot_y, "This report is in /proc/kmsg after a restart.", PSOGM_DIM); - // A verbose boot keeps the screen up for ever; any other powers off after - // PANIC_OFF_MS (see panic_blink), and says so rather than surprising anyone. - text_at(left, sh - ch * 4 + ch + 4, + text_at(left, foot_y + foot_step, boot_verbose ? "schdbg, or Ctrl-G on the console, asks more." : "The machine powers itself off in 3 minutes.", PSOGM_DIM); - text_at(left, sh - ch * 4 + (ch + 4) * 2, + text_at(left, foot_y + foot_step * 2, "PSOGM -- Purple Screen of Guru Meditation", PSOGM_DIM); gfx_flush_all(); } @@ -559,6 +568,25 @@ report_serial(char *reason, addr_t *pcs, int npcs) static void panic_blink(void) __attribute__((noreturn)); +// The report as it was drawn, so GMMOS can draw it again on the way back +// from one of its own screens. +static char *saved_reason; +static addr_t saved_pcs[10]; + +void +psogm_repaint(void) +{ + paint(saved_reason, saved_pcs, 10); +} + +void +psogm_footer(int *x, int *y, int *step) +{ + *x = foot_x; + *y = foot_y; + *step = foot_step; +} + volatile int panic_stop; static volatile int panic_owner = -1; @@ -593,8 +621,17 @@ panic(char *s) pcs[i] = 0; getcallerpcs(&s, pcs); + // Counted for the owner alone. A second panic means the first report, or + // GMMOS running after it, is what broke: report that one too, and then only + // blink. Re-entering GMMOS would most likely crash it the same way again. + static int depth; + depth++; + gather(s, pcs, 10); report_serial(s, pcs, 10); + saved_reason = s; + for (int i = 0; i < 10; i++) + saved_pcs[i] = pcs[i]; if (gfx_ready()) { paint(s, pcs, 10); @@ -605,6 +642,8 @@ panic(char *s) } panicked = 1; + if (gfx_ready() && depth == 1) + gmmos_run(s, pcs, 10); panic_blink(); } diff --git a/kernel/gmmos/gmmos.c b/kernel/gmmos/gmmos.c new file mode 100644 index 0000000..5bad2ca --- /dev/null +++ b/kernel/gmmos/gmmos.c @@ -0,0 +1,1018 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// GMMOS -- Guru Meditation Micro OS. See include/gmmos/gmmos.h for what it is. +// +// Four screens, one at a time: +// +// the report PSOGM as panic() drew it, with a live status line -- the +// power-off countdown -- and the keys +// QR pages the last 300 lines of this boot's log, as many QR symbols as +// it takes, one per screen, numbered so a phone can take them +// in order +// the log the same log on screen, scrolled with the arrows +// terminal a handful of commands for looking around a dead machine +// +// Rules this file keeps, because the kernel underneath it is dead: nothing +// that takes a lock, sleeps, allocates, or waits for an interrupt. Every +// buffer is static. Time comes from the TSC, keys from the micro drivers, and +// the only output is the framebuffer, redrawn whole after each key. + +#include "core/types.h" +#include "core/defs.h" +#include "core/param.h" +#include "core/klog.h" +#include "core/sbuf.h" +#include "core/proc.h" +#include "arch/x86.h" +#include "arch/memlayout.h" +#include "arch/mmu.h" +#include "dev/kbd.h" +#include "dev/pci.h" +#include "dev/acpi.h" +#include "gfx/gfx.h" +#include "gfx/theme.h" +#include "gfx/qr.h" +#include "gmmos/gmmos.h" + +extern uint ticks; + +// How long the report stays up on an ordinary boot before the machine powers +// itself off: a crashed laptop otherwise sits with its fans up until somebody +// holds the power button. Any key cancels it -- somebody is there. +#define OFF_MS (3 * 60 * 1000) +#define QR_LINES 300 + +static char *reason; +static addr_t *pcs; +static int npcs; + +static int sw, sh, cw, ch; +static const struct gmmos_kbd *kbd; + +enum { MAIN, QRPAGES, LOG, TERM }; +static int mode = MAIN; + +// --- time ------------------------------------------------------------------ + +// Milliseconds since anything: only differences are used. From the TSC when +// it was measured, otherwise from counting the 1 ms waits of the main loop, +// which drifts by however long drawing took and is good enough for a +// countdown. +static uint64 counted_ms; + +static uint64 +now_ms(void) +{ + if (tsc_hz >= 1000) + return rdtsc() / (tsc_hz / 1000); + return counted_ms; +} + +// --- text ------------------------------------------------------------------ + +// A string NUL-terminated at its builder's end, or cut at its capacity. +static char * +sbz(struct sbuf *sb) +{ + sb->p[sb->len < sb->cap ? sb->len : sb->cap - 1] = 0; + return sb->p; +} + +// At most n characters of s, at pixel position x, y. +static void +textn(int x, int y, const char *s, int n, uint32 color) +{ + char buf[256]; + int i; + + if (n > (int)sizeof(buf) - 1) + n = sizeof(buf) - 1; + for (i = 0; i < n && s[i]; i++) + buf[i] = s[i]; + buf[i] = 0; + gfx_text_at(x, y, buf, color); +} + +// The full-screen layout every screen but the report shares: a title, a +// body of rows, and a line of keys at the bottom. +static int mx, my, cols, rows; + +static void +screen(const char *title, const char *keys) +{ + gfx_set_bg(PSOGM_BG); + gfx_clear(PSOGM_BG); + textn(mx, my, title, cols, PSOGM_FG); + gfx_fill_rect(mx, my + ch + ch / 2, 120, 2, SCH_ACCENT); + textn(mx, sh - my - ch, keys, cols, PSOGM_DIM); +} + +// Row r of the body, 0 at the top. +static int +body_y(int r) +{ + return my + ch * 3 + r * ch; +} + +static int +body_rows(void) +{ + return rows - 5; +} + +// --- the log --------------------------------------------------------------- + +// This boot's log, copied out of the ring once on the way in and cleaned up +// for a screen: no escape sequences, no carriage returns, and the timestamps' +// padding squeezed out -- "[ 12.34]" is "[12.34]" -- which matters when +// every byte is a byte of a QR code. +static char logbuf[KLOG_BYTES]; +static int lstart[8192]; +static int nlines; + +static void +log_snapshot(void) +{ + uint64 have = klog_len(), mine = klog_boot_bytes(), off = have - mine; + int partial = 0; + + if (mine > sizeof(logbuf) - 1) { + off = have - (sizeof(logbuf) - 1); + partial = 1; + } + int n = klog_read(off, logbuf, sizeof(logbuf) - 1); + if (n < 0) + n = 0; + + int j = 0; + for (int i = 0; i < n; i++) { + char c = logbuf[i]; + if (c == 0x1B) { // CSI: ESC [ params final + if (i + 1 < n && logbuf[i + 1] == '[') { + i += 2; + while (i < n && !(logbuf[i] >= 0x40 && logbuf[i] <= 0x7E)) + i++; + } + continue; + } + if (c == '\r' || c == 0) + continue; + if (c == ' ' && j > 0 && logbuf[j - 1] == '[') + continue; + logbuf[j++] = c; + } + logbuf[j] = 0; + + // A cut through the middle of a line leaves its tail, which is noise. + int i = 0; + if (partial) + while (i < j && logbuf[i++] != '\n') + ; + nlines = 0; + while (i < j && nlines < (int)NELEM(lstart)) { + lstart[nlines++] = i; + while (i < j && logbuf[i] != '\n') + i++; + logbuf[i++] = 0; + } +} + +static const char * +logline(int i) +{ + return logbuf + lstart[i]; +} + +// --- QR pages -------------------------------------------------------------- + +static struct qr pq; // about 62 KB: static, never on this stack +static char payload[QR_MAX_BYTES + 1]; +static int pfirst[129]; +static int npages, page; +static int density = 1; // 0 large modules, 1 normal, 2 dense + +// Pixels the symbol may take, on its square. +static int +qr_room(void) +{ + int avail = sh - my * 2 - ch * 6; + if (sw - mx * 2 < avail) + avail = sw - mx * 2; + return avail; +} + +// The largest version whose modules still come out at least this many pixels +// wide. Larger modules are easier on a phone camera and hold less; + and - +// trade one for the other. +static int +qr_version(void) +{ + static const int minpx[3] = { 8, 6, 4 }; + for (int v = QR_MAX_VERSION; v > 1; v--) + if (qr_room() / (17 + 4 * v + 8) >= minpx[density]) + return v; + return 1; +} + +// Page header: "SchOS log 3/12\n", and on the first page the panic itself. +static int +page_header(char *buf, int cap, int p) +{ + struct sbuf sb = { buf, 0, cap }; + sb_puts(&sb, "SchOS log "); + sb_putu(&sb, (uint64)(p + 1)); + sb_putc(&sb, '/'); + sb_putu(&sb, (uint64)npages); + sb_putc(&sb, '\n'); + if (p == 0) { + sb_puts(&sb, "panic: "); + sb_puts(&sb, reason); + sb_putc(&sb, '\n'); + } + return sb.len < cap ? sb.len : cap; +} + +static int +page_budget(int p) +{ + char hdr[160]; + int saved = npages; + npages = 999; // the widest the header can get + int h = page_header(hdr, sizeof(hdr), p); + npages = saved; + return qr_capacity(qr_version()) - h; +} + +static int +linelen(int i) +{ + return (int)strlen(logline(i)) + 1; +} + +static void +paginate(void) +{ + int i = nlines > QR_LINES ? nlines - QR_LINES : 0; + + npages = 0; + while (i < nlines && npages < (int)NELEM(pfirst) - 1) { + int budget = page_budget(npages), used = 0; + pfirst[npages++] = i; + while (i < nlines) { + int len = linelen(i); + if (used > 0 && used + len > budget) + break; + used += len; + i++; + } + } + pfirst[npages] = i; + if (page >= npages) + page = npages ? npages - 1 : 0; +} + +static int +build_page(int p) +{ + int cap = qr_capacity(qr_version()); + int n = page_header(payload, cap, p); + + for (int i = pfirst[p]; i < pfirst[p + 1] && n < cap; i++) { + const char *s = logline(i); + while (*s && n < cap) + payload[n++] = *s++; + if (n < cap) + payload[n++] = '\n'; + } + return n; +} + +static void +draw_qr(void) +{ + char title[96]; + struct sbuf sb = { title, 0, sizeof(title) }; + + if (npages == 0) { + screen("The log as QR codes", "Esc the report"); + textn(mx, body_y(0), "This boot's log is empty.", cols, PSOGM_FG); + gfx_flush_all(); + return; + } + + sb_puts(&sb, "The log as QR codes -- page "); + sb_putu(&sb, (uint64)(page + 1)); + sb_puts(&sb, " of "); + sb_putu(&sb, (uint64)npages); + screen(sbz(&sb), + "Space/Right next Left back + - bigger, smaller Esc the report"); + textn(mx, my + ch * 2 + ch / 2, + "The last 300 lines of this boot, oldest first. Scan the pages in order.", + cols, PSOGM_DIM); + + int n = build_page(page); + if (qr_encode(&pq, (const unsigned char *)payload, n) != 0) { + textn(mx, body_y(1), "This page did not encode.", cols, PSOGM_FG); + gfx_flush_all(); + return; + } + + const int quiet = 4; + int cells = pq.size + 2 * quiet; + int scale = qr_room() / cells; + if (scale < 1) + scale = 1; + int card = cells * scale; + int x0 = (sw - card) / 2; + int y0 = my + ch * 4; + + gfx_fill_rect(x0, y0, card, card, PSOGM_CARD); + for (int y = 0; y < pq.size; y++) + for (int x = 0; x < pq.size; x++) + if (pq.mod[y][x]) + gfx_fill_rect(x0 + (x + quiet) * scale, y0 + (y + quiet) * scale, + scale, scale, PSOGM_INK); + gfx_flush_all(); +} + +// --- the log on screen ----------------------------------------------------- + +static int ltop = -1; + +static void +draw_log(void) +{ + char title[96]; + struct sbuf sb = { title, 0, sizeof(title) }; + int r = body_rows(); + + if (ltop < 0 || ltop > nlines - r) + ltop = nlines - r; + if (ltop < 0) + ltop = 0; + + sb_puts(&sb, "The log, this boot -- lines "); + sb_putu(&sb, (uint64)(nlines ? ltop + 1 : 0)); + sb_putc(&sb, '-'); + sb_putu(&sb, (uint64)(ltop + r < nlines ? ltop + r : nlines)); + sb_puts(&sb, " of "); + sb_putu(&sb, (uint64)nlines); + screen(sbz(&sb), "Up Down PgUp PgDn Home End Esc the report"); + for (int i = 0; i < r && ltop + i < nlines; i++) + textn(mx, body_y(i), logline(ltop + i), cols, PSOGM_FG); + gfx_flush_all(); +} + +static void +log_key(int k) +{ + int r = body_rows(); + switch (k) { + case KBD_UP: ltop--; break; + case KBD_DN: ltop++; break; + case KBD_PGUP: ltop -= r - 1; break; + case KBD_PGDN: ltop += r - 1; break; + case KBD_HOME: ltop = 0; break; + case KBD_END: ltop = nlines; break; + } + if (ltop < 0) + ltop = 0; +} + +// --- terminal -------------------------------------------------------------- + +#define TLINES 256 +#define TCOLS 160 + +static char tout[TLINES][TCOLS]; +static int tn; // lines ever written +static char tin[TCOLS], tlast[TCOLS]; +static int tinlen; + +static void +tline(const char *s) +{ + int w = cols < TCOLS - 1 ? cols : TCOLS - 1; + do { + char *d = tout[tn++ % TLINES]; + int i; + for (i = 0; i < w && s[i]; i++) + d[i] = s[i]; + d[i] = 0; + s += i; + } while (*s); +} + +static void +draw_term(void) +{ + screen("GMMOS terminal -- help lists the commands", + "Enter run Up the last command Esc the report"); + + int r = body_rows() - 1; + int first = tn > r ? tn - r : 0; + if (first < tn - TLINES) + first = tn - TLINES; + int row = 0; + for (int i = first; i < tn; i++) + textn(mx, body_y(row++), tout[i % TLINES], cols, PSOGM_FG); + + char line[TCOLS + 16]; + struct sbuf sb = { line, 0, sizeof(line) }; + sb_puts(&sb, "gmmos> "); + sb_puts(&sb, tin); + sb_putc(&sb, '_'); + textn(mx, body_y(row), sbz(&sb), cols, SCH_ACCENT); + gfx_flush_all(); +} + +static int +streq(const char *a, const char *b) +{ + return strncmp(a, b, TCOLS) == 0; +} + +// A number in base, or in hex with 0x before it whatever base says. +static uint64 +parse_num(const char *s, int base, int *ok) +{ + uint64 v = 0; + int any = 0; + + if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { + base = 16; + s += 2; + } + for (; *s; s++) { + int d; + if (*s >= '0' && *s <= '9') d = *s - '0'; + else if (*s >= 'a' && *s <= 'f') d = *s - 'a' + 10; + else if (*s >= 'A' && *s <= 'F') d = *s - 'A' + 10; + else break; + if (d >= base) + break; + v = v * base + d; + any = 1; + } + *ok = any && *s == 0; + return v; +} + +// Whether va is mapped in the kernel's page tables, walked by hand so that +// reading it cannot fault. A fault in here would be a panic inside GMMOS. +static int +mapped(uint64 va) +{ + uint64 cr3; + + // Non-canonical addresses do not even page-fault; they are a #GP. + if ((uint64)((int64)(va << 16) >> 16) != va) + return 0; + asm volatile("mov %%cr3, %0" : "=r"(cr3)); + uint64 *t = (uint64 *)P2V(PTE_ADDR(cr3)); + for (int level = 3; level >= 0; level--) { + uint64 e = t[(va >> (12 + 9 * level)) & 511]; + if (!(e & PTE_P)) + return 0; + if (level == 0 || (level < 3 && (e & PTE_PS))) + return 1; + t = (uint64 *)P2V(PTE_ADDR(e)); + } + return 0; +} + +static void +cmd_help(void) +{ + tline("help this list"); + tline("why the panic and its call chain"); + tline("log [N] the last N lines of the log (20)"); + tline("grep TEXT lines of the log containing TEXT"); + tline("ps processes"); + tline("cpus processors"); + tline("pci PCI devices"); + tline("mem ADDR [LEN] bytes at a kernel address; both in hex (80, up to 400)"); + tline("uptime how long it ran, and how long since"); + tline("clear empty this screen"); + tline("restart restart the machine"); + tline("poweroff power it off"); + tline("exit back to the report, as Esc does"); +} + +static void +cmd_why(void) +{ + char line[160]; + struct sbuf sb = { line, 0, sizeof(line) }; + sb_puts(&sb, "panic: "); + sb_puts(&sb, reason); + tline(sbz(&sb)); + for (int i = 0; i < npcs && pcs[i]; i++) { + sb.len = 0; + sb_puts(&sb, " #"); + sb_putu(&sb, (uint64)i); + sb_puts(&sb, " "); + sb_puthex(&sb, (uint64)pcs[i], 16); + if (ksym_ready() && ksym_lookup((uint64)pcs[i], 0)) { + char name[64]; + ksym_fmt(name, sizeof(name), (uint64)pcs[i]); + sb_puts(&sb, " "); + sb_puts(&sb, name); + } + tline(sbz(&sb)); + } +} + +static void +cmd_log(const char *arg) +{ + int ok, n = 20; + if (*arg) { + n = (int)parse_num(arg, 10, &ok); + if (!ok || n <= 0) { + tline("log: N is a number of lines"); + return; + } + } + for (int i = nlines > n ? nlines - n : 0; i < nlines; i++) + tline(logline(i)); +} + +static int +contains(const char *s, const char *pat) +{ + int m = (int)strlen(pat); + for (; *s; s++) + if (strncmp(s, pat, m) == 0) + return 1; + return 0; +} + +static void +cmd_grep(const char *arg) +{ + int found = 0; + if (!*arg) { + tline("grep: what to look for?"); + return; + } + for (int i = 0; i < nlines; i++) + if (contains(logline(i), arg)) { + tline(logline(i)); + found++; + } + if (!found) + tline("(nothing)"); +} + +static void +cmd_ps(void) +{ + static const char *const st[] = { + "unused", "embryo", "sleeping", "runnable", "running", "zombie", + }; + struct procinfo pi; + char line[96]; + struct sbuf sb = { line, 0, sizeof(line) }; + + tline(" PID PPID STATE NAME"); + for (int i = 0; proc_peek(i, &pi) == 0; i++) { + sb.len = 0; + sb_putu_pad(&sb, (uint64)pi.pid, 5); + sb_putu_pad(&sb, (uint64)pi.ppid, 6); + sb_puts(&sb, " "); + sb_pad(&sb, pi.state >= 0 && pi.state < (int)NELEM(st) ? st[pi.state] : "?", 10); + sb_puts(&sb, pi.name); + if (pi.killed) + sb_puts(&sb, " (killed)"); + tline(sbz(&sb)); + } +} + +static void +cmd_cpus(void) +{ + char line[96]; + struct sbuf sb = { line, 0, sizeof(line) }; + int me = cpunum(); + + for (int i = 0; i < ncpu; i++) { + sb.len = 0; + sb_puts(&sb, "cpu"); + sb_putu(&sb, (uint64)i); + sb_puts(&sb, " apic "); + sb_putu(&sb, (uint64)cpus[i].apicid); + sb_puts(&sb, i == me ? " panicked; runs GMMOS" + : cpus[i].started ? " stopped by the panic" + : " never started"); + tline(sbz(&sb)); + } +} + +static void +cmd_pci(void) +{ + char line[160], desc[64]; + struct sbuf sb = { line, 0, sizeof(line) }; + + for (int i = 0; i < pci_count(); i++) { + struct pci_dev *d = pci_get(i); + sb.len = 0; + sb_puthex(&sb, d->bus, 2); + sb_putc(&sb, ':'); + sb_puthex(&sb, d->slot, 2); + sb_putc(&sb, '.'); + sb_putu(&sb, d->func); + sb_puts(&sb, " "); + sb_puthex4(&sb, d->vendor); + sb_putc(&sb, ':'); + sb_puthex4(&sb, d->device); + sb_puts(&sb, " "); + pci_describe(d, desc, sizeof(desc)); + sb_pad(&sb, desc, 28); + sb_puts(&sb, d->driver ? d->driver : "-"); + tline(sbz(&sb)); + } + if (pci_count() == 0) + tline("(none found)"); +} + +static void +cmd_mem(const char *arg) +{ + char a[TCOLS], *b; + int ok, ok2 = 1; + + safestrcpy(a, arg, sizeof(a)); + for (b = a; *b && *b != ' '; b++) + ; + if (*b) + *b++ = 0; + uint64 va = parse_num(a, 16, &ok); + uint64 len = *b ? parse_num(b, 16, &ok2) : 0x80; + if (!ok || !ok2 || len == 0) { + tline("mem: mem ADDR [LEN], both in hex"); + return; + } + if (len > 0x400) + len = 0x400; + + char line[128]; + struct sbuf sb = { line, 0, sizeof(line) }; + for (uint64 row = va & ~15ull; row < va + len; row += 16) { + if (!mapped(row)) { + sb.len = 0; + sb_puthex(&sb, row, 16); + sb_puts(&sb, " not mapped"); + tline(sbz(&sb)); + row = (row | (PGSIZE - 1)) - 15; // on to the next page + continue; + } + volatile uchar *p = (volatile uchar *)row; + char asc[17]; + sb.len = 0; + sb_puthex(&sb, row, 16); + sb_puts(&sb, " "); + for (int i = 0; i < 16; i++) { + uchar c = p[i]; + sb_putc(&sb, ' '); + sb_puthex(&sb, c, 2); + asc[i] = c >= 0x20 && c < 0x7F ? (char)c : '.'; + } + asc[16] = 0; + sb_puts(&sb, " "); + sb_puts(&sb, asc); + tline(sbz(&sb)); + } +} + +static uint64 entered_ms; + +static void +cmd_uptime(void) +{ + char line[96]; + struct sbuf sb = { line, 0, sizeof(line) }; + sb_puts(&sb, "ran for "); + sb_putu(&sb, (uint64)ticks / HZ); + sb_puts(&sb, " s; dead for "); + sb_putu(&sb, (now_ms() - entered_ms) / 1000); + sb_puts(&sb, " s"); + tline(sbz(&sb)); +} + +static void +restart(void) +{ + // The chipset's reset port and the keyboard controller first: neither runs + // anything of the dead kernel. ACPI's reset register after them, then a + // triple fault, which resets every PC there is. + outb(0xCF9, 0x02); + microdelay(50); + outb(0xCF9, 0x06); + microdelay(100000); + outb(0x64, 0xFE); + microdelay(100000); + acpi_reboot(); + microdelay(100000); + struct { ushort limit; uint64 base; } __attribute__((packed)) none = { 0, 0 }; + asm volatile("lidt %0; int3" : : "m"(none)); + for (;;) + hlt(); +} + +static void enter(int m); + +static void +run(void) +{ + char cmd[TCOLS]; + const char *arg; + char line[TCOLS + 8]; + struct sbuf sb = { line, 0, sizeof(line) }; + + sb_puts(&sb, "gmmos> "); + sb_puts(&sb, tin); + tline(sbz(&sb)); + + safestrcpy(cmd, tin, sizeof(cmd)); + if (tinlen) + safestrcpy(tlast, tin, sizeof(tlast)); + tin[0] = 0; + tinlen = 0; + + char *p = cmd; + while (*p == ' ') + p++; + char *w = p; + while (*p && *p != ' ') + p++; + if (*p) + *p++ = 0; + while (*p == ' ') + p++; + arg = p; + + if (!*w) return; + else if (streq(w, "help")) cmd_help(); + else if (streq(w, "why")) cmd_why(); + else if (streq(w, "log")) cmd_log(arg); + else if (streq(w, "grep")) cmd_grep(arg); + else if (streq(w, "ps")) cmd_ps(); + else if (streq(w, "cpus")) cmd_cpus(); + else if (streq(w, "pci")) cmd_pci(); + else if (streq(w, "mem")) cmd_mem(arg); + else if (streq(w, "uptime")) cmd_uptime(); + else if (streq(w, "clear")) tn = 0; + else if (streq(w, "restart") || streq(w, "reboot")) restart(); + else if (streq(w, "poweroff")) { + acpi_poweroff_now(); + tline("poweroff: the firmware did not power off"); + } + else if (streq(w, "exit")) enter(MAIN); + else { + sb.len = 0; + sb_puts(&sb, w); + sb_puts(&sb, ": no such command; help lists them"); + tline(sbz(&sb)); + } +} + +static void +term_key(int k) +{ + if (k == '\n') { + run(); + } else if (k == '\b' || k == 0x7F) { + if (tinlen) + tin[--tinlen] = 0; + } else if (k == 0x03) { // Ctrl-C: drop the line + tin[0] = 0; + tinlen = 0; + } else if (k == 0x0C) { // Ctrl-L + tn = 0; + } else if (k == KBD_UP) { + safestrcpy(tin, tlast, sizeof(tin)); + tinlen = (int)strlen(tin); + } else if (k >= 0x20 && k < 0x7F && tinlen < TCOLS - 1 && tinlen < cols - 10) { + tin[tinlen++] = (char)k; + tin[tinlen] = 0; + } +} + +// --- the report ------------------------------------------------------------ + +static int cancelled; // a key was pressed: no power-off +static int pending; // 'r' or 'p', waiting for a second press +static uint64 pending_until; + +static void +draw_status(void) +{ + int x, y, step; + char line[96]; + struct sbuf sb = { line, 0, sizeof(line) }; + + psogm_footer(&x, &y, &step); + gfx_set_bg(PSOGM_BG); + + if (pending == 'r') { + sb_puts(&sb, "Press R again to restart."); + } else if (pending == 'p') { + sb_puts(&sb, "Press P again to power off."); + } else if (!boot_verbose && !cancelled) { + uint64 left = (OFF_MS - (now_ms() - entered_ms)) / 1000; + if ((int64)left < 0) + left = 0; + sb_puts(&sb, "Powers off in "); + sb_putu(&sb, left / 60); + sb_putc(&sb, ':'); + sb_putc(&sb, (char)('0' + left % 60 / 10)); + sb_putc(&sb, (char)('0' + left % 10)); + sb_puts(&sb, kbd ? ". Any key keeps it on." : "."); + } else { + sb_puts(&sb, "Staying on. Nothing here is lost until it restarts."); + } + gfx_fill_rect(x, y + step, sw * 2 / 3 - x, ch + 2, PSOGM_BG); + textn(x, y + step, sbz(&sb), (sw * 2 / 3 - x) / cw, PSOGM_DIM); + + gfx_fill_rect(x, y + step * 3, sw - x, ch + 2, PSOGM_BG); + if (kbd) + textn(x, y + step * 3, + "Space log as QR L read the log T terminal R restart P power off", + (sw - x) / cw, PSOGM_FG); + else + textn(x, y + step * 3, + "No keyboard GMMOS can read on this machine yet.", + (sw - x) / cw, PSOGM_FG); + gfx_flush_all(); +} + +static void +main_key(int k) +{ + if (k == 0x1B) { + pending = 0; + } else if (k == 'r' || k == 'R' || k == 'p' || k == 'P') { + int want = k | 0x20; + if (pending == want) { + if (want == 'r') + restart(); + acpi_poweroff_now(); + pending = 0; + } else { + pending = want; + pending_until = now_ms() + 3000; + } + } else if (k == ' ') { + enter(QRPAGES); + return; + } else if (k == 'l' || k == 'L') { + enter(LOG); + return; + } else if (k == 't' || k == 'T') { + enter(TERM); + return; + } + draw_status(); +} + +// --- screens --------------------------------------------------------------- + +static void +redraw(void) +{ + switch (mode) { + case MAIN: psogm_repaint(); draw_status(); break; + case QRPAGES: draw_qr(); break; + case LOG: draw_log(); break; + case TERM: draw_term(); break; + } +} + +static void +enter(int m) +{ + mode = m; + pending = 0; + if (m == QRPAGES) + paginate(); + if (m == LOG) + ltop = -1; + redraw(); +} + +static void +qr_key(int k) +{ + if (k == ' ' || k == KBD_RT || k == KBD_PGDN || k == '\n') { + if (page + 1 < npages) + page++; + } else if (k == KBD_LF || k == KBD_PGUP || k == '\b') { + if (page > 0) + page--; + } else if (k == KBD_HOME) { + page = 0; + } else if (k == KBD_END) { + page = npages ? npages - 1 : 0; + } else if ((k == '+' || k == '=') && density > 0) { + density--; + page = 0; + paginate(); + } else if (k == '-' && density < 2) { + density++; + page = 0; + paginate(); + } else { + return; + } + draw_qr(); +} + +static void +key(int k) +{ + if (mode != MAIN && (k == 0x1B || (mode != TERM && (k == 'q' || k == 'Q')))) { + enter(MAIN); + return; + } + switch (mode) { + case MAIN: main_key(k); break; + case QRPAGES: qr_key(k); break; + case LOG: log_key(k); draw_log(); break; + case TERM: term_key(k); if (mode == TERM) draw_term(); break; + } +} + +void +gmmos_run(char *r, addr_t *p, int n) +{ + reason = r; + pcs = p; + npcs = n; + entered_ms = now_ms(); + + gfx_dims(&sw, &sh); + cw = gfx_char_w(); + ch = gfx_char_h(); + mx = cw * 3; + my = ch; + cols = (sw - mx * 2) / cw; + rows = (sh - my * 2) / ch; + + log_snapshot(); + if (gmmos_ps2.probe()) + kbd = &gmmos_ps2; + tline("GMMOS, the Guru Meditation Micro OS. The kernel is dead; this is what"); + tline("runs after it. help lists the commands."); + draw_status(); + + uint64 last_status = now_ms(), last_blink = now_ms(); + int lights = 0, keyed = 0; + + for (;;) { + int k = kbd ? kbd->poll() : 0; + if (k) { + if (!keyed && kbd->leds) { + kbd->leds(0); // somebody is here: the lights have done their job + lights = 0; + } + keyed = 1; + int was = cancelled; + cancelled = 1; + // The key that cancels the countdown does only that, on the report: + // whoever pressed it may not have read the keys yet. + if (!was && mode == MAIN && !boot_verbose) + draw_status(); + else + key(k); + continue; + } + + uint64 t = now_ms(); + if (pending && t >= pending_until) { + pending = 0; + if (mode == MAIN) + draw_status(); + } + if (mode == MAIN && t - last_status >= 1000) { + last_status = t; + draw_status(); + } + // The lights until somebody presses a key, as panic_blink did: on a + // machine whose screen is black, they are all there is to say it died. + if (!keyed && kbd && kbd->leds && t - last_blink >= 333) { + last_blink = t; + lights = !lights; + kbd->leds(lights); + } + if (!boot_verbose && !cancelled && t - entered_ms >= OFF_MS) { + acpi_poweroff_now(); + cancelled = 1; // still on: stay up rather than retry for ever + draw_status(); + } + + microdelay(1000); + counted_ms++; + } +} diff --git a/kernel/gmmos/ps2.c b/kernel/gmmos/ps2.c new file mode 100644 index 0000000..0af08f0 --- /dev/null +++ b/kernel/gmmos/ps2.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +// GMMOS's PS/2 keyboard: kernel/dev/kbd.c with everything but the keys taken +// out. +// +// No interrupt: the status port is polled. That also makes it immune to the +// IRQ 1 polarity quirk of some AMD laptops -- the ASUS TUF among them -- which +// is about how the interrupt is routed, and nothing here is routed. No +// /dev/input, no grab, no terminal chords. Scancode set 1, as the controller +// translates it, through the same keymap the console uses. + +#include "core/types.h" +#include "arch/x86.h" +#include "core/defs.h" +#include "dev/kbd.h" +#include "dev/keymap.h" +#include "gmmos/gmmos.h" + +// kbd.c's tables, which are static there. Hardware facts, so they are short. +static const uchar shiftcode[256] = { + [0x1D] = CTL, [0x2A] = SHIFT, [0x36] = SHIFT, [0x38] = ALT, + [0x9D] = CTL, [0xB8] = RALT, +}; +static const uchar togglecode[256] = { + [0x3A] = CAPSLOCK, [0x45] = NUMLOCK, [0x46] = SCROLLLOCK, +}; + +static uint shift; +static int e0, e1skip; + +// Bytes that arrived while a command was waiting for its answer. Setting the +// lights is a command, and a key pressed during it would otherwise be eaten. +static uchar held[16]; +static int nheld, iheld; + +// A byte from the controller, or -1. A byte from the mouse is read too -- left +// in the buffer, it blocks every keyboard byte behind it -- and dropped. +static int +rd(void) +{ + uint st = inb(KBSTATP); + if (!(st & KBS_DIB)) + return -1; + uchar d = inb(KBDATAP); + if (st & 0x20) + return -1; + return d; +} + +static void +wr(uchar b) +{ + for (int i = 0; i < 10000; i++) { + if (!(inb(KBSTATP) & 0x02)) { + outb(KBDATAP, b); + return; + } + microdelay(10); + } +} + +// Wait up to 20 ms for the keyboard's answer to a command, keeping anything +// else that arrives first. +static void +answer(void) +{ + for (int i = 0; i < 2000; i++) { + int d = rd(); + if (d < 0) { + microdelay(10); + continue; + } + if (d == 0xFA || d == 0xFE) + return; + if (nheld < (int)sizeof(held)) + held[nheld++] = (uchar)d; + } +} + +static int +probe(void) +{ + // No controller decodes the port: every bit reads as one. + if (inb(KBSTATP) == 0xFF) + return 0; + for (int i = 0; i < 64 && (inb(KBSTATP) & KBS_DIB); i++) + (void)inb(KBDATAP); + shift = 0; + e0 = e1skip = 0; + + // A controller with no keyboard behind it -- a desktop with only USB ones + // -- must not be offered as one: GMMOS would list keys that do nothing. So + // ask the keyboard itself to echo, and wait 50 ms for it to. + wr(0xEE); + for (int i = 0; i < 5000; i++) { + int d = rd(); + if (d == 0xEE) + return 1; + if (d < 0) + microdelay(10); + } + return 0; +} + +static void +leds(int on) +{ + wr(0xED); + answer(); + wr(on ? 0x07 : 0x00); + answer(); +} + +static int +poll(void) +{ + int d; + + if (iheld < nheld) { + d = held[iheld++]; + } else { + nheld = iheld = 0; + d = rd(); + } + if (d < 0) + return 0; + + // Answers and self-test results, not keys. + if (d == 0xFA || d == 0xFE || d == 0xAA || d == 0xEE || d == 0x00 || d == 0xFF) + return 0; + // Pause is E1 1D 45 E1 9D C5, and means nothing here. Read as keys, it + // would be a Ctrl press and two Num Lock toggles. + if (e1skip) { + e1skip--; + return 0; + } + if (d == 0xE1) { + e1skip = 5; + return 0; + } + if (d == 0xE0) { + e0 = 1; + return 0; + } + + // As in kbdgetc: a release clears its modifier, a press with E0 before it + // is looked up with the high bit set. + if (d & 0x80) { + d = e0 ? d : (d & 0x7F); + shift &= ~shiftcode[d]; + e0 = 0; + return 0; + } + if (e0) { + d |= 0x80; + e0 = 0; + } + shift |= shiftcode[d]; + shift ^= togglecode[d]; + + return (int)keymap_lookup((uint)d, shift); +} + +const struct gmmos_kbd gmmos_ps2 = { + .name = "PS/2", + .probe = probe, + .poll = poll, + .leds = leds, +}; diff --git a/kernel/proc/proc.c b/kernel/proc/proc.c index bde72f0..3c4160d 100644 --- a/kernel/proc/proc.c +++ b/kernel/proc/proc.c @@ -1857,6 +1857,31 @@ procdump(void) // Fill `out` with the `idx`-th live process (0-based, in table order). // Returns 0 on success, -1 when idx is past the end. +// proc_snapshot without the lock, and only the fields a crash screen lists. +// For GMMOS: the lock may be held by a processor the panic stopped, and a torn +// read of one line is a far smaller loss than a crash screen that hangs. +int +proc_peek(int idx, struct procinfo *out) +{ + struct proc *p; + int n = 0; + + for (p = ptable.proc; p < &ptable.proc[NPROC]; p++) { + if (p->state == UNUSED) + continue; + if (n++ != idx) + continue; + out->pid = p->pid; + out->ppid = p->parent ? p->parent->pid : 0; + out->state = p->state; + out->sz = p->sz; + out->killed = p->killed; + safestrcpy(out->name, p->name, sizeof(out->name)); + return 0; + } + return -1; +} + int proc_snapshot(int idx, struct procinfo *out) {