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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
1018 lines
25 KiB
C
1018 lines
25 KiB
C
// 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++;
|
|
}
|
|
}
|