From 9ab8a7a426efd380bb3187b6aa40da2e21533b4f Mon Sep 17 00:00:00 2001 From: Nexoniarz Date: Tue, 22 Sep 2026 22:19:00 +0200 Subject: [PATCH] gfx: the chosen resolution reaches every head, and the greeting stops racing Two things. **The F8 resolution now applies to the other displays as well.** It already reached them once vgpud started -- that was the last commit but one -- but not before: choosing 1024x768 changed the screen it was chosen on and left the other at 1280x800, so the menu and then the splash appeared on it as a small rectangle in the middle of a larger black one. Which is a fair reading of "the resolution change is not changing resolution". The resolution offered in that menu is a statement about the machine's display, not about one adapter of it, so both stages before vgpud now follow it: the loader mirrors_match() puts every mirror GOP into the primary's mode, called from mirrors_show() so it cannot be forgotten at a new call site. Two integer comparisons in the common case. the kernel setup_scanout() takes gfx's dimensions over GET_DISPLAY_INFO's. Same argument as vgpud's bring_up(): the host window's size is a fact about QEMU, and the console's mode is the one somebody chose. **And the greeting is no longer a race.** "Welcome to SchOS!" was printed after every job had been started, including the shell on the console -- so which of the two reached the screen first was up to the scheduler, and it went both ways about evenly. A boot that sometimes greets you above the prompt and sometimes below it does not read as two orderings of one boot; it reads as a line printed by mistake. schinitd now holds back the job that owns the console -- `own_tty`, which is exactly the job whose output lands there -- until after the greeting is written. That is enough to settle it: this process writes the line, and only then asks PID 1 to spawn the shell, so the fork has not happened yet. Nothing waits on it that was not already waiting. Verified: F8 -> Resolution -> 1024x768 -> boot, with the virtio head screendumped over QMP at three points during startup and once at the prompt -- 1024x768 throughout, and byte-identical to the stdvga head. Three consecutive boots put the greeting at the same offset, ahead of the prompt every time. Both supported configurations: one boot each, no traps, no panics, socktest and memfdtest green. Co-Authored-By: Claude Opus 5 --- boot/uefi/main.c | 41 ++++++++++++++++++++++ cmd/schinitd/main.c | 33 ++++++++++++++++- docs/architecture/graphics.md | 9 +++++ kernel/drivers/gpu/virtio_gpu/virtio_gpu.c | 13 +++++++ 4 files changed, 95 insertions(+), 1 deletion(-) diff --git a/boot/uefi/main.c b/boot/uefi/main.c index 28c931e..d21f94d 100644 --- a/boot/uefi/main.c +++ b/boot/uefi/main.c @@ -1045,6 +1045,45 @@ mirrors_find(EFI_GRAPHICS_OUTPUT_PROTOCOL *primary) } } +/* Put every mirror into the primary's mode, if it has one. + * + * Without this, choosing 1024x768 at the F8 menu changed the screen it was + * chosen on and left the other one at whatever the firmware had picked -- so + * the menu and then the splash appeared on it as a small rectangle in the + * middle of a larger black one. The resolution in that menu is a statement + * about the machine's display, not about one adapter of it. + * + * Cheap to call on every frame: the common case is two integer comparisons and + * no mode set at all. A mirror with no matching mode keeps the one it has and + * gets the centred-and-cropped treatment below, which is still better than + * nothing. + */ +static void +mirrors_match(EFI_GRAPHICS_OUTPUT_PROTOCOL *primary) +{ + UINT32 w = primary->Mode->Info->HorizontalResolution; + UINT32 h = primary->Mode->Info->VerticalResolution; + + for (UINTN i = 0; i < mirror_n; i++) { + EFI_GRAPHICS_OUTPUT_PROTOCOL *g = mirror_gop[i]; + + if (g->Mode->Info->HorizontalResolution == w && + g->Mode->Info->VerticalResolution == h) + continue; + + for (UINT32 m = 0; m < g->Mode->MaxMode; m++) { + EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *mi = 0; + UINTN sz = 0; + if (EFI_ERROR(g->QueryMode(g, m, &sz, &mi)) || !mi) + continue; + if (mi->HorizontalResolution == w && mi->VerticalResolution == h) { + g->SetMode(g, m); + break; + } + } + } +} + /* Put what is on the primary onto all of them. Centred when the sizes differ, * cropped when the other display is smaller -- the same rules the kernel's * console mirror and vgpud's both use, because it is the same picture. */ @@ -1058,6 +1097,8 @@ mirrors_show(void) if (!primary->Mode->FrameBufferBase) return; /* nothing to copy *from* */ + mirrors_match(primary); + UINTN sw = primary->Mode->Info->HorizontalResolution; UINTN sh = primary->Mode->Info->VerticalResolution; UINTN delta = (UINTN)primary->Mode->Info->PixelsPerScanLine * 4; diff --git a/cmd/schinitd/main.c b/cmd/schinitd/main.c index b4c0c4a..4fb7bf1 100644 --- a/cmd/schinitd/main.c +++ b/cmd/schinitd/main.c @@ -631,9 +631,11 @@ main(void) unsigned int highest = adopt(); next_cookie = highest + 1; + // Everything except whatever owns the console. That one waits for the + // greeting below -- see the comment on it. for (int i = 0; i < njobs; i++) { struct job *j = &jobs[i]; - if (j->disabled || !j->run_at_load) + if (j->disabled || !j->run_at_load || j->own_tty) continue; if (j->pid) continue; // adopted; it never stopped running @@ -658,6 +660,35 @@ main(void) out("\nWelcome to SchOS!" " (F8 at boot for the full log, or dmesg once you are in)\n\n"); + // *Now* the shell on the console. + // + // It used to be started with everything else, and then this greeting was + // printed -- which made the two a race that the shell won about half the + // time. A boot that sometimes reads + // + // Welcome to SchOS! (F8 at boot for the full log, ...) + // root@schos / » + // + // and sometimes reads the same two lines the other way round is not two + // orderings of one boot, it is a greeting that looks like it was printed + // by mistake. + // + // Holding it back until after the write is enough to settle it: this + // process writes the line, and only then asks PID 1 to spawn the shell, so + // the fork cannot even have happened yet. Nothing else waits on it -- the + // console shell is the last job to start either way, and starting it a + // few microseconds later costs nothing a person can see. + // + // `own_tty` rather than a name: the job that claims the console as its + // controlling terminal is exactly the job whose output lands here. + for (int i = 0; i < njobs; i++) { + struct job *j = &jobs[i]; + if (j->disabled || !j->run_at_load || !j->own_tty || j->pid) + continue; + vout("schinitd: starting "); vout(j->label); vout("\n"); + ask_spawn(j); + } + // From here on this program does two things: answer PID 1's heartbeat, and // act on the exits it reports. There is no wait() in this program and // there cannot be -- the children are not ours. diff --git a/docs/architecture/graphics.md b/docs/architecture/graphics.md index 5607318..a1aa51c 100644 --- a/docs/architecture/graphics.md +++ b/docs/architecture/graphics.md @@ -138,6 +138,15 @@ system information, the splash, each segment of the kernel load. Blt rather than a second framebuffer to draw into, because a virtio GOP does not have one — the same fact that made a virtio-only machine reboot-loop. +Each stage puts the other displays into the mode the F8 menu chose, rather than +leaving them in whatever the firmware picked. Without that, choosing 1024×768 +changed the screen it was chosen on and left the other one at 1280×800, showing +the menu and then the splash as a small rectangle in the middle of a larger +black one. The loader sets the mode on each mirror GOP; the kernel's virtio +driver takes `gfx`'s dimensions over `GET_DISPLAY_INFO`'s, for the same reason +`vgpud` does — the host window's size is a fact about QEMU, not about the +machine. + **The kernel.** `display_mirror_console()` copies the console's newly-drawn rows to the display class whenever that is a *different screen*. Cheap because `gfx` remembers which rows those were: a line of boot text is one cell-row diff --git a/kernel/drivers/gpu/virtio_gpu/virtio_gpu.c b/kernel/drivers/gpu/virtio_gpu/virtio_gpu.c index 7e4437d..ca9aa74 100644 --- a/kernel/drivers/gpu/virtio_gpu/virtio_gpu.c +++ b/kernel/drivers/gpu/virtio_gpu/virtio_gpu.c @@ -28,6 +28,7 @@ #include "display.h" #include "dma.h" #include "virtio.h" +#include "gfx.h" // One control queue is enough for 2D: there is also a cursor queue, which is // only worth having once there is a hardware cursor to move. @@ -280,6 +281,18 @@ setup_scanout(struct vgpu *g) g->width = (int)info->pmodes[0].r.width; g->height = (int)info->pmodes[0].r.height; + // But the console's mode wins, when there is a console. + // + // GET_DISPLAY_INFO answers with the size of the *host's window*, which says + // nothing about the machine -- somebody dragged a corner. The resolution + // chosen at the F8 menu is the console's, and a second head that shows the + // console in a different mode shows it letterboxed for no reason. Same + // argument, and the same fix, as in cmd/vgpud/main.c's bring_up(). + if (gfx_ready() && gfx_width() && gfx_height()) { + g->width = (int)gfx_width(); + g->height = (int)gfx_height(); + } + // A host resource to scan out from. { struct virtio_gpu_resource_create_2d *c = g->cmd;