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
|
|
@ -802,6 +802,37 @@ mirror_pass(void)
|
||||||
present_rect(mirror_x, mirror_y + first, mirror_w, last - first + 1);
|
present_rect(mirror_x, mirror_y + first, mirror_w, last - first + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tell the kernel where the scanout is, so it can put a crash screen there.
|
||||||
|
//
|
||||||
|
// The one thing a userspace display driver cannot do for itself. When the
|
||||||
|
// machine panics this process does not get another turn -- there is no
|
||||||
|
// scheduling after a panic -- so the head it owns would sit there showing a
|
||||||
|
// shell prompt from before the crash, which is the most misleading thing a
|
||||||
|
// screen can do.
|
||||||
|
//
|
||||||
|
// So the kernel is told, in advance, where to paint: it writes the report into
|
||||||
|
// this framebuffer and asks the kernel driver it took this device from to
|
||||||
|
// reset it and flush one frame. See PCIIOC_PANICFB in include/pciuser.h.
|
||||||
|
//
|
||||||
|
// Called again after every modeset, because the size is half of what was
|
||||||
|
// registered. Failing is not fatal to anything: it costs the crash screen on
|
||||||
|
// this head and nothing else, which is exactly what the situation was before
|
||||||
|
// the kernel could do this at all.
|
||||||
|
static void
|
||||||
|
register_panic_fb(void)
|
||||||
|
{
|
||||||
|
struct pciuser_panicfb pfb;
|
||||||
|
|
||||||
|
pfb.index = (unsigned int)index_;
|
||||||
|
pfb.paddr = fb_paddr;
|
||||||
|
pfb.width = (unsigned int)width;
|
||||||
|
pfb.height = (unsigned int)height;
|
||||||
|
pfb.stride = (unsigned int)width * 4;
|
||||||
|
if (ioctl(pcifd, PCIIOC_PANICFB, &pfb) < 0)
|
||||||
|
vputs("vgpud: the kernel would not take a panic framebuffer; "
|
||||||
|
"a crash will leave this head as it is\n");
|
||||||
|
}
|
||||||
|
|
||||||
// --- the modeset --------------------------------------------------------------
|
// --- the modeset --------------------------------------------------------------
|
||||||
//
|
//
|
||||||
// docs/plan/hybrid.md milestone H3: *"a modeset API, which is the thing an
|
// docs/plan/hybrid.md milestone H3: *"a modeset API, which is the thing an
|
||||||
|
|
@ -981,6 +1012,7 @@ serve(int fd)
|
||||||
// The ground first, then the console re-laid onto it: the
|
// The ground first, then the console re-laid onto it: the
|
||||||
// mirror's placement is derived from both sizes, and one
|
// mirror's placement is derived from both sizes, and one
|
||||||
// of them just changed.
|
// of them just changed.
|
||||||
|
register_panic_fb();
|
||||||
draw_mark();
|
draw_mark();
|
||||||
mirror_geometry();
|
mirror_geometry();
|
||||||
mirror_pass();
|
mirror_pass();
|
||||||
|
|
@ -1103,6 +1135,7 @@ main(int argc, char *argv[])
|
||||||
int mirroring = console_open() == 0;
|
int mirroring = console_open() == 0;
|
||||||
|
|
||||||
bring_up();
|
bring_up();
|
||||||
|
register_panic_fb();
|
||||||
draw_mark();
|
draw_mark();
|
||||||
if (mirroring)
|
if (mirroring)
|
||||||
mirror_geometry();
|
mirror_geometry();
|
||||||
|
|
|
||||||
|
|
@ -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
|
Once painted, `panicked` is set and `consputc` spins rather than writing, so
|
||||||
nothing can scribble over the report.
|
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
|
### It is no longer the only copy
|
||||||
|
|
||||||
This screen used to be the only artefact of a panic — this file said so, and so
|
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
|
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.
|
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
|
Reattachment is a Schcom milestone, not a hybrid one, and it is recorded that
|
||||||
way rather than counted here.
|
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
|
## The honest limitation
|
||||||
|
|
||||||
> **There is no IOMMU, so a userspace driver is isolated against its own bugs and
|
> **There is no IOMMU, so a userspace driver is isolated against its own bugs and
|
||||||
|
|
|
||||||
|
|
@ -610,6 +610,14 @@ void fbclose(struct inode*);
|
||||||
// Called from sys_mmap: a mapping of /dev/fb0 is a licence to draw with no
|
// Called from sys_mmap: a mapping of /dev/fb0 is a licence to draw with no
|
||||||
// further syscalls, so it is the last the kernel hears about it.
|
// further syscalls, so it is the last the kernel hears about it.
|
||||||
void fb_mapped(void);
|
void fb_mapped(void);
|
||||||
|
|
||||||
|
// The crash screen, on displays other than the console's. kernel/dev/device.c.
|
||||||
|
void display_panic_present(void);
|
||||||
|
// Where a userspace display driver said it is scanning out, and which kernel
|
||||||
|
// driver it took the device from. kernel/dev/pciuser.c.
|
||||||
|
int pciuser_panic_surface(int idx, uint64 *paddr, int *w, int *h,
|
||||||
|
uint32 *stride,
|
||||||
|
const struct pci_driver **drv);
|
||||||
int fbread(struct inode*, uint64, char*, int);
|
int fbread(struct inode*, uint64, char*, int);
|
||||||
int fbwrite(struct inode*, uint64, char*, int);
|
int fbwrite(struct inode*, uint64, char*, int);
|
||||||
int fb_ioctl(int request, addr_t arg);
|
int fb_ioctl(int request, addr_t arg);
|
||||||
|
|
|
||||||
|
|
@ -132,4 +132,21 @@ struct pci_driver {
|
||||||
// implement it keeps its device: refusing is always safe, and for anything
|
// implement it keeps its device: refusing is always safe, and for anything
|
||||||
// on the path to a shell it is also correct.
|
// on the path to a shell it is also correct.
|
||||||
int (*release)(struct pci_dev *);
|
int (*release)(struct pci_dev *);
|
||||||
|
// Put one last frame on a device this driver was told to let go of.
|
||||||
|
//
|
||||||
|
// Called from panic(), and only from there: interrupts are off, no other
|
||||||
|
// CPU is coming back, and the process that was driving the device will not
|
||||||
|
// run again. The framebuffer is one the userspace driver registered with
|
||||||
|
// PCIIOC_PANICFB and the kernel has already painted the report into; all
|
||||||
|
// that is left is to make the device show it.
|
||||||
|
//
|
||||||
|
// The contract is what panic context allows: **no allocation, no locks, no
|
||||||
|
// sleeping.** Whatever memory this needs has to already exist, because the
|
||||||
|
// allocator may be exactly what was being held when the machine died. A
|
||||||
|
// driver that cannot meet that leaves this null, and the screen it handed
|
||||||
|
// over stays frozen -- which is what happens today and is not a
|
||||||
|
// regression.
|
||||||
|
//
|
||||||
|
// Returns 0 if something was put on screen.
|
||||||
|
int (*panic_present)(struct pci_dev *, uint64 fb_paddr, int w, int h);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,7 @@ struct pciuser_dma {
|
||||||
#define PCIIOC_RELEASE 0x9003 // in: int index
|
#define PCIIOC_RELEASE 0x9003 // in: int index
|
||||||
#define PCIIOC_DMA 0x9004 // struct pciuser_dma
|
#define PCIIOC_DMA 0x9004 // struct pciuser_dma
|
||||||
#define PCIIOC_CFG 0x9006 // struct pciuser_cfg -- read config space
|
#define PCIIOC_CFG 0x9006 // struct pciuser_cfg -- read config space
|
||||||
|
#define PCIIOC_PANICFB 0x9007 // struct pciuser_panicfb -- see below
|
||||||
#define PCIIOC_IRQ 0x9005 // in: int index -- deliver this device's
|
#define PCIIOC_IRQ 0x9005 // in: int index -- deliver this device's
|
||||||
// interrupts to this process
|
// interrupts to this process
|
||||||
|
|
||||||
|
|
@ -123,6 +124,52 @@ struct pciuser_dma {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// --- the crash screen, on a display the kernel no longer drives ------------
|
||||||
|
//
|
||||||
|
// A userspace display driver leaves a hole the kernel cannot see around: when
|
||||||
|
// the machine panics, the screen this process is scanning out is frozen at
|
||||||
|
// whatever it last put there. The machine is dead and its second monitor is
|
||||||
|
// still showing a shell prompt.
|
||||||
|
//
|
||||||
|
// The kernel cannot fix that by itself. It let go of the device deliberately
|
||||||
|
// (PCIIOC_CLAIM), and the process that took it will not run again -- panic
|
||||||
|
// means no scheduling, so there is nobody left to ask.
|
||||||
|
//
|
||||||
|
// So a userspace display driver tells the kernel, in advance, where its
|
||||||
|
// scanout lives:
|
||||||
|
//
|
||||||
|
// struct pciuser_panicfb pfb;
|
||||||
|
// pfb.index = index; // the device, already claimed
|
||||||
|
// pfb.paddr = dma.paddr; // the framebuffer, from PCIIOC_DMA
|
||||||
|
// pfb.width = 1280;
|
||||||
|
// pfb.height = 800;
|
||||||
|
// pfb.stride = 1280 * 4; // bytes per row; XRGB8888 only
|
||||||
|
// ioctl(fd, PCIIOC_PANICFB, &pfb);
|
||||||
|
//
|
||||||
|
// and on a panic the kernel paints the crash report into that memory and asks
|
||||||
|
// the driver it handed the device to -- its *kernel* driver, the one that was
|
||||||
|
// told to let go -- to put it on screen one last time. That driver resets the
|
||||||
|
// device and brings up a minimal queue from static memory: no allocation, no
|
||||||
|
// locks, nothing that can fail differently depending on what the machine was
|
||||||
|
// doing when it died.
|
||||||
|
//
|
||||||
|
// Registering is a promise about memory, so it is checked like one: the range
|
||||||
|
// must lie inside a block this process got from PCIIOC_DMA on this device.
|
||||||
|
// Without that check 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.
|
||||||
|
//
|
||||||
|
// A zero paddr unregisters. The registration goes away with the claim and with
|
||||||
|
// the process, so a driver that exits leaves nothing behind.
|
||||||
|
struct pciuser_panicfb {
|
||||||
|
unsigned int index; // in: the claimed device
|
||||||
|
unsigned long long paddr; // in: physical address of the scanout, 0 to
|
||||||
|
// unregister
|
||||||
|
unsigned int width; // in: pixels
|
||||||
|
unsigned int height;
|
||||||
|
unsigned int stride; // in: bytes per row
|
||||||
|
};
|
||||||
|
|
||||||
// Registers are reached with mmap() on this device, where the *offset is the
|
// Registers are reached with mmap() on this device, where the *offset is the
|
||||||
// physical address of the BAR*:
|
// physical address of the BAR*:
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -525,8 +525,13 @@ panic(char *s)
|
||||||
gather(s, pcs, 10);
|
gather(s, pcs, 10);
|
||||||
report_serial(s, pcs, 10);
|
report_serial(s, pcs, 10);
|
||||||
|
|
||||||
if (gfx_ready())
|
if (gfx_ready()) {
|
||||||
paint(s, pcs, 10);
|
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;
|
panicked = 1;
|
||||||
for (;;)
|
for (;;)
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,10 @@
|
||||||
#include "netdev.h"
|
#include "netdev.h"
|
||||||
#include "theme.h"
|
#include "theme.h"
|
||||||
#include "text.h"
|
#include "text.h"
|
||||||
|
#include "gfx.h"
|
||||||
|
#include "pci.h"
|
||||||
|
#include "mmu.h"
|
||||||
|
#include "memlayout.h"
|
||||||
|
|
||||||
static struct device devices[NDEVICE];
|
static struct device devices[NDEVICE];
|
||||||
static int ndevices;
|
static int ndevices;
|
||||||
|
|
@ -186,6 +190,104 @@ display_wait_vblank(void)
|
||||||
((struct display_ops *)d->ops)->wait_vblank(d);
|
((struct display_ops *)d->ops)->wait_vblank(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- the crash screen, on every display the machine has ---------------------
|
||||||
|
//
|
||||||
|
// panic() paints the report into the console framebuffer, which is one screen
|
||||||
|
// on a machine that may have several. The others showed whatever they were
|
||||||
|
// showing when the machine died -- a shell prompt, most misleadingly -- and
|
||||||
|
// this is what walks them and puts the report there instead.
|
||||||
|
//
|
||||||
|
// Called from panic(), with interrupts off and everything the rules of that
|
||||||
|
// place imply: no allocation, no locks, no sleeping. It is deliberately the
|
||||||
|
// last thing the paint path does, so that a fault in here cannot cost the
|
||||||
|
// screen that was already drawn correctly.
|
||||||
|
//
|
||||||
|
// Two cases, and they are not the same:
|
||||||
|
//
|
||||||
|
// the kernel still drives it display_acquire() hands back a buffer; copy
|
||||||
|
// and present, the ordinary path
|
||||||
|
// userspace drives it the kernel let go of the device and the
|
||||||
|
// process holding it will not run again, so
|
||||||
|
// the driver it was taken from is asked to
|
||||||
|
// take it back for one frame
|
||||||
|
//
|
||||||
|
// The second is the one worth having. It is also the one that only works
|
||||||
|
// because the userspace driver said in advance where it was scanning out --
|
||||||
|
// see PCIIOC_PANICFB in include/pciuser.h.
|
||||||
|
|
||||||
|
// Copy the console framebuffer into a surface, with the same rules vgpud's
|
||||||
|
// mirror uses: the overlap, centred if the surface is larger, top-left if it
|
||||||
|
// is smaller. Both sides are XRGB8888; anything else is refused by whoever
|
||||||
|
// registered it.
|
||||||
|
static void
|
||||||
|
panic_copy_console(volatile uchar *dst, int dw, int dh, uint32 dstride)
|
||||||
|
{
|
||||||
|
const volatile uchar *src = gfx_fb_ptr();
|
||||||
|
int sw = (int)gfx_width(), sh = (int)gfx_height();
|
||||||
|
uint32 sstride = gfx_fb_stride();
|
||||||
|
int cw = sw < dw ? sw : dw;
|
||||||
|
int ch = sh < dh ? sh : dh;
|
||||||
|
int x0 = (dw - cw) / 2, y0 = (dh - ch) / 2;
|
||||||
|
|
||||||
|
if (!src || gfx_fb_bpp() != 32)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int y = 0; y < ch; y++) {
|
||||||
|
const volatile uint32 *s = (const volatile uint32 *)(src + (uint64)y * sstride);
|
||||||
|
volatile uint32 *d = (volatile uint32 *)(dst + (uint64)(y0 + y) * dstride);
|
||||||
|
for (int x = 0; x < cw; x++)
|
||||||
|
d[x0 + x] = s[x];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
display_panic_present(void)
|
||||||
|
{
|
||||||
|
if (!gfx_ready())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// The kernel's own display, when it is not the console's. On a machine where
|
||||||
|
// the console is the VBE framebuffer and a virtio-gpu is the display class,
|
||||||
|
// these are two different screens and only one of them has the report.
|
||||||
|
{
|
||||||
|
struct display_mode m;
|
||||||
|
uint32 stride = 0;
|
||||||
|
struct device *d = display_get();
|
||||||
|
volatile uchar *buf = display_acquire(&stride);
|
||||||
|
|
||||||
|
if (buf && display_mode(&m) == 0 && m.bpp == 32 &&
|
||||||
|
(uint64)buf != (uint64)gfx_fb_ptr()) {
|
||||||
|
panic_copy_console(buf, m.width, m.height, stride ? stride : m.stride);
|
||||||
|
display_present(0, 0, m.width, m.height);
|
||||||
|
cprintf(" The report is on the %s display too.\n",
|
||||||
|
d ? d->name : "second");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// And every display the kernel handed to a process. The report is painted
|
||||||
|
// into memory that process registered, then its old kernel driver is asked
|
||||||
|
// to put it on screen -- which means resetting the device out from under a
|
||||||
|
// driver that is still nominally running. That is safe here and nowhere
|
||||||
|
// else: nothing is going to run again.
|
||||||
|
for (int i = 0; i < pci_count() && i < NPCI; i++) {
|
||||||
|
const struct pci_driver *drv = 0;
|
||||||
|
uint64 paddr = 0;
|
||||||
|
uint32 stride = 0;
|
||||||
|
int w = 0, h = 0;
|
||||||
|
|
||||||
|
if (!pciuser_panic_surface(i, &paddr, &w, &h, &stride, &drv))
|
||||||
|
continue;
|
||||||
|
// A packed surface, because that is what the device is about to be told it
|
||||||
|
// is. A padded one would shear, which is worse than not appearing.
|
||||||
|
if (stride != (uint32)w * 4)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
panic_copy_console((volatile uchar *)P2V(paddr), w, h, stride);
|
||||||
|
if (drv->panic_present((struct pci_dev *)pci_get(i), paddr, w, h) == 0)
|
||||||
|
cprintf(" The report is on the %s display too.\n", drv->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Drive the whole display path once and report what happened: find the class,
|
// Drive the whole display path once and report what happened: find the class,
|
||||||
// read the mode, acquire a back buffer, draw into RAM, present it.
|
// read the mode, acquire a back buffer, draw into RAM, present it.
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,24 @@ int mappages(pml4e_t *pgdir, void *va, uint64 size, uint64 pa, int perm);
|
||||||
// renumbers it afterwards.
|
// renumbers it afterwards.
|
||||||
static struct proc *owner[NPCI];
|
static struct proc *owner[NPCI];
|
||||||
|
|
||||||
|
// What used to drive each device, for the one thing a kernel driver is still
|
||||||
|
// needed for after it has been told to let go: the crash screen.
|
||||||
|
//
|
||||||
|
// d->drv is cleared when the claim succeeds, deliberately -- a driver that has
|
||||||
|
// handed a device over must not be reachable through the device it no longer
|
||||||
|
// drives. This is the exception, and it is narrow: nothing calls through here
|
||||||
|
// except panic(), the device is reset first, and by then the process that was
|
||||||
|
// driving it is not going to run again.
|
||||||
|
static const struct pci_driver *released_drv[NPCI];
|
||||||
|
|
||||||
|
// Where a userspace display driver is scanning out, so panic() can put the
|
||||||
|
// report there. See PCIIOC_PANICFB in include/pciuser.h.
|
||||||
|
struct panicfb {
|
||||||
|
uint64 paddr;
|
||||||
|
uint32 width, height, stride;
|
||||||
|
};
|
||||||
|
static struct panicfb panicfb[NPCI];
|
||||||
|
|
||||||
static struct spinlock pciuser_lock;
|
static struct spinlock pciuser_lock;
|
||||||
|
|
||||||
// Interrupt delivery, one device per process.
|
// Interrupt delivery, one device per process.
|
||||||
|
|
@ -339,9 +357,11 @@ pciuser_release_all(struct proc *p)
|
||||||
if (mine)
|
if (mine)
|
||||||
owner[i] = 0;
|
owner[i] = 0;
|
||||||
release(&pciuser_lock);
|
release(&pciuser_lock);
|
||||||
if (mine)
|
if (mine) {
|
||||||
|
panicfb[i].paddr = 0;
|
||||||
pciuser_stop(i);
|
pciuser_stop(i);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// And its ports. The bitmap itself is closed by the next switchuvm() on each
|
// And its ports. The bitmap itself is closed by the next switchuvm() on each
|
||||||
// CPU; see ioperm_revoke_all().
|
// CPU; see ioperm_revoke_all().
|
||||||
|
|
@ -469,6 +489,7 @@ do_claim(uint64 argp)
|
||||||
return -EBUSY;
|
return -EBUSY;
|
||||||
if (d->drv->release((struct pci_dev *)d) != 0)
|
if (d->drv->release((struct pci_dev *)d) != 0)
|
||||||
return -EBUSY;
|
return -EBUSY;
|
||||||
|
released_drv[idx] = d->drv;
|
||||||
((struct pci_dev *)d)->driver = 0;
|
((struct pci_dev *)d)->driver = 0;
|
||||||
((struct pci_dev *)d)->drv = 0;
|
((struct pci_dev *)d)->drv = 0;
|
||||||
}
|
}
|
||||||
|
|
@ -554,8 +575,10 @@ do_release(uint64 argp)
|
||||||
|
|
||||||
acquire(&pciuser_lock);
|
acquire(&pciuser_lock);
|
||||||
int mine = (owner[idx] == myproc());
|
int mine = (owner[idx] == myproc());
|
||||||
if (mine)
|
if (mine) {
|
||||||
owner[idx] = 0;
|
owner[idx] = 0;
|
||||||
|
panicfb[idx].paddr = 0;
|
||||||
|
}
|
||||||
release(&pciuser_lock);
|
release(&pciuser_lock);
|
||||||
|
|
||||||
// Same halt as the death path, for the same reason. A driver that releases
|
// Same halt as the death path, for the same reason. A driver that releases
|
||||||
|
|
@ -685,6 +708,84 @@ do_dma(uint64 argp)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register where this process is scanning out, so panic() can draw there.
|
||||||
|
//
|
||||||
|
// The check is the whole of the security of this call: the range has to lie
|
||||||
|
// inside a block this process got from PCIIOC_DMA. Without it, a claimed
|
||||||
|
// device would be a way to nominate any physical address in the machine as the
|
||||||
|
// place the kernel writes four megabytes of crash report -- page tables, the
|
||||||
|
// kernel's own text -- at the one moment nothing is left to notice.
|
||||||
|
static int
|
||||||
|
do_panicfb(uint64 argp)
|
||||||
|
{
|
||||||
|
struct pciuser_panicfb req;
|
||||||
|
struct proc *p = myproc();
|
||||||
|
|
||||||
|
if (copy_from_user(&req, argp, sizeof(req)) < 0)
|
||||||
|
return -EFAULT;
|
||||||
|
if ((int)req.index >= pci_count() || (int)req.index >= NPCI)
|
||||||
|
return -ENODEV;
|
||||||
|
if (owner[req.index] != p)
|
||||||
|
return -EACCES; // claim it first
|
||||||
|
|
||||||
|
if (req.paddr == 0) { // unregister
|
||||||
|
acquire(&pciuser_lock);
|
||||||
|
panicfb[req.index].paddr = 0;
|
||||||
|
release(&pciuser_lock);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.width == 0 || req.height == 0 || req.stride < req.width * 4)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
uint64 need = (uint64)req.height * req.stride;
|
||||||
|
uint64 end = req.paddr + need;
|
||||||
|
if (end < req.paddr)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
int ok = 0;
|
||||||
|
acquire(&pciuser_lock);
|
||||||
|
for (int i = 0; i < PCIUSER_MAXDMA; i++) {
|
||||||
|
uint64 base, lim;
|
||||||
|
if (dmablk[i].proc != p || !dmablk[i].kva)
|
||||||
|
continue;
|
||||||
|
base = dma_phys(dmablk[i].kva);
|
||||||
|
lim = base + dmablk[i].size;
|
||||||
|
if (req.paddr >= base && end <= lim) { ok = 1; break; }
|
||||||
|
}
|
||||||
|
if (ok) {
|
||||||
|
panicfb[req.index].paddr = req.paddr;
|
||||||
|
panicfb[req.index].width = req.width;
|
||||||
|
panicfb[req.index].height = req.height;
|
||||||
|
panicfb[req.index].stride = req.stride;
|
||||||
|
}
|
||||||
|
release(&pciuser_lock);
|
||||||
|
|
||||||
|
// Not EINVAL: the argument is fine, the memory is not this process's to
|
||||||
|
// nominate. EACCES is the difference between "you asked wrong" and "no".
|
||||||
|
return ok ? 0 : -EACCES;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The panic path's view of the above. Called with interrupts off, from a
|
||||||
|
// machine that is already dead: no locks, because whoever held one is not
|
||||||
|
// going to release it, and a crash screen that deadlocks is worse than no
|
||||||
|
// crash screen.
|
||||||
|
int
|
||||||
|
pciuser_panic_surface(int idx, uint64 *paddr, int *w, int *h, uint32 *stride,
|
||||||
|
const struct pci_driver **drv)
|
||||||
|
{
|
||||||
|
if (idx < 0 || idx >= NPCI || owner[idx] == 0 || panicfb[idx].paddr == 0)
|
||||||
|
return 0;
|
||||||
|
if (!released_drv[idx] || !released_drv[idx]->panic_present)
|
||||||
|
return 0;
|
||||||
|
*paddr = panicfb[idx].paddr;
|
||||||
|
*w = (int)panicfb[idx].width;
|
||||||
|
*h = (int)panicfb[idx].height;
|
||||||
|
*stride = panicfb[idx].stride;
|
||||||
|
*drv = released_drv[idx];
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Hand a device's interrupts to the calling process.
|
// Hand a device's interrupts to the calling process.
|
||||||
static int
|
static int
|
||||||
do_irq(uint64 argp)
|
do_irq(uint64 argp)
|
||||||
|
|
@ -795,6 +896,7 @@ pciuser_ioctl(uint64 request, uint64 argp)
|
||||||
case PCIIOC_DMA: return do_dma(argp);
|
case PCIIOC_DMA: return do_dma(argp);
|
||||||
case PCIIOC_IRQ: return do_irq(argp);
|
case PCIIOC_IRQ: return do_irq(argp);
|
||||||
case PCIIOC_CFG: return do_cfg(argp);
|
case PCIIOC_CFG: return do_cfg(argp);
|
||||||
|
case PCIIOC_PANICFB: return do_panicfb(argp);
|
||||||
default: return -ENOTTY;
|
default: return -ENOTTY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@ find_cfg(struct pci_dev *d, int want, uint32 *out_mult)
|
||||||
// --- virtqueue -------------------------------------------------------------
|
// --- virtqueue -------------------------------------------------------------
|
||||||
|
|
||||||
static int
|
static int
|
||||||
queue_setup(struct vgpu *g)
|
queue_setup_mem(struct vgpu *g, uint8 *mem)
|
||||||
{
|
{
|
||||||
w16(g->common, VCOMMON_Q_SELECT, CTRL_QUEUE);
|
w16(g->common, VCOMMON_Q_SELECT, CTRL_QUEUE);
|
||||||
|
|
||||||
|
|
@ -179,8 +179,6 @@ queue_setup(struct vgpu *g)
|
||||||
uint32 avail_sz = 6 + 2 * qsize;
|
uint32 avail_sz = 6 + 2 * qsize;
|
||||||
uint32 used_sz = 6 + 8 * qsize;
|
uint32 used_sz = 6 + 8 * qsize;
|
||||||
|
|
||||||
uint8 *mem = dma_alloc(4096 * 3, 4096);
|
|
||||||
g->ring = mem;
|
|
||||||
if (!mem)
|
if (!mem)
|
||||||
return -1;
|
return -1;
|
||||||
memset(mem, 0, 4096 * 3);
|
memset(mem, 0, 4096 * 3);
|
||||||
|
|
@ -197,6 +195,13 @@ queue_setup(struct vgpu *g)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
queue_setup(struct vgpu *g)
|
||||||
|
{
|
||||||
|
g->ring = dma_alloc(4096 * 3, 4096);
|
||||||
|
return queue_setup_mem(g, g->ring);
|
||||||
|
}
|
||||||
|
|
||||||
// Submit one command and wait for the reply.
|
// Submit one command and wait for the reply.
|
||||||
//
|
//
|
||||||
// Two descriptors: the request, which the device reads, and the response, which
|
// Two descriptors: the request, which the device reads, and the response, which
|
||||||
|
|
@ -359,10 +364,8 @@ vgpu_acquire(struct device *d, uint32 *stride_out)
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
vgpu_present(struct device *d, int x, int y, int w, int h)
|
present_g(struct vgpu *g, int x, int y, int w, int h)
|
||||||
{
|
{
|
||||||
struct vgpu *g = vgpu_of(d);
|
|
||||||
|
|
||||||
if (!g || !g->ready)
|
if (!g || !g->ready)
|
||||||
return;
|
return;
|
||||||
if (x < 0) { w += x; x = 0; }
|
if (x < 0) { w += x; x = 0; }
|
||||||
|
|
@ -390,6 +393,12 @@ vgpu_present(struct device *d, int x, int y, int w, int h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
vgpu_present(struct device *d, int x, int y, int w, int h)
|
||||||
|
{
|
||||||
|
present_g(vgpu_of(d), x, y, w, h);
|
||||||
|
}
|
||||||
|
|
||||||
// No wait_vblank. The device presents whole rectangles atomically, so there is
|
// No wait_vblank. The device presents whole rectangles atomically, so there is
|
||||||
// nothing to synchronise against -- and a null op is more honest than one that
|
// nothing to synchronise against -- and a null op is more honest than one that
|
||||||
// spins and achieves nothing. display_wait_vblank() handles the absence.
|
// spins and achieves nothing. display_wait_vblank() handles the absence.
|
||||||
|
|
@ -512,6 +521,14 @@ vgpu_probe(struct pci_dev *d)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Registers kept from before a handover, per device. Filled in by
|
||||||
|
// vgpu_release() and read only by vgpu_panic_present(); see both.
|
||||||
|
static struct {
|
||||||
|
struct pci_dev *pdev;
|
||||||
|
volatile uint8 *common, *notify;
|
||||||
|
uint32 notify_mult;
|
||||||
|
} handed_over[VGPU_MAX];
|
||||||
|
|
||||||
// Hand the device to a userspace driver.
|
// Hand the device to a userspace driver.
|
||||||
//
|
//
|
||||||
// docs/plan/hybrid.md's first eviction, and the reason it is this device: the
|
// docs/plan/hybrid.md's first eviction, and the reason it is this device: the
|
||||||
|
|
@ -576,12 +593,162 @@ vgpu_release(struct pci_dev *d)
|
||||||
if (g->cmd) { dma_free(g->cmd, 4096); g->cmd = 0; }
|
if (g->cmd) { dma_free(g->cmd, 4096); g->cmd = 0; }
|
||||||
if (g->resp) { dma_free(g->resp, 4096); g->resp = 0; }
|
if (g->resp) { dma_free(g->resp, 4096); g->resp = 0; }
|
||||||
|
|
||||||
|
// Keep the addresses, and only the addresses.
|
||||||
|
//
|
||||||
|
// This is what makes the crash screen possible on a device the kernel no
|
||||||
|
// longer drives: panic() cannot ioremap -- that allocates, and the allocator
|
||||||
|
// is exactly the sort of thing that may have been what died -- so the
|
||||||
|
// mappings made at probe are kept, and the panic path finds the device
|
||||||
|
// through them rather than through anything it has to build. Registers, not
|
||||||
|
// state: there is no queue here, no framebuffer, nothing the userspace
|
||||||
|
// driver could be confused by.
|
||||||
|
for (int i = 0; i < VGPU_MAX; i++)
|
||||||
|
if (handed_over[i].pdev == d || handed_over[i].pdev == 0) {
|
||||||
|
handed_over[i].pdev = d;
|
||||||
|
handed_over[i].common = g->common;
|
||||||
|
handed_over[i].notify = g->notify;
|
||||||
|
handed_over[i].notify_mult = g->notify_mult;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
g->ready = 0;
|
g->ready = 0;
|
||||||
g->pdev = 0;
|
g->pdev = 0;
|
||||||
kinfo("virtio-gpu: released to userspace; screen is the VBE framebuffer\n");
|
kinfo("virtio-gpu: released to userspace; screen is the VBE framebuffer\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- the crash screen, on a device this driver was told to let go ----------
|
||||||
|
//
|
||||||
|
// docs/plan/hybrid.md's bargain has a clause nobody wrote down: a device the
|
||||||
|
// kernel hands away is a device the kernel cannot use to tell you the machine
|
||||||
|
// died. The second monitor sat there showing a shell prompt from before the
|
||||||
|
// panic, which is the most misleading thing a screen can do.
|
||||||
|
//
|
||||||
|
// It does not have to be that way, and the reason is the whole argument for
|
||||||
|
// virtio: presenting is an explicit operation over a queue, and a queue can be
|
||||||
|
// rebuilt from nothing in a few hundred instructions. So on a panic the kernel
|
||||||
|
// takes the device back -- resets it, builds one queue out of static memory,
|
||||||
|
// points a scanout at the framebuffer the userspace driver registered, and
|
||||||
|
// flushes it once.
|
||||||
|
//
|
||||||
|
// The 20 KB below is the price, and it is paid whether or not the machine ever
|
||||||
|
// panics. Allocating it on demand would be cheaper and would not work: panic
|
||||||
|
// context has no allocator it can trust, because a corrupted heap is one of
|
||||||
|
// the things that gets a machine here.
|
||||||
|
static uint8 panic_pool[4096 * 5] __attribute__((aligned(4096)));
|
||||||
|
|
||||||
|
// Every spin in here is bounded. The unbounded ones in vgpu_probe() are
|
||||||
|
// correct where they are -- a device that never answers during boot is a
|
||||||
|
// machine that cannot work -- but a crash screen that hangs instead of
|
||||||
|
// appearing has taken away the serial log's last chance to be read.
|
||||||
|
static int
|
||||||
|
panic_wait_status(volatile uint8 *common, uint8 want)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 1000000; i++)
|
||||||
|
if (r8(common, VCOMMON_STATUS) == want)
|
||||||
|
return 0;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
vgpu_panic_present(struct pci_dev *d, uint64 fb_paddr, int w, int h)
|
||||||
|
{
|
||||||
|
static struct vgpu pg; // static: panic context allocates nothing
|
||||||
|
struct vgpu *g = &pg;
|
||||||
|
int slot = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < VGPU_MAX; i++)
|
||||||
|
if (handed_over[i].pdev == d) { slot = i; break; }
|
||||||
|
if (slot < 0 || !handed_over[slot].common || !handed_over[slot].notify)
|
||||||
|
return -1;
|
||||||
|
if (w <= 0 || h <= 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
memset(g, 0, sizeof(*g));
|
||||||
|
g->common = handed_over[slot].common;
|
||||||
|
g->notify = handed_over[slot].notify;
|
||||||
|
g->notify_mult = handed_over[slot].notify_mult;
|
||||||
|
g->width = w;
|
||||||
|
g->height = h;
|
||||||
|
|
||||||
|
// Memory space and bus mastering. The userspace driver has them on, but
|
||||||
|
// asserting them costs one config write and covers the case where it died
|
||||||
|
// and pciuser_stop() turned them off -- which is a likely way to arrive
|
||||||
|
// here.
|
||||||
|
pci_enable(d, 0x0006);
|
||||||
|
|
||||||
|
// Reset. Everything the userspace driver set up -- its queue, its resources,
|
||||||
|
// its scanout -- goes away here, which is the point: what follows must not
|
||||||
|
// depend on any of it, because a driver that crashed may have left it in any
|
||||||
|
// state at all.
|
||||||
|
w8(g->common, VCOMMON_STATUS, 0);
|
||||||
|
if (panic_wait_status(g->common, 0) != 0)
|
||||||
|
return -1;
|
||||||
|
w8(g->common, VCOMMON_STATUS, VSTATUS_ACK);
|
||||||
|
w8(g->common, VCOMMON_STATUS, VSTATUS_ACK | VSTATUS_DRIVER);
|
||||||
|
|
||||||
|
w32(g->common, VCOMMON_DFEAT_SEL, 1);
|
||||||
|
if (!(r32(g->common, VCOMMON_DFEAT) & (1u << (VIRTIO_F_VERSION_1 - 32))))
|
||||||
|
return -1;
|
||||||
|
w32(g->common, VCOMMON_GFEAT_SEL, 0);
|
||||||
|
w32(g->common, VCOMMON_GFEAT, 0);
|
||||||
|
w32(g->common, VCOMMON_GFEAT_SEL, 1);
|
||||||
|
w32(g->common, VCOMMON_GFEAT, 1u << (VIRTIO_F_VERSION_1 - 32));
|
||||||
|
w8(g->common, VCOMMON_STATUS,
|
||||||
|
VSTATUS_ACK | VSTATUS_DRIVER | VSTATUS_FEATURES_OK);
|
||||||
|
if (!(r8(g->common, VCOMMON_STATUS) & VSTATUS_FEATURES_OK))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
g->cmd = panic_pool + 4096 * 3;
|
||||||
|
g->resp = panic_pool + 4096 * 4;
|
||||||
|
if (queue_setup_mem(g, panic_pool) != 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
w8(g->common, VCOMMON_STATUS,
|
||||||
|
VSTATUS_ACK | VSTATUS_DRIVER | VSTATUS_FEATURES_OK | VSTATUS_DRIVER_OK);
|
||||||
|
|
||||||
|
// A resource the size of the report, backed by the memory the userspace
|
||||||
|
// driver registered and the panic path has already painted into.
|
||||||
|
{
|
||||||
|
struct virtio_gpu_resource_create_2d *c = g->cmd;
|
||||||
|
hdr_init(c, VIRTIO_GPU_CMD_RESOURCE_CREATE_2D);
|
||||||
|
c->resource_id = 1;
|
||||||
|
c->format = VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM;
|
||||||
|
c->width = (uint32)w;
|
||||||
|
c->height = (uint32)h;
|
||||||
|
if (cmd(g, sizeof(*c), sizeof(struct virtio_gpu_ctrl_hdr)) != 0)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
struct virtio_gpu_resource_attach_backing *a = g->cmd;
|
||||||
|
hdr_init(a, VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING);
|
||||||
|
a->resource_id = 1;
|
||||||
|
a->nr_entries = 1;
|
||||||
|
struct virtio_gpu_mem_entry *e = (struct virtio_gpu_mem_entry *)(a + 1);
|
||||||
|
e->addr = fb_paddr;
|
||||||
|
e->length = (uint32)w * (uint32)h * 4;
|
||||||
|
e->padding = 0;
|
||||||
|
if (cmd(g, sizeof(*a) + sizeof(*e),
|
||||||
|
sizeof(struct virtio_gpu_ctrl_hdr)) != 0)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
struct virtio_gpu_set_scanout *so = g->cmd;
|
||||||
|
hdr_init(so, VIRTIO_GPU_CMD_SET_SCANOUT);
|
||||||
|
so->r.x = so->r.y = 0;
|
||||||
|
so->r.width = (uint32)w;
|
||||||
|
so->r.height = (uint32)h;
|
||||||
|
so->scanout_id = 0;
|
||||||
|
so->resource_id = 1;
|
||||||
|
if (cmd(g, sizeof(*so), sizeof(struct virtio_gpu_ctrl_hdr)) != 0)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
g->ready = 1; // so vgpu_present will talk to the device
|
||||||
|
present_g(g, 0, 0, w, h);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static const struct pci_match virtio_gpu_match[] = {
|
static const struct pci_match virtio_gpu_match[] = {
|
||||||
{ VIRTIO_VENDOR, VIRTIO_DEV_GPU, 0, 0 },
|
{ VIRTIO_VENDOR, VIRTIO_DEV_GPU, 0, 0 },
|
||||||
{ 0, 0, 0, 0 },
|
{ 0, 0, 0, 0 },
|
||||||
|
|
@ -607,4 +774,5 @@ const struct pci_driver virtio_gpu_driver = {
|
||||||
.probe = vgpu_probe,
|
.probe = vgpu_probe,
|
||||||
.attached = vgpu_attached,
|
.attached = vgpu_attached,
|
||||||
.release = vgpu_release,
|
.release = vgpu_release,
|
||||||
|
.panic_present = vgpu_panic_present,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue