gfx: the console and the crash screen stop drawing on a driver by name
Same disease as the network stack, one subsystem over. kernel/dev/vga.c
was three things wearing one name: the owner of the framebuffer, a
bitmap drawing library, and the text console. Only the first is a
driver, and because they shared a file they shared a prefix -- so
panic.c drew the crash screen with vga_fill_rect, console.c printed with
vga_putc, /dev/fb0 handed out vga_fb_phys, and every one of those files
named a display driver it had no business knowing about.
Split three ways:
- kernel/drivers/gpu/vga/vga_fb.c owns the VBE framebuffer -- the mode
the loader programmed and handed over, the aperture, and the display
class vtable. It is a driver and now lives with the driver that
finds the card on the bus.
- kernel/dev/gfx.c draws: pixels, shapes, glyphs, the cursor, scroll,
the ANSI erase operations. It names no driver. The framebuffer
arrives through gfx_attach_scanout(), so a driver publishes what it
owns and this file draws on it without knowing what it is.
- include/gfx.h is the interface, and says why it is deliberately not
the display class: that one is about frames -- acquire, present,
vblank -- and the crash screen has to work when nothing else does.
main() no longer calls a display driver. platform_register_devices()
takes the framebuffer address and brings up whatever owns it, which is
where driver names belong; kernel/drivers/registry.c is the file whose
entire job is naming every driver in the kernel.
virtio-gpu got the e1000 treatment as well: instances in an array, the
instance hung on dev->priv by an `attached` hook, and every op reading
it rather than a file-static. It refused the second adapter outright
before, which is not hypothetical -- integrated plus discrete is an
ordinary laptop. The VGA driver deliberately has no such hook, and says
so: there is one framebuffer the loader programmed however many
VGA-class controllers the bus turns up, and a second one publishing the
same scanout is the truth rather than a bug.
Private headers moved next to their drivers: include/virtio.h and
include/xhci.h each had exactly one includer. include/ is for headers
the rest of the kernel needs.
Deleted rather than moved: consoleinit() programmed the sixteen ANSI
colours into the VGA DAC and disabled text-mode blink, by writing ports
0x3C0-0x3C9 from the console. The comment three lines above the colour
table already said this does nothing in a 32bpp linear mode -- there is
no palette to index -- so it was both dead and in the wrong file.
Verified, and this is a refactor so the evidence is that nothing moved:
- bootcheck -Fb -Net: 59 [ok], no failures. That covers the console
through every command, /dev/fb0's geometry, writing pixels through
write() and mapping the framebuffer through mmap.
- make crashshot: the crash screen renders exactly as before --
shapes, glyphs, logo and QR code -- which is the whole of gfx.c
exercised at once, in a panic, with the scanout attached by a driver.
- make schcom-test: five pixel assertions from the host oracle still
pass, so the display class and the compositor above it are unmoved.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
20cc673a41
commit
817d666068
22 changed files with 942 additions and 752 deletions
161
kernel/drivers/bus/xhci/xhci.h
Normal file
161
kernel/drivers/bus/xhci/xhci.h
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
#pragma once
|
||||
|
||||
// eXtensible Host Controller Interface -- the register and data-structure
|
||||
// definitions from the xHCI 1.2 specification.
|
||||
//
|
||||
// Why xHCI and not UHCI/OHCI/EHCI: xHCI is the only host controller interface
|
||||
// that speaks every USB speed, so one driver covers USB 1.1 through 3.x. The
|
||||
// older controllers would each need their own, and a machine with all of them
|
||||
// needs the companion-controller handover dance on top. There is no hardware
|
||||
// worth supporting that has no xHCI.
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Capability registers -- at the start of BAR0, read-only
|
||||
// ---------------------------------------------------------------------------
|
||||
#define XHCI_CAPLENGTH 0x00 // byte: where the operational regs begin
|
||||
#define XHCI_HCIVERSION 0x02 // word
|
||||
#define XHCI_HCSPARAMS1 0x04
|
||||
#define XHCI_HCSPARAMS2 0x08
|
||||
#define XHCI_HCSPARAMS3 0x0C
|
||||
#define XHCI_HCCPARAMS1 0x10
|
||||
#define XHCI_DBOFF 0x14 // doorbell array offset from BAR0
|
||||
#define XHCI_RTSOFF 0x18 // runtime register offset from BAR0
|
||||
|
||||
#define HCS1_MAXSLOTS(x) ((x) & 0xFF)
|
||||
#define HCS1_MAXINTRS(x) (((x) >> 8) & 0x7FF)
|
||||
#define HCS1_MAXPORTS(x) (((x) >> 24) & 0xFF)
|
||||
|
||||
// Scratchpad buffer count is split across two fields, high part first.
|
||||
#define HCS2_SPB_HI(x) (((x) >> 21) & 0x1F)
|
||||
#define HCS2_SPB_LO(x) (((x) >> 27) & 0x1F)
|
||||
|
||||
#define HCC1_AC64 (1 << 0) // 64-bit addressing
|
||||
#define HCC1_CSZ (1 << 2) // contexts are 64 bytes, not 32
|
||||
#define HCC1_XECP(x) (((x) >> 16) & 0xFFFF) // extended caps, in dwords
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Operational registers -- at BAR0 + CAPLENGTH
|
||||
// ---------------------------------------------------------------------------
|
||||
#define XHCI_USBCMD 0x00
|
||||
#define XHCI_USBSTS 0x04
|
||||
#define XHCI_PAGESIZE 0x08
|
||||
#define XHCI_DNCTRL 0x14
|
||||
#define XHCI_CRCR 0x18 // 64-bit
|
||||
#define XHCI_DCBAAP 0x30 // 64-bit
|
||||
#define XHCI_CONFIG 0x38
|
||||
#define XHCI_PORTSC(p) (0x400 + 0x10 * ((p) - 1)) // ports are 1-based
|
||||
|
||||
#define CMD_RS (1 << 0) // run/stop
|
||||
#define CMD_HCRST (1 << 1) // host controller reset
|
||||
#define CMD_INTE (1 << 2) // interrupter enable
|
||||
#define CMD_HSEE (1 << 3)
|
||||
|
||||
#define STS_HCH (1 << 0) // halted
|
||||
#define STS_HSE (1 << 2) // host system error
|
||||
#define STS_EINT (1 << 3) // event interrupt (write 1 to clear)
|
||||
#define STS_PCD (1 << 4) // port change detect (write 1 to clear)
|
||||
#define STS_CNR (1 << 11) // controller not ready
|
||||
|
||||
#define CRCR_RCS (1 << 0) // ring cycle state
|
||||
|
||||
// PORTSC. The change bits are write-1-to-clear, and so is PED -- writing back
|
||||
// a value read from this register clears whatever happened to be set. Every
|
||||
// write has to mask them off first.
|
||||
#define PORT_CCS (1 << 0) // current connect status
|
||||
#define PORT_PED (1 << 1) // port enabled (RW1C!)
|
||||
#define PORT_OCA (1 << 3)
|
||||
#define PORT_PR (1 << 4) // port reset
|
||||
#define PORT_PLS(x) (((x) >> 5) & 0xF)
|
||||
#define PORT_PP (1 << 9) // port power
|
||||
#define PORT_SPEED(x) (((x) >> 10) & 0xF)
|
||||
#define PORT_CSC (1 << 17)
|
||||
#define PORT_PEC (1 << 18)
|
||||
#define PORT_PRC (1 << 21)
|
||||
#define PORT_RW1C 0x00FE0002u // change bits 17..23, plus PED
|
||||
|
||||
// PORTSC speed encoding. These are the same numbers as USB_SPEED_* in usb.h,
|
||||
// which is not a coincidence -- the core uses this encoding directly so that
|
||||
// nothing has to be translated on its way to a slot context.
|
||||
#define XSPEED_FULL USB_SPEED_FULL
|
||||
#define XSPEED_LOW USB_SPEED_LOW
|
||||
#define XSPEED_HIGH USB_SPEED_HIGH
|
||||
#define XSPEED_SUPER USB_SPEED_SUPER
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Runtime registers -- at BAR0 + RTSOFF. Interrupter n is at 0x20 + 32*n.
|
||||
// ---------------------------------------------------------------------------
|
||||
#define XHCI_MFINDEX 0x00
|
||||
#define XHCI_IR0 0x20
|
||||
#define IR_IMAN 0x00
|
||||
#define IR_IMOD 0x04
|
||||
#define IR_ERSTSZ 0x08
|
||||
#define IR_ERSTBA 0x10 // 64-bit
|
||||
#define IR_ERDP 0x18 // 64-bit
|
||||
|
||||
#define IMAN_IP (1 << 0) // interrupt pending (RW1C)
|
||||
#define IMAN_IE (1 << 1) // interrupt enable
|
||||
#define ERDP_EHB (1 << 3) // event handler busy (RW1C)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Transfer Request Blocks
|
||||
// ---------------------------------------------------------------------------
|
||||
struct trb {
|
||||
uint64 param;
|
||||
uint32 status;
|
||||
uint32 control;
|
||||
} __attribute__((packed));
|
||||
|
||||
#define TRB_TYPE(t) (((t) & 0x3F) << 10)
|
||||
#define TRB_GET_TYPE(c) (((c) >> 10) & 0x3F)
|
||||
#define TRB_C (1 << 0) // cycle bit
|
||||
#define TRB_TC (1 << 1) // toggle cycle (link TRBs)
|
||||
#define TRB_ISP (1 << 2)
|
||||
#define TRB_CH (1 << 4) // chain
|
||||
#define TRB_IOC (1 << 5) // interrupt on completion
|
||||
#define TRB_IDT (1 << 6) // immediate data
|
||||
#define TRB_SLOT(s) ((uint32)(s) << 24)
|
||||
#define TRB_GET_SLOT(c) (((c) >> 24) & 0xFF)
|
||||
#define TRB_GET_EPID(c) (((c) >> 16) & 0x1F) // transfer events
|
||||
|
||||
// Transfer TRBs
|
||||
#define TRB_NORMAL 1
|
||||
#define TRB_SETUP 2
|
||||
#define TRB_DATA 3
|
||||
#define TRB_STATUS 4
|
||||
#define TRB_LINK 6
|
||||
// Command TRBs
|
||||
#define TRB_ENABLE_SLOT 9
|
||||
#define TRB_DISABLE_SLOT 10
|
||||
#define TRB_ADDRESS_DEV 11
|
||||
#define TRB_CONFIG_EP 12
|
||||
#define TRB_EVAL_CTX 13
|
||||
#define TRB_RESET_EP 14
|
||||
#define TRB_NOOP_CMD 23
|
||||
// Event TRBs
|
||||
#define TRB_XFER_EVENT 32
|
||||
#define TRB_CMD_COMPLETE 33
|
||||
#define TRB_PORT_STATUS 34
|
||||
|
||||
#define TRB_CC(status) (((status) >> 24) & 0xFF)
|
||||
#define CC_SUCCESS 1
|
||||
#define CC_SHORT_PACKET 13
|
||||
|
||||
// Setup Stage transfer type, and Data Stage direction.
|
||||
#define TRT_NONE 0
|
||||
#define TRT_OUT 2
|
||||
#define TRT_IN 3
|
||||
#define DIR_IN (1 << 16)
|
||||
|
||||
// Endpoint types in an endpoint context. Direction is part of the type, and
|
||||
// the numbering is not arbitrary: OUT types are 1-3, control is 4, and the IN
|
||||
// types are the OUT ones plus four.
|
||||
#define EPTYPE_ISOCH_OUT 1
|
||||
#define EPTYPE_BULK_OUT 2
|
||||
#define EPTYPE_INT_OUT 3
|
||||
#define EPTYPE_CONTROL 4
|
||||
#define EPTYPE_ISOCH_IN 5
|
||||
#define EPTYPE_BULK_IN 6
|
||||
#define EPTYPE_INT_IN 7
|
||||
|
||||
// Doorbell targets: 0 is the command ring; for a device slot, DCI 1 is EP0.
|
||||
#define DCI_EP0 1
|
||||
216
kernel/drivers/gpu/vga/vga_fb.c
Normal file
216
kernel/drivers/gpu/vga/vga_fb.c
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
// The VBE linear framebuffer: the display the loader left running.
|
||||
//
|
||||
// This is the driver half of what used to be kernel/dev/vga.c. That file was
|
||||
// three things at once -- the owner of the framebuffer, a bitmap drawing
|
||||
// library, and the text console -- and only the first is a driver. The other
|
||||
// two are now kernel/dev/gfx.c and do not name any driver at all, so the crash
|
||||
// screen and the console draw on whatever hardware is bound rather than on
|
||||
// this one by name. See include/gfx.h.
|
||||
//
|
||||
// What stays here is everything that knows *where the pixels are*: the mode the
|
||||
// loader programmed and handed over in the boot block, the aperture it lives
|
||||
// in, and the display class vtable that presents all of it as one of possibly
|
||||
// several displays.
|
||||
|
||||
#include "types.h"
|
||||
#include "x86.h"
|
||||
#include "memlayout.h"
|
||||
#include "defs.h"
|
||||
#include "device.h"
|
||||
#include "display.h"
|
||||
#include "gfx.h"
|
||||
#include "dma.h"
|
||||
#include "schboot.h" /* the hand-off block */
|
||||
|
||||
static volatile uchar *fb;
|
||||
// Physical base of the linear framebuffer, as reported by VBE. Everything that
|
||||
// needs to map the framebuffer -- fork's VMA copy, mmap of /dev/fb0 -- asks
|
||||
// rather than assuming a value. Three places used to hardcode 0xfd000000,
|
||||
// which is only where QEMU's stdvga happens to put it.
|
||||
static uint64 fb_phys;
|
||||
static uint32 fb_stride;
|
||||
static uint32 fb_bpp;
|
||||
static uint32 fb_width = 800;
|
||||
static uint32 fb_height = 600;
|
||||
|
||||
// Bytes of framebuffer, measured in scanlines rather than as
|
||||
// width * height * bpp/8 -- the two differ whenever the card pads its rows,
|
||||
// and it is the stride that says how far apart two rows really are.
|
||||
uint64
|
||||
vga_fb_size(void)
|
||||
{
|
||||
if (!fb)
|
||||
return 0;
|
||||
return (uint64)fb_stride * (uint64)fb_height;
|
||||
}
|
||||
|
||||
// The framebuffer address VBE reported, for the PCI half of this driver to
|
||||
// cross-check against the aperture it finds on the bus. Zero before init.
|
||||
uint64
|
||||
vga_fb_phys(void)
|
||||
{
|
||||
return fb_phys;
|
||||
}
|
||||
|
||||
// Read the mode the loader programmed and publish it as a scanout.
|
||||
//
|
||||
// Called from platform_register_devices(), because the framebuffer is a fact
|
||||
// about the machine that the loader established before any bus was walked --
|
||||
// the PCI display driver binds much later, and the console has to be able to
|
||||
// draw long before that.
|
||||
//
|
||||
// Deliberately does NOT clear the screen. The loader has already drawn the
|
||||
// splash, and wiping it here is what produced the old two-act boot: a purple
|
||||
// splash for an instant, then an abrupt cream screen filling with log lines.
|
||||
// The splash stays up for the whole of boot, and console_takeover() clears it
|
||||
// exactly once, when userspace starts and the display changes owner.
|
||||
void
|
||||
vga_fb_init(uint64 phys_addr)
|
||||
{
|
||||
struct fb_info info;
|
||||
|
||||
if (phys_addr == 0)
|
||||
phys_addr = *(uint32*)(P2V(SCHBOOT_VBE_LFB));
|
||||
|
||||
fb = (volatile uchar*)P2V(phys_addr);
|
||||
fb_phys = phys_addr;
|
||||
fb_stride = *(ushort*)(P2V(SCHBOOT_VBE_STRIDE));
|
||||
fb_width = *(ushort*)(P2V(SCHBOOT_VBE_WIDTH));
|
||||
fb_height = *(ushort*)(P2V(SCHBOOT_VBE_HEIGHT));
|
||||
fb_bpp = *(uchar*)(P2V(SCHBOOT_VBE_BPP));
|
||||
|
||||
if (fb_width == 0) fb_width = 800;
|
||||
if (fb_height == 0) fb_height = 600;
|
||||
if (fb_stride == 0) fb_stride = fb_width * (fb_bpp / 8);
|
||||
|
||||
cprintf("vga: %dx%d, %d bpp, stride %d, LFB: 0x%x\n",
|
||||
fb_width, fb_height, fb_bpp, fb_stride, (uint32)phys_addr);
|
||||
|
||||
info.ptr = fb;
|
||||
info.phys = fb_phys;
|
||||
info.size = vga_fb_size();
|
||||
info.stride = fb_stride;
|
||||
info.bpp = fb_bpp;
|
||||
info.width = fb_width;
|
||||
info.height = fb_height;
|
||||
gfx_attach_scanout(&info);
|
||||
}
|
||||
|
||||
// --- display class implementation ------------------------------------------
|
||||
//
|
||||
// See include/display.h. The VBE framebuffer presenting itself as a generic
|
||||
// display, so a compositor is written against the class rather than against
|
||||
// this file.
|
||||
|
||||
static uchar *back; // shadow buffer in RAM, or 0 if never acquired
|
||||
static uint64 back_bytes;
|
||||
|
||||
static void
|
||||
vga_disp_mode(struct device *d, struct display_mode *m)
|
||||
{
|
||||
(void)d;
|
||||
m->width = (int)fb_width;
|
||||
m->height = (int)fb_height;
|
||||
m->bpp = (int)fb_bpp;
|
||||
m->stride = fb_stride;
|
||||
}
|
||||
|
||||
static void *
|
||||
vga_disp_acquire(struct device *d, uint32 *stride_out)
|
||||
{
|
||||
(void)d;
|
||||
if (stride_out)
|
||||
*stride_out = fb_stride;
|
||||
if (back)
|
||||
return back;
|
||||
if (!fb || fb_height == 0 || fb_stride == 0)
|
||||
return 0;
|
||||
|
||||
back_bytes = (uint64)fb_stride * fb_height;
|
||||
|
||||
// From the DMA arena, which is the wrong pool and is used here because it is
|
||||
// the only *physically contiguous* allocator in the kernel -- kalloc hands
|
||||
// out single pages whose neighbours are adjacent only by chance, and a
|
||||
// framebuffer wants one flat span.
|
||||
//
|
||||
// At 800x600x32 this is 1.9 MB of a 4 MB arena meant for device rings, which
|
||||
// is tolerable only because nothing else large lives there yet. The real fix
|
||||
// is a contiguous page allocator, or reserving this at boot from the memory
|
||||
// map -- which is work that belongs with the E820 change in docs/roadmap.md
|
||||
// §3b, since that is what stops the kernel pretending 224 MB exists.
|
||||
//
|
||||
// Allocated on first use rather than at boot on purpose: a machine that never
|
||||
// starts a compositor should not pay two megabytes for one.
|
||||
back = dma_alloc(back_bytes, 4096);
|
||||
if (!back) {
|
||||
cprintf("display: no room for a %d KB back buffer; drawing will tear\n",
|
||||
(int)(back_bytes / 1024));
|
||||
return 0;
|
||||
}
|
||||
memset(back, 0, back_bytes);
|
||||
cprintf("display: %d KB back buffer, double buffered\n",
|
||||
(int)(back_bytes / 1024));
|
||||
return back;
|
||||
}
|
||||
|
||||
// Blit a rectangle from the shadow buffer to the framebuffer.
|
||||
//
|
||||
// Row at a time rather than one memmove for the whole rectangle: only a
|
||||
// full-width rectangle is contiguous in either buffer, and a compositor
|
||||
// updating one window is the case worth being right about.
|
||||
static void
|
||||
vga_disp_present(struct device *d, int x, int y, int w, int h)
|
||||
{
|
||||
(void)d;
|
||||
if (!back || !fb)
|
||||
return;
|
||||
|
||||
if (x < 0) { w += x; x = 0; }
|
||||
if (y < 0) { h += y; y = 0; }
|
||||
if (x + w > (int)fb_width) w = (int)fb_width - x;
|
||||
if (y + h > (int)fb_height) h = (int)fb_height - y;
|
||||
if (w <= 0 || h <= 0)
|
||||
return;
|
||||
|
||||
int bytes = w * (int)(fb_bpp / 8);
|
||||
uint32 off = (uint32)x * (fb_bpp / 8);
|
||||
for (int row = y; row < y + h; row++)
|
||||
memmove((void *)(fb + row * fb_stride + off),
|
||||
back + row * fb_stride + off, bytes);
|
||||
}
|
||||
|
||||
// Wait for vertical retrace, by polling the VGA input status register.
|
||||
//
|
||||
// Bit 3 of 0x3DA is set during vertical retrace. This is the original 1987 VGA
|
||||
// interface and it still works on everything that emulates a VGA, QEMU's
|
||||
// stdvga included -- VBE's own protected-mode interface for this is optional
|
||||
// and frequently absent, so the old port is the portable answer.
|
||||
//
|
||||
// Two loops, not one: waiting only for the flag to *be* set would return
|
||||
// immediately if a retrace is already in progress, having just missed the
|
||||
// start of it. Waiting for the current one to end and the next to begin is
|
||||
// what actually lands the blit between frames.
|
||||
//
|
||||
// The counters are a deadlock guard. On hardware that never reports retrace
|
||||
// this returns after a bounded spin instead of hanging the caller forever,
|
||||
// which matters because this can be called with the console locked.
|
||||
static void
|
||||
vga_disp_wait_vblank(struct device *d)
|
||||
{
|
||||
int guard;
|
||||
|
||||
(void)d;
|
||||
for (guard = 0; guard < 1000000; guard++)
|
||||
if (!(inb(0x3DA) & 0x08))
|
||||
break;
|
||||
for (guard = 0; guard < 1000000; guard++)
|
||||
if (inb(0x3DA) & 0x08)
|
||||
break;
|
||||
}
|
||||
|
||||
const struct display_ops vga_display_ops = {
|
||||
.mode = vga_disp_mode,
|
||||
.acquire = vga_disp_acquire,
|
||||
.present = vga_disp_present,
|
||||
.wait_vblank = vga_disp_wait_vblank,
|
||||
};
|
||||
|
|
@ -1,10 +1,15 @@
|
|||
// Display controller -- the first driver to find its hardware by asking.
|
||||
//
|
||||
// It does not draw anything: kernel/dev/vga.c already owns the framebuffer,
|
||||
// It does not draw anything: vga_fb.c in this directory owns the framebuffer,
|
||||
// and it learned where that is from the bootloader's VBE call. What this adds
|
||||
// is a second, independent answer to the same question, from the bus rather
|
||||
// than from the BIOS -- and a complaint when the two disagree.
|
||||
//
|
||||
// No `attached` hook and no per-instance state, unlike the e1000 and
|
||||
// virtio-gpu: there is exactly one framebuffer the loader programmed, however
|
||||
// many VGA-class controllers the bus turns up. A second one binding here
|
||||
// publishes the same scanout, which is the truth -- it is the same screen.
|
||||
//
|
||||
// That is worth more than it sounds. Three places in the kernel used to
|
||||
// hardcode 0xfd000000 as the framebuffer address, and the only reason nobody
|
||||
// noticed was that QEMU's stdvga happens to put it there. A disagreement
|
||||
|
|
|
|||
180
kernel/drivers/gpu/virtio_gpu/virtio.h
Normal file
180
kernel/drivers/gpu/virtio_gpu/virtio.h
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
#pragma once
|
||||
|
||||
// virtio 1.0 over PCI — the transport, and virtio-gpu's 2D protocol.
|
||||
//
|
||||
// Next to the driver rather than in include/, because exactly one driver reads
|
||||
// it. When a second virtio device arrives -- virtio-net and virtio-blk are the
|
||||
// obvious ones -- the *transport* half of this belongs in a shared place and
|
||||
// the 2D protocol stays here; splitting it before there is a second caller
|
||||
// would be guessing at where the seam goes.
|
||||
//
|
||||
// Written from the OASIS specification (virtio 1.1, chapters 2, 4.1 and 5.7),
|
||||
// which is a public document with no licence on what you write from it. Same
|
||||
// reasoning as kernel/drivers/xhci.c.
|
||||
//
|
||||
// Why virtio-gpu at all, when there is already a framebuffer: the VBE
|
||||
// framebuffer is a flat block of memory the loader happened to be told about.
|
||||
// It has no mode setting, no notion of a frame being complete, and no way to
|
||||
// ask the host to present anything -- so a compositor drawing into it is
|
||||
// racing the scanout with no way to synchronise. virtio-gpu has resources,
|
||||
// explicit transfer and an explicit flush, which is the model every real GPU
|
||||
// driver has. It is the smallest device that teaches the right shape.
|
||||
|
||||
// --- PCI transport ---------------------------------------------------------
|
||||
|
||||
#define VIRTIO_VENDOR 0x1AF4
|
||||
// Modern (1.0) device IDs are 0x1040 + the virtio device type. GPU is type 16.
|
||||
#define VIRTIO_DEV_GPU 0x1050
|
||||
|
||||
// Vendor-specific PCI capability, which is how a virtio device says where in
|
||||
// its BARs each configuration structure lives.
|
||||
#define PCI_CAP_ID_VNDR 0x09
|
||||
|
||||
#define VIRTIO_PCI_CAP_COMMON_CFG 1
|
||||
#define VIRTIO_PCI_CAP_NOTIFY_CFG 2
|
||||
#define VIRTIO_PCI_CAP_ISR_CFG 3
|
||||
#define VIRTIO_PCI_CAP_DEVICE_CFG 4
|
||||
|
||||
// Offsets within the common configuration structure. Written out rather than
|
||||
// declared as a struct: the fields are little-endian by specification and of
|
||||
// mixed width, and a struct invites the compiler to pad or a caller to forget
|
||||
// that these are MMIO and must be accessed at exactly the stated width.
|
||||
#define VCOMMON_DFEAT_SEL 0x00 // le32
|
||||
#define VCOMMON_DFEAT 0x04 // le32
|
||||
#define VCOMMON_GFEAT_SEL 0x08 // le32
|
||||
#define VCOMMON_GFEAT 0x0C // le32
|
||||
#define VCOMMON_MSIX_CFG 0x10 // le16
|
||||
#define VCOMMON_NUM_QUEUES 0x12 // le16
|
||||
#define VCOMMON_STATUS 0x14 // u8
|
||||
#define VCOMMON_CFG_GEN 0x15 // u8
|
||||
#define VCOMMON_Q_SELECT 0x16 // le16
|
||||
#define VCOMMON_Q_SIZE 0x18 // le16
|
||||
#define VCOMMON_Q_MSIX 0x1A // le16
|
||||
#define VCOMMON_Q_ENABLE 0x1C // le16
|
||||
#define VCOMMON_Q_NOTIFY_OFF 0x1E // le16
|
||||
#define VCOMMON_Q_DESC 0x20 // le64
|
||||
#define VCOMMON_Q_DRIVER 0x28 // le64
|
||||
#define VCOMMON_Q_DEVICE 0x30 // le64
|
||||
|
||||
// device_status bits, set in this order during bring-up.
|
||||
#define VSTATUS_ACK 1
|
||||
#define VSTATUS_DRIVER 2
|
||||
#define VSTATUS_DRIVER_OK 4
|
||||
#define VSTATUS_FEATURES_OK 8
|
||||
#define VSTATUS_FAILED 0x80
|
||||
|
||||
#define VIRTIO_F_VERSION_1 32 // feature bit: this is a 1.0 device
|
||||
|
||||
// --- split virtqueue -------------------------------------------------------
|
||||
|
||||
#define VRING_DESC_F_NEXT 1
|
||||
#define VRING_DESC_F_WRITE 2 // device writes it; i.e. this is our buffer
|
||||
|
||||
struct vring_desc {
|
||||
uint64 addr;
|
||||
uint32 len;
|
||||
uint16 flags;
|
||||
uint16 next;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct vring_avail {
|
||||
uint16 flags;
|
||||
uint16 idx;
|
||||
uint16 ring[];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct vring_used_elem {
|
||||
uint32 id;
|
||||
uint32 len;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct vring_used {
|
||||
uint16 flags;
|
||||
uint16 idx;
|
||||
struct vring_used_elem ring[];
|
||||
} __attribute__((packed));
|
||||
|
||||
// --- virtio-gpu, 2D --------------------------------------------------------
|
||||
|
||||
#define VIRTIO_GPU_CMD_GET_DISPLAY_INFO 0x0100
|
||||
#define VIRTIO_GPU_CMD_RESOURCE_CREATE_2D 0x0101
|
||||
#define VIRTIO_GPU_CMD_RESOURCE_UNREF 0x0102
|
||||
#define VIRTIO_GPU_CMD_SET_SCANOUT 0x0103
|
||||
#define VIRTIO_GPU_CMD_RESOURCE_FLUSH 0x0104
|
||||
#define VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D 0x0105
|
||||
#define VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING 0x0106
|
||||
|
||||
#define VIRTIO_GPU_RESP_OK_NODATA 0x1100
|
||||
#define VIRTIO_GPU_RESP_OK_DISPLAY_INFO 0x1101
|
||||
|
||||
#define VIRTIO_GPU_MAX_SCANOUTS 16
|
||||
|
||||
// B8G8R8X8, which is what a 32-bit little-endian 0x00RRGGBB word looks like in
|
||||
// memory -- the same layout vga.c already draws into, so a framebuffer can be
|
||||
// handed over without a conversion pass.
|
||||
#define VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM 2
|
||||
|
||||
struct virtio_gpu_ctrl_hdr {
|
||||
uint32 type;
|
||||
uint32 flags;
|
||||
uint64 fence_id;
|
||||
uint32 ctx_id;
|
||||
uint32 padding;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_rect {
|
||||
uint32 x, y, width, height;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_display_one {
|
||||
struct virtio_gpu_rect r;
|
||||
uint32 enabled;
|
||||
uint32 flags;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_resp_display_info {
|
||||
struct virtio_gpu_ctrl_hdr hdr;
|
||||
struct virtio_gpu_display_one pmodes[VIRTIO_GPU_MAX_SCANOUTS];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_resource_create_2d {
|
||||
struct virtio_gpu_ctrl_hdr hdr;
|
||||
uint32 resource_id;
|
||||
uint32 format;
|
||||
uint32 width;
|
||||
uint32 height;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_mem_entry {
|
||||
uint64 addr;
|
||||
uint32 length;
|
||||
uint32 padding;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_resource_attach_backing {
|
||||
struct virtio_gpu_ctrl_hdr hdr;
|
||||
uint32 resource_id;
|
||||
uint32 nr_entries;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_set_scanout {
|
||||
struct virtio_gpu_ctrl_hdr hdr;
|
||||
struct virtio_gpu_rect r;
|
||||
uint32 scanout_id;
|
||||
uint32 resource_id;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_transfer_to_host_2d {
|
||||
struct virtio_gpu_ctrl_hdr hdr;
|
||||
struct virtio_gpu_rect r;
|
||||
uint64 offset;
|
||||
uint32 resource_id;
|
||||
uint32 padding;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct virtio_gpu_resource_flush {
|
||||
struct virtio_gpu_ctrl_hdr hdr;
|
||||
struct virtio_gpu_rect r;
|
||||
uint32 resource_id;
|
||||
uint32 padding;
|
||||
} __attribute__((packed));
|
||||
|
|
@ -59,9 +59,26 @@ struct vgpu {
|
|||
void *fb; // the framebuffer we scan out from
|
||||
uint32 fb_bytes;
|
||||
int ready;
|
||||
// Which PCI device this instance drives, so attached() can match the device
|
||||
// the bus publishes against the instance probe() set up.
|
||||
struct pci_dev *pdev;
|
||||
};
|
||||
|
||||
static struct vgpu gpu;
|
||||
// More than one, because two display adapters in a machine is ordinary --
|
||||
// integrated plus discrete on real hardware, two -device virtio-gpu-pci under
|
||||
// QEMU -- and this driver used to refuse the second outright.
|
||||
#define VGPU_MAX 2
|
||||
|
||||
static struct vgpu gpus[VGPU_MAX];
|
||||
|
||||
// The instance behind a device, or 0. Every op goes through this rather than a
|
||||
// file-static, which is what makes the second adapter possible at all. See
|
||||
// include/netdev.h and the e1000 for the same arrangement on the network side.
|
||||
static struct vgpu *
|
||||
vgpu_of(struct device *d)
|
||||
{
|
||||
return d ? (struct vgpu *)d->priv : 0;
|
||||
}
|
||||
|
||||
// --- MMIO accessors --------------------------------------------------------
|
||||
//
|
||||
|
|
@ -309,51 +326,61 @@ setup_scanout(struct vgpu *g)
|
|||
static void
|
||||
vgpu_mode(struct device *d, struct display_mode *m)
|
||||
{
|
||||
m->width = gpu.width;
|
||||
m->height = gpu.height;
|
||||
struct vgpu *g = vgpu_of(d);
|
||||
|
||||
if (!g)
|
||||
return;
|
||||
m->width = g->width;
|
||||
m->height = g->height;
|
||||
m->bpp = 32;
|
||||
m->stride = (uint32)gpu.width * 4;
|
||||
m->stride = (uint32)g->width * 4;
|
||||
}
|
||||
|
||||
static void *
|
||||
vgpu_acquire(struct device *d, uint32 *stride_out)
|
||||
{
|
||||
struct vgpu *g = vgpu_of(d);
|
||||
|
||||
if (!g)
|
||||
return 0;
|
||||
if (stride_out)
|
||||
*stride_out = (uint32)gpu.width * 4;
|
||||
*stride_out = (uint32)g->width * 4;
|
||||
// No separate back buffer: the guest-side framebuffer already *is* one. The
|
||||
// host cannot see it until TRANSFER_TO_HOST_2D, so drawing into it is
|
||||
// inherently off-screen -- which is the whole reason this device is better to
|
||||
// composite against than a raw VBE aperture.
|
||||
return gpu.ready ? gpu.fb : 0;
|
||||
return g->ready ? g->fb : 0;
|
||||
}
|
||||
|
||||
static void
|
||||
vgpu_present(struct device *d, int x, int y, int w, int h)
|
||||
{
|
||||
if (!gpu.ready)
|
||||
struct vgpu *g = vgpu_of(d);
|
||||
|
||||
if (!g || !g->ready)
|
||||
return;
|
||||
if (x < 0) { w += x; x = 0; }
|
||||
if (y < 0) { h += y; y = 0; }
|
||||
if (x + w > gpu.width) w = gpu.width - x;
|
||||
if (y + h > gpu.height) h = gpu.height - y;
|
||||
if (x + w > g->width) w = g->width - x;
|
||||
if (y + h > g->height) h = g->height - y;
|
||||
if (w <= 0 || h <= 0)
|
||||
return;
|
||||
|
||||
{
|
||||
struct virtio_gpu_transfer_to_host_2d *t = gpu.cmd;
|
||||
struct virtio_gpu_transfer_to_host_2d *t = g->cmd;
|
||||
hdr_init(t, VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D);
|
||||
t->r.x = x; t->r.y = y; t->r.width = w; t->r.height = h;
|
||||
t->offset = (uint64)y * gpu.width * 4 + (uint64)x * 4;
|
||||
t->offset = (uint64)y * g->width * 4 + (uint64)x * 4;
|
||||
t->resource_id = 1;
|
||||
if (cmd(&gpu, sizeof(*t), sizeof(struct virtio_gpu_ctrl_hdr)) != 0)
|
||||
if (cmd(g, sizeof(*t), sizeof(struct virtio_gpu_ctrl_hdr)) != 0)
|
||||
return;
|
||||
}
|
||||
{
|
||||
struct virtio_gpu_resource_flush *f = gpu.cmd;
|
||||
struct virtio_gpu_resource_flush *f = g->cmd;
|
||||
hdr_init(f, VIRTIO_GPU_CMD_RESOURCE_FLUSH);
|
||||
f->r.x = x; f->r.y = y; f->r.width = w; f->r.height = h;
|
||||
f->resource_id = 1;
|
||||
cmd(&gpu, sizeof(*f), sizeof(struct virtio_gpu_ctrl_hdr));
|
||||
cmd(g, sizeof(*f), sizeof(struct virtio_gpu_ctrl_hdr));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -372,10 +399,16 @@ const struct display_ops virtio_gpu_display_ops = {
|
|||
static int
|
||||
vgpu_probe(struct pci_dev *d)
|
||||
{
|
||||
struct vgpu *g = &gpu;
|
||||
struct vgpu *g = 0;
|
||||
|
||||
if (g->ready)
|
||||
return -1; // one display is enough for now
|
||||
for (int i = 0; i < VGPU_MAX; i++)
|
||||
if (!gpus[i].ready) {
|
||||
g = &gpus[i];
|
||||
break;
|
||||
}
|
||||
if (!g)
|
||||
return -1; // every slot already drives an adapter
|
||||
g->pdev = d;
|
||||
|
||||
pci_enable(d, 0x0006); // memory space + bus master
|
||||
|
||||
|
|
@ -443,9 +476,23 @@ static const struct pci_match virtio_gpu_match[] = {
|
|||
{ 0, 0, 0, 0 },
|
||||
};
|
||||
|
||||
// Hang the instance on the device the bus has just published. probe() runs
|
||||
// before device_add(), so there is nothing to attach it to in there -- see the
|
||||
// comment on `attached` in include/pci.h.
|
||||
static void
|
||||
vgpu_attached(struct pci_dev *d, struct device *dev)
|
||||
{
|
||||
for (int i = 0; i < VGPU_MAX; i++)
|
||||
if (gpus[i].ready && gpus[i].pdev == d) {
|
||||
dev->priv = &gpus[i];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const struct pci_driver virtio_gpu_driver = {
|
||||
.name = "virtio-gpu",
|
||||
.match = virtio_gpu_match,
|
||||
.ops = &virtio_gpu_display_ops,
|
||||
.probe = vgpu_probe,
|
||||
.attached = vgpu_attached,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -99,10 +99,17 @@ usb_bind_all(struct usb_dev *dev)
|
|||
// is visible in one place, which is the same reasoning as the driver arrays
|
||||
// above.
|
||||
void
|
||||
platform_register_devices(void)
|
||||
platform_register_devices(uint64 fb_addr)
|
||||
{
|
||||
struct device *d;
|
||||
|
||||
// The framebuffer first, before anything that might want to draw. It is a
|
||||
// driver call, and this file is where driver names belong -- main() used to
|
||||
// call vga_init() directly, which put one display driver's name in the
|
||||
// kernel's startup path and would have needed editing to boot on a machine
|
||||
// whose framebuffer came from anywhere else.
|
||||
vga_fb_init(fb_addr);
|
||||
|
||||
if ((d = device_add("kbd", DEVBUS_PLATFORM, DEVCLASS_INPUT, "ps2kbd")))
|
||||
device_set_desc(d, "PS/2 keyboard");
|
||||
if ((d = device_add("mouse", DEVBUS_PLATFORM, DEVCLASS_INPUT, "ps2mouse")))
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "gfx.h"
|
||||
#include "param.h"
|
||||
#include "spinlock.h"
|
||||
#include "dma.h"
|
||||
|
|
@ -219,8 +220,8 @@ tablet_report(struct hid *h, const uchar *rep, int len)
|
|||
int rawx = rep[1] | (rep[2] << 8);
|
||||
int rawy = rep[3] | (rep[4] << 8);
|
||||
|
||||
int w = (int)vga_get_width();
|
||||
int h_px = (int)vga_get_height();
|
||||
int w = (int)gfx_width();
|
||||
int h_px = (int)gfx_height();
|
||||
if(w <= 0) w = 800;
|
||||
if(h_px <= 0) h_px = 600;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue