aboutsummaryrefslogtreecommitdiff
path: root/mubios/services/vbe.c
diff options
context:
space:
mode:
authorRichard Qin <richard.qin.x@gmail.com>2026-11-10 02:29:20 +0800
committerRichard Qin <richard.qin.x@gmail.com>2026-11-10 02:29:20 +0800
commit7ecf4901b486693e525d3daafc7d6e9c3a4abcd0 (patch)
tree722ed5d7919170cedef44bdb22bc87ab98a47682 /mubios/services/vbe.c
parent03c2c766aad0746ca3e71b44756b443ae8742e95 (diff)
downloadrcc-project-7ecf4901b486693e525d3daafc7d6e9c3a4abcd0.tar.gz
rcc-project-7ecf4901b486693e525d3daafc7d6e9c3a4abcd0.tar.xz
[mubios] Complete VGA text initialization and add VBE graphics services
Load the 8x16 font and VGA palette, restore text memory mapping, and synchronize BIOS cursor and page state with the hardware. Allocate QEMU standard VGA PCI memory resources and expose VBE controller information, mode queries and switching, logical scanline lengths, and display start control. Signed-off-by: Richard Qin <richard.qin.x@gmail.com>
Diffstat (limited to 'mubios/services/vbe.c')
-rw-r--r--mubios/services/vbe.c320
1 files changed, 320 insertions, 0 deletions
diff --git a/mubios/services/vbe.c b/mubios/services/vbe.c
new file mode 100644
index 0000000..5119625
--- /dev/null
+++ b/mubios/services/vbe.c
@@ -0,0 +1,320 @@
+/*
+ RCC-Project
+
+ Copyright (C) 2026 Richard Qin
+
+ This file is part of RCC-Project.
+
+ RCC-Project is free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 3 of the License, or (at your option) any
+ later version.
+
+ RCC-Project is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ RCC-Project; see the file COPYING. If not see
+ <http://www.gnu.org/licenses/>.
+ */
+
+#include "vbe.h"
+#include "regs.h"
+
+static const char oem_name[] = "mubios QEMU standard VGA";
+static const char vendor_name[] = "mubios";
+static const char product_name[] = "QEMU standard VGA VBE";
+static const char product_revision[] = "2.0";
+
+static void
+put16 (volatile u8 *buffer, u16 offset, u16 value)
+{
+ buffer[offset] = (u8)value;
+ buffer[offset + 1u] = (u8)(value >> 8);
+}
+
+static void
+put32 (volatile u8 *buffer, u16 offset, u32 value)
+{
+ put16 (buffer, offset, (u16)value);
+ put16 (buffer, (u16)(offset + 2u), (u16)(value >> 16));
+}
+
+static u32
+far_pointer (const void *pointer)
+{
+ u32 address = (u32)pointer;
+
+ return ((address >> 4) << 16) | (address & 0x0Fu);
+}
+
+static void
+clear_block (volatile u8 *buffer, u16 size)
+{
+ u16 index;
+
+ for (index = 0; index < size; ++index)
+ buffer[index] = 0u;
+}
+
+static u16
+controller_info (volatile u8 *buffer)
+{
+ /* VBE 1.x callers provide 256 bytes; "VBE2" requests 512 bytes. */
+ u8 extended = buffer[0] == 'V' && buffer[1] == 'B' && buffer[2] == 'E'
+ && buffer[3] == '2';
+
+ clear_block (buffer, extended ? 512u : 256u);
+ buffer[0] = 'V';
+ buffer[1] = 'E';
+ buffer[2] = 'S';
+ buffer[3] = 'A';
+ put16 (buffer, 0x04u, 0x0200u);
+ put32 (buffer, 0x06u, far_pointer (oem_name));
+ /* Capabilities=0: VGA-compatible, fixed six-bit DAC, no stereo. */
+ put32 (buffer, 0x0Eu, far_pointer (vbe.mode_list));
+ put16 (buffer, 0x12u, (u16)(vbe.memory_size >> 16));
+ if (extended)
+ {
+ put16 (buffer, 0x14u, 0x0100u);
+ put32 (buffer, 0x16u, far_pointer (vendor_name));
+ put32 (buffer, 0x1Au, far_pointer (product_name));
+ put32 (buffer, 0x1Eu, far_pointer (product_revision));
+ }
+ return VBE_OK;
+}
+
+static u16
+mode_info (volatile u8 *buffer, u16 number)
+{
+ const struct vbe_mode *mode = vbe_find_mode (number & 0x3FFFu);
+ u32 frame_size;
+ u32 pages;
+
+ if (mode == (const struct vbe_mode *)0)
+ return VBE_FAILED;
+ clear_block (buffer, 256u);
+ /* Supported, extended info, color graphics, LFB available, no bank window.
+ BIOS character output in these graphics modes is not advertised. */
+ put16 (buffer, 0x00u, 0x00DBu);
+ put16 (buffer, 0x10u, (u16)(mode->width * (mode->bpp / 8u)));
+ put16 (buffer, 0x12u, mode->width);
+ put16 (buffer, 0x14u, mode->height);
+ buffer[0x16u] = 8u;
+ buffer[0x17u] = 16u;
+ buffer[0x18u] = 1u;
+ buffer[0x19u] = (u8)mode->bpp;
+ buffer[0x1Bu] = 6u; /* Direct color, packed BGR/RGB565 pixels. */
+ frame_size = (u32)mode->width * mode->height * (mode->bpp / 8u);
+ pages = vbe.memory_size / frame_size - 1u;
+ /* DISPI display-start Y is limited independently of the VRAM capacity. */
+ if (pages > vbe.max_height / mode->height)
+ pages = vbe.max_height / mode->height;
+ buffer[0x1Du] = (u8)(pages > 255u ? 255u : pages);
+ if (mode->bpp == 16u)
+ {
+ buffer[0x1Fu] = 5u;
+ buffer[0x20u] = 11u;
+ buffer[0x21u] = 6u;
+ buffer[0x22u] = 5u;
+ buffer[0x23u] = 5u;
+ }
+ else
+ {
+ buffer[0x1Fu] = 8u;
+ buffer[0x20u] = 16u;
+ buffer[0x21u] = 8u;
+ buffer[0x22u] = 8u;
+ buffer[0x23u] = 8u;
+ if (mode->bpp == 32u)
+ {
+ buffer[0x25u] = 8u;
+ buffer[0x26u] = 24u;
+ }
+ }
+ put32 (buffer, 0x28u, vbe.framebuffer);
+ return VBE_OK;
+}
+
+static int
+graphics_active (void)
+{
+ return (vbe_read (DISPI_ENABLE) & 1u) != 0u;
+}
+
+static u16
+scanline_length (struct bios_regs *regs)
+{
+ u8 subfunction = (u8)regs->ebx;
+ u16 width = vbe_read (DISPI_XRES);
+ u16 height = vbe_read (DISPI_YRES);
+ u16 bytes_per_pixel = (u16)(vbe_read (DISPI_BPP) / 8u);
+ u16 old_width;
+ u16 old_x;
+ u16 old_y;
+ u32 pixels;
+ u32 pitch;
+ u32 lines;
+
+ /* Logical scanline operations require an active direct-color mode. */
+ if (!graphics_active ()
+ || (bytes_per_pixel != 2u && bytes_per_pixel != 3u
+ && bytes_per_pixel != 4u))
+ return VBE_FAILED;
+ if (subfunction == 0u || subfunction == 2u)
+ {
+ pixels = (u16)regs->ecx;
+ if (subfunction == 2u)
+ pixels = (pixels + bytes_per_pixel - 1u) / bytes_per_pixel;
+ /* DISPI virtual widths are multiples of eight pixels. */
+ pixels = (pixels + 7u) & ~7u;
+ pitch = pixels * bytes_per_pixel;
+ if (pixels < width || pixels > vbe.max_width || pitch > 0xFFFFu
+ || pitch * height > vbe.memory_size)
+ return VBE_FAILED;
+ old_width = vbe_read (DISPI_VIRT_WIDTH);
+ old_x = vbe_read (DISPI_X_OFFSET);
+ old_y = vbe_read (DISPI_Y_OFFSET);
+ vbe_write (DISPI_X_OFFSET, 0u);
+ vbe_write (DISPI_Y_OFFSET, 0u);
+ vbe_write (DISPI_VIRT_WIDTH, (u16)pixels);
+ if (vbe_read (DISPI_VIRT_WIDTH) != pixels)
+ {
+ vbe_write (DISPI_VIRT_WIDTH, old_width);
+ vbe_write (DISPI_X_OFFSET, old_x);
+ vbe_write (DISPI_Y_OFFSET, old_y);
+ return VBE_FAILED;
+ }
+ }
+ else if (subfunction == 3u)
+ {
+ /* Maximum width still has to hold one complete visible image. */
+ pixels = vbe.memory_size / ((u32)bytes_per_pixel * height);
+ if (pixels > vbe.max_width)
+ pixels = vbe.max_width;
+ if (pixels > 0xFFFFu / bytes_per_pixel)
+ pixels = 0xFFFFu / bytes_per_pixel;
+ pixels &= ~7u;
+ }
+ else if (subfunction != 1u)
+ return VBE_UNSUPPORTED;
+
+ if (subfunction != 3u)
+ pixels = vbe_read (DISPI_VIRT_WIDTH);
+ pitch = pixels * bytes_per_pixel;
+ lines = vbe.memory_size / pitch;
+ if (lines > (u32)vbe.max_height + height)
+ lines = (u32)vbe.max_height + height;
+ regs->ebx = (regs->ebx & 0xFFFF0000u) | (u16)pitch;
+ regs->ecx = (regs->ecx & 0xFFFF0000u) | (u16)pixels;
+ regs->edx
+ = (regs->edx & 0xFFFF0000u) | (u16)(lines > 0xFFFFu ? 0xFFFFu : lines);
+ return VBE_OK;
+}
+
+static u16
+display_start (struct bios_regs *regs)
+{
+ u8 subfunction = (u8)regs->ebx;
+ u32 x = (u16)regs->ecx;
+ u32 y = (u16)regs->edx;
+ u32 width = vbe_read (DISPI_XRES);
+ u32 height = vbe_read (DISPI_YRES);
+ u32 virtual_width = vbe_read (DISPI_VIRT_WIDTH);
+ u32 bytes_per_pixel = vbe_read (DISPI_BPP) / 8u;
+ u32 pitch;
+ u16 old_x;
+ u16 old_y;
+
+ if (!graphics_active ()
+ || (bytes_per_pixel != 2u && bytes_per_pixel != 3u
+ && bytes_per_pixel != 4u))
+ return VBE_FAILED;
+ if (subfunction == 1u)
+ {
+ regs->ebx &= 0xFFFF00FFu;
+ regs->ecx = (regs->ecx & 0xFFFF0000u) | vbe_read (DISPI_X_OFFSET);
+ regs->edx = (regs->edx & 0xFFFF0000u) | vbe_read (DISPI_Y_OFFSET);
+ return VBE_OK;
+ }
+ if (subfunction != 0u && subfunction != 0x80u)
+ return VBE_UNSUPPORTED;
+
+ pitch = virtual_width * bytes_per_pixel;
+ /* The entire displayed rectangle must lie in VRAM. QEMU expresses its
+ start address in dwords, so the byte offset must be dword-aligned. */
+ if (x + width > virtual_width || y > vbe.max_height
+ || (y + height) > vbe.memory_size / pitch
+ || x * bytes_per_pixel > vbe.memory_size - (y + height) * pitch
+ || ((x * bytes_per_pixel + y * pitch) & 3u) != 0u)
+ return VBE_FAILED;
+ old_x = vbe_read (DISPI_X_OFFSET);
+ old_y = vbe_read (DISPI_Y_OFFSET);
+ /* QEMU applies display changes at its display refresh. No guest-side
+ retrace busy wait is needed for BL=80h. */
+ vbe_write (DISPI_Y_OFFSET, 0u);
+ vbe_write (DISPI_X_OFFSET, (u16)x);
+ vbe_write (DISPI_Y_OFFSET, (u16)y);
+ if (vbe_read (DISPI_X_OFFSET) != x || vbe_read (DISPI_Y_OFFSET) != y)
+ {
+ vbe_write (DISPI_Y_OFFSET, 0u);
+ vbe_write (DISPI_X_OFFSET, old_x);
+ vbe_write (DISPI_Y_OFFSET, old_y);
+ return VBE_FAILED;
+ }
+ return VBE_OK;
+}
+
+void
+vbe_service (struct bios_regs *regs)
+{
+ u16 status = VBE_FAILED;
+ u16 mode;
+ u8 function = (u8)regs->eax;
+ volatile u8 *buffer = (volatile u8 *)(((u32)regs->es << 4) + (u16)regs->edi);
+
+ /* VBE uses AX status codes, including for unavailable hardware/functions. */
+ if (!vbe.present)
+ goto done;
+ switch (function)
+ {
+ case 0x00u:
+ status = controller_info (buffer);
+ break;
+ case 0x01u:
+ status = mode_info (buffer, (u16)regs->ecx);
+ break;
+ case 0x02u:
+ status = vbe_set_mode ((u16)regs->ebx);
+ break;
+ case 0x03u:
+ status = vbe_get_mode (&mode);
+ if (status == VBE_OK)
+ regs->ebx = (regs->ebx & 0xFFFF0000u) | mode;
+ break;
+ case 0x06u:
+ status = scanline_length (regs);
+ break;
+ case 0x07u:
+ status = display_start (regs);
+ break;
+ case 0x08u:
+ /* Capabilities advertise a fixed six-bit DAC; accept only that format.
+ */
+ if ((u8)regs->ebx == 1u
+ || ((u8)regs->ebx == 0u && (u8)(regs->ebx >> 8) == 6u))
+ {
+ regs->ebx = (regs->ebx & 0xFFFF00FFu) | 0x0600u;
+ status = VBE_OK;
+ }
+ break;
+ default:
+ /* No banking, VBE PM interface, custom timings, DDC or stereo is
+ * claimed. */
+ status = VBE_FAILED;
+ break;
+ }
+done:
+ regs->eax = (regs->eax & 0xFFFF0000u) | status;
+}