kernel/dev/mouse.c held the pointer inside literal 800x600 bounds. That was correct while 800x600 was the only mode the loader could set, which it was until the resolution became a boot-time choice. At 1280x800 the pointer simply stopped two thirds of the way across and would not go further -- which reads as the cursor drifting away from the host's rather than as a clamp. vga.c grows vga_get_width/vga_get_height for it. The mode is chosen at boot now, so nothing above the framebuffer may hardcode its size, and these are the first two callers that needed to ask. Verified that the system still builds and boots at 1280x800, and that bootcheck is 108 [ok] and schcom-test passes. I did not manage to photograph the pointer past the old limit in the time available -- there is no reader for /dev/mouse in the image and the screendump attempt did not resolve, so the fix rests on inspection rather than on a picture. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
569 lines
18 KiB
C
569 lines
18 KiB
C
#include "types.h"
|
|
#include "x86.h"
|
|
#include "memlayout.h"
|
|
#include "defs.h"
|
|
#include "theme.h"
|
|
#include "font.h"
|
|
#include "logo.h"
|
|
#include "device.h"
|
|
#include "display.h"
|
|
#include "dma.h"
|
|
|
|
#define BACKSPACE 0x100
|
|
#define CHAR_HEIGHT 16
|
|
#define CHAR_WIDTH 8
|
|
#define COLOR_BG SCH_BG
|
|
|
|
volatile uchar *vga_mem = 0;
|
|
// Physical base of the linear framebuffer, as reported by VBE. Every other
|
|
// part of the kernel that needs to map the framebuffer -- fork's VMA copy,
|
|
// mmap of /dev/fb0 -- asks for this rather than assuming a value. Three
|
|
// places used to hardcode 0xfd000000, which is only where QEMU's stdvga
|
|
// happens to put it.
|
|
static uint64 vga_phys = 0;
|
|
static uint32 vga_stride = 0;
|
|
static uint32 vga_bpp = 0;
|
|
static uint32 vga_width = 800;
|
|
static uint32 vga_height = 600;
|
|
static int cursor_x = 0;
|
|
static int cursor_y = 0;
|
|
// Current fill color for glyph backgrounds, backspace erase, and scroll —
|
|
// normally COLOR_BG, but panic() switches this to paint the whole console
|
|
// a solid color (e.g. PSOGM's purple) instead of leaving cream-colored boxes
|
|
// around each character.
|
|
static uint32 vga_bg = COLOR_BG;
|
|
|
|
void
|
|
vga_set_bg(uint32 color)
|
|
{
|
|
vga_bg = color;
|
|
}
|
|
|
|
// Written by boot/bios/stage2.S's VBE call and read by boot/bios/stage2main.c under
|
|
// the same name (VBE_INFO_ADDR) — not 0x9000, since stage2's own code now
|
|
// runs up to nearly 0x9000 once the boot-menu/progress-bar logic is
|
|
// included, which would otherwise clobber a mode info block placed there.
|
|
#define VBE_INFO_ADDR 0x30000
|
|
|
|
void
|
|
vga_init(uint64 phys_addr) {
|
|
if (phys_addr == 0) {
|
|
phys_addr = *(uint32*)(P2V(VBE_INFO_ADDR) + 40);
|
|
}
|
|
vga_mem = (volatile uchar*)P2V(phys_addr);
|
|
vga_phys = phys_addr;
|
|
|
|
vga_stride = *(ushort*)(P2V(VBE_INFO_ADDR) + 16);
|
|
vga_width = *(ushort*)(P2V(VBE_INFO_ADDR) + 18);
|
|
vga_height = *(ushort*)(P2V(VBE_INFO_ADDR) + 20);
|
|
vga_bpp = *(uchar*)(P2V(VBE_INFO_ADDR) + 25);
|
|
|
|
if (vga_width == 0) vga_width = 800;
|
|
if (vga_height == 0) vga_height = 600;
|
|
if (vga_stride == 0) vga_stride = vga_width * (vga_bpp / 8);
|
|
|
|
cprintf("VGA: %dx%d, %d bpp, stride %d, LFB: 0x%x\n", vga_width, vga_height, vga_bpp, vga_stride, (uint32)phys_addr);
|
|
|
|
// Deliberately does NOT clear the screen.
|
|
//
|
|
// The bootloader 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 kernel log lines. The splash now stays up
|
|
// for the whole of boot, and console_takeover() clears it exactly once --
|
|
// when userspace starts and the display changes owner.
|
|
}
|
|
|
|
// The mode that was actually set. Exported because the resolution is chosen at
|
|
// boot now (press R -- see boot/bios/stage2.S), so nothing above may assume it.
|
|
uint32
|
|
vga_get_width(void)
|
|
{
|
|
return vga_width;
|
|
}
|
|
|
|
uint32
|
|
vga_get_height(void)
|
|
{
|
|
return vga_height;
|
|
}
|
|
|
|
void
|
|
vga_draw_pixel(int x, int y, uint32 color)
|
|
{
|
|
if (!vga_mem || x < 0 || x >= vga_width || y < 0 || y >= vga_height)
|
|
return;
|
|
|
|
uint32 offset = (y * vga_stride) + (x * (vga_bpp / 8));
|
|
|
|
if (vga_bpp == 32) {
|
|
*(volatile uint32*)(vga_mem + offset) = color;
|
|
} else if (vga_bpp == 24) {
|
|
vga_mem[offset] = color & 0xFF;
|
|
vga_mem[offset + 1] = (color >> 8) & 0xFF;
|
|
vga_mem[offset + 2] = (color >> 16) & 0xFF;
|
|
} else if (vga_bpp == 16) {
|
|
uint16 c16 = (((color >> 19) & 0x1F) << 11) | (((color >> 10) & 0x3F) << 5) | ((color >> 3) & 0x1F);
|
|
*(volatile uint16*)(vga_mem + offset) = c16;
|
|
}
|
|
}
|
|
|
|
void
|
|
vga_clear(uint32 color)
|
|
{
|
|
for (int y = 0; y < vga_height; y++) {
|
|
for (int x = 0; x < vga_width; x++) {
|
|
vga_draw_pixel(x, y, color);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
vga_draw_char(int x, int y, uchar c, uint32 color)
|
|
{
|
|
// No `c > 127` rejection any more: font_data holds 256 Latin-1 glyphs, and
|
|
// a uchar cannot index past them.
|
|
if (!vga_mem) return;
|
|
|
|
for (int row = 0; row < CHAR_HEIGHT; row++) {
|
|
uchar row_data = font_data[c][row];
|
|
for (int col = 0; col < CHAR_WIDTH; col++) {
|
|
if (row_data & (0x80 >> col)) {
|
|
vga_draw_pixel(x + col, y + row, color);
|
|
} else {
|
|
vga_draw_pixel(x + col, y + row, vga_bg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
vga_puts(int x, int y, char *s, uint32 color)
|
|
{
|
|
while (*s) {
|
|
vga_putc((uchar)*s, color);
|
|
s++;
|
|
}
|
|
}
|
|
|
|
void
|
|
vga_putc(int c, uint32 color)
|
|
{
|
|
if (!vga_mem) return;
|
|
|
|
if (c == BACKSPACE || c == 0x08 || c == '\b' || c == 127) {
|
|
if (cursor_x >= CHAR_WIDTH) {
|
|
cursor_x -= CHAR_WIDTH;
|
|
for (int i = 0; i < CHAR_HEIGHT; i++) {
|
|
for (int j = 0; j < CHAR_WIDTH; j++) {
|
|
vga_draw_pixel(cursor_x + j, cursor_y + i, vga_bg);
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (c == '\n') {
|
|
cursor_x = 0;
|
|
cursor_y += CHAR_HEIGHT;
|
|
} else if (c == '\r') {
|
|
cursor_x = 0;
|
|
} else if (c == '\t') {
|
|
// Advance to the next multiple of eight columns, erasing as it goes so a
|
|
// tab overwrites rather than skipping over stale characters.
|
|
//
|
|
// There was no tab case at all, so '\t' fell through to vga_draw_char and
|
|
// was drawn as glyph 9 -- which in CP437 is a hollow circle. Pressing Tab
|
|
// printed a circle. Same for every other control character: the font has
|
|
// a picture for each of them, and none of those pictures is what the
|
|
// character means.
|
|
int col = cursor_x / CHAR_WIDTH;
|
|
int stop = (col + 8) & ~7;
|
|
while (col < stop && cursor_x < (int)vga_width) {
|
|
for (int i = 0; i < CHAR_HEIGHT; i++)
|
|
for (int j = 0; j < CHAR_WIDTH; j++)
|
|
vga_draw_pixel(cursor_x + j, cursor_y + i, vga_bg);
|
|
cursor_x += CHAR_WIDTH;
|
|
col++;
|
|
}
|
|
} else if (c < 32) {
|
|
return; // any other control character: not a glyph
|
|
} else {
|
|
vga_draw_char(cursor_x, cursor_y, (uchar)c, color);
|
|
cursor_x += CHAR_WIDTH;
|
|
}
|
|
|
|
if (cursor_x >= vga_width) {
|
|
cursor_x = 0;
|
|
cursor_y += CHAR_HEIGHT;
|
|
}
|
|
|
|
if (cursor_y > (vga_height - CHAR_HEIGHT)) {
|
|
uint32 line_size = vga_stride * CHAR_HEIGHT;
|
|
uint32 total_size = vga_stride * vga_height;
|
|
uint32 copy_size = total_size - line_size;
|
|
|
|
memmove((void*)vga_mem, (void*)(vga_mem + line_size), copy_size);
|
|
|
|
for (int y = vga_height - CHAR_HEIGHT; y < vga_height; y++) {
|
|
for (int x = 0; x < vga_width; x++) {
|
|
vga_draw_pixel(x, y, vga_bg);
|
|
}
|
|
}
|
|
cursor_y = vga_height - CHAR_HEIGHT;
|
|
}
|
|
}
|
|
|
|
void
|
|
vga_screen_reset(void)
|
|
{
|
|
vga_bg = COLOR_BG;
|
|
vga_clear(COLOR_BG);
|
|
cursor_x = 0;
|
|
cursor_y = 0;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Drawing primitives for the crash screen */
|
|
/* */
|
|
/* The crash screen lays itself out rather than printing, which needs */
|
|
/* two things the console never did: filled shapes, and text at an */
|
|
/* absolute position that leaves the cursor alone. */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
int
|
|
vga_ready(void)
|
|
{
|
|
return vga_mem != 0;
|
|
}
|
|
|
|
void
|
|
vga_dims(int *w, int *h)
|
|
{
|
|
if (w) *w = (int)vga_width;
|
|
if (h) *h = (int)vga_height;
|
|
}
|
|
|
|
int vga_char_w(void) { return CHAR_WIDTH; }
|
|
int vga_char_h(void) { return CHAR_HEIGHT; }
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Framebuffer geometry */
|
|
/* */
|
|
/* vga.c is the only place that reads the VBE mode info, so it is the */
|
|
/* only place that knows where the framebuffer actually is. Everything */
|
|
/* else asks. See kernel/dev/fb.c for the device these back. */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
uint64 vga_fb_phys(void) { return vga_phys; }
|
|
|
|
// 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 (!vga_mem)
|
|
return 0;
|
|
return (uint64)vga_stride * (uint64)vga_height;
|
|
}
|
|
|
|
uint32 vga_fb_stride(void) { return vga_stride; }
|
|
uint32 vga_fb_bpp(void) { return vga_bpp; }
|
|
|
|
volatile uchar *vga_fb_ptr(void) { return vga_mem; }
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Cursor addressing and erasing */
|
|
/* */
|
|
/* What an ANSI escape parser needs and vga_putc alone cannot give it: */
|
|
/* the ability to put the cursor somewhere specific and to erase a */
|
|
/* region without writing spaces over it. See kernel/core/console.c. */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
int vga_cols(void) { return vga_width ? (int)(vga_width / CHAR_WIDTH) : 0; }
|
|
int vga_rows(void) { return vga_height ? (int)(vga_height / CHAR_HEIGHT) : 0; }
|
|
|
|
void
|
|
vga_cursor_get(int *col, int *row)
|
|
{
|
|
if (col) *col = cursor_x / CHAR_WIDTH;
|
|
if (row) *row = cursor_y / CHAR_HEIGHT;
|
|
}
|
|
|
|
// Clamped rather than validated: an escape sequence is untrusted input that
|
|
// arrives from any program that can write to the terminal, and "CUP to row
|
|
// 9999" should put the cursor on the last row, not off the end of the
|
|
// framebuffer.
|
|
void
|
|
vga_cursor_set(int col, int row)
|
|
{
|
|
int maxc = vga_cols(), maxr = vga_rows();
|
|
|
|
if (maxc <= 0 || maxr <= 0)
|
|
return;
|
|
if (col < 0) col = 0;
|
|
if (row < 0) row = 0;
|
|
if (col >= maxc) col = maxc - 1;
|
|
if (row >= maxr) row = maxr - 1;
|
|
|
|
cursor_x = col * CHAR_WIDTH;
|
|
cursor_y = row * CHAR_HEIGHT;
|
|
}
|
|
|
|
// Fill a run of character cells with the background colour.
|
|
static void
|
|
erase_cells(int col, int row, int count)
|
|
{
|
|
int maxc = vga_cols();
|
|
|
|
while (count-- > 0) {
|
|
int x = col * CHAR_WIDTH, y = row * CHAR_HEIGHT;
|
|
for (int i = 0; i < CHAR_HEIGHT; i++)
|
|
for (int j = 0; j < CHAR_WIDTH; j++)
|
|
vga_draw_pixel(x + j, y + i, vga_bg);
|
|
if (++col >= maxc) { col = 0; row++; }
|
|
}
|
|
}
|
|
|
|
// ED -- erase in display. 0: cursor to end, 1: start to cursor, 2: all.
|
|
void
|
|
vga_erase_display(int mode)
|
|
{
|
|
int col, row, maxc = vga_cols(), maxr = vga_rows();
|
|
|
|
vga_cursor_get(&col, &row);
|
|
switch (mode) {
|
|
case 0: erase_cells(col, row, (maxr - row) * maxc - col); break;
|
|
case 1: erase_cells(0, 0, row * maxc + col + 1); break;
|
|
default: vga_clear(vga_bg); break;
|
|
}
|
|
}
|
|
|
|
// EL -- erase in line. Same three modes, bounded to the current row.
|
|
void
|
|
vga_erase_line(int mode)
|
|
{
|
|
int col, row, maxc = vga_cols();
|
|
|
|
vga_cursor_get(&col, &row);
|
|
switch (mode) {
|
|
case 0: erase_cells(col, row, maxc - col); break;
|
|
case 1: erase_cells(0, row, col + 1); break;
|
|
default: erase_cells(0, row, maxc); break;
|
|
}
|
|
}
|
|
|
|
// Row pointer computed once per row rather than per pixel. The crash screen
|
|
// paints a solid card behind the QR symbol, and at 8 pixels per module that is
|
|
// tens of thousands of writes going through vga_draw_pixel's bounds check and
|
|
// multiply.
|
|
void
|
|
vga_fill_rect(int x0, int y0, int w, int h, uint32 color)
|
|
{
|
|
if (!vga_mem)
|
|
return;
|
|
if (x0 < 0) { w += x0; x0 = 0; }
|
|
if (y0 < 0) { h += y0; y0 = 0; }
|
|
if (x0 + w > (int)vga_width) w = (int)vga_width - x0;
|
|
if (y0 + h > (int)vga_height) h = (int)vga_height - y0;
|
|
if (w <= 0 || h <= 0)
|
|
return;
|
|
|
|
// Only the 32bpp case gets the fast path; anything else is rare enough that
|
|
// correctness through vga_draw_pixel is the better trade.
|
|
if (vga_bpp != 32) {
|
|
for (int y = y0; y < y0 + h; y++)
|
|
for (int x = x0; x < x0 + w; x++)
|
|
vga_draw_pixel(x, y, color);
|
|
return;
|
|
}
|
|
for (int y = y0; y < y0 + h; y++) {
|
|
volatile uint32 *row = (volatile uint32 *)(vga_mem + y * vga_stride) + x0;
|
|
for (int x = 0; x < w; x++)
|
|
row[x] = color;
|
|
}
|
|
}
|
|
|
|
void
|
|
vga_fill_circle(int cx, int cy, int r, uint32 color)
|
|
{
|
|
for (int dy = -r; dy <= r; dy++)
|
|
for (int dx = -r; dx <= r; dx++)
|
|
if (dx * dx + dy * dy <= r * r)
|
|
vga_draw_pixel(cx + dx, cy + dy, color);
|
|
}
|
|
|
|
// Text at an absolute position, leaving the console cursor alone. The crash
|
|
// screen lays itself out, so vga_putc's cursor tracking and scrolling are
|
|
// exactly what it does not want.
|
|
void
|
|
vga_text_at(int x, int y, const char *s, uint32 color)
|
|
{
|
|
for (int i = 0; s[i]; i++)
|
|
vga_draw_char(x + i * CHAR_WIDTH, y, (uchar)s[i], color);
|
|
}
|
|
|
|
// --- Logo ------------------------------------------------------------------
|
|
//
|
|
// The boot splash's logo is drawn by the *loader*, not here -- see splash() in
|
|
// boot/bios/stage2main.c. It has to be, because the kernel does not reach vga_init()
|
|
// until roughly the last 300ms of a 2.8s boot: drawing it here put the logo on
|
|
// screen for one frame before console_takeover() wiped it, which is worse than
|
|
// not having one.
|
|
//
|
|
// What lives here is the same drawing code against the same mask, for the
|
|
// things that want the mark *after* boot -- the crash screen, and eventually
|
|
// whatever schinit shows. Keeping one copy of the compositing logic is why this
|
|
// is not simply deleted.
|
|
|
|
// Blend `fg` over `bg` by coverage a (0..255). Per-channel, integer only: this
|
|
// runs before any floating point is safe to touch.
|
|
static uint32
|
|
blend(uint32 bg, uint32 fg, uint a)
|
|
{
|
|
uint32 r = (((fg >> 16) & 0xFF) * a + ((bg >> 16) & 0xFF) * (255 - a)) / 255;
|
|
uint32 g = (((fg >> 8) & 0xFF) * a + ((bg >> 8) & 0xFF) * (255 - a)) / 255;
|
|
uint32 b = (((fg ) & 0xFF) * a + ((bg ) & 0xFF) * (255 - a)) / 255;
|
|
return (r << 16) | (g << 8) | b;
|
|
}
|
|
|
|
void
|
|
vga_draw_logo(int x, int y, uint32 color, uint32 bg)
|
|
{
|
|
if (!vga_mem)
|
|
return;
|
|
for (int dy = 0; dy < LOGO_H; dy++) {
|
|
for (int dx = 0; dx < LOGO_W; dx++) {
|
|
uint a = LOGO_INK(dy * LOGO_W + dx);
|
|
// Skip the background rather than painting it. The mask is a square but
|
|
// the mark is a circle, so most of it is empty -- and drawing that empty
|
|
// area in bg would stamp a visible box over whatever is underneath.
|
|
if (a == 0)
|
|
continue;
|
|
vga_draw_pixel(x + dx, y + dy, a == 255 ? color : blend(bg, color, a));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Centred, for callers that own the whole screen.
|
|
void
|
|
vga_logo_centered(int cy, uint32 color, uint32 bg)
|
|
{
|
|
if (!vga_mem || vga_width < LOGO_W)
|
|
return;
|
|
vga_draw_logo(((int)vga_width - LOGO_W) / 2, cy - LOGO_H / 2, color, bg);
|
|
}
|
|
|
|
// --- display class implementation ------------------------------------------
|
|
//
|
|
// See include/display.h. This is the VBE linear framebuffer presenting itself
|
|
// as a generic display, so a compositor can be written against the class and
|
|
// keep working when virtio-gpu arrives.
|
|
|
|
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)
|
|
{
|
|
m->width = (int)vga_width;
|
|
m->height = (int)vga_height;
|
|
m->bpp = (int)vga_bpp;
|
|
m->stride = vga_stride;
|
|
}
|
|
|
|
static void *
|
|
vga_disp_acquire(struct device *d, uint32 *stride_out)
|
|
{
|
|
if (stride_out)
|
|
*stride_out = vga_stride;
|
|
if (back)
|
|
return back;
|
|
if (!vga_mem || vga_height == 0 || vga_stride == 0)
|
|
return 0;
|
|
|
|
back_bytes = (uint64)vga_stride * vga_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)
|
|
{
|
|
if (!back || !vga_mem)
|
|
return;
|
|
|
|
if (x < 0) { w += x; x = 0; }
|
|
if (y < 0) { h += y; y = 0; }
|
|
if (x + w > (int)vga_width) w = (int)vga_width - x;
|
|
if (y + h > (int)vga_height) h = (int)vga_height - y;
|
|
if (w <= 0 || h <= 0)
|
|
return;
|
|
|
|
int bytes = w * (int)(vga_bpp / 8);
|
|
uint32 off = (uint32)x * (vga_bpp / 8);
|
|
for (int row = y; row < y + h; row++)
|
|
memmove((void *)(vga_mem + row * vga_stride + off),
|
|
back + row * vga_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;
|
|
|
|
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,
|
|
};
|