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