panic: the crash screen reaches every display, not just the console's
"it also doesn't show crash screen on virtio gpu or just other gpu than default
vga" -- true, and on the virtio-gpu it was worse than nothing. The head kept
showing the console from a second before the panic: one screen saying the
kernel is dead, the other showing a shell prompt.
panic() painted into the console framebuffer and stopped there. Two cases were
missing, and they are not the same problem.
**The kernel still drives it.** display_acquire() hands back a buffer; the
report is copied in and presented. Ordinary, and a no-op when the display
class's buffer *is* the console's, so a one-screen machine does not do the work
twice.
**A process drives it.** The hard one, and the one hybrid.md owed an answer
for. The kernel let go of the device deliberately, and the process holding it
will never run again -- there is no scheduling after a panic -- so there is
nobody left to ask. It is not a race that can be won by being careful.
So the userspace driver says where it is scanning out *in advance*:
PCIIOC_PANICFB, registered by vgpud after bring-up and after every modeset. On
a panic the kernel paints the report into that memory and asks the kernel
driver the device was taken from to put it on screen one last time --
`panic_present` in struct pci_driver, which virtio-gpu implements by resetting
the device out from under a driver that is still nominally running. That is
safe here and nowhere else, because nothing is going to run again.
The panic path allocates nothing. It cannot: a corrupted heap is one of the
ways a machine gets here, and an allocator that deadlocks takes the crash
screen *and* the serial log's last chance to be read. So the driver keeps its
BAR mappings across the handover, rebuilds one queue from 20 KB of static
memory, and bounds every spin -- including the reset acknowledge, which is an
unbounded `while` in probe() and correct there.
**The registration is checked like the promise it is.** The range must lie
inside a block that process got from PCIIOC_DMA on that device. Without that,
a claimed device would be a way to nominate any physical address in the machine
-- page tables, kernel text -- to be overwritten with four megabytes of crash
report at the one moment nothing is left to notice. That is a worse bug than
the one this fixes. It goes away with the claim and with the process.
What it buys is worth stating plainly: a device can be evicted to userspace
without the kernel losing its ability to tell you the machine died. The
alternative was a driver boundary that is also a blind spot.
Verified both ways under QEMU with two heads, `echo c > /proc/sysrq`, and a
screendump of each over QMP:
vgpud holding the device both heads byte-identical, serial says
"The report is on the virtio-gpu display too."
kernel holding it same, via display_acquire/display_present
(/proc/pci shows 1af4:1050 [virtio-gpu])
Both supported configurations: one boot each, no traps, no panics, socktest and
memfdtest green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
38ea83bf05
commit
e72205cdc0
10 changed files with 567 additions and 9 deletions
|
|
@ -337,6 +337,40 @@ when the crash happens before there is a framebuffer at all.
|
|||
Once painted, `panicked` is set and `consputc` spins rather than writing, so
|
||||
nothing can scribble over the report.
|
||||
|
||||
### It reaches every display, including the ones the kernel gave away
|
||||
|
||||
The report used to go to the console framebuffer and stop there. On a machine
|
||||
with two heads that meant one screen saying the kernel was dead and another
|
||||
showing a shell prompt from a few seconds earlier — the most misleading thing
|
||||
a screen can do.
|
||||
|
||||
`display_panic_present()` in `kernel/dev/device.c` walks the rest of them, and
|
||||
there are two cases:
|
||||
|
||||
**The kernel still drives it.** `display_acquire()` hands back a buffer; the
|
||||
report is copied in and presented. This is the ordinary path, and it does
|
||||
nothing at all when the display class's buffer *is* the console's — a machine
|
||||
with one screen does not get the work done twice.
|
||||
|
||||
**A process drives it.** Harder, and the one worth having. The kernel let go of
|
||||
the device deliberately, and the process holding it will never run again —
|
||||
there is no scheduling after a panic. So `vgpud` registers its scanout in
|
||||
advance (`PCIIOC_PANICFB`), the kernel paints the report into that memory, and
|
||||
the driver the device was taken from is asked to put it on screen one last
|
||||
time: `panic_present` in `struct pci_driver`.
|
||||
|
||||
For virtio-gpu that means resetting the device out from under a driver that is
|
||||
still nominally running — safe here and nowhere else, because nothing is going
|
||||
to run again — and rebuilding one queue from 20 KB of static memory. No
|
||||
allocation, no locks, no sleeping, and every spin bounded: a crash screen that
|
||||
hangs has taken away the serial log's last chance to be read.
|
||||
|
||||
Both cases say so on the serial line:
|
||||
|
||||
```
|
||||
The report is on the virtio-gpu display too.
|
||||
```
|
||||
|
||||
### It is no longer the only copy
|
||||
|
||||
This screen used to be the only artefact of a panic — this file said so, and so
|
||||
|
|
|
|||
|
|
@ -241,6 +241,9 @@ VBE, and the driver restarts and takes the device back. During the gap the VBE
|
|||
head carries a live console and the virtio-gpu head still shows the dead
|
||||
driver's last frame, which is exactly what a device nobody is driving does.
|
||||
|
||||
(That last sentence is still true of a *crashed driver*, and no longer true of
|
||||
a crashed *kernel* -- see "The clause nobody wrote down" below.)
|
||||
|
||||
Reattachment is a Schcom milestone, not a hybrid one, and it is recorded that
|
||||
way rather than counted here.
|
||||
|
||||
|
|
@ -362,6 +365,45 @@ the region back.
|
|||
|
||||
---
|
||||
|
||||
## The clause nobody wrote down: a panic has to reach the screen
|
||||
|
||||
Handing a device away costs something that was not on the list. The kernel
|
||||
panics, paints the report into the console framebuffer, and halts — and the
|
||||
display it gave to a process keeps showing whatever that process last put
|
||||
there. On a machine with two heads, one says the kernel is dead and the other
|
||||
shows a shell prompt.
|
||||
|
||||
It is not a race that can be won by being careful. There is no scheduling after
|
||||
a panic, so the process holding the device is never going to run again, and the
|
||||
kernel let go of the device deliberately.
|
||||
|
||||
**So the userspace driver says in advance where it is scanning out**, with
|
||||
`PCIIOC_PANICFB` (see `include/pciuser.h`), and the kernel does the rest:
|
||||
|
||||
1. paints the report into that memory,
|
||||
2. asks the *kernel* driver the device was taken from to put it on screen —
|
||||
`panic_present` in `struct pci_driver`,
|
||||
3. which resets the device and rebuilds one queue from **static** memory: no
|
||||
allocation, no locks, no sleeping, because a corrupted heap is one of the
|
||||
ways a machine gets here in the first place.
|
||||
|
||||
The registration is checked like the promise it is: the range must lie inside a
|
||||
block that process got from `PCIIOC_DMA` on that device. Without that it would
|
||||
be a way to nominate any physical address in the machine to be overwritten at
|
||||
panic, which is a worse bug than the one it fixes.
|
||||
|
||||
What this buys is a property worth stating plainly: **a device can be evicted
|
||||
to userspace without the kernel losing its ability to tell you the machine
|
||||
died.** That is the difference between a driver boundary and a blind spot, and
|
||||
it is one more thing the in-kernel arrangement got for free and the hybrid one
|
||||
has to earn.
|
||||
|
||||
It costs 20 KB of static memory in the virtio-gpu driver, paid whether or not
|
||||
the machine ever panics. Allocating it on demand would be cheaper and would not
|
||||
work.
|
||||
|
||||
---
|
||||
|
||||
## The honest limitation
|
||||
|
||||
> **There is no IOMMU, so a userspace driver is isolated against its own bugs and
|
||||
|
|
|
|||
Loading…
Reference in a new issue