SchOS/kernel/gmmos/gmmos.c
Nexoniarz f6c4b6377f GMMOS: USB keyboards on xHCI
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
2026-10-25 01:23:14 +02:00

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++;
}
}