diff --git a/cmd/vgpud/main.c b/cmd/vgpud/main.c index 8d928b7..d2c1f16 100644 --- a/cmd/vgpud/main.c +++ b/cmd/vgpud/main.c @@ -366,6 +366,14 @@ static unsigned long long fb_uaddr, fb_paddr; static unsigned int fb_bytes; // what was allocated, not what is in use static int max_w, max_h; // the mode the allocation was sized for +// The console, learned by console_open() and read by bring_up() one line +// before it picks a mode. Declared up here rather than beside the mirror +// because that ordering -- the console decides the mode, not the other way +// round -- is the whole of why the head comes up the size the F8 menu asked +// for. +static volatile unsigned char *con_fb; // the console's framebuffer +static unsigned con_w, con_h, con_stride; + // The resource is recreated on every modeset; the framebuffer behind it is // not. PCIIOC_DMA has no free -- a driver's blocks come back when it exits and // not before -- so a modeset that allocated would exhaust sixteen slots and @@ -426,6 +434,30 @@ bring_up(void) vputs("vgpud: display is "); vputn(width); vputs("x"); vputn(height); vputs("\n"); + // But the console's mode wins, when there is a console. + // + // GET_DISPLAY_INFO answers with the size of the *host's window*, which the + // person running QEMU drags to whatever they like and which has nothing to + // do with the machine. The resolution the F8 menu offered at boot is the + // one that was actually chosen, and it is the console's -- so a head that + // mirrors the console and ignores that choice shows the right pixels in + // the wrong mode, letterboxed or cropped, on a display that could simply + // have been the right size. + // + // This is also the only chance to get it right without a second modeset: + // the DMA allocation below is sized from these numbers and fixed for the + // life of the process, so asking for the console's size here is what makes + // `schmode` able to reach it later. + if (con_w && con_h) { + if ((int)con_w != width || (int)con_h != height) { + vputs("vgpud: following the console at "); + vputn((int)con_w); vputs("x"); vputn((int)con_h); + vputs(" rather than the window\n"); + } + width = (int)con_w; + height = (int)con_h; + } + // The framebuffer is claimed **before** the resource is created, because // the resource has to be created at a size we can actually back. // @@ -660,8 +692,6 @@ draw_mark(void) #define MIRROR_MS 100 #define MIRROR_TICKS 10 // the same interval, in sleep()'s units -static volatile unsigned char *con_fb; // the console's framebuffer -static unsigned con_w, con_h, con_stride; static int mirror_x, mirror_y, mirror_w, mirror_h; static int mirror_full; // next pass copies every row @@ -687,12 +717,15 @@ mirror_geometry(void) mirror_full = 1; } -// Returns 0 if there is a console to mirror. Every failure here is a reason to +// Learn the console: its size, its format, and where its pixels are. +// +// Called *before* the device is brought up, because the answer decides what +// mode to bring it up in -- see bring_up(). Every failure here is a reason to // keep showing the mark rather than a reason to die: a machine with no // framebuffer console is a machine this daemon still has to hold the device // on. static int -mirror_open(void) +console_open(void) { struct fb_var_screeninfo var; struct fb_fix_screeninfo fix; @@ -729,7 +762,6 @@ mirror_open(void) con_w = var.xres; con_h = var.yres; con_stride = fix.line_length; // NOT xres*4; the card may pad each row - mirror_geometry(); return 0; // fd deliberately left open; see above } @@ -1065,8 +1097,15 @@ main(int argc, char *argv[]) if (!common || !notify) die("no modern virtio capabilities -- not a 1.0 device"); + // Before bring_up(), which needs to know the console's size to pick a + // mode. A machine with no console to mirror brings up whatever the device + // reports, as it always did. + int mirroring = console_open() == 0; + bring_up(); draw_mark(); + if (mirroring) + mirror_geometry(); present(); vputs("vgpud: pid "); @@ -1075,8 +1114,8 @@ main(int argc, char *argv[]) // From here the head shows the console rather than the mark. The mark // stays on screen if there is nothing to mirror, and stays as the ground - // around the console either way. - if (mirror_open() == 0) { + // around the console when the two sizes do not match. + if (mirroring) { mirror_pass(); vputs("vgpud: mirroring the console\n"); } diff --git a/docs/architecture/graphics.md b/docs/architecture/graphics.md index 20eed21..ee69952 100644 --- a/docs/architecture/graphics.md +++ b/docs/architecture/graphics.md @@ -150,6 +150,24 @@ non-integer ratio is text you cannot read. itself, and when there is one, the mirror goes away. Until then the honest thing for a working display to show is the system running on it. +### The F8 resolution is the head's resolution + +`vgpud` used to size itself from `GET_DISPLAY_INFO`, which answers with the +size of the *host's window* — a number the person running QEMU sets by dragging +a corner, and which says nothing about the machine. The resolution the F8 menu +offered at boot is the one that was actually chosen, and it is the console's. + +A head that mirrors the console and ignores that choice shows the right pixels +in the wrong mode: pick 1024×768 at the menu and the second display stayed at +whatever the window was, with the console letterboxed inside it. So +`console_open()` runs *before* `bring_up()`, and the mode comes from the +console when there is one. + +That ordering is the whole of it, and it is not cosmetic: the DMA allocation is +sized from those numbers and fixed for the life of the process, so a mode +chosen after the fact could only ever be smaller than the window that was +already paid for. + ### /run is not a tmpfs Found while testing the above, and older than it: `schmode` reported *"cannot