The third micro driver: xhci.c with everything but finding a keyboard and reading it taken out. It does not take over the live driver's state -- the kernel may have died inside that driver -- so each controller is halted, reset and enumerated from nothing. Of the live driver it uses the register mapping and the scratchpad pages the controller was lent (DCBAA entry 0, read before the reset). Root ports, and USB 2 hubs up to two deep (power, reset, route string, transaction translator for a slow device behind a high-speed hub). A boot-protocol keyboard interface gets Set Configuration, Set Protocol boot, Set Idle, one interrupt IN endpoint and a Normal TRB, re-queued after every report. No interrupts, no hot-plug, nothing that is not a keyboard or a hub. A static 256 KB pool instead of the DMA allocator. Checked under QEMU with the PS/2 controller switched off (i8042=off): a USB keyboard on a root port, and one behind a hub, both type into the GMMOS terminal. QEMU's hub is full speed, so the transaction-translator path is not exercised. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
1049 lines
26 KiB
C
1049 lines
26 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;
|
|
// Every micro driver, in the order they are tried. All that answer are
|
|
// polled: a laptop has its own keyboard and maybe a USB one on the side.
|
|
static const struct gmmos_kbd *const drivers[] = {
|
|
&gmmos_ps2, &gmmos_i2c, &gmmos_xhci,
|
|
};
|
|
static const struct gmmos_kbd *kbds[NELEM(drivers)];
|
|
static int nkbd;
|
|
|
|
static int
|
|
poll_keys(void)
|
|
{
|
|
for (int i = 0; i < nkbd; i++) {
|
|
int k = kbds[i]->poll();
|
|
if (k)
|
|
return k;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
set_lights(int on)
|
|
{
|
|
for (int i = 0; i < nkbd; i++)
|
|
if (kbds[i]->leds)
|
|
kbds[i]->leds(on);
|
|
}
|
|
|
|
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("keyboards which keyboards GMMOS is reading");
|
|
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, "keyboards")) {
|
|
for (int i = 0; i < nkbd; i++)
|
|
tline(kbds[i]->name);
|
|
}
|
|
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, nkbd ? ". 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 (nkbd)
|
|
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();
|
|
for (int i = 0; i < (int)NELEM(drivers); i++)
|
|
if (drivers[i]->probe())
|
|
kbds[nkbd++] = drivers[i];
|
|
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 = poll_keys();
|
|
if (k) {
|
|
if (!keyed) {
|
|
set_lights(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 && t - last_blink >= 333) {
|
|
last_blink = t;
|
|
lights = !lights;
|
|
set_lights(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++;
|
|
}
|
|
}
|