Two things, both asked for. **The crash report no longer says which monitors it reached.** "The report is on the virtio-gpu display too" was a line about the crash screen printed on the crash screen. The report says what happened and where to read it again; that is not part of it. **And the second head now shows the F8 menu and the boot.** It used to be black until vgpud started several seconds into userspace -- through the splash, through the menu, through every boot message -- which from the outside is indistinguishable from a display that does not work. Three stages now cover the whole boot and hand over to each other without coordinating: *The loader.* LocateProtocol() answers with the first GOP and offers no way to ask for the rest, so the rest are found by handle and fed the primary's framebuffer as a Blt source at the points where a screen is finished: the menu, the resolution chooser, 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 two commits ago. *The kernel.* gfx remembers which rows were drawn, as one span rather than a list of rectangles, because console output is lines and a span is what lines make. display_mirror_console() copies that span across: a line of boot text is one cell-row transfer, not a screen. Primed once after pci_init(), because a display that has just appeared has a black scanout and the console it should be showing was drawn by the loader into a framebuffer it knows nothing about. *Then vgpud*, from the same console framebuffer, unchanged. **Two bugs found building it, both worth the retelling.** The mirror ran per character from inside vt_feed -- which is inside the console lock -- and vga_disp_acquire() announces its back buffer with a cprintf. A CPU taking a lock it already holds, with a call chain that said exactly that. It runs at the end of consolewrite() and cprintf() now, outside the lock, which is also the better batching: one transfer per write instead of one per character. And "is this the console's own screen" was asked of the pointer acquire() returned. vga_fb hands back a back buffer it copies onto the console's framebuffer afterwards, so on a one-screen machine the answer was "a different screen" -- and the console was mirrored onto itself through two megabytes of intermediate, allocated for the purpose. display_ops grew scanout_phys, which is a fact about the hardware rather than a policy a driver can get subtly wrong, and it is asked first: it has no side effects and acquire() has two. Verified with two heads under QEMU, screendumping the virtio head every 400 ms from power-on: splash, splash, splash, then the console, and it stays. The F8 menu appears on it; choosing 1024x768 there brings both heads up at 1024x768 byte-identical. A panic still reaches both. Both supported configurations: one boot each, no traps, no panics, socktest and memfdtest green. One gap remains and is documented rather than hidden: while the kernel's driver resets the device during probe, QEMU shows "Display output is not active" for a fraction of a second. A device being taken over has no scanout until the new driver sets one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
98 lines
4.4 KiB
C
98 lines
4.4 KiB
C
#pragma once
|
|
|
|
// Drawing, and the text console under it, on whatever display is bound.
|
|
//
|
|
// This was kernel/dev/vga.c, and every consumer called it by that name:
|
|
// panic.c drew the crash screen with vga_fill_rect, the console printed with
|
|
// vga_putc, /dev/fb0 handed out vga_fb_phys(). None of that code has anything
|
|
// to do with a VGA -- it is bitmap drawing over a linear framebuffer, and the
|
|
// only VGA-specific part was where the framebuffer came from.
|
|
//
|
|
// So the driver pushes its scanout in here (gfx_attach_scanout) and everything
|
|
// above draws through these calls. A second display driver becomes a second
|
|
// caller of one function rather than an edit to the crash screen. This is the
|
|
// same arrangement as include/netdev.h, for the same reason.
|
|
//
|
|
// Not the same thing as the display class in include/display.h. That is the
|
|
// compositor's interface -- acquire a back buffer, present a rectangle, wait
|
|
// for vblank -- and it is about *frames*. This is the kernel's own drawing
|
|
// surface, used by the console and the crash screen, which do not double
|
|
// buffer and must keep working when nothing else in the system does.
|
|
|
|
// A linear framebuffer, exactly as the hardware presents it.
|
|
struct fb_info {
|
|
volatile uchar *ptr; // kernel-mapped base, or 0 if there is none
|
|
uint64 phys; // physical base, for mmap and for page tables
|
|
uint64 size; // bytes, stride * height rather than w * h * bpp
|
|
uint32 stride; // bytes per row, which may exceed width * bpp/8
|
|
uint32 bpp;
|
|
uint32 width, height;
|
|
};
|
|
|
|
// A driver publishing the framebuffer it owns. Called once, early: the console
|
|
// draws long before any bus is enumerated, so whoever owns the display the
|
|
// loader left running has to say so before then.
|
|
void gfx_attach_scanout(const struct fb_info *);
|
|
|
|
// The scanout as attached, for the few callers that need the memory itself --
|
|
// /dev/fb0 maps it, and fork copies the mapping. Returns 0 if none.
|
|
const struct fb_info *gfx_scanout(void);
|
|
|
|
int gfx_ready(void);
|
|
void gfx_dims(int *w, int *h);
|
|
uint32 gfx_width(void);
|
|
uint32 gfx_height(void);
|
|
uint64 gfx_fb_phys(void);
|
|
uint64 gfx_fb_size(void);
|
|
uint32 gfx_fb_stride(void);
|
|
uint32 gfx_fb_bpp(void);
|
|
volatile uchar *gfx_fb_ptr(void);
|
|
|
|
// The rows drawn since this was last called, for whoever is keeping a second
|
|
// screen in step with this one. Returns 0 when nothing has changed.
|
|
int gfx_damage_take(int *y0, int *y1);
|
|
|
|
// Pixels and shapes.
|
|
void gfx_draw_pixel(int x, int y, uint32 color);
|
|
void gfx_clear(uint32 color);
|
|
void gfx_fill_rect(int x, int y, int w, int h, uint32 color);
|
|
void gfx_fill_circle(int cx, int cy, int r, uint32 color);
|
|
void gfx_draw_logo(int x, int y, uint32 color, uint32 bg);
|
|
void gfx_logo_centered(int cy, uint32 color, uint32 bg);
|
|
|
|
// The built-in 8x16 font: glyphs at a position, and text that leaves the
|
|
// cursor alone. The crash screen lays itself out rather than printing, which
|
|
// is what needs the second one.
|
|
void gfx_draw_char(int x, int y, uchar c, uint32 color);
|
|
void gfx_text_at(int x, int y, const char *s, uint32 color);
|
|
int gfx_char_w(void);
|
|
int gfx_char_h(void);
|
|
|
|
// The text console: a cursor, scrolling, and the erase operations an ANSI
|
|
// escape parser needs. See kernel/core/console.c.
|
|
void gfx_putc(int c, uint32 color);
|
|
void gfx_puts(int x, int y, char *s, uint32 color);
|
|
void gfx_set_bg(uint32 color);
|
|
void gfx_screen_reset(void);
|
|
int gfx_cols(void);
|
|
int gfx_rows(void);
|
|
|
|
// For the text layer in kernel/dev/vt.c: one cell with its own colours, and
|
|
// a screen scroll that leaves the cursor alone.
|
|
// One terminal cell. `cp` is a Unicode codepoint, not a byte -- see the note on
|
|
// struct vtcell in vt.h for why that distinction is the whole feature.
|
|
void gfx_draw_cell(int col, int row, uint32 cp, uint32 fg, uint32 bg);
|
|
|
|
// Bring the console typeface up, once, early. Falls back to the built-in
|
|
// bitmap and says nothing if the font will not open -- see the note in gfx.c
|
|
// on why it cannot print for itself.
|
|
void gfx_font_init(void);
|
|
int gfx_font_ready(void);
|
|
void gfx_cell_size(int *w, int *h);
|
|
// A filled rectangle in pixels, with the per-pixel checks hoisted out.
|
|
void gfx_fill(int x, int y, int w, int h, uint32 color);
|
|
void gfx_scroll_up(uint32 bg);
|
|
void gfx_cursor_get(int *col, int *row);
|
|
void gfx_cursor_set(int col, int row);
|
|
void gfx_erase_display(int mode);
|
|
void gfx_erase_line(int mode);
|