SchOS/kernel/core/panic.c
Nexoniarz cb9b06d46f GMMOS: the Guru Meditation Micro OS, alive under the PSOGM
A panic no longer ends in a picture. The report is drawn as before, and
then GMMOS runs under it: the real kernel stays dead -- the other
processors are halted, and nothing of it is called that locks, sleeps,
allocates or waits for an interrupt -- and GMMOS keeps its own clock
(the TSC), its own keyboard (micro drivers, polled), and the screen.

On the report: a live status line counting down the three-minute
power-off, which any key cancels, and the keys.

  Space  the last 300 lines of this boot's log as QR codes, a page per
         symbol, numbered; + and - trade module size for capacity
  L      the log on screen, scrolled
  T      a terminal: why, log, grep, ps, cpus, pci, mem (page tables
         walked first, so a bad address cannot fault), uptime, restart,
         poweroff
  R R    restart; P P power off -- twice, so a stray key does neither

The first micro driver is PS/2, polled: kbd.c with everything but the
keys taken out. The probe asks the keyboard to echo, so a controller
with nothing behind it is not offered as one. Polling also sidesteps the
IRQ 1 polarity quirk, which is about interrupt routing.

A panic inside GMMOS (or a second one in the report) is reported and
then only blinks; it does not re-enter GMMOS.

Checked under QEMU: every screen; two QR pages read off a screenshot
with zbar decode to the log text exactly; restart reboots; left alone it
still powers off at three minutes.

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

734 lines
26 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
//
// PSOGM — the SchOS crash screen.
//
// The name is a nod to the Amiga's "Guru Meditation", but nothing else about
// this is a nod to 1985. Three things it is built around:
//
// *It takes the whole screen.* An earlier version dimmed what was there and
// floated a panel over it, on the argument that a crash screen which wipes the
// display tells you the machine is gone while one that leaves your work visible
// tells you it stopped. That looked good and was wrong: the thing is called a
// Purple Screen, and half a screen of somebody's abandoned work behind the
// report is a distraction at the worst possible moment.
//
// *It is not frightening.* No red, no capitals, no "FATAL". A calm sentence,
// the system's own mark, and then the facts. Whoever is reading this is
// already having a bad time.
//
// *It says considerably more than it needs to.* The screen is the only artefact
// of a kernel panic — there is no core dump and no log to go back to — so
// everything cheap to collect is collected: which CPU, which process, how far
// into the boot, where the kernel is loaded, and a call chain. Anything left
// out here is gone.
//
// Everything is also written to the serial console, in the same order. That is
// what tools/bootcheck.ps1 reads, and it is what you get when the crash happens
// before there is a framebuffer at all.
#include "core/types.h"
#include "dev/kbd.h"
#include "arch/x86.h"
#include "core/defs.h"
#include "gfx/gfx.h"
#include "core/param.h"
#include "arch/memlayout.h"
#include "arch/mmu.h"
#include "arch/x86.h"
#include "core/proc.h"
#include "core/spinlock.h"
// Purple, because the name says so, and white on it because nothing else is
// legible at every brightness a monitor might be set to. The palette lives in
// theme.h with everything else; this file used to keep its own copy under the
// same names but with different values, which meant theme.h described a crash
// screen nobody had ever seen.
#include "gfx/theme.h"
#include "gfx/logo.h" /* LOGO_H -- the mark's height, for the layout below */
#include "gfx/qr.h"
#include "dev/acpi.h"
#include "gmmos/gmmos.h"
extern uint ticks;
extern int panicked;
/* ------------------------------------------------------------------ */
/* Small formatting helpers */
/* */
/* cprintf goes to the console, which during a panic is also the screen */
/* being painted over -- so the panel builds its strings itself and */
/* places them with gfx_text_at. */
/* ------------------------------------------------------------------ */
static char *
put_str(char *p, char *end, const char *s)
{
while (*s && p < end - 1)
*p++ = *s++;
*p = '\0';
return p;
}
static char *
put_dec(char *p, char *end, uint64 v)
{
char tmp[24];
int i = 0;
if (v == 0)
tmp[i++] = '0';
while (v && i < (int)sizeof(tmp)) { tmp[i++] = '0' + (v % 10); v /= 10; }
while (i-- && p < end - 1)
*p++ = tmp[i];
*p = '\0';
return p;
}
static char *
put_hex(char *p, char *end, uint64 v, int digits)
{
const char *d = "0123456789abcdef";
if (p < end - 2) { *p++ = '0'; *p++ = 'x'; }
for (int i = digits - 1; i >= 0 && p < end - 1; i--)
*p++ = d[(v >> (i * 4)) & 0xF];
*p = '\0';
return p;
}
/* ------------------------------------------------------------------ */
/* The report */
/* ------------------------------------------------------------------ */
struct field {
const char *label;
char value[72];
};
#define NFIELD 10
static struct field fields[NFIELD];
static int nfields;
static struct field *
field(const char *label)
{
if (nfields >= NFIELD)
return &fields[NFIELD - 1];
struct field *f = &fields[nfields++];
f->label = label;
f->value[0] = '\0';
return f;
}
// Collect everything worth knowing while the machine is still in the state
// that produced the fault. Interrupts are already off and no lock is taken:
// a panic that deadlocks trying to report itself tells you nothing at all.
// A short, shoutable name for the failure, derived from the panic message
// rather than declared at every call site.
//
// Windows stop codes are genuinely useful: they are the thing you search for,
// and they stay the same across builds while the prose around them changes.
// Taking the message up to its first colon (or its first few words) gets that
// for free -- "sysrq: crash requested" becomes SYSRQ, "short filewrite" becomes
// SHORT_FILEWRITE -- with no risk of a call site forgetting to supply one.
static void
stop_code(char *out, int cap, const char *reason)
{
int n = 0;
for (int i = 0; reason[i] && n < cap - 1; i++) {
char c = reason[i];
if (c == ':')
break;
if (c == ' ' || c == '\t') {
if (n == 0 || out[n-1] == '_')
continue;
out[n++] = '_';
continue;
}
if (c >= 'a' && c <= 'z') c = c - 'a' + 'A';
if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')
out[n++] = c;
}
while (n > 0 && out[n-1] == '_')
n--;
if (n == 0)
{ out[n++] = 'P'; out[n++] = 'A'; out[n++] = 'N'; out[n++] = 'I'; out[n++] = 'C'; }
out[n] = '\0';
}
static void
gather(char *reason, addr_t *pcs, int npcs)
{
char *e;
struct field *f;
nfields = 0;
f = field("Stop code");
stop_code(f->value, sizeof(f->value), reason);
f = field("Reason");
put_str(f->value, f->value + sizeof(f->value), reason);
f = field("CPU");
e = f->value + sizeof(f->value);
put_dec(f->value, e, mycpu() ? (uint64)mycpu()->id : 0);
if (!mycpu())
put_str(f->value, e, "unknown (very early boot)");
f = field("Process");
e = f->value + sizeof(f->value);
if (myproc()) {
char *p = put_str(f->value, e, myproc()->name);
p = put_str(p, e, " pid ");
put_dec(p, e, (uint64)myproc()->pid);
} else {
put_str(f->value, e, "none -- in the kernel, not on anybody's behalf");
}
f = field("Uptime");
e = f->value + sizeof(f->value);
{
char *p = put_dec(f->value, e, (uint64)ticks);
p = put_str(p, e, " ticks (about ");
p = put_dec(p, e, (uint64)ticks / HZ);
put_str(p, e, " s)");
}
// Every address below is a link-time address. Printing where the kernel
// actually sits is what lets addr2line resolve them afterwards, and it is
// the single most useful line here when the trace is all you have.
f = field("Kernel base");
e = f->value + sizeof(f->value);
put_hex(f->value, e, (uint64)KERNBASE, 16);
f = field("Fault PC");
e = f->value + sizeof(f->value);
if (npcs > 0 && pcs[0])
put_hex(f->value, e, (uint64)pcs[0], 16);
else
put_str(f->value, e, "unavailable");
}
/* ------------------------------------------------------------------ */
/* The QR code */
/* */
/* A panic report is only worth what can be carried away from it, and */
/* at this point there is no network, no log to write to, and often no */
/* second machine on the desk. Photographing the screen works but */
/* turns a call chain into something that has to be retyped by hand, */
/* with sixteen hex digits per frame and no way to tell 8 from B. */
/* */
/* A phone camera is a working out-of-band channel that needs no */
/* driver on our side at all. See kernel/core/qr.c. */
/* ------------------------------------------------------------------ */
// Deliberately not a URL. This encodes the report itself, so it goes wherever
// the person holding the phone decides -- and reaches nothing on its own.
//
// Compact because the budget is 134 bytes for the largest symbol this encoder
// builds, and a crash with a deep call chain is exactly when the budget is
// tightest. Two decisions buy most of the room: frames are stored as offsets
// from the kernel base rather than absolute addresses, which halves 16 hex
// digits to 8, and the fields are single letters. The base is included, so
// nothing is lost -- addr2line wants the link-time address and can be given it.
//
// Fields are appended in priority order and the loop simply stops when the
// buffer is full, so a long reason string costs call-chain depth rather than
// producing a report that will not encode.
static int
build_qr_payload(char *buf, int cap, char *reason, addr_t *pcs, int npcs)
{
char *p = buf, *end = buf + cap;
// Magic and format version, so whatever reads this later can tell what it is
// looking at and can change the layout without ambiguity.
p = put_str(p, end, "SXPSOGM1\n");
// The reason, not the stop code: the stop code is derived from the reason by
// stop_code() and carries strictly less, so encoding both would spend bytes
// on something the reader can recompute.
//
// Held back from the end of the buffer so that a long message costs some of
// the call chain but never all of it. A report with a sentence and no
// addresses is much harder to act on than one with addresses and a truncated
// sentence.
{
char *reason_end = end - 34; // three frames and their separators
if (reason_end > p)
p = put_str(p, reason_end, reason);
}
p = put_str(p, end, "\nc");
p = put_dec(p, end, mycpu() ? (uint64)mycpu()->id : 0);
p = put_str(p, end, " p");
p = put_dec(p, end, myproc() ? (uint64)myproc()->pid : 0);
p = put_str(p, end, " t");
p = put_dec(p, end, (uint64)ticks);
if (myproc()) {
p = put_str(p, end, " ");
p = put_str(p, end, myproc()->name);
}
p = put_str(p, end, "\nb");
p = put_hex(p, end, (uint64)KERNBASE, 16);
p = put_str(p, end, "\n");
for (int i = 0; i < npcs && pcs[i]; i++) {
// Nine bytes: eight hex digits and the separator. Stopping here rather than
// letting put_hex truncate matters -- half an address in a crash report is
// worse than one fewer address, because it looks like a real one.
if (end - p < 10)
break;
if (i)
p = put_str(p, end, " ");
// put_hex prefixes "0x", which is 14 wasted bytes across seven frames.
{
const char *d = "0123456789abcdef";
uint64 off = (uint64)pcs[i] - (uint64)KERNBASE;
for (int k = 7; k >= 0; k--)
*p++ = d[(off >> (k * 4)) & 0xF];
*p = '\0';
}
}
return (int)(p - buf);
}
// One static symbol rather than a local: struct qr is about 3.4 KB and the
// panic path runs on whatever stack the fault left behind, which may not have
// that much room. It is only ever touched here, after everything else has
// stopped.
static struct qr panic_qr;
// Where the card and its caption are, once drawn: the text column must stop
// short of it on the rows they share. The reason and the call chain used to
// run straight underneath it.
static int qr_x0 = -1, qr_y0, qr_y1;
static void
text_at(int x, int y, const char *s, uint32 color)
{
char buf[128];
int n = 0, cap = sizeof(buf) - 1;
if (qr_x0 >= 0 && y + gfx_char_h() > qr_y0 && y < qr_y1) {
int fit = (qr_x0 - gfx_char_w() - x) / gfx_char_w();
if (fit < cap)
cap = fit < 0 ? 0 : fit;
}
while (s[n] && n < cap) {
buf[n] = s[n];
n++;
}
buf[n] = 0;
gfx_text_at(x, y, buf, color);
}
// Returns the width used, so the text column knows what it may not overlap.
static int
draw_qr_panel(int sw, int sh, int ch, char *reason, addr_t *pcs, int npcs)
{
char payload[QR_CARD_BYTES];
int n = build_qr_payload(payload, sizeof(payload), reason, pcs, npcs);
if (n <= 0 || qr_encode(&panic_qr, (unsigned char *)payload, n) != 0)
return 0;
// Four modules of quiet zone, which the standard requires and which decoders
// genuinely need -- without it many will not find the symbol at all.
const int quiet = 4;
int cells = panic_qr.size + 2 * quiet;
// Scale to about a quarter of the display, then clamp. The lower bound is
// what a phone can still resolve from a photograph of a monitor; the upper
// bound stops the thing dominating a screen whose actual job is the text.
int scale = (sw / 4) / cells;
if (scale < 3) scale = 3;
if (scale > 8) scale = 8;
int card = cells * scale;
int x0 = sw - card - sw / 24;
int y0 = sh / 8;
gfx_fill_rect(x0, y0, card, card, PSOGM_CARD);
for (int y = 0; y < panic_qr.size; y++)
for (int x = 0; x < panic_qr.size; x++)
if (panic_qr.mod[y][x])
gfx_fill_rect(x0 + (x + quiet) * scale, y0 + (y + quiet) * scale,
scale, scale, PSOGM_INK);
gfx_text_at(x0, y0 + card + ch, "Scan for a text copy", PSOGM_DIM);
gfx_text_at(x0, y0 + card + ch * 2 + 2, "of this report.", PSOGM_DIM);
qr_x0 = x0;
qr_y0 = y0;
qr_y1 = y0 + card + ch * 3 + 2;
return card + sw / 24;
}
// The frame pointers, one per line, as one string per row.
static void
trace_line(char *buf, int cap, int i, addr_t pc)
{
char *e = buf + cap;
char *p = put_str(buf, e, " #");
p = put_dec(p, e, (uint64)i);
p = put_str(p, e, i < 10 ? " " : " ");
p = put_hex(p, e, (uint64)pc, 16);
// And the name, when the kernel is carrying a symbol table. This is the
// difference between a crash screen you photograph and then decode against
// a disassembly, and one you can read. See kernel/core/ksym.c.
if (ksym_ready()) {
char name[64];
if (ksym_lookup((uint64)pc, 0)) {
ksym_fmt(name, sizeof(name), (uint64)pc);
p = put_str(p, e, " ");
put_str(p, e, name);
}
}
}
/* ------------------------------------------------------------------ */
// Where paint() put its closing lines, for GMMOS to write into.
static int foot_x, foot_y, foot_step;
static void
paint(char *reason, addr_t *pcs, int npcs)
{
int sw, sh;
gfx_dims(&sw, &sh);
int ch = gfx_char_h();
int cw = gfx_char_w();
// The whole display, one colour. An earlier version dimmed the previous
// screen and floated a panel over it, which looked good and was wrong: the
// thing is called a Purple Screen, and half a screen of somebody's abandoned
// work behind the report is a distraction at the worst possible moment.
gfx_clear(PSOGM_BG);
// Glyphs are drawn with their background filled, so the console's cream has
// to become the background or every line of text arrives in its own box.
gfx_set_bg(PSOGM_BG);
// The symbol goes first, because it decides how much width the text has. It
// is drawn before anything can fail: if the encoder refuses the payload the
// screen simply has no card and the report is unchanged.
draw_qr_panel(sw, sh, ch, reason, pcs, npcs);
int left = sw / 8;
if (left > 110) left = 110;
int y = sh / 8;
// Everything above the three closing lines has to fit above them. At
// 800x600 with a 21-pixel cell it did not: the call chain ran down into the
// footer, the footer was drawn over it, and what showed was the tail of a
// trace line poking out past the end of a sentence ("asks more.0x5d") with
// the tops of "Call chain" underlining the line above. So the layout is
// measured first -- the text, and room for two frames of the chain -- and
// what gives way is the margin, then the logo. Never the report.
int foot = sh - ch * 5 - 4 - ch / 2;
int body = (ch + ch / 2) + ch * 2 + ch + nfields * (ch + 2) +
ch + (ch + 2) + ch * 2;
int logo = 1;
if (y + LOGO_H + ch * 2 + body > foot)
y = ch;
if (y + LOGO_H + ch * 2 + body > foot)
logo = 0;
// Shift, the system's own mark, from the same include/gfx/logo.h the loader
// splash and the boot console draw. It replaces Guru, a chamfered face that
// lived only here and was drawn from primitives a few lines up this file.
//
// Guru was an *icon*: a pictogram denoting one state, with a face. The
// argument for it was identity -- ":(" is recognisably Microsoft's, and a
// crash screen is the single screenshot most likely to leave the machine, so
// it is the worst place to be wearing somebody else's face. That argument
// holds. It just does not follow that the answer is a second mark. The logo
// already signs the system, it is already the thing on the splash the
// machine started with, and a reader who has seen it twice before the crash
// recognises it faster than a face they have never seen.
//
// A fixed 128 px rather than scaled: the mask is 128x128 and there is no
// resampler here. The face was scalable and that bought nothing -- it was
// drawn at one size.
if (logo) {
gfx_draw_logo(left, y, PSOGM_FG, PSOGM_BG);
y += LOGO_H + ch * 2;
}
// Left-aligned, not centred. Centred text reads as a poster; this is a
// report, and a report is read down the left edge.
text_at(left, y, "SchOS ran into a problem and needs to restart.",
PSOGM_FG);
y += ch + ch / 2;
text_at(left, y, "Nothing on your disks was touched by this.",
PSOGM_DIM);
// A rule in the boot splash's accent, the same width as the one under the
// wordmark there, tying this screen to the one the machine started with.
//
// Equal space above and below on purpose: at any less it sits close enough to
// the line above to read as an underline of that sentence rather than as a
// divider before the facts.
y += ch * 2;
gfx_fill_rect(left, y, 120, 2, SCH_ACCENT);
y += ch;
// Facts: quiet labels down the left, bright values in a second column.
int vx = left + 14 * cw;
for (int i = 0; i < nfields; i++) {
text_at(left, y, fields[i].label, PSOGM_DIM);
text_at(vx, y, fields[i].value, PSOGM_FG);
y += ch + 2;
}
y += ch;
text_at(left, y, "Call chain", PSOGM_DIM);
y += ch + 2;
for (int i = 0; i < npcs && pcs[i] && y + ch <= foot; i++) {
char line[48];
trace_line(line, sizeof(line), i, pcs[i]);
text_at(vx - 3 * cw, y, line, PSOGM_FG);
y += ch;
}
// Three closing lines, bottom left. The first used to be an addr2line
// command, which the call chain above no longer needs -- it prints names.
//
// What replaces it is the thing nobody would guess: **this screen is no
// longer the only copy.** The log survives into the next boot (see
// include/core/klog.h), so the right instruction on a crash screen is now
// "restart and read it" rather than "photograph this".
//
// Kept under about 45 characters each: the QR card is on the right, and the
// 62-character command line this replaces used to run underneath it.
//
// Four lines now, the second and fourth GMMOS's: the second says whether
// and when the machine powers off, the fourth which keys do what. Drawn
// here as they stand before GMMOS has looked for a keyboard.
foot_x = left;
foot_y = sh - ch * 5 - 4;
foot_step = ch + 4;
text_at(left, foot_y,
"This report is in /proc/kmsg after a restart.", PSOGM_DIM);
text_at(left, foot_y + foot_step,
boot_verbose ? "schdbg, or Ctrl-G on the console, asks more."
: "The machine powers itself off in 3 minutes.",
PSOGM_DIM);
text_at(left, foot_y + foot_step * 2,
"PSOGM -- Purple Screen of Guru Meditation", PSOGM_DIM);
gfx_flush_all();
}
// Same report, down the serial line. This is the copy that survives: it is what
// bootcheck reads, and the only one that exists when the framebuffer does not.
static void
report_serial(char *reason, addr_t *pcs, int npcs)
{
cprintf("\n");
cprintf(" :(\n\n");
cprintf(" SchOS ran into a problem and needs to restart.\n");
cprintf(" PSOGM -- Purple Screen of Guru Meditation\n\n");
for (int i = 0; i < nfields; i++) {
// Pad the label out so the values line up, without a printf width
// specifier -- cprintf has no such thing.
int n = 0;
while (fields[i].label[n]) n++;
cprintf(" %s", fields[i].label);
for (int pad = n; pad < 14; pad++)
cprintf(" ");
cprintf("%s\n", fields[i].value);
}
cprintf("\n Call chain\n");
for (int i = 0; i < npcs && pcs[i]; i++) {
char name[64];
ksym_fmt(name, sizeof(name), (uint64)pcs[i]);
cprintf(" #%d %p %s\n", i, pcs[i], name);
}
if (!ksym_ready())
cprintf("\n Resolve with: addr2line -e build/kernel/kernel <address>\n");
else
cprintf("\n For a line number: addr2line -e build/kernel/kernel <address>\n");
// Where to go next. Worth printing even though the machine is about to stop,
// because this text is also what ends up in the log -- and the log is the
// thing being pointed at.
cprintf(" This report is in /proc/kmsg after a restart.\n");
cprintf(" Then: schdbg whose <addr> -- who owns a page\n");
cprintf(" schdbg watch <addr> -- who writes to one\n");
cprintf(" Ctrl-G on the console -- every view, no shell needed\n");
cprintf(" Reason: %s\n", reason);
cprintf(" System halted.\n");
}
static void panic_blink(void) __attribute__((noreturn));
// The report as it was drawn, so GMMOS can draw it again on the way back
// from one of its own screens.
static char *saved_reason;
static addr_t saved_pcs[10];
void
psogm_repaint(void)
{
paint(saved_reason, saved_pcs, 10);
}
void
psogm_footer(int *x, int *y, int *step)
{
*x = foot_x;
*y = foot_y;
*step = foot_step;
}
volatile int panic_stop;
static volatile int panic_owner = -1;
__attribute__((noreturn))
void
panic(char *s)
{
addr_t pcs[10];
cli();
// One processor reports; any other that panics meanwhile -- the same broken
// state tends to reach several at once -- stops where it is. The owner may
// panic again while drawing, and that nested report is let through.
int me = cpunum();
int owner = __sync_val_compare_and_swap(&panic_owner, -1, me);
if (owner != -1 && owner != me)
for (;;) {
cli();
hlt();
}
// And the rest are stopped outright, see trap().
panic_stop = 1;
lapic_nmi_others();
// Stop every other path from writing to the console. consputc checks this
// and spins, which is what keeps another CPU from painting over the report
// while it is being drawn.
cons_panic_mode();
for (int i = 0; i < 10; i++)
pcs[i] = 0;
getcallerpcs(&s, pcs);
// Counted for the owner alone. A second panic means the first report, or
// GMMOS running after it, is what broke: report that one too, and then only
// blink. Re-entering GMMOS would most likely crash it the same way again.
static int depth;
depth++;
gather(s, pcs, 10);
report_serial(s, pcs, 10);
saved_reason = s;
for (int i = 0; i < 10; i++)
saved_pcs[i] = pcs[i];
if (gfx_ready()) {
paint(s, pcs, 10);
// And on every other display the machine has, including the ones the
// kernel handed to a process. Deliberately after the console's copy: a
// fault in there must not cost the screen that is already right.
display_panic_present();
}
panicked = 1;
if (gfx_ready() && depth == 1)
gmmos_run(s, pcs, 10);
panic_blink();
}
// The last thing a dead kernel can say, on a machine that cannot say anything.
//
// A laptop has no serial port. If the panic screen cannot be drawn -- no
// framebuffer yet, the wrong framebuffer, a display the firmware handed over
// in a state we did not expect -- then everything above this has produced a
// black screen and a machine that appears to have hung. "Frozen" and "panicked
// with a full report nobody can see" look identical, and they call for
// completely different next steps.
//
// So: blink the keyboard lights. It is the one output every PC has had since
// before any of this existed, it needs no driver, no interrupt and no memory,
// and it works when the graphics do not. Linux does the same thing and for the
// same reason.
//
// Every wait here is bounded. This runs after `panicked` is set, so there is
// nothing left to protect and nothing left to return to -- but a machine that
// hangs inside its own death rattle tells you even less than one that stops
// quietly, so no loop in here can be entered that cannot be left.
static void
kbd_out(uchar byte)
{
// Wait for the controller to have room, then write. Bit 1 of the status
// port is "input buffer full"; writing while it is set loses the byte.
for (int i = 0; i < 100000; i++) {
if (!(inb(KBSTATP) & 0x02)) {
outb(KBDATAP, byte);
return;
}
microdelay(10);
}
}
static void
kbd_drain_ack(void)
{
// The keyboard answers 0xFA. Reading it matters less than not leaving it in
// the buffer for the next command to mistake for its own answer.
for (int i = 0; i < 100000; i++) {
if (inb(KBSTATP) & KBS_DIB) {
(void)inb(KBDATAP);
return;
}
microdelay(10);
}
}
// Scroll, Num and Caps, which is bits 0, 1 and 2 of the byte after 0xED.
#define LED_ALL 0x07
// How long the report stays up before the machine powers itself off, on an
// ordinary boot. A crashed laptop otherwise sits with its fans up -- the other
// processors are still running -- until somebody holds the power button, and
// three minutes is long enough to photograph the screen or scan the QR code.
// A verbose boot is somebody debugging on purpose, and keeps it up for ever.
#define PANIC_OFF_MS (3 * 60 * 1000)
static void
panic_blink(void)
{
int on = 0;
uint elapsed = 0;
for (;;) {
if (!boot_verbose && elapsed >= PANIC_OFF_MS) {
acpi_poweroff_now();
elapsed = 0; // still on: blink on, and try again later
}
kbd_out(0xED);
kbd_drain_ack();
kbd_out(on ? LED_ALL : 0x00);
kbd_drain_ack();
on = !on;
// About a third of a second either way. Slow enough to read across a room
// as deliberate rather than as a stuck light, fast enough that nobody
// watching a machine they think has hung will have walked away first.
//
// microdelay spins on the TSC, which needs no timer, no interrupt and no
// calibration that may not have happened yet -- all three of which a
// kernel that died during boot may be missing.
for (int i = 0; i < 300; i++)
microdelay(1000);
elapsed += 300;
}
}