smp: a verbose boot triple-faulted, and vgpud drew a test card
Two reports, and the first one was not an F8 bug at all.
**Choosing "Boot SchOS (verbose)" appeared to do nothing.** The menu worked,
the flag crossed, and the kernel really did boot verbose -- and then the
machine *reset* partway through and the second boot was quiet. So what anybody
ended up looking at was an ordinary boot, which reads as "verbose is broken".
PROBE before startothers, ncpu=2 <- verbose boot
SchOS UEFI loader <- the firmware, again
PROBE before startothers, ncpu=2 <- quiet boot
PROBE after startothers
mpmain() opened with
kinfo("cpu%d: starting\n", cpunum());
enable_sse();
idtinit();
and the order is the bug. enable_sse() clears CR0.EM; until it runs, *any* SSE
instruction on that 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 that CPU's IDT; until it runs there is nowhere for a fault to go. So the
#UD is not a panic -- it is a double fault with no handler, then a triple
fault, then the firmware comes back up. A triple fault prints nothing, which is
why there was no last line to go on.
**Why only on a verbose boot**, which is what disguised it as an F8 problem:
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 that same line through the
TrueType rasteriser, and that is where the SSE lands.
Setup first, announcement second. The general rule, stated where the next
person will meet it: a processor announces that it is up after it is able to
run, not before.
Verbose now boots once and puts the whole log on the screen -- `verbose boot:`
lines, `cpu1: starting`, the mount, `schinit: ready`, the jobs starting -- which
is what F8 was for and what a machine with no serial port needs.
**And vgpud drew a test card.** Three colour bands, four gradients, a
checkerboard, and the driver's own 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
restarted instance to look different from one that never stopped. Both
milestones closed a while ago and the test card stayed, so a second display
showed something garish with nothing to explain it.
It draws the Shift mark on SCH_BG now, from the same include/logo.h the loader
splash and the kernel console use. That answers the same question a black
screen cannot -- something is driving this device -- as the system rather than
as a test rig.
Also: vgpud's "no control socket" line is vputs, not puts. It is progress, not
news, and it was printing on the console of every quiet boot.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
3f13989883
commit
bcd4d4e2bd
4 changed files with 96 additions and 42 deletions
|
|
@ -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,48 +557,49 @@ 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 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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue