diff --git a/cmd/vgpud/main.c b/cmd/vgpud/main.c index f5822c0..565dc09 100644 --- a/cmd/vgpud/main.c +++ b/cmd/vgpud/main.c @@ -28,6 +28,8 @@ #include "virtio.h" #include "schinit.h" #include "socket.h" +#include "theme.h" // SCH_BG, SCH_ACCENT -- one palette, see docs/branding +#include "logo.h" // the mark, the same bytes the loader and console draw // --- small output, since lib/ has no printf --------------------------------- @@ -555,49 +557,50 @@ present(void) // --- something to look at --------------------------------------------------- -// Not a compositor. A pattern that is obviously deliberate, so a screenshot -// answers "did a userspace process put this here" without ambiguity -- a black -// screen and a working screen look identical if all you draw is black. +// The mark, on the theme ground. +// +// This used to be a test card: three colour bands, four gradients, a +// checkerboard, and this instance's pid in binary along the bottom edge. That +// was scaffolding for hybrid.md's H2 and H3 -- a screenshot had to answer "did +// a *userspace process* put this here", and "did the driver come back" needed +// a second run to look different from a first that never stopped. Both +// milestones are closed, and what was left was a garish test pattern on a +// second display with no explanation of itself. +// +// The mark answers the same question a black screen cannot -- something is +// driving this device -- and answers it as the system rather than as a test +// rig. Same include/logo.h the loader splash and the kernel console draw, so +// there is one mark and one set of bytes. static void -draw_test_pattern(void) +draw_mark(void) { volatile unsigned int *fb = (volatile unsigned int *)fb_uaddr; + int x0 = (width - LOGO_W) / 2; + int y0 = (height - LOGO_H) / 2; - // A tally of this instance's pid along the bottom edge, because the thing - // H2 has to show is that the driver *came back* -- and a second run that - // redraws an identical picture is indistinguishable from a first run that - // never stopped. One block per bit, low bit at the left. - int mypid = getpid(); + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + fb[y * width + x] = SCH_BG; - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - unsigned int c; - int band = (y * 8) / height; - switch (band) { - case 0: c = 0x0C0E13; break; /* SCH_BG */ - case 1: c = 0x7C5CFF; break; /* SCH_ACCENT */ - case 2: c = 0x4B1D9A; break; /* PSOGM purple */ - case 3: c = (unsigned int)((x * 255 / width) * 0x010101); break; - case 4: c = (unsigned int)(x * 255 / width) << 16; break; - case 5: c = (unsigned int)(x * 255 / width) << 8; break; - case 6: c = (unsigned int)(x * 255 / width); break; - default: - // A checkerboard, so a stride bug is visible as a shear rather - // than as a plausible-looking gradient. - c = (((x / 16) + (y / 16)) & 1) ? 0xE6E8EC : 0x0C0E13; - break; - } - fb[y * width + x] = c; + if (x0 < 0 || y0 < 0) + return; // a display smaller than the mark: leave it + + for (int dy = 0; dy < LOGO_H; dy++) { + for (int dx = 0; dx < LOGO_W; dx++) { + unsigned ink = LOGO_INK(dy * LOGO_W + dx); + if (!ink) + continue; // the mask is a square; the mark is not + // Coverage, not a stencil: the mark has soft edges, and stamping + // them as solid would show every one of them as a staircase. + unsigned r = (((SCH_ACCENT >> 16) & 0xFF) * ink + + ((SCH_BG >> 16) & 0xFF) * (255 - ink)) / 255; + unsigned g = (((SCH_ACCENT >> 8) & 0xFF) * ink + + ((SCH_BG >> 8) & 0xFF) * (255 - ink)) / 255; + unsigned b = (((SCH_ACCENT ) & 0xFF) * ink + + ((SCH_BG ) & 0xFF) * (255 - ink)) / 255; + fb[(y0 + dy) * width + (x0 + dx)] = (r << 16) | (g << 8) | b; } } - - for (int b = 0; b < 12; b++) { - unsigned int c = (mypid >> b) & 1 ? 0x4ADE80 : 0x12151C; /* SCH_OK */ - for (int y = height - 24; y < height - 8; y++) - for (int x = 16 + b * 24; x < 16 + b * 24 + 16; x++) - if (y >= 0 && x < width) - fb[y * width + x] = c; - } } // --- the modeset -------------------------------------------------------------- @@ -776,7 +779,7 @@ serve(int fd) } else if (r < 0) { reply(fd, "the device refused the modeset\n"); } else { - draw_test_pattern(); + draw_mark(); present(); reply_dim(fd, width, height, " ok\n"); puts("vgpud: mode is now "); putn(width); puts("x"); @@ -875,7 +878,7 @@ main(int argc, char *argv[]) die("no modern virtio capabilities -- not a 1.0 device"); bring_up(); - draw_test_pattern(); + draw_mark(); present(); vputs("vgpud: pid "); @@ -912,7 +915,7 @@ main(int argc, char *argv[]) // restarts this process, and the device has to come back with it. int srv = control_socket(); if (srv < 0) { - puts("vgpud: no control socket; the mode is fixed at " + vputs("vgpud: no control socket; the mode is fixed at " "whatever the device reported\n"); for (;;) sleep(100); diff --git a/docs/architecture/boot.md b/docs/architecture/boot.md index 63f9854..8d493c6 100644 --- a/docs/architecture/boot.md +++ b/docs/architecture/boot.md @@ -144,6 +144,32 @@ Three details survive that fix: ## 4. SMP +### A processor announces itself after it can run, not before + +`mpmain()` used to open with `kinfo("cpu%d: starting")` and *then* call +`enable_sse()` and `idtinit()`. On a verbose boot with more than one CPU that +reset the machine — silently, with no panic and no last line, because the +failure is a triple fault and a triple fault prints nothing. + +Two of those three lines have to come first, for different reasons: + +- **`enable_sse()` clears `CR0.EM`.** Until it runs, *any* SSE instruction on + this CPU faults, and the kernel is not built with `-mno-sse`, so the compiler + emits them freely for struct copies and inline `memset`. +- **`idtinit()` loads this CPU's IDT.** Until it runs there is nowhere for a + fault to go — so the `#UD` above is not a panic, it is a double fault with no + handler, then a triple fault, then the firmware comes back. + +**Why only on a verbose boot**, which is what made it look like an F8 bug +rather than an SMP one: `kinfo()` is log-only when the boot is quiet, so the +work is a few bytes into a ring and no XMM is involved. A verbose boot renders +the same line through the TrueType rasteriser, and that is where the SSE lands. + +The symptom was that choosing "Boot SchOS (verbose)" appeared to do nothing: +the machine really did reboot, and the second boot was quiet, so what you ended +up looking at was an ordinary boot. + + `startothers()` wakes each application processor through the local APIC, pointing it at a trampoline copied to `0x7000`. Each AP runs `mpenter()` → `mpmain()`, reports `cpu%d: starting`, and enters the scheduler. diff --git a/docs/branding/README.md b/docs/branding/README.md index 286691b..7fcdb0e 100644 --- a/docs/branding/README.md +++ b/docs/branding/README.md @@ -156,6 +156,7 @@ If either is edited, edit both. |---|---| | Loader splash | Shift, from `include/logo.h`, shaded per row by `metal()` in `boot/common/ui.c` — silver, not accent. | | Kernel console / early boot | Shift, same mask, composited flat in a theme colour by `gfx_draw_logo`. | +| A display with no compositor | Shift again, drawn by `vgpud` on `SCH_BG`. It replaced a test card — colour bands, gradients, a checkerboard and the driver's pid in binary — which was scaffolding for [../plan/hybrid.md](../plan/hybrid.md)'s H2 and H3 and outlived them. The mark answers the same question a black screen cannot, *something is driving this device*, and answers it as the system rather than as a test rig. | | PSOGM | Shift, the same mask again, by `gfx_draw_logo()` from `kernel/core/panic.c`. At its native 128 px — there is no resampler in the crash path, and the mark was only ever drawn at one size there anyway. | | Serial crash report | `:(`, as text. The mark cannot be drawn in ASCII, and a mood marker in a log is not a mark. | | SchFS | **Nowhere yet.** `uberblock.svg` has no consumer; it is waiting for `projects/schfs-linux/` to have a README worth heading. | diff --git a/kernel/core/main.c b/kernel/core/main.c index 7c51830..9494343 100644 --- a/kernel/core/main.c +++ b/kernel/core/main.c @@ -284,10 +284,34 @@ enable_sse(void) static void mpmain(void) { - kinfo("cpu%d: starting\n", cpunum()); + // **Set the processor up before saying anything.** This used to announce + // itself first, and that reset the machine on any verbose boot with more + // than one CPU -- silently, with no panic and no last line, because the + // failure is a triple fault and a triple fault prints nothing. + // + // Two of the three lines below have to come first, and for different + // reasons: + // + // enable_sse() clears CR0.EM. Until it runs, *any* SSE instruction on + // this CPU faults -- and the kernel is not built with -mno-sse, so the + // compiler emits them freely for struct copies and inline memset. A + // quiet boot got away with it because kinfo() is log-only then and the + // work is a few bytes into a ring; a verbose boot renders the line + // through the TrueType rasteriser, which is where the XMM lands. + // + // idtinit() loads this CPU's IDT. Until it runs there is nowhere for a + // fault to go, so the #UD above is not a panic -- it is a double fault + // with no handler, then a triple fault, then the firmware comes back up. + // That is why the only symptom was "F8 verbose reboots into a normal + // boot": the machine really did reboot, and the second one was quiet. + // + // The general rule this is an instance of: a processor announces that it + // is up after it is able to run, not before. enable_sse(); // allow user XMM/SSE instructions idtinit(); // load idt register syscallinit(); // syscall set up + + kinfo("cpu%d: starting\n", cpunum()); xchg(&mycpu()->started, 1); // tell startothers() we're up scheduler(); // start running processes }