SchOS/kernel/gmmos/gmmos.c
Nexoniarz c6d90e69fa GMMOS: I2C keyboards, and every keyboard at once
The second micro driver: i2c_hid.c with everything but reading a
keyboard's input reports taken out. No ACPI -- AML cannot run on a dead
kernel -- so the live driver hands over what it found at boot
(i2c_hid_keyboards): the DesignWare controller, the address, the report
size, the AMD GPIO pin if there is one, and the parsed report descriptor.
No power commands, no reset, a read-only transfer.

usb_hid.c's report-protocol rewrite is split out as hid_boot_report(),
which does nothing but rewrite, and kernel/gmmos/hidkeys.c turns boot
reports into keys for this and the USB micro drivers to come.

GMMOS now probes every micro driver and polls all that answer.

Not run: QEMU has no DesignWare I2C, and the laptops at hand have PS/2
keyboards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-25 01:15:50 +02:00

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